From 7009bbeb8200246807b09c2af41526347620a436 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Sat, 8 Oct 2022 00:04:01 +0200 Subject: [PATCH 001/498] db migrations to support new webhook operations (#17671) --- .../airbyte/bootloader/BootloaderAppTest.java | 2 +- ...40_12_001__AddWebhookOperationColumns.java | 50 +++++++++++++++++++ .../configs_database/schema_dump.txt | 2 + 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/configs/migrations/V0_40_12_001__AddWebhookOperationColumns.java diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index 7ca66e367b2c..d284a6f02b86 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -138,7 +138,7 @@ void testBootloaderAppBlankDb() throws Exception { val configsMigrator = new ConfigsDatabaseMigrator(configDatabase, configsFlyway); // this line should change with every new migration // to show that you meant to make a new migration to the prod database - assertEquals("0.40.11.002", configsMigrator.getLatestMigration().getVersion().getVersion()); + assertEquals("0.40.12.001", configsMigrator.getLatestMigration().getVersion().getVersion()); val jobsPersistence = new DefaultJobPersistence(jobDatabase); assertEquals(VERSION_0330_ALPHA, jobsPersistence.getVersion().get()); diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/configs/migrations/V0_40_12_001__AddWebhookOperationColumns.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/configs/migrations/V0_40_12_001__AddWebhookOperationColumns.java new file mode 100644 index 000000000000..9b2fe897ed92 --- /dev/null +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/configs/migrations/V0_40_12_001__AddWebhookOperationColumns.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.db.instance.configs.migrations; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.jooq.DSLContext; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: update migration description in the class name +public class V0_40_12_001__AddWebhookOperationColumns extends BaseJavaMigration { + + private static final Logger LOGGER = LoggerFactory.getLogger(V0_40_12_001__AddWebhookOperationColumns.class); + + @Override + public void migrate(final Context context) throws Exception { + LOGGER.info("Running migration: {}", this.getClass().getSimpleName()); + + // Warning: please do not use any jOOQ generated code to write a migration. + // As database schema changes, the generated jOOQ code can be deprecated. So + // old migration may not compile if there is any generated code. + final DSLContext ctx = DSL.using(context.getConnection()); + addWebhookOperationConfigColumn(ctx); + addWebhookOperationType(ctx); + addWebhookConfigColumnsToWorkspaceTable(ctx); + } + + private void addWebhookConfigColumnsToWorkspaceTable(final DSLContext ctx) { + ctx.alterTable("workspace") + .addColumnIfNotExists(DSL.field( + "webhook_operation_configs", + SQLDataType.JSONB.nullable(true))) + .execute(); + } + + private void addWebhookOperationType(final DSLContext ctx) { + ctx.alterType("operator_type").addValue("webhook").execute(); + } + + private void addWebhookOperationConfigColumn(final DSLContext ctx) { + ctx.alterTable("operation").addColumnIfNotExists(DSL.field("operator_webhook", + SQLDataType.JSONB.nullable(true))).execute(); + } + +} diff --git a/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt b/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt index 2c9a2ea0ec52..682bb706bd5f 100644 --- a/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt +++ b/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt @@ -134,6 +134,7 @@ create table "public"."operation"( "tombstone" bool not null default false, "created_at" timestamptz(35) not null default null, "updated_at" timestamptz(35) not null default null, + "operator_webhook" jsonb null, constraint "operation_pkey" primary key ("id") ); @@ -178,6 +179,7 @@ create table "public"."workspace"( "created_at" timestamptz(35) not null default null, "updated_at" timestamptz(35) not null default null, "geography" geography_type not null default null, + "webhook_operation_configs" jsonb null, constraint "workspace_pkey" primary key ("id") ); From 39220f4b1ebee7fce23acebcdbed7e1d259229b6 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 7 Oct 2022 19:15:27 -0700 Subject: [PATCH 002/498] Update lowcode docs (#17752) * Update lowcode docs * Update * fix links * more links * Schemas * indent * Update docs/connector-development/config-based/advanced-topics.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/advanced-topics.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/low-code-cdk-overview.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/understanding-the-yaml-file/request-options.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/understanding-the-yaml-file/requester.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/understanding-the-yaml-file/stream-slicers.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/understanding-the-yaml-file/requester.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * Update docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> * capitalization * fix schema * fix schema * small changes so greenhouse validates * fix brackets * update code samples * Add missing types * fix * reset Co-authored-by: Brian Lai <51336873+brianjlai@users.noreply.github.com> --- .../error_handlers/default_error_handler.py | 5 +- .../config-based/advanced-topics.md | 37 +- .../config-based/assets/connector-flow.png | Bin 0 -> 317947 bytes .../config-based/low-code-cdk-overview.md | 44 +- .../config-based/source_schema.yaml | 628 ++++++++++++++++++ .../authentication.md | 121 +++- .../error-handling.md | 185 +++++- .../understanding-the-yaml-file/pagination.md | 116 +++- .../record-selector.md | 108 ++- .../request-options.md | 67 +- .../understanding-the-yaml-file/requester.md | 69 +- .../stream-slicers.md | 189 +++++- .../yaml-overview.md | 198 +++--- 13 files changed, 1575 insertions(+), 192 deletions(-) create mode 100644 docs/connector-development/config-based/assets/connector-flow.png create mode 100644 docs/connector-development/config-based/source_schema.yaml diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py index 179db638661f..4275d0ce6ba6 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py @@ -54,8 +54,9 @@ class DefaultErrorHandler(ErrorHandler, JsonSchemaMixin): 4. ignore HTTP 404 ` error_handler: - - http_codes: [ 404 ] - action: IGNORE + response_filters: + - http_codes: [ 404 ] + action: IGNORE ` 5. retry if error message contains `retrythisrequest!` substring ` diff --git a/docs/connector-development/config-based/advanced-topics.md b/docs/connector-development/config-based/advanced-topics.md index 75e15721229a..d06e46c31708 100644 --- a/docs/connector-development/config-based/advanced-topics.md +++ b/docs/connector-development/config-based/advanced-topics.md @@ -65,11 +65,21 @@ TopLevel(param=ParamType(k="v")) More details on object instantiation can be found [here](https://airbyte-cdk.readthedocs.io/en/latest/api/airbyte_cdk.sources.declarative.parsers.html?highlight=factory#airbyte_cdk.sources.declarative.parsers.factory.DeclarativeComponentFactory). -### $options +## $options Parameters can be passed down from a parent component to its subcomponents using the $options key. This can be used to avoid repetitions. +Schema: + +```yaml + "$options": + type: object + additionalProperties: true +``` + +Example: + ```yaml outer: $options: @@ -230,7 +240,7 @@ some_object: ``` Some components also pass in additional arguments to the context. -This is the case for the [record selector](understanding-the-yaml-file/record-selector.md), which passes in an additional `response` argument. +This is the case for the [record selector](./understanding-the-yaml-file/record-selector.md), which passes in an additional `response` argument. Both dot notation and bracket notations (with single quotes ( `'`)) are interchangeable. This means that both these string templates will evaluate to the same string: @@ -244,11 +254,11 @@ For example, The macros available can be found [here](https://github.com/airbytehq/airbyte/blob/master/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/macros.py). -Additional information on jinja templating can be found at https://jinja.palletsprojects.com/en/3.1.x/templates/# +Additional information on jinja templating can be found at [https://jinja.palletsprojects.com/en/3.1.x/templates/#](https://jinja.palletsprojects.com/en/3.1.x/templates/#) ## Component schema reference -A JSON schema representation of the relationships between the components that can be used in the YAML configuration can be found [here](https://github.com/airbytehq/airbyte/blob/master/airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json). +A JSON schema representation of the relationships between the components that can be used in the YAML configuration can be found [here](./source_schema.yaml). ## Custom components @@ -281,4 +291,21 @@ This class can then be referred from the yaml file using its fully qualified cla pagination_strategy: class_name: "my_connector_module.MyPaginationStrategy" my_field: "hello world" -``` \ No newline at end of file +``` + +## How the framework works + +1. Given the connection config and an optional stream state, the `StreamSlicer` computes the stream slices to read. +2. Iterate over all the stream slices defined by the stream slicer. +3. For each stream slice, + 1. Submit a request to the partner API as defined by the requester + 2. Select the records from the response + 3. Repeat for as long as the paginator points to a next page + +[connector-flow](./assets/connector-flow.png) + +## More readings + +- [Record selector](./understanding-the-yaml-file/record-selector.md) +- [Stream slicers](./understanding-the-yaml-file/stream-slicers.md) +- [Source schema](./source_schema.yaml) \ No newline at end of file diff --git a/docs/connector-development/config-based/assets/connector-flow.png b/docs/connector-development/config-based/assets/connector-flow.png new file mode 100644 index 0000000000000000000000000000000000000000..c76dbd756e351f6ce1f3cfe14768e0537bd9f630 GIT binary patch literal 317947 zcmb6B2{@GP-#?Bkp)wJvp|QJFQZtr2j2M+rk$nrpSSDqgjC~IoWS1p(gK*2v;6~Q5 z8%vf#BeG6tjO=3>%kOeO&-490$NzsEzfZr8nd2JQb)MI9p67L*@7L@7dKIawt#R(` z<+DsoOy?dxxUbK|#71LcV*cYaE3hZAe(Vbq(}_pVRaJE#s;Ua>VlhvjJE57Ft|i!_ zP}~o1Tq9Xqqfn$zk`iaJUiuLc(fX*M&gO36uI2$@l5pBv3yW#q^V7_RtxP$3Ej5my zXyLsID5==wFlvhqyS*A^F7nc)wQD-dLPk^A?htIR)D?%)$FbPZdZo8O+alT1zJ zc}W(yQ~iKzQ11?%|NL!%)=4Itt9Qv7%?|fM@WC|5o$5PSbm;;uPwbDf=+cgLKUw>lm6 z;Krj@w_mP7G+qBH=X&*NuD!Y0cjf44zySoJz2W0M)ZRHd+Ri>YI)bwP6%-ykb4QSA zuMh2g;h{(a(748E(}zzVKW4fP{65WeBK$eiDd5)$;CmVP0)3E(V`2lg+`#w#`;-53 zl#TYD`G0;h|8ZRKu7T>qhrrgr7K=u^xIM$T8``Zp0;l@=+|bnB^s$zrEyfvYZHKWz zL%p3}99Lmd@>T?XI-}jKg}t4fT-+4Bl|}z8p$Pmw-VGBK{Gac z2bF?Ki6YJl3kxe@?Vc*?-&gcC&hqR-sjUns(0US3{MFIgxCYY&r#!{IO~8JLXB zO`yb0Hy;;wYww#bZess+lK(l+eYBe`_W29<=NK2^S*L|YB zpa0)0xw!rJV*w8cJ3a!FhDyQy&$)rBO2>N@b)S2qou1r(?hG^!a1Dfl)SpWKmjC}d z@_(=RziOKP-i;_Q|6A3_4UJXBI0M&oNBrNe`R~gA``~|9RDvBp`u}Q* z|7z!d_W})#II9HvKb?j+>x7UPW@5U-^zi;&L+=x`#wXF}F^>;5wiz2wQSGwf0| zENA}dZnlEKtcB`R;~#h~z6~YsAbG4f<`3DqLq`otNko27j`nv5pI>^_XbsNIh341K zM=r_ne*5yw?|(Lvn^9I(PtyKBRC#+Rym|Hc0-?U%$WzWlr~Vy%;8g6n;Fg(k87T!{ zCtA7471YW9{CQz<2EWh3_?})-*=Si)Rp*@aJw1j|Hjb{@sIRK1lJm3J9fw{}@HmB2 zA4thcD4>45!^h8G8nvzJ0fC09&Y@p?bP2t&_;plv8sd+yn;sXrAa&wDeS&#{FA!10 z#zwPIFf=uHY{kp+Yw9G_F$$9$Q&n5hSpNA*0}L(C((&eT^C=Eh)O%4TEps)j4M4^Mse2(*^sFe&E5Og@3eYz82tfS*Zdo@+c21g7#yaZ=@`j{HUm`GOLzfx_8k3wDDVDed)*w{*P^^qBD5Wlg#LR zOTDSux~9o1LC+{!e1hqv>|6HKlZOWA{fS;QwID8~dqYUV)?gqtIIG-%Qje7WkBtHp z2%bDeze&cpxTI`tZOPoaWmFbhV$yxq*4{p^xVSi}yu4gSMn?1j?ePzK|!dh zs;csj(Uj7ZE#Dve)YjxW-J`a;XF*lPBil>8HA{c(Kh+R?Gse1GGY+S}Vh54L<&G2>+NH4^Bv@Td*nE|h&Ee|rLv7ExjabHo z5$*`BFB*7=gkz-cXDXs1bQbm50<$n!eWJmsjRY~vMFZ?vRtitq7(PTqaVATueS6ea z(Ib+>y&&|j_D+IMITr(Uny7_jWp^@|1G!NKHSo#a?7-l z0D}Fgu6|DCLk(ya=WcNfg$*>62Xg;sQ_e;$_Pp&a%N>@uMZMSjbQaF}q608|mPKQ) zj2q^i#V$xv`Z>wUzBzNDNkXobpUB%%zM&d-l*I;j&-dttCWwrV4M+~7Q>-_ur-#~d z`^zF>=EJG2@e_AguSglrMe|FGv8LH7g5Z{|y4^nfswSEkF=LW?rao`$2|*Br2QqYm z&AZvMMAIE5rYPIxPILU}OgeA1ua9MBh<55(z2?6WB6dUOAa&xU&j)CELPJQFjZmPq zl_sVQ;eLvfKH_*kK$=tVbqs|^ZWg?zzr-y-CXx7I=6(=RW{_lvS|(t3x*W!iMysz&e5{j^s@!;#))IwI%<#^RsjNHy533dp0E{Lp`>a>$f zy~iO)&Zo9!T*6UqH|h2AF(Rblyzg4Ye0khP%!1+A#WwX#31&EzJ5Z;t###t8luP%e zg({cr{5^WMmg^t}uTSN+97gv_H&)eH+m9l6S=n!atp_^&Z}qnb#-?qE`p;TGh}7{-x!l&yKtjV34H6)PSM`?{Py>$h z4W}VL$AJ+iLnzO!(X%3l>kxFYuba4Px%mCYA_7{(3{q)eNTtWvxAAE~y2K3v=lCEv z;88*-3n(Ep;KSFp(7OYA8pCKhMw-I2l{>FQ*G-j*!0S8F`X!8F@%NxqxTDy5S{(?- zwpASQQF)lP{b<^%nKDo1{Ra7*Ayolt>_TJ~`YHMp_qc7Xrg!R?BE$D*^ zf_SIj6tXk&rLkv;NeZ??yHPA{M!Vbk&A&X8F+n) zsQh5Tp)?bYaP{d}UAwxkgz}wkADZaOPv=E_H^Ev3T$V=nIXhBRM0ro>L^Djz&JdE& zrfxZ6dqYk?byb+th*;-mKFrIY-a-Wjto>jT&m*0q@Hz-Z1Tu4CO-eK=gK86~G7cxF zpUxB)O4wgS3d>%$jsz&u1kZ+7_A@TLXez9s^G7Fjjr-|hh_X*UIb3VAS~C^uK{NO< z$Xq{7YVwUy;Jm}uBth#QSLja+PpgZzRg^Z+aKy}c`yB4?@~F;+1(Eai)EY$dpcWs6 zM@%U^K02JbOWaRgwJ^YQ%ZkS3N#;^e*Vb2^HU@K@h3+7S~q zV8AcLgKLM!E~MGY2E^dASDJNaf}xbzjn{t>UpVbHgaquUmt3P~u?3kSHinq{cWn-t{z2z7BPo7;Kdj=};0MO3Jzb7q`?)m7=^YrMAjDg!${wg@HUCS|%6} zbe-B^*t5!%B&<>^Y*W{E=Wh~9Xf0+k!8(rLqkC#%SnwrcKP2=r%x*PG0qA{Dd0Zgr zrN}!Oln~YdAF2!wi#C&NiQmWusnS&KDM_>71twSjmti(yTMrN|LVP3YKsO-^C)R>@ zOEOIqfVo&)eo(DH2X+|SOx(Ksy*3z;TT`yH`9i4`Q3Z{}42y$|h=lSjj5Jw&Mfc!! zpsJ3cU%)2BZe&1`a3^*$IHONGeLok|rn|+C3rF5I_G9ySi0AWXHKuY4k`fI3n+%t@ zf!Q)8t?s@_*(4p+A}G%EM{|P-n_I%&6xM9ksM&j$rsG1J%6;qmtd6j?hmA*7PQ#Kv zYnGOOdDXkPs+fVPd7W%Mn-o-9aoL5rt#|nzEr%`ZlLzvgfyU|UNeZ(*XRaVCG4<7T!g?gZA4)E+Tf)P!1% zjA))H_#&Ts78_(uQWh1V^sY4|Km+e%w5uVQE#^>}noj2ND9Fem44}w~2Ba4ics_DO z$)b_=^)ataIc-8EJWQ~vEDSmujaOW~ioGt@bvP8}bR`cJ!EZ5)w}E=x(=KS_c{iwv zAMx+X8w_)LdLM7jYh#7eXqS9rKxR=@pKlYai#M?CI2^jTrNuuSY_VCbrOhQtm2LyZ zl(JG%(*@8>)~HNV4m(7aJ`<#=EfOBEs*yPhmXi^>T>z`<%r=fU0S}dPDz`tWiaE$( z$CY?Q`agZo#;6FQL+{k@(lX#@Ijh zTRZ!NtXJ6~MnNGSgn?lbsry1o!y9b#ZKpV{GYFi5q-&u*LzS{?XW{NoSrKa5u1QKS<_Y5zR8r1X1uWWlL$efqkmNGnG#0A@{`nI&j zekocDqYB18DrFNheZ{{r!X+o#>t}S;hvZKg#)rszM8;}qCqNcM>lIPwv4xMMrtFgQ}ntXk2aYpJrN5~c18j$ ze&)Wh<=v14&ApF?b|9QiGZdsXfEZCqIL{m1kD#_#qZf_k6k5P>VlC@jKZDd}Q7QHHQW=W>b!4U@9CMu*1qOr^Z2C zjXa@ptAABhXx(821;y^Ls@}3BwVWT9@p$Octb#3^vx_^j7E%fc1LA`yL>T_Ey_1+_ zL!n3HF8OpE(8t+O>wF5U%+NTnfMRFB>+M=GvY@fSgaS=oAPVk4&|1~K-)nsG`JaL_ zCYus!9E|w6iwr|Zn$TA9D4P7B(asm*BV@9gAavbtQy2mdJG2zrJGT*Uz?;onyBJ9V z!dTu`Zb9m^0!{Y>gS5a<<(*ee@diLpA);{z+=~Uj{Rj{!@IGCV;Z3Y3=k0_+d1d6s zF@s&YexhryeXnE=*8xFKv=)-vUV**@ED>kCs*I(B=ybqnp|{=E9+q634GSRU z=!}Gc_Tu$CT5bTa$ly5ekx+|OXgfbnPO{}?sDW?1EgL;sm#u?#e#N%?H?>uL-^Pxb z=pTnSS>h%}NN-r@k3IqbMxiSXj$Q478G*4IEB?+i#;Ik`ahb*8;t^I^G^HmQyjjlvhQ2ku}X4a$_LIA)3 zU@N(&OWcs08tcqN2<6mPSin%_#o9%AQaJw#=VdY&3&i1csqSI%y6L+E{Q&MEv#E;9 zucl1^Gi^mU93c`O?I4D2zhRJSErvbVYY5T4CI1$3!@H~^4j-ZdO!TZ+!t^aS%U3a* z_)y>RCKkVrw^!lzZq6Ty`w#x^o7yu=Bhou6WVU^g4GWjbw>%|n{Y(bk)DDK*QE*v3 zmA}8T_y#UmpS<#7{q%6)XzgsEM|#on_N8B`)LCVd@0_4v&Klc=9-f-+pnYtCn2i;NCH|2hVO7P|hwyi`Fs}SawFhP}lmIrUJ+v`H9>7PdGZgk1JeN1dh za{HqCV?V8>&eST?WW{JtqvP|@)00kD~_5=SMUB11tCLdyf6*yU?0W;_~hS)8tJsscG^@@B1tcj(XN< zalNCR{*56`N$g)jfA}{RW-N?UzE}y{Gf46A>^dc=`qF-)3`65_i3@V9Mo4~`ga+By zK)=OhNZ7*y3N3uMgiJ%>g5#ME@8)93oy#9LjfzNAh zl+u!_Vmc~bEJ4tyXAMr6k`(r>F}kYl!V>=aG%&_~g9N-9-+a3oMXdD%;O5Sw+Kq1u zy`R4e8M@z2@7UY;!u%u59!ItL{=TeGCHM^b&Ex57rwO_PsUA=yZ)t#a9%$s&Zdz@6 zMOCfE)2Y3CW0Mv)+P0q1zYH8cYFrPjqn}mza(L~dpV05z$$|K4>`n4i{a#nP{U~g5 zx2U4N-T=I)8DM#U^7%EZi_G7cH4pmI{7q|mhx=`5TnBH%6!))*af$Kqcjz^`SMRUU zBU8%^?>fYnLnGk2JGg3KK``pUs4eP!w!uq|A~bCcNX!^ zK1D}b6YJ~EG4hS+`n)m?f`58?$@P9~{UPIZpt&`+WhH68w1F;0c@f&PBPW9#t|^&*R? z7?K@d^}L}RXLmR%p**3sd`mJb@n(X}*8&3Pon?qDQ3D(|;%;ie?VQYFA9{4jhu()Zf~vJ0h!B zBkKBAQ7)^bosJ1(NStgND#7}6RQz1P)6FOmdY0sE(L5P!izsA)knpvyxvUrpJf+z( zJ8;+cpl#VBoq;m1{3N3;)jCbq$T)o}a1u=9%#!RCqX4&cP{B#8sCzpl`W1s)?2R^N z+=KRV)m}wrcB4kCL&Bt3<19;#ivRuRfH|Oxlc$oT4dfgK^M##8Lk#t!YH? z#utyF!+q32;=mo`KQOe%%(6BnEr4N_l3r4!fqgxh^1NZQx~i&58`Db~VjucibQR%r z?UUkoP`RUrM^(#@?Q<#;^Bq&LfVX!DRTN^;Bo4dRJ34zgws+P8loJm!UKb z_I%2^(H+;WknPvs8IajH-3#SpVAP6Rc@?1Uf?}uxy!Sk@!sqmD$|C$21((Cdml8z! z9U2I1NKH*m2^^^kh$TZc*xBtWv5Pjnpv`!LC&-(PF{W8CS<)6s;#Pm3SZaLl@L5lh zH_HNx4(PW(TKub>8q9u}*|4_J)*=*u8z4rwMFEtS;G&`1*?ORh=0efHVK@)HeYjJ|HoT4w@vhHbUMjyDRFm9sU3m6ZYvbnaw32aK99rqNZF{n zoDc20g9BQK);li}*m9mB6R4(*-nTjG8<^+8k|ToBf-{`7vzXIB?7WFOJ1N}~>)-(3 zec8yEAW=oxDna$O#aKH_LbHm;D}K+yI4aA|kd`6Mw9M9A`0&@?uN)1<9!??Un=9Qo^K zc{8`OI1 z8EO}jLxo@$L(xx#-ze+cXH()>Uup?aesgWsyv8LL5uZ(&|MX6ovu{Jb$>NerV3iXv zOBSj9a6%T_L-+zw_i*70KnAEG>WtRNZLE@?>&NKfdUp_LSS1i@akJ9o@NcJ*Ps8T( zM^0|JT$4=!^={Z4LBmC7(nP%%%tQYl0zvob)2BWA`}@J4YHs`gxpT}YA~>%1G;?y4 zdi|RCxoM?#Wgaz7qd8G2cxK&A-z}!j?vppj3KIHkUD@?sE1nb%#g`r{obTf6UFX!P zub5-WH6K4->tb*0>r%IQ|Lp-D-q|VyM-wnuF(*U2w{6`bwNWt+{j#uGyz-22=BnmM zSWJ7={wMr?YfRn!%sp!s+_3l!Q~`(k%mrzKC2k}|RAwickF4G|m^;to!NR$3u$1Vt z+B+&<7vHAtF~~f92U!RjlRvyr=7i*iQAk#|z}-2ETMj^EzIGO3B)G}6ld$a&ofaM= zW$1>WiF8WoxiJIdj$92w1 z>h0`Ls-WeZ9IG!I6{iKTg#nAA!1Q2ul(3P87qz6YDH_2Y!o0I|mIY|H_nv)#cINGYb|8ENBcpXp4KiEU?pIVMovtcoAh;_N}WaA!lkj^DiRF z+MI&Bu7O99%GH(y$QlCnDrNDNda2|h(0+5L7RDARVRc15KFUnQ1t5d*`f;f=h=8?N zUGiPQuo#EvF_qCrb~$h@V$T~JWq>*pIFB0zrg z=8eai)8Ar3jJafULktEwBDoY)1|LZx)?53$n7ZGJhH{u1p=B;PV~d z+41HR5LhNDJ5076b>59SjO-(m$!)|y%D~Y3oFkhY8DQZd=aZz^h`8|Z-uCua?I+U| zc*R1CvC*e-EhpjVSI}6A!kERgvHWd@NOD_CnrVxk0GB;2l%;&>R=l+;XH$WzIpQm^RTeRQ_HVzel8%lp&TmE&t+v55P|? zb^~Bke-6Yh=akcFNQ`f(al%B^wZm@7XB&&k^FZ3lNLIN}F-w|IV7nP#7JCqIl8&ae zo4)U6O-Qe-91hh>ror!QYP|%mH)G<6;)| z<~jm%pkfO~f+W$LQSptLA^lX6XlkU}3njOCEsd0b&C5H2K~Z{)YYZUYASmd7RBsqm zZAhx4jfO>k3kqeC0|o{-YDc*VP34C3s`uv@@aahcv@Dh)jD3XUwsn_|7kf&5DYi~Y zau8y~9!4Q@o9~golL=5~jU(++Yj7i!0;dr&} zjQQ_eakccy>U}V+MsB{By6=d7K7Vy9WuzDFYRGddShnNk1?Ut@5@`Sry~(-bKgVAfxjbxQ4%?+ zh#!moq0Tn%pZb z(MM(h0rm8Zws5S1BfRWo%T=l`WB zG#;WF5H52Cl`4&XUEe+@%P{ERo__i_y~4Go|Hv#I7npu*Z&y66AI+&jn3D=ugB$HDKg13)`)dE2nli-7J} z+Tx>wTnv<-fWLGu8PWfR!7qkSElQ`WG4@F3e$BRo9P~^;&~5?M&~F&QD#ew7-(aPH zdwkXW#++dF(Ek2@l3&km1il%s{L9@5YQ8j|(TN=axWDbo z8CFF11#`5U^L)9?Vc2iU(i+|zi18kkJ8j3*wt9$Q<@UO*|o9nd~4?EY#(e`e&^z3wZjH=IORg%c| z)f!SYX|Qq)CTr%6GB@UwtJ>jd`iEp=Yg}ptGWVI*8O<9^PPRoTRbX#|hFmAy3P(CG z;RvMl<|e0NGq)Tg4n}o#^=D6jN08*)U+QXZN>Ru>g)5;|V*9SD$cWih)I}H>v(}s0 znf(|XNi?__P^iYrF!SCt;0U}4AJI=OfhTuw|EfK(nbN@>^u^@&-q7$Spjj0wd`O4p z*y~F=H9|KlZpw+i`N0LMl#@}BWPZ~|@{l!}Y!iKF0DyDfkg(aVocVtUU7f`iMBeK4 z2>+hFk;1ZM4LOz7tCC-+1{9MNYx3J6<4%RQ_am2kvHl`YL&NS7s3?>%7kXQP5*0H-aGX%r|WZ%_LK2^ zw)phA7b*&dA7BUiV}cFjpUycsIq8$?MCvU$dHLS-+g^%7ii_c@v(EP}UAols`Lq7r zK!5*+)W7;y#6$;L}^5!tgOt7)~3F%KilEMRQq7EO(zD7IB#zkssSVJ zD0|4Ls7!TxQ8ld}Ah}@f(pHgfiyshpR~>XY*ygCa;Z9Z9d1QuKiFR4}3kO zDcF)`FsoHE$UGQ^cM#j?R@ddtLX6Z4Jmi z?6#*AXs&beIpN>Fjo%klAKq9VuerUIEAxsCw04|d*1+>E>XYZdw8&eai$I1OdloWM z_ey?0;z{qq#dXm)270Lp1_Dxml5OfGTNMs(2K-60Ea^q*2o`yqVJKK8hCjlzT_L{- zClv*XXcVJb?`U^R>_-C0jy%jXc+a~`5C&j7%E<7rVVCxgAo2r@YTP#rft?R4$d?RY zc6jbQ1`s9;F?U&pl0~~*#SKz-SX2~%(?*xGAom4Mj02z~c7th8R8`qTgw6WyM7$oL zO~9T7(pZ0JOSP*zXbCLdiw%qjIIA&xA1M&?a3YV1I~y!ahXRY@7XU=lzoabj=0MrWPS1%vjNfD99J12cY#D+^i&tT z4!^j-Pl$*K5cm^F;a(Xf7mbRqwIXIgs+(JvrS8Ncm{g3wa;@>ar{XN@b^`EjuL91h zVnZzBwEguRSdakRejlD+b+3Vt;CoknC=5w{Fln3ljhT?3(3YuWXezAgpAhA*KMP_8 zL^6wdK#f(gJedP9P)>U$wN(m`u7GY?_Qh<7+O0mGdEjgH&zLGZY30bWmBOgbNg&T27S?^;xZeax^s&alzw^s<`E1t6c2Q zv`N;u{EJ)rtbDVYf$!xN)>qih?TZHeJRxPmfNezb!L#XB>+(PnH5Xc*u8cT}Ec0cdI@g#hgC+)SZ>6N@}=onia z55O>h$}iwv$B=VcWLgw&LFS*i1&{R z8c8zxBlPYwi!%Y~^7HZH)3?VVTW1+XXg|l?%BagxG6+T8vVT?@?|h(c0`ij+7rgm{ zXZApTzZT2u%^6Pu{$y*6IJ}>keWpmv7s6H+&3{#YQ_u2eWX~P2ufk7V28_`Ob$5Mf z%!cjL5Lz*U3|(wQ*oyg*BZ3tHNfD6c+!^7v-+XU?4t{K%({nNn^}xlBLV!W+-9^|q zM_}`6+G~Xzl)>a~ANskrh*Sq#KoRI5#)$Q{&QXlu+Bl{K<^eR(Z+oGR9B<-tAJa?0 zv`adQHHd+YaEoUlvY>=0yvdVOZL6BeOZ^ff>h#COgB?YRwMWsy&E0|trK@LZZsL!jptOB?FFV6x{be)H5X955?eoEES2uBmjY*nX! zadcl`tpj8!1QYaIFi5sG*kbsRCbCtf?K}~ndU{Y(J1j(yDqX-QvpWVAQ5tO_)vl7P zvH7mghl|k(NN)fDt3xjM7S1onZEI;%3l!1PR_nKSx(^0&{%h>;{d;+etC_oty%bgu z0rmz$ew9%ildv&%%q+~Yu*(6fBzOam*&zK(2MC;u3Q)b<>wtW**RrFXsVFPv1nA48?i(kE-U5aoA5Q8td<5oRHE#&;SIqCG z&wCVnaN=2acg}MrlGi^>~oD3Ow*bzlYhFriw5$)&0 z^?f3P>CM26k~|iY0D))}zE#EJy`+;N8pQ=PaYqyIf&=P+HDg;`KzmfoHqb%|28)Jl zP4~46WcpO0v7A5yv#5^1?6RS20A&}j*fX!}85Xl>tU(x{u)0+qu=>><==;11Sk-m5 zv!tkhfF=MGU0_%Y+nBtrz@(RPS6rOgn0<@tDw$WU&ne`dxj!qOeR7wb(CJsAZ#>)W1_eDjcPZLw;Ah9G{jZo*qg8@l z{cUr!ZPjF99-=p+Xes)2?E4*q$p|@uBSpvKt)LLy!H2ZOH<+-mjdg!{> z#8}nd2(a<_hy1lC+o-goGqC5Moe|M5I+=SVMaL&59ypAAoi&frDhjxIw-ngZ_Yf5v zbF-<6gy#{w=~)e&_Ideodi?X_+eu$7u~Ibm{)e>Lo7uMbe5)vRqRD(#sIxi#`}gk! zQFgs(FZ=pQRw6>S-DH&QG7-RSo59VxjRJv6A)$f))Ei*7Oo z^|hmX4-{Fa+gZcHx-^>xBdEAo*C=0k;d=%&ifg-Mn^%b{Q-NHg>^uAx-q!i6`GP4S z=<^l7qkj{A-X}R>WDKS+b=gkAlmo*ua)1%O}LfS~E|t0Cwu!C8u%Q zlPjzzL#v9){{@==F;&{^di4dR*Gx&)*UzzJk}GK8Bg2IOKs#;8;8OG1Ok?nJ>TiIQ z=I*Of{uC(fS4zKZP866HhhLVSn)x)Q|Mb$MCuS08H@_A5?3=k<3k8FoN}XgGv&s@i z&%)N8sq3gwIF9N`u@V>y?bueYrglmy4vrl7Z`j5T&;J&Le0c_Mb5etd3VLllR;6(v ze`@HOxkvCJ`xNwt)z{^4lSgS*P_)zEN!tjM9xH~a1o6Nzx8X&3(pq_=+Ftr~H$NB5 zn!j&FQl*CzEKjHBp1%LPFmeHI6_hJVe?ZwIu5YZW>EqCdqHc^EYry{`1m1 z0wyc4k+5C!ZTjSf_=!sZ%0>_w{g7OX#wWz{y2Seg%N3^P>?8I8S~ue;FRpoh)a8lg z_{}KEty*>ue88_SXiA?X+ReVAgt!htgd#L8EKKe#|MbP z+-Fk@C3&+otRrr|h*q%YEiEazDCf@d#bn}V4G{3JEe!bpLF{y} zvqQrwgm2vx(6G^G%n7=)(Xph0cw4JRfPc>b5jf$qIJ}J* z9S91Ky*RJ%--5=1HaRx0(bXPI$Myp<@Ej;#i!^=vqsg)MtO~jowM!DoEYY<2Ak*&R z-Yf>hOeM_4&?Es2XvPbCr;C^x1n@m&;XFLCh59`)*femU5JMCnPV_E ziY5$f%K1l5;sne^NmmfwPu01;k$~fUwTh(!zp9F*w#L z-%tgVeNi#(kI|=B^6h7fsP~}#MuxbZkkS-4Aa6rP^Qt4CI*3!bV+nVx%32N++F*b; zhBP#8Morw0z?)DLz15I1R%`55(=P*dzyU9VRG#TtaV>y=q%aUkl252sJ0UyQxCPIu z&Z=qaSv8nAhV8cKp-(ddAypxiCdwIk7qBC7PPHP!*c7v&vha)W%t+bY@EDR!P9l-e ztj=qb*%J@=w)R8Wjz%5t_ty-IA4&tdP!@K|utFOI?k$`n)qVxQNv8#=5r^-rPrH#X z+@=|c!=?Bh+`aOZMDfX53u_%Z0O}S^<&UA zlWEP{$UHvm4Y-v$h@iGbNE*Hs;V&v=o&SxrKU(+4(=@4kjinY6A~^YhTQS;9vdG0l_p3of#&RO~=OY+aZmKKl1GQ9S+|8 z8W{&fIRFu9A#qf`{p;rF#zD02{;wOYZtT9L4GW$-Q6A1~Iz(j85xvWgoyxwo@U?XL z=5%Gmp;qHwMi^aXzf%7&^(ZeXh`x~PUoiPOLvpjW3SrV`rQl|z@%?D!;JL)n0+{ZE zA_i@a=r@^^Q5O7nA0hu<=sCjd=U-A)9_Z@F9DF<+^2~E@GJNsq9!g$NbTyxa5fiNW zgWhKbcbo4EK3DE%^5>GINvV>8w8^_;LWjMPXZPpN=RYYp)}WQiM(oD?r`YK1sL;~~ z+a)!V)Wv3bv~YOF?(Qy@P8*9Y#-6MOlJS#)jOG4&0&Guz+z(@>R5^ZCWRv1Zx^Q6& za=Z2A6p)2qV8X?a=$H9d30#7NY42gj=97?wnNug>VJwWs0EVZ?XuGNM%=D=r)R7{A z%7}8Te)}1wx(M*fh=SW{W?I_M7fN2g@H{Yj9KVlFF)&!TaQGdYOm3JkN~kF&F0S|3 zS(w1UPvpB^$;_9gOlN`SMcw8)W3Yf}Cbr2EEA=-WX4Nzi2?NqF*~UA@)q5lYckemNS;rG8N@e(~*F)<>y}BhnN;bwEv~uAnm*@rXFspU| zDyiI=<^;`z1iX&fxww@KutM?_RQ?VNGd0o8naVyj3=Gqdtk0EWFzKgla#YRo1Y6#! z==2vRONkjVrCP|(m2=*^!v;FtfmzJBb};U>)$04HX)vMyu7;efb<5khdt(p6P<(d& zs_8VOT%VdaA)FV|XqHO3;SmYwtbdrkTIRQ(^56KRO>BYP%A3W9?vf;w6Y5@x(12Mu z8gL_#kKz11Fb!r|JoV=#Hn~=2WlHiamz*#a`(tIZv|Wq3n{Bz>S|!=R&`&ws!n@O zw6MlwZrT18g3inXJZwrOPCPHkG7d?BdPE9t$(~vZ5i3jBZuAdjS?Jw7LA3&=E9`5f zKUf#_O;POoBsCkYz-!n|IbIo(YOb-U<>vK%kHI_;l^xgqmx847OK;a`;g#~oKb^@T zG3{ymkwmv^NWQ->YsMf&ifY14AD|TS=(|OorlLHHE$)~y&iL1KqA{tF@ zF83{)RTH9Yxz^OxBi-;*@dm4g4A^cIXyI=li7a@Da#|t4JxaFwmsMgn5_NUm&%^zO_OOpot8=!?>i9RuLBPtDFgQf~= zN`#8pX|h{XUze<-)UCqpAbs$-2DNo>pi#dE=Kl;c*RzWmJvjKavT}3s=ySNH&sJ_D z%eKnSBWKH~+*QocPnBYmUtepF+Ei}-*f^>Slq{JCn`{NGcIYEE?={vD10OPdw>TJ( zr4i|E`iKMSWGZX?QQuW<^FL@h=S`m!vE#R^d$(@i`bqU(dvjzBXUt-fTh>w)AE!>z z^PlpruRX%#w0!wG%z1+JnUJ6sw|6%zRFLg7hs|2cO`5{uMK}kD{D9M#El?y13QV9K z2o4JWKKzU>WiS|Y1C^g)=Y|E;P&$hS?$Yv3l=sE>8*_l_-%!UX-WrhfwdyKc$_UZh z&jtXdX~1wtX}_yHB*r!qtnHm&7e8mm_p1=<-F#jESS>}S)@}k!d2|3FTu9njXA{+e z<8U%$?Mvt$6$dOJ3Ps9He$)NU2ysimx(o3|WlOaK6GX|ekMSI^Q2T71lA|Uz#HqN` zKew$U%(aQbBQUS6NC^-!8$-+G{2buhw%Ab%khY3(BU~+;&f4qn$DZW_oXfzp$SYU~ zMX;TB^lz0{gJPM}TYz_)b)O^wc(>2^*+uXv4V$&7d$+IMU1MhyXwJ9l0_;xv4XS2> zn0$Qj8PLdK6u)$+rY3SdO{d$(^gTO{(2PhH@rVo{HQ(oZ@sf=h-l6V(-X6=W?0^&n z`{-yF0H%c@wVN8z$rH_j{;f462|z@}sA$C0MsO|{U?BIK44Ty0FKs$h}CA1 zPpf*j+ZSpfJEfHkQUO5$koXYKYi-JhbsQVluOvhjLb0zE+>`)MIHcQezQ`jo1~A-% zE;8apI@={_29yZdy0@B$fkc`8jD7cY%YWQw(1dxGXkYPT-P+z=<6fIZXHpOg1(-H~sdt$+rzDAnTalz?8Z+=` z;r<4!a26FCR)ACo{M&#E5ts`8=q!ZpSldzn;*V6`q6>_XaB-#KV8Qj!4L6!0D?cj2 znG&7|p{1Hy3&}_sQnIIn;}BuW01%M20?fFgE7pL)7C9`g6c4C8j+@x~T5T|hp2ex` z1bFUQUDY{og)vfpJY9TN(A!olAQ{*aT4#4(+aqvsO+}$OG^^sOe0-?N{;`b&fBSqW zux`t*F25&s(98)MCwh`jo@q;q!f5_sSuaWw1bq1P}upJ6@~ zH$p@=tkmum@A>L=R&GUEHQ4KA*bmBPE{qV3$~=$Cj1vStzs~bfH z!#>r@CY{8Z9@lpPmk#Br7uS!zf;D$rYv^=_)qcGn0O4r+&=VOSWOWeG z=#9-D9evQq0HoYUQABb*`bcHRNrJf6t&cDx8vZ|&y?0bo*&8)HV?zbSNd%u6b z@m(u_uogGD=RBv}^XzBueckRbtmzHE{F67(t|Lj%VD=CtY=(sYq$+fMnOt!qwH)IM zuzp=^?hB1U>;x?S5-mhM_BT^7cg!L7knDU;S*gPAAYjk0E%=@L6rYe-UaXv^vr{>D z@(@UMoD`NTxdubHM2B*c;>@22QyAluWtCxYUHvK{(Kq;=N4#J2-gLJWf2yf!Q}PHM zdyVLgI4;Ux2^im1P&0AClT_pie#yEhrbJn*Sk~R8oLflR*49e~`(A`jH$s_&M-=GV zGU5&0HXyh}UVvG5t{CfQNeoSz@?Ays72u=+D?3ru8B|ucB7+>euhU%>>?J#Q65bgm zn}+N^w|@K@_R0`9aH7s+V~aW1C?=6t^rEOISyQSgZeNmFQ&m$X z+bb0P<)T0(SEYZPke{svB`fc}ju^L@$;&j)!i%~6Y*V_KQ<|AaApJSOFsMT@u8!)|$0gzZvWsywT&glvcz^k?@wuJWd^? zhp{}Vc)04SdPfoU#{PG}>?!SWm@2K93ee~vbJZ5o>(wF|_=YQ)hu_SW z-Yp&Y_PoFE6kqj(uc`m%rN^9cJjwax#WyKtfMTDU$u&Dz2wmmp8gXsYVNy~?Lb>WI zWX>vER?gSTIRlZd2-$R}s%u9s&y*Y*#&;L)+MFGxQ)I5&Yie7D22)|>A6rh%1=Ag> z?uks(07NvRr7X-Q8pD$uy!~wUCr$0c3u9F@pEj3=*&W_GSk`&g?E>$dF_uuX{j91S zc4(v#{JASh8qD<``#tvNqDE0oIh&eEG&4s|vFZ@P1<|YKGHi%O@xX7z zjFh69mQ0I$3K`hnnw+d^^raxKMD>digQJ zYlx9aY_Mh-YgnRV(KTIc(ys3Q1`_*D1C3U7U_n+Z7lajE`qujx;_O28jU09Q#&=2} zBVd2LNrHm)-FJWczf#5jKNkGI|0tP&q>C{Xb}}X6Z|=i zJ@P{|n?uO>3P2n)KSv761wx+O-)Aj8VtteNG*!&Gomo=T$gTrWY_ahKddz_^Flt7i z90Z(TD?G_GA*%W5GfkVC)M{#%eSKHwSkwmK8u1QRH1LtV6QMg>$oy2e%&$?%C>}s( z3arlHS~lwR)ERU73|*tnZPV-gBc+28WFVE-o0X5K4VYMOO5ut(Mb}yb` zlVWxPdPDLxMXsg{YX~8MB|gsHnG76AW-oBu_uu*|lo7(<{4U}BGgG_iKHH-yo3%)9 zC{Ky+s%-7Qs$SFX5TD^NPI(Z>L($m#C)Tgn^1H^vn)LK0M5DjwMj;zQD%s~avBUJs z{oy9cA2v~b(081bQ819Sd}9xsgVD=;R5p3wKEQ@)rB=%%f1U*$$3*55^xd|idX+Ce zsMH0ye)qbte}Pmh`-5dVd+`xw8(_OS*hAQTZEZt^O54RU_M6(hT_QAU9%sUlx$oo> zl$#AQC4m@?R=Uo%XKpuhOQE)1ADgifkaqL3xVa3Kp?0(mG+gbHLAxkWO9*|qz7+LH6N+9Q&Y7NV|=}JZzb(z#*9klz+X9v4Snz3e;fZB}< zVgP7O2Q$&pl$$%m0#2H_$$;mI3g^CedFJFo<(K~V%$prZG9s{Me7Slz=+3u?N5_f# z&et{k8{W0Q4;Tp^ks-&JAboI9)1{5D=paAvM-hub6*g4+h9%IvZa8 z-n4pmmoPmIlz)@yLR#oY+PIZI9e=KFw7!=fR{FrD zS?ZvNz^feAm=e*e1uBJ37TI!m@Y82;>Zyhi1ps&KiUAO=%VtVn!F>NY#@PfzX8!xK zaxG&8ZnOE3p3U0jORBQU+z~4;05Ll;t7HG;upK9^Yo80}A|&Zgyy75l*~WuCn?T_c zYAMT9es9OCV9(piUH7{5&8gtV8=@XJaBu1)-L<6xbi#F39J~FMY`x(QZVL$Xc`pCH z3BM#tb}@`F7MJ!aEh5!%9?om;biBcI`y78|eJj+fdf}4C4b3I(65F}g=A3bno}I0 z4Ja>|H~5q|zj)zI7h&r0?XFL+?mP+*rC+k>wRrmUH$ZH=0W=5H_45pjF7?l<9dcRx z?#iRiL$(#YKTTP5yfYY;nAk&I9pe;GI(MDJi!eJxa%>9I7R<@n&5Rn{|Gvf690M?| z&hqf^J6qBE)(${fGl6h)N6L{$WaQ)w?HwFcwjOb>xcv!(PP;{C4ZRIgwlt*nHa^C8@u2(%#GRU&uyjOr%!e%MpwA-O1 z3DR5Bhx*N5w4^MX0;+6?z)dZ?P>ur#IBbX0u0r!2PV9dqM8e#3AhbevF~u%%FOQ6T zF7=yo?6n%=`a<{xC{0)DZA4=spd%g3nI~H2EDiVc+y@p(`r>(3`NP+OvmykR01Q|r z&OLh)q?S?V$%mNcU=HmeAPePRwl3cJ$+lH}<|0(f1%Y7R(QmN;4kW-(O@%3Gh#H1W zDOcO7qnk4ZB=!KYDvAycR;3sxkJFf z+;B%>rl_vPoD>}pQr9E2G<+(!fLx%pgack%fXhxENG}>7T;Ndnx;o#e*L^+U$`s(O zC=Xu)RT9;A^}i~rl3({P{jXrX?cxEKX>+LT zXq$%uqAL&Y8~nN)C8x%3LE3s-2NzZT6ua^Q4s2Vt<_^;Qa29>nNzq9UxBgMt z|JfI~CX~fAc8&s#+(K6Ck^bS6%P$r#$ULE+aTn|8l6_|i7m~lLe_rw65%986+p2pE z#9q?B3Kz(D|KZBXxD>l^j4R5*jr=JN(73jb3Xhn!2yegfvm1x z4#>36Z$=$_Q+kQGpVsZ~1sCsqZ^KKA*BusP-d79Hil%UYEMLMqN-DN{2WFku!KqBj zOcFWDc`eWCEO+?CYIn|FuTJlXk|7L)1=!?qI=|!yF4$@nb`0ZfwuFapRkiXcf9iTD zA^nj%A>w}8tadS>ReNXmO0QP<{8NFU!0tCX3o6Trd!K;432?7lj(-^NlPsZgA4&!o zpP>4Ar-gS)^AO6QmP-K>m3EZ_WLob$&mLUen0J-g{L2HlyuD?`gOuuoYNX=+$Ffe0 z67uhu!x^|7m3qiOQy$9|zM;&`xt(Duc0&4bfPzOGFT*NUDoo8^H}k4aWI3qIwL1X2 ziPnsn@N{4tg<2(AN4_UPRJ$x3ukgtuyU)zME&(v2&b3%EI?&I?Jeytchfsh z-FEe-Z?Mx&orMC+XWLAHys0-rMEMi#4?jcp!d1-@T>=pQn4*A&Y2NkYPK?e-55`F3 zLS(bL<5iWQ;giq0d=?zxXStr(*_CC~lGR`bSZ77CmS(I@VI*IKVCAJeFa zdyOieE$_ecXw9`Zr*7!tNT|eFH|eEc5|!T!eHcto=DTaW16yo+voF&s{QkJdN~^l9 zKbngc2P2@$&kJ^eO@eUDbKx=M10C%#Kh7$e4b`h>{$4 zowHE5x~<&Na6+VchxVQwU$*$Fv9ykHhx(E|nbV%|XFpDBq|^N1Zf43ZT)19V#zrcj z>$>9bE-nz!uD$Q`6ZNz)|++p?>-PyfiIV zA$c$?<3Zy;@D6oyRHF`x1o3Er+SqX7%AjT_!sWWiCcww@FbOB~^Bv%q9=GY0$0-Ae z4o-6<3|4j$c*?-}scC#&S-8(G0eh_NpMxF-QcxfEL)WJ9)D|?oCLJF6?otK%Xx+|#HwyF!< zc_?Pp1^!c0#;|xpMky4nbj6H;zz8UR{k_Dic_st+EgRs z=7Z1j0gacsQk!DS<+{!2P|KxX!VxQXwe5setzy+_S+Cz{p0FUD4DTxEwv*--1h{FT zM^?07L9h&zx3F`^>O9xTLA&pcR@!Br-aEq8RluE)FMPpp;hnXpdbvnKRe2bEvupGk zz@rI;n^tG*LWFGN4Np)uIr$XqK5*7`1sXvZ_AWry3-PSyyK(dG3E)b}D)8t%6AM@b z>c_dOq#9nlR+6Ys3M~a%(-Z6{s7IH?Q9fQVD=1AgLGLj?Y>+_4XH?Mp^HDX#f$ETcFPPCBV#lsMdc%b0>?_ z#M^5!zvmdv*+SdJ*7FAMc43*r+4{S}=$@0YZm#e@^{><5oRq8F)!G~F8rl3%vLN=N#^i)51^H;0u(!6w2V&&hw8k#v;Xx;;16inj?ovX`3A zscm*XvHJxV&XrnNMC1ZA48~@v|3g&c4UgUywAIfL@V}`pEiGe0q=51#(rIPeGDE>x zW3lB>xI*4l#WDlHVjl={e#|$gL9gJ7KZSj4(HpJWyYP77M1^BodE6O31#c_SYORgG z7QckOz5)eaTf7w_Xs)F!QJj$4G?x)*Rrt!hgPk<`y@-FLl=n+SJoZ&gZ-~x9c&kwI z8yp5$LVbA;MgT~SDbhH|(+}KW_Y(7XWvmse+ok@!hK9%#k zJ!0Rqjy6q{+?}C68aFMeDM}N#DI5%A{#`Uu_@Tw{kVEus>zF;H8ow4eY3CEXalc@f zO+uPv_%GJrHwj^#ocoGDl%{T;J*Tv7w-XXV*QP zszObMif6nR1aOVg&&R5(eODo2J}R&)SJG>rhHKL>zk3~dP!shdci50UqJy#0PcHN+fDAaauma_hs|alk3h$L#h522?nT^tR^UTlW51&f*(pO z1%Ch51tD{UYXfs=)ar|^yuyi)*4s=H-DR1!$3d8d9kTP9vrY~32fT|ZnXc%*Obf-? z0opEsh@I@r@?97FF&W$Wi%Rt4jF4H`+wKKiar(g4dA9!_<}1JCPnLZISp%Xd zAh20Z{@U4U!w<-yZZ}SmXEx+hsiy-wen+TwwvF%lVpP3u2v!^Z$bf($2Sia%{velV z4F#sP#b@l!H$K`bwQ%f(R0#F39UD?hvBw5?ollU>b8bkAZa9^h(tXD&?0r)nb3a&=I6tt`CP{r0RtWCK2;NU z(T?2f=^a?0jtkzN{m%LkJ0~EVJ02olZKc8789;S=3I}{!nCo#tU_L|0!ar^CgWiaW zmKj05qgGM>C@APImkQ+=9(v$m|2hFCi`tK{{T_U)y}(O17lcm%o?miiCUky&-p8VEXe!9!%3B^t2`d68Ucv!B^5y%47)(jD#}g9)?kQ-k7HTCvKxx^>2N-O zJXu__kr>FSVpnbn9~hw-8Xy~0DIr)Gx(u16jt{Q=GI^+uT3LCPr=yj5`_EHNFI-$o z*IjEw<&^Rf*ylhF1*A_S60910S6A1a8$t)MbzLuG3QN-i7;k}?%iH;}cb1pCdpXY5 z*_nK2AlRzcW~LYjonpCn{KAOyz zS3Sv3@Ra^Ttuw&5i}UhA)dBSBVw zN%J6fCLD2^(>7AZtn9Cae0i9B2^K*W;H{8J0}_t?xT9(W2;voTRUVM8vLVMh zEoB#gZJR^B!qo_=*z=AS6-cjmkcTSAan)x_<#g=rT84sJXI+TYq5bdE&HL)t6}0sq z4wM1AJ7wMqy$26=JeFHF;QHu@thbbt+0paYXa&I{9Gn3h9yb_D+>rrKoZAj1M++nK zc7!f>uJHS>;%gQjmjSi~;R4h)84ar5N)-jmSl}jWdM7Rg+g41aoCeO@r#W~2 zl320+zwYN)vAoDiQ;ESIJ}?XRq-s zJo3;}yX7U2Xq9o(77{_7_;{cfs7J@F{4L^LAlv5S)kc4H1yr+v^|$J~5QN+7phpYf zm=^GpX4QF`bnnhtf{w+ZU%GQ^2Z)5rxm94uTr_5-u9`0|pO;N+2I(>@8rD^2*B@P0 zzDPOk9aTceTR2f{b;t5Jr&g(o;v2&NU_DCWlmc~m4vhsmNd8H0Q~DPk?^rr(>BCPR zbv#Z4o`=_}Tdo?S+V~EO#ecXOGVek_ZQs%57tMYfQIE3UtH*vBOXiMyU?B_P+X0F# zzlS!5j^0lq+<6PnE**Jubyeu*+n6!4IRgz!cZ#$oweD+!U6oW}N$J?}s`aQt7YnN+ z5x@N2U|JM6`Sj^biTEXNcZY+!O%0tAcH0L~QqVs!9CCIKBvRIvX>PV&a6?W_^8Cw_ zO@%Rg$2Ff8Dz{?>we;13($pdyq}w_G>M4Uir`9GR2gr{$uyB!^U&2aA&BC@7-hBRO z**2l23l0bP*P9Zcs$SNq@m_>tH3*8Gb|~`MM)e=T~X}+|xF0GwFlT`gj!V z{jB)Oa-nH|i=cshO>}+l!X?jm8$6J5RRW=&mz_4S;2hE3J{jI-+IO8MNPWK*&CH2V zDgtVwE|8c~GlB8TMVcLNHiuYp+Fj#?Y+y%v7eKPwrC}np+0>pRfmXk>tn+SkHjVrib`LxNrFW!s8|06E*qx;+Qy7*!TAEpq`zFG+-e7OAVmS zp0RM|3!nCA3i{4M4BLf^VE$mQ%^0J1XCd{Eg~~L~CKkrT^S5!bhg(=VzWK7fxQ8C1;h(r=L_IDj3jH*Yy#rUSZNv$%0`7$S4XKAC1}SH&}R3|>ad5)?fapupPK z|4t0>m|BMinB@}xaIq!ejw)LN?dpOl>4IPri>GqKX)b8>Z&%^ph~FZuWKphDsw95S zdGA5;K{PU_*Md!{sCvk+V%#FN{%&iKUeberU5;$p|F>}Gacoca99R&A#$$5*=0R1# z4_7YQ`H@>g1qU;Q#`1(71gz)<$w^Yypgt`J1fhvV8{{7l*<=)` zdQ!eeuT8MRM!cpNC4?_rByVC&5~#)nfHH9tELMTrUr>YXn9!!e2>*Y{BLviz1aI}y zB_DfjPHyLgy*DH62RC)dnqmo*^UKAhR+(Us!4fNtbnmD+T%v_iOWu=7@PA@JG2U-HTptL{IE>BwP6HxH;TXz*CAoROP^q3VdqI~RNPmRlr@pu&KC4wvJ5j|+N zG!k2yGo~fQB22#_{fp@ z&u>B154NxND+>Nalir)2roYIk8|z7hG0jaf_T9(F&2Kn}ao{;%0#Z4my`2yK)iym7 zcXM!Qn97)zqUZ*4(xB`)jlvonn*-3Vq3VR^JJru<+`;b6sp~V^lqcNhR_-cXVf_rw zWUuy}yX>89If6LJ51G3y{lZ&fnDj*Dvd_|gc(1f-zlWFo4s@v=E2|v1Lp!;ayNl=R zO+8MFl_;nOJrEa)<3GRrx-=0Knf}&tj;kzS5Y2e660Lz3ZLw~Wt>;lJZ+vvRaKHhS zASZdzVc}amQ`(I;TryZoeXHH3obLvqf3u%ihU$C%MTs zhhD$0VJ&6v5!zDQr;w(+S96wBZ__PME4{!Z{E)Do1U9UqPg0>G3Wa1G<~57}G2_&% zwe|Zv3QACk^{=Oz(Lf{tAQbPHF-$99E^e^@r$^Pi$I|6#Q%JIkxFx-+(*TWq(3X zSvz`E{e5>q7mVP*dv(mCbo%RLxVL(OVT<%Uq!(=CcTXT*KhOsf zMjZ`cL1yY_OUMzOn`+(sFjMW?BBZ6GrZBdicxV$lbui0*_+-fvnu9yKn9Lcv_|;I; z&iVoSOW?4NKN39A#Z#O0nT3P*n+jPSy*|lBzUzb;!Rv+)%j?jfZ4Ieq*j}Hk(nmF$ zeOtpa>JQ~<$;}sGGQwAqrIu$XDDfoyC+hK_EHjIhR_S9FColYZ@`9I0_leilvo4Co zB$A=g6Qd_~+O}zYs+kJ+28ge&#HYspy33nzUP6Iyfb&6VU!QO9)@!y>Z8CrI=dhQ?2uSqzwsAzDgdDQI+@hUF-?`~*`fa3iv*3ZpU_ zQ&W8IWXQRsbAGj$%}!8)-=X6cX9DI~ZQXMC@n~15aqtcYzD^gbu53gL|F1h9HmO-r z``Zpi6}hX(_Qkg}t>R0HG)#m9r!3SP_T7{NeN3b*l&6GS*{FkPKJN|$0FZTwd| z!Cx+fp;euEXaiA&*&@z?52XrL_~#+wdu%{cDJS=A&tjjBPb>Zh+phn8|8qw=BbsMi2o3j;TRj_6#Qhj#VBOB>22|VtX1ZQ1 z{G8b>uSgV<+7&Jqjzb33{&5d9Kt}YNmRcr~i0 z@kRwZeOhN;SZ+N2;tvGj+>wD_%WC*(m(uDsY3mj=SN15jG7WpczdBtlhfk;Gln@zv zR(0--K@Yb#X0(Cc$1IU!nD%2+#aL|cg--F+&Tq(raopl)dMCa+!2!kTda$&eF3fgK z#A9h1Y@VTeD?btms6~E4Sz3W9fCTL*szPD<}z`MR!qOu z4GZO}v|b%kNU18AUB$vJ0%CLbb$4nPywrp>*`WC;f=i!+unQQ7o>>R576pm^^BQ|&hC?n{9 z1Yf4=8tb1II091(p{G-4Og8M99&Qf}ND$sRGWx6{-O>p3HGLkFgoL}9^;@w4I^?=4Ha}7Fc z$SITEPoMPd3m?MQDI#6#Dlp%c1CbOYmX#8-v5xV5>}F;^IZyT6>(j&o9!6UPx3JXG zRmS>*mo4sIU>3)tsm?2|E6)4`YLqrNDnA!4p|CTApularNuWi{ZX))>Ts_p8!u zNEkssa~-=Lqb{#yCU_uRwFlR=Q5>K>=@%RG@-Zv@K9}v@`j@$qKD9h+h_sYkUDz(A zx!mbSZ_MYd$lRNAFr~bo-yWKMSQQdAQeGY>7;YGIFx3?3`_6$yczStq9jbGXrLbZ% z6cerP!;@MnsByNasGLAiOc(Uq9HMHCC9Z}FsEFOXQTkkOd;rziO>Ra^pn{y?Pb*(M zGK_G_!JpT>QZqc9h*{7%v?LuHO_mfD`PX2F}A>JmHKLd||=87nd}v6Ox~D ziBp}>x6@=l3p1{3DvZA5fF&u_SA~Qzty85x_8r)16ck8S#;9X*>3i7&t>u+_t%EXK zoi0wWigfA68`i1))n98q<+`N}UkbjZziOFLQVI=hZzsN-eS(0R8XCq}rw)qv$QfLbHg2<`Ro{Uf%a6pf?cg$bwNx4=2g*CFF&g;edi~>EFQ-5X-a74JCXH^ z2-;VLk^<}5L6Y?60TyXlO4DbjJk@fjhW4pZZ>QUMM7u_jd+hPp2~HnBE9GRWb1tGg z;`T?^)*QOW1qh5cfII=8B|I{8-b??>roF2umbM#gLljGaO9&eu3r5;#ic1ap1- z)XFZ06Om}?I*P@z3SY+^^wS$wwbqBn7pGP=%%zwU!Z#treHhdM%9^-M2vAJxt9BYI zjj>PtIedh@S(3D=~2KKoFd(MfFz-!GFFoZmX65MF%O8pfe==80D&lXJq-nWjnRoGR($K30)`6c(Bp3#d34r(gF!w5y#_G@NdX7KGE zh)S@S=R3W=nC{}S=mRB!cjDe!d+-G6772eZV-1Y@%EhrZREmSc39mJBW#;rp==fl# zOU1LcoT7ocwRS=FJ?0abzGKf!VZnMaI;GI*4-|*W6)N7gI{`x;ZSq1~*@{_Wbv=yb?H$UaJjg$mp$YT--CL0G& zA27bMu4~p0zw2}{rx_vj=H7~5Sqzwqws;Pf2G0kcH@!Z zkHOyy#|No)!oh~&pA2T|{d41lABZuFS%o|e?psxMK9P2K@bar0Q&ZCW`gq$PY^d+P~y_k{%dN`;#Xz08>3LPZ&@uqbVxTic2z+n*Pg z->1AtNGV+1w;dHOjd96%K6ez?O|}v@woYwpA4_CM$4k)bt3=RXqbeHQ2TR`8rwsR( zg&t2#hQ@=rx059rl)$6P~PcyF|x z`d!PNGD;`CqF^xxD=@|`YJ=I2tub%hRvgQ9uXHg{-`knpuH+V47uwKx3ad*4?=;2@i28>3m~N&7;S|I+o9nad)CaEtn3?vq%-vYW)W<6QxYf`}?H+ zfMwqn&svNZDyX~#gMTA7Cc-7YpZyRcX0X!+*AUts8F>ap?DNWnnHc*1JV0jz$jH;?)XVly@5QQ{37-ggQR=VKwc{{(fF=g zPw4jay&R}oPfz1_9V@%{qAceOr+K}uppl;UxG`9){3kw@7Y(S_7eX84wQQ(%ic%P9 z2+H#NXp?`g!p{8~XnF&|fd1&W{1#MiP+haFSt(c zDaD^t^x2-WTbZIg!HB7XR@_XRbS^X@uIVnLF=@R4hyr5)i?$*EJ zW@WqO5JBD!O`gUu{ad#+Xe67rQ1Y|11x`hbz;sag>`_hs)rlyr2ObLDm0%<*8uqLs zbU;^9(`yO(PZpEWE_(34DudrT+{~>EMU$3G)&1WSB^9f{?J;E?=H>}0UnO{7z4(fv z$D{ATdR{EnnNj8(j6wYL;J=l+yiii~G2*v~=^yAAOc$K1sJZ1cKc|~fbI|yRV*ki+ zTPugYF)S%VOjmtZc6=mpUfq_@6(Q$3NJVCBRtlMnsp-|ZYbB&EZLI4(gF5sn% za~?~*$<^sKOp5pVRM=wgxGsJioU;wm0}=iQD=d$Vj=y(Gv^4VSOUono^k#sRd-_PRRbDzG`D>}=E;Krmtvg98q?dpbmJq^$)Cc( zUfO0NXW5_4Su@z`G~`+X{*x|xV@Xf(u5jI>%D%AydRIJdxpJ92UfD3dRL&@uT*~Jo zNnccnMyEu1`5*){unBQH*I{#{vU5lE?9~wD1|Qxa_0@qYjjQ3%34&g!g!h?L=}Tsd z{+yZ*>h~)QENU&cN2-cy2|n^Y18~}O(0gsJ{k78Vs%x;hxuTrGj2=|ySxtq0sIeL2 zt;HAw84>si|CfvVM#o~~EkW9d-P3*R$v!#Q=l31@=It3VIk>WZGt9F!sq#%xDa>tE zjDWF@O}lpzLPxX#=fA#E86hVB%*TtO#qBe+yxc#43LTB@z;Q(*B0Av>E*akoREG$I zI?;&sG8LV1BlH8tTWD1mCTCEno}gUsZ*P1>oU1%94hr+-fo+?OmFb)Jo4Fgk@FJfb z(;gT6H0VD*4y`WnSMM=k2Fj%ET#JdIjM$-t+CXo>si$-1c?Nf7=I~?cwRAy*(u8=f zr2I87LpQx^6?Qyza@6LsG(n%kX?)j~F=-Zb1vWjC08tf7d~JFU5T&I?L0G$K|t8pD&pl5A|`( z8It(C2?8-@Tn>#%uO+vDr+UzqVP#3qLvtDmiqvpac5M%{sWDD#a4eieeSW81?u{sy zfNhV$TbA6+G3$hJLr^J@GF2%e*oZi!2PtpBV=ji%o`4-gupd=h&xJ>&v|=jvZqUrO zU&F^BYDR`&c^9M+V57ScdgLbx~X{j$&M#?wthhM0qBn3u9u_)!P3u zPgkvUzjW9IC5`o4J`^L&@a#t@9+GYWqY1_q+2{yc45?NYsp9WAlxLmN)X~YLRLtj; z!dWvWysw;`^tQ%Fx{S*+7sf5!%hx*WGLtNAtG(wd(7azK2A{<>jYtp#stB7#@>Nw( z;M88uO(Ee+E!saR+;-o(R$3VITu;z`!(2zw{gNhFy&XC9?Y02X145%ObGCs!M!MC!+~ZD^(4f~Oa-!HvilrJy{7l^9@0EGA^KeDR?w#wZ#uy|L7QkBMW= zdN4zgWmEK>OepsTapHxCC##oU#5W2XDPv62FzTKG%;_^2>`bGt4YBikre)mWY+#;y z3SQ^EkD=f9^ ziu^1vg~bHFW(63#S*+&G9ReT+OU&=oj;2&s95 zg&T|=$EqIPRGr4^-N&!$5MhVu6>f2kA6sSafn)U%P1pc#8Xm`2$;bUrTQ_?y_&s+a z7@ufdWi9DIZyfyb|9Jthhv@o&iW}_j9?Nu?6vu8US{Q|N^;Hetv0^%}>8x&aS#F%I z_e{EcL}at?ypp>IBJ`k}IwYkJ6$i}}*UvD~PglnRtZLL_tB+{j4`cD;`H5^;F^oOq zvAsiYkgWKz*=zR4w@(f54Y7k9YVN^&C&wycKa^_E9HA~TH7s3VOb2{=5wf1KcM!q7 ze$#g>?km#EZNuzc&&w-EfYlq_>AF68g`>VuZTvG_#(x7R@c8i|woRy1-b~n*{61_p@-ZA#{!G zg2UUI|9f$61w|>Uh+Iv0fLKfAKnztG&FAQ>a&9W9LbF|4gNrbbf&yUe?mXf*#rNt3 zcrlkv^qCjDJ!8K*m?6qj_pbYxk*lBC&MWmJOdjOMnfY(Z^^IfbZx!72s4uDljriuu z?OwpK2H}ja#$?*!My=dUAv2*kIZg13HFnx-Loi1Y@JxfmNd{MsYH8J?ugqMSWhj${FjTJ^jXhAP#f)qEsGf~k##>0QCdS%kF#%LZBY&l-&`s5yV zs;QmG!!NYk{swv_PaZqsau^}TaOc=))wSvp1#-qNQpRA)sFeTHV)4ZfO)jMs>BXU; zp%yI$&U6JV@uMTNyX?g4ir(yn6YjoLv{nrsto&CjfNgos%)yU7xX-VeSv0 zG}zq+iE--mXZ~M4FFO^YpZcX0_lytgXM9h)DWk{twcWlj4%(^iNYdh6uY~4tt#!IZ zr`P8CZ1)mvUnIW>+LSLi6Byk-T&ko$H)o?eT{-?0Wk!0__cClRy8W{PIg_5#CyyXS zXI{sBRqT_|0O+E%Tva7fciI(UdCM*zo2&4&%+tA`y#Q;iWEZCrlTa1LVPW?4v7Vm% zc8#5nbHVlC>Xp=`JuSyq7|AO&JJ(X53tE?J38y|sUhXW=#^fr`+rYTKr^?>sF;lWG z51aiAeLfk`G?g9KhjDb&ou1Fi6)PgS_1Ngf){I_ikW?ioi4{3IzI6z!=~d{h8NS5t zu6N7&1Gaj>{}4?~eqpw=bF8aUy4q|95(lvma$>94iPFNVw}Vy0T% zbYnsPa>>hJeiuLiR#0UG8yRkcoFL~`-N{ik@OLiIl&|FuuZ?ox)KM6+oOQxeu5#sJ zs#l68l9rMLQw!3W3^M+@COuc7{TvczFv^2G$S;V32G$%*y#}b$wOq-YuS6N$b56Ha zUd;}VC60HXY${$IZ@8DSPne5~A&7A{NLK2V=Bi?%Qd0VVmRSSCQ^mL{AJ#q$f&<&m zI-6-s$z!=$oF2*LS9Bq}yukpsAbf#g6A{|i(y)PPm_&jMW*Kw)o113s`9J`OO&4kR zmu_UQ-(;XIo9Zh89A>U7>NZE;n~(Fw^rJUM*Khh{25ooukFBDyGcx0Ab^es4Z5Cq# z3Xw8@s-LMo9boj@y7RAAg4ei>``Sd+^iRz>l+$V8hi+yfXydV>{K_hhgYEkLL5Qun z_M8VkY!}z(jY0b!y26?mX|EhsD{1xXCtV%VLUc^l?ByHjwR`o!OnPkRG*#<}Qcfi} z?$BzK=LH{QabGS0xT`v2dUx?bi(%)wi3hGp{^03`*1{B4=979ta0* zwnzuV&;_eB2n zsXpS|R-^d{w!ptUImgfIR-r#ewQ#b(7$h_tG1L2a9je`~|9SAUZNq&KF=}I^UQO=K zX_aV=&WCskf2AeT1mGvIftLD#=$*?kB%xh>%rk2T)?Ad1gLb*0fyWd1k@5xvgBBYR zlN-rYq%G zkcT+pZnk44QKXuTP&i>wTuNQ~(nW+IjoiyGpPhTVs->eUf3DDUhq)~2-)f;ZIJ%}= z6`~e1jk&d24&h&)t{uJ|o>*H??&E(@EETwAm7iNr{(cc6WDWLFIL)UnRO4NEW-U`Az6!w$j(3woKLxX^U@@BOL%+2dVWQG0*( z!wl*zm>^HT(`cFD#)0>C@zUbcpaU-lwO|j0WNgr2e&?`u4RrC8W>G3Mun(k38>0Zm z5U?V2Q)ZQWN>i6psJH6Fy}@1k&fcuw&*s#mk#A$9hb3YQ3u4n#^7;M_6dwp^?|ovp zJ)MqS;!>$wXEpwFJZ8z&w;B#Fu&7>w=P3}npom1iku^LW`O*dUN|)J#8XrVheiVE5 zu5z8-9;uZT>Ie~Cn z*W$}^{jeRHMJO9#r|+9lPH0|fKtwfRhM@_VkAsw}Rl_ly=m3`}Fb5RmT~&P?vM2sB z1OP=~)4RMvB53EAXFkK8XO#m@_0%59D4eH$j?v%yd|V-QT`Q|U%j)u(+qBKw-5%{mP^O~tXz!r3>1!DvlTAlF&EG%89r)Z4%`#3hMg?i1Vk z_g77a?Yd5=C{MPYi@Rs;ReqjmJGh*pzI83*df+k?W)W_LwNnKBR29QKE4EDDYi;E9 zQT}QyKT-usb3zHKm6NSyj*hM86uI_SJKD8X{T=`SHZDxpw_Ji|!C(HRwhAgvf0i9L za0*v=CeA{IaduMA?@Ru=cr+UF9q(d(%jk^0W8u@XOu~zjf z@)yUw6O{ypFkUX0HU*PgbZ&Yn=C&{O8e_dMrI1&}hgNZM><=569VL@?w+pBy(wnt5 z*c!xmEEehmst;{TQ)Fq|NegnW{KnK=0VZwl20--}i*D%$%{CK%9q=ko?!UpvV`mz1 ziC2lRzA8i&Iri9~nh+84K7_G4r|J=+O7(lFf`!VKb3p+K{TwYYN{H&t_!?>F8zrn+ zgm1M}&y_b2hB+gqEx@Xbig`+C>LRa@D&y4E#knM49%|*4nI(Jmcve9sj!PW#tkClN z;XkP|-IY!9N9%nq8;^U-SL8f&P6!(ObT*GGy1fk|Jc0so%B{=*jJ3sd-)@I+it}_hR@W93&;-UP@RSCkMS_o&>H2!oU_=mEY z{}EU;e^Df3`PR3t{CL|W#ozGLh}%d0-1~nB0$0dX8QZ?L)9J_CR+IL-3jvAe{AKn> zN9gyHL2}1H-k&-vg#LeMo5v9>_|n#Vr_HZs&K>%eS3?gItj0h7yh=TO%DDg}BGBJF zoiPS6j2-zCTSrbXUOQ!Cs;~O<-ExU}0rO$l)*8Uf_FoLvw%ZqBP%ZzaF3_W#S8Cd- zp{deACMO$f`-dO%Vw6I}2i~LvyzNZHHWhN!v{i)(ry}SRgHB>1&il^y&EFYk9N?Usll$D~ zoa_2+cPC)T@3}|9((`ss2sXb#7mUcOx%zv^`+^n`Z>wl={<_qrX7?TRcGU^6x;qX3 z9;aoNNGpW9DHs`oRZ3%OQH#SnI6iZx4N;5Vz5?B|XHN%B9b2Di9PDKhzHnt^siND$ zb!oJ=qQVR#Sa;|aGlRx~LZLQzy0vTRQ@XwmLRQ^kbg>cCq)TBOQ=B`$?#|?Iv>Vmd z7oA8R?ibW`t#v3C{F&62V@t*;c}z%aa$_<{xVa253K}0(t%*RJN3Y-V)I_2IiBNJZ z$kTed*tI%suVs{ED>ns2(eqTz1@ z4KHbuq3)4Ydv|!B5_|(^hEYv2p0lyB4Hvp>6FsBJ4M!1*y{GI|5vwgSvfMCCS?(pu zr^2Nov|~<~VteRDo;b_nWr1_)GB1&EGD#7IZabj9Fg|F#@zL|@LPBFvA$6Pgmc+zC zwCkXGvs#F*k@-Wy{uj|x6~xCR>RR=*lo4%*mL>!k5CTGy%rvqHm#fi*-qPf#5|{3h z3?wM!uQL{HDlEsQ2R$D?a9pd7L`%k4+vhW`y^US~r3gCeT_dr~`Qj`IsJ7$C(nJU| zowDR_S(L3%HVD zpSw3^Futwc(+M-r4urF9q|1nq@4x z0Xub&5M{iYmp}6#s-0WpoLog%uNH$|Nm>}M&92VOaW{9G_W%?>U_2=X`Y`Mpl?ZTM z-62Hz90dolvb;>g07l{$B>q%@GTZPAe0nZrA(y=Vx-MrOL=~Un)Im=mLjHj_jCqvh zSX?~A>4)s*xKZmW_Cu70K7P3WQia$8Xz)uIlfqN{rr-V<^UyVonD4^sOg{?i<6BqI)488ouv7-3qmH3ySME$ce@~QghO#83i z5vVzc;YR(NR0zcv^$?W2dRIuL^IKk)IoD>YjRlf5`Q1>q-tw$V!1b3^pac5!Yxg(J zt@+9dX5qJNeJ{Ap{!%vA|FniNc^TC`&6=1a5(w*g!|(5`)_>%)3FBD-2IbU8S5216 z3+(+J9aQ54msPzIUQWO98UONr;xz zWN~4lFA^Q>q_(bPnAk}E&T-d>NgXlqPfsA<_`V^rdYYJR-2b0B2E!rg)8LJsH!qGC zD=Q=^UQlct^J)C`7@&m1JQ4Sbr#oudQ}3NXNtOgCO>rbsZ?d|=1(R@ohic=Net7M= zN&g6C)KM~XAk<=V;!DBeLTI39A-=Fg6+y?R!lvD!^bMaJj}M>a9xlB)U`czOzx4$- zP~zTc$Zx73_oJD$><&J^aHxmN95t{*Dw|Q(aj-DxfIl?^Fu(nKgiBN21f=%Pod3q_ z!*Ant+Rc|L0?raSSzUs51+`SljMQcu=Zh41-rw7(I7+(3a^f9i=wWdNQs6saKO58; z4P~;I$|FoiGBXTShWbWbpdPPrI5f1}T z-)YchI*K2w*(#!NTCGweSDt_Rokaq3>4X`Dfx>Uyc4%~$@9K0CBjpXp94mX0I-=<3 zhQdkKJbHcldY*GW#zkp^@!IfKe@EC3(H+bE(a8uIiL=QjQ!+E$J7p+lK>EU22=xe! zgIJJJtB(3HmmTPMz}`;gJ849r0#SWKt)Tez;`lcIVq2JWKAYHB*KnB+gjZxia}xjx zasW5)-Z@bMQDWBmQv3?y=N**CVo^&)QBiDN7P25;gYQMV`2?cmR~7E^@SZGX#zxJQ zxY`0{IsdXfrw#{+N~;K@0F6WC2UJro5EOiH>NKqE>8x0D!uBxSBP;qKU;thnpq{5> zZd(bSkV?+gK7*8n10@He$9Uexq&%lFFq;oXsH>{|HXU7U$e2GQfT5tK*iJ#ox!@sJ zC7ZQNDJ`*QM=~9>fsV|j;cOuy*inq~OQaCKNu`9vqC|jz-*h8`i#1fZA39pRAC)Jo zz?##pCj!tw3kV?OV^HoGulFUf9_b9OK6ud1(-Mgt9tgFY%djj*&TWn^m*{FfKxd5_ zl&61EMuAyQD56|(8cRPZA6zIrk0{qlT1P1)ZDdZnK*PPn@sPSdeok&9K6+lkJ)OKn z4E*lTgpJdG7c@Nia{E`cC&N|2YA6v`zsEUYvA(HbeKj|I^TVHR$$P0&Wi2u(I?{Uc z6w?P5)OiS2(8uhG){AB@R^ubaqmZ@lD1DMQl~(F9m385_;AX*v1qowm1TTG`Kh()> z#Kz{9l*-C{4Y8xr&_&Kd!nsYl|1=&eB{y2Wc*E znUXqO3Xs#7S54hv18YWQWe8Qf&`%{-@h`>bfS!kw!NsSz#&@2Vo-}jQfL4a1-khq` zE0i5APDrZpNF)5TgLMl+w5R|HzpZoC)|TXX(>&w6cQf)HId~+NIBXjt;`>55LDcVEp_0(g=iMNATG+0H&Xc69RZmbzpD|FS`E5P-CNv z8RgmnElw)l(R{l3d(d59dg>Z1Wvl*{@kd~g@nil{c~v_ z-hQ-{^g`pCZ{7#`QkQ(3ck0bf3O^tT*L5BfdFUTyUiF|8|6(9xIRhZ?La#qYot`at&MRxQRXJ@u~0MLwY?9sK{P;H$hkjdSJM}M;hQAa{aTnm|O?O z_kl$BA$1l_r);s~=ESH6y@uBYzD4!cu%;BH>ei@N1$_{hlB9D!aV(xVwXF$?3TDLP7nF zsbWY&tQfJVU!p^y#C~fZ%>X^bev<2A_7${9QgxyPG%`}uoi8!F<=EYKXc0$~hub@e zHew@o4vlVjj15Ol?C?31x|2dAVIgw~a>8}7U1AImOjtab0f~(l2g|^Q|3^CRe{b-` z@fc_pN!QNF*?)cGXY(s)N)d?$RyV$WAL6;4eJcOa_$sRMf6SNB!!G`kl6^E7HlxD(<4!p-oiIsl@c=@1C?2z)?E#vOeEmcR8(rv z{7m~kb_}}oWz>Hauo~zx6P2}!bv`As|EV|}7K9Zm!$Z!tRft>4IzIDnH&cOl?RMsw zw>v$Zal1lXAEAhV98Bu)P~u+iD?~`ELe1(ZE3G>+IUq7DF*Y4slgB)R2u*{BKq0Qy zLhrJubTTI_Sf=Mp1pcKvW7kGIMEncr4lR%Z5oz=MDQBueM$#tXsKA<4V=4M?e z;GL~~XTB?$iW%~G_&qt=!=Ug~aN%Simg@*zW>3wrSvmrbcM!5P{b16GkXqCKK8^Ca+cVscd&jmANQ~&a5j#+Dh@s~n z)3|l!@sgtb0B$21mM#w*L~%+vyu7u(TeBAx)s{dRjt)pSil1H(ydwGf_TqW+LZrC< zXfgys>F*D#qC?**D>SZ~+*%y=JlQQ{n4Fuo<^+$~!5;f4uge7G%6P?v=y|psP#*_G zas*BPfd|{({6%lZ4^VrE)dw%>7`5v`8_d^EMfo$=#?~VGLt;@OH^V^*9Gfq_2n2%e zYCvCTx9TQ7LD5a2Li1$E8y(55=fR+V)=S@*`cUc|w&n(H2{O{HYAruX5j%37KSuC{BI#~PDn!IUv451ZI1MQ zES!ab`V)+uo&VH=py_~Rid>xYiV^r*w-f$l!@}Q-zk7p0Po>;2!gw67R&4)pKfXpK zgwFg`oreZJ_2KHG!rnjDE)jhb8-Xq3^#R#oQ^5srUAb?T;meoY6tB6P1t*-heKxj) zJ0>a3fdJY3Pr@SMUP~o*EfXM%?18vPi$I8d_ATKw50utk$AW}gQF%9AjdWpip;zpq zVZMcX4dr-_${h3^rtxdhw}X`bAEcA*n+ zHv8Cen|^?jjG#oLHNoDR91(x5_&5R)G!?tvV{Jy3QqX-DckqWD)^YX29(4}dtuAbD z#xeZ2=xy<)f(3W31o#AW;9>xS8jVNhVmxQr-P@w>2nC2Vnd-EI=8O%w*GHDMe0~Jr z96*u7C6ONJ{cy8`~eT@m59|(ONc*t^>6ZLLMqkz~bc=e?_%WSZB4juXBm7%Ha z`BcJXb20b<@_fv|FEF%P)Ii6(^7-|BG5U#cg@=P4 zF~xZR7D7a|mg?^+f=gB*+aaubq$NQMB({v`DQ%{mIq2W#4b3%e_}iUnQ}QXZs}~`- z95BC;d=3uXw$G-0oXj;_Mh{!E?8~>={83@W+)w}h`OmP zA3XsblPp!HtLe9kiv2DX0M-+8HDaKzAb*Rn?$a5aGRCsYFyq8cGAXOPwAJGv@=kQy9<3oGCw^thTjs3Yc-t_m4bXxQB1WS#5r zme7l&70!9aqdceCugqtK||EZ34lQ$eW{-=Z3}8g@Qw~>ljLhPQIbFP z%VHW1;)jk5#?G?ma;?M*HYTm5ek_xC%9VTmW;TdkV}81Qc2UkoFe~anobNtMC9!D0 zh136n_3cJ|v4evIk}vp)+ExFO*8Q!(YN>9}Mf~vPwRN|=5xntM)Gx3xR#{|!x7JP9 z_y;x3>wO!;P6AVa68T=B(61CYZ56S;De)AAqucPTQ(n}DX`AF*q2_0IX>2Pvz)1(G zGS*Rs1v7&^=^F;^dalIK{T23YjFdgGS(F)EtKhKL>e_|QDuA}#s`C7Z z41PTsWz$KPPdD-6cu+qXRNO1BG1dP|SIJzqv@rvsq}7cM_x1SXo9*8D#x8rqk=Y?x zddsr0o$T!l?TilnYk!utj`qy?Y#sd<4$8a*tSQskUlkCIg6oBaK)w>{e$yt%s2j?r zdx_O&y11c|6_rnGM`6wM^Z+|+g^VJ;j1+2NEL0`@u2q3N{kyo;&io9d{x2!DSHoB& z7m`fpbwEd_JfMWx((L`+)uPjjO*vale_mYojJKlMxh*&)F(v>QQTVHhCYS;DE%f#P zB(1&%FwX)v^*ZU!lcr^Z-l}GUXJA{+kH#)_j7P@?k}|{7fM2L7bg}C!o6UL#$eB4C zOcEem=PubVT(F&F%-cXe*x7o5|AReKXs;pkr=~ZI_)#sKtyDNa)xBhn-M?Fn4rT@= zqW`SXt%FYZ2FhE)<$Gh4gLuIPLmc&ej0rEaYkk{GgiW((1!f4r<^3bwB~*`qFE8`m zHccyb1))mP(SS{NcSn-96}&Z89k&%MC;FxYQ*jO0u#*-|rb&698OF7^)z^1E?C$EZ zy#h7I>?_SJ^8$vKuKyI*%uN+IaOhzo?j?K5g3#LBYi_C=4`j_4rJ8-E-cHg(;Tesf zihyX7amCLW<^fUVVMtXOrSsE++?MgmM-5~NZrN<2NZbMW&zFJ)ATS>OLZ$Q$2E-bM z_+!hqe_Ph4yhzEqXHhlQQiU#CemiwZ!3PMThjR1R6_O%R^2!3n^&y}gUYmXhl*qUv zh}0d|1%8h^9iLS*S6Hhze~2~U^b84b0~F5=Z9>{&cJz2&GBgkzk;el~^Qs$^p436{ znk-}Ebiq8c)(i94ldQN(cjs_&0Iu4P{VvQmQ612HV;+TsUQO`YePE6SPB(2-vez8D zt6Vmv4f_{ZJAI+U&bI7;O=mPO>%oz6gT&$th-B`vvO8kEblRF?N@+jm7I{(7JHAQ> zZjGtQ?j9GfqH7$UPfLpoz)*2;(U#Zpvb9dk*7fN!YWDs(a8;MI!##l=!3n|7SU+LT z@s*&^We2c&PuIYC``&|GG{v+j$aA*XynUFiAZ#@RIBQ^ZEy|#a=n&N|z?kI;aBRi$ zQ}U4jn~f?f#dp4ga7iBXrEeH#u28dT+IL&-X;39k9iag;K*ab01cn}S>010zj=Xdl z3yKG-lRMQ^d(P8R8A#(9p6+^O8=GC1?HwIBW;nHYamYXqtDeVo#0*tZcj4Nk->6i) zteD0nQQp?vGWRusnAHd8tezk>f8BHOkv_$g$SH~yH&k$l06|qY4AIt5 zDx239UQpKnbjOO-$m`gw5xsdEz0#MDArv#E#m5B;HYjTy!^qF^p4Co3QI3*BfKspL zk~C(_h3EMO!sjrCj+Fq%oUJ2bb;#xUqBt;kKQ=rg!;0^^Ah1|W^;Bd@j zOPac=nKnb^=0_)5^8PqEhm7J@ zIc4Nbbt(Q-H5en)8Dj2ZTVQClvo(PX@XF_brezN{Ixp9Llu3%t^V2kp?FV{|t@7nN zw&4t?i#~&$p)DKX>&%3WE{XRyE*|F7bMoIS&w5;;88M=bfjvg#`i+DX61+Eb0!(R+0P=NFBYYdk^(q2vlJ#EqOp#o1ydW;E zRj)w}U}vg<=>g8y&-z?K^$|kM%QeD*#}Lp+^Zuz?ej&C|j|ok?s0nbx=NEJQg3<&0 z`&)lU&|6>UpV3Z6+X_N#!1PA~d(I;ixXX{;jZ!CG)O_i!AkQ7al<_ z=4&%dfMdtcW=z3yE#j;hzzPLh&(DlrbKo>rJvcJbw((HC%si;dJeW$!_zWz-q$lr- zH^w$BVi#rwdJ2g=7k0C07-h3zc~Qn_>EG0h{|N`!K@7h5Xne*FwREWXyi@o4mNk&F zhcA(hy)&bgS%0l1Nig~rJ~01J%~#?c8UTw3`n$b~l=4?f)r#4v(TCUp>p!;s*r$GP zdGjxy2Vawwu@jd;{ACBEd7w?1(c8itjHR`K)+P<70FhiUAS(;loua|PjE&R@{Z%c^ zcB8MwJ-#R&09py}r~d?195h;-h@4JhRt_StQ|>&?t0x0{$>~_YZnu~%e*Mrn{``oX zL;6h~@mSH0)R;SNJx&fru$!arNkjs(r3t}Ogu?Q>?tTAr9KwGgqX#>X(dl%w)l^N5 zL|53s-Y;}uCq*xpY#!iCmLf)2V;iJDp2(g1I<)J3^85KaKir-E{rz%zMCv%1vpi0& zCcA96W(3XN6bE0p{BB3t@fH8jC%&_Tc?>#*#QK5yQ5r364zAZBl& zENYYm6ecNj4qjDDt+_BQpHWB&Y8t#Ujhiu%?vbD;Yh=|RHk3byfM~t5@^OjR-IDpNjU<8C&j*q8t z645s}hx6YfUnOIFM+->PpN?TpnKio6vS_$DIdB_Kc&(k?GVM}A0EGV&fOGHoxjB&W zC%OS{uga?Ncx{*uI1Q}u3)dDXqPa(vHb0cER(99qQ^3gsO-9uM5VYGTb+3gl$PS+P zfenoI9r5b1H9Ojd#gv~L8!5_yhRyMj#m$)392O@OlC#a9FUZlY4w?YC+n@^%K}H22 zBft{2%>F`$Mz4JS4#H-pW*+?x7P)f$U)eF2roK#J9UH`KN9~D0`EQYdOEYv+-2xvG zHysRSc~;fGG6p^L=2!$CsN*49=6=hQ%ie#e7dt-`uQb9J{g$t7gOq|aq7!K;WOlO& z9xySv+!GG03hn`)6UCEQ?7*OL@7#*sX7}(WbGc)%8!K6}DtT+?W~c7%S|B<71;!7^eE_QGqiw+>F^+2FIQn1vHW zbQT;N>!gerSTi_=$;&T&G+oe{Q7|<5UOh%)YJ;F>dO4`%AaPdo1`?nZ9qfA_dd7dR z9CfvEwo($JQduelNCv95s@jar@6A|!yJV)sFV8urU&NL&i)wvBS~_c_l1Iwz5#^@T9pNK=F+QBW(IrncxYXpxRR`N9Yg34 zro>DBs%_bPKWVL_B9jiqX!0JyImJ0nKvSy$l0~#lp?Rb`PD1}3Z4UR*E{Q2+>x?Wt zJ{y3egjbIT0ujIYSD9#F3Ipj+MW&#M?-txI9u1Fa`PBMj>e3oQyZ@ETX(u2cpm%GH zu~ZZZ%Alclb?~K>;QdV-ep5#{e+=DJx?17U_?fZMVn=#2vLyZ9alv9UZTOgDfAo%!tloWDM}R?|XurBK zfz9qV>#j5tWDY{bNVsrF*J+G^EI@4-WY`6iK1L|fgXyo9;X#G_M{yYI+@Y&tSgl9z zues^DGUUp;mK!H9?mWTe68?O%RgPFO`bwiJ&zBmzwm%~fQSR-1)mE^&5}J&hkC}>K z$mOk=+sS9lpH?3`t{NVDU3H&r51s#^&+W_MRrkNV&KfYZ-Y$LXUS3pdisD7DTIIa# zIs9jrtL@uIb<}g(#!=0$9qv2#p47Gf(*POa`4BE#3OPX1WB~~7cZ$JTlrb$g z)W-Ui_P(_9`{-qnrRwV6{BwRG#4s779c+@1>y*!OHrB9^6{ZgdB>4Gy!`-I{GXc2$ zh!*drSDbvqKC!5uP=4LW$JwwC6rd9Zl=BzS&JODF0pNAF@D3}L(pz`pT-BH(E2r7f z5YklI)zia2Ds9Gs*CSsMIgHN_X<<{OFFEfPE=kxA2fa{b#l)ppn^4x{PI+=yZDT!B3B0=< zl~ZeOX^|mpNzt}Pod&G^rVYlw>}BVNAihPSWlP@}f<+W>I0L)L^o^jb0{^O&lNqMg zS!+=#k);_7V?;1|ytYvk1V(J}c4i4UF*&?9Za~LSE`XPoq9dvX);EXY3w}neI->;^ zGWw&J9b6^qFGc=6!#GZKs0h~xspu73y?CITQ&jK*ZZ)j7OOjCvt z+ktR32Qo%aJFAZdK7OQWj9+H9uzeuA#&h}E9uina`pubx2Qu1-fe(pJa3)H#uX>g<3O&U=X?O7`R1V^W1J_efV&1OlS{4Z>G~0rHz|mvNP*Mp#>z_txlCsZvxrTc=1=w$Vtxewr@kdC zS(XKMvww{@*jVfxQHx=(Iqi0D{ER*DFk@L(Z4|MD;as$4F?9JYD@%f-qkRQI@20#o z5d$oh7|5sh%O8N%PG{VBn{OtE(&W-kZ#E0gaY7Zkweb_t0a5HlBYa1Y+xSM~_~y5S zy1UyCIu9WFrH#EtH*`$F6~KHzy$}3W!8YvWz(!M;|UpUD=nY zV6PG${H9^!r2R9JuBC9{`(vrF!M8@rWXhQv@sl%kM=4au@xsWI=w=mcELmy(70Ewk zb#fwWWaB5?c{iTda#;!9nm-N1@T<&vLAfhA&l2I)jl{onK8@Is`B>D&X5vY=5ozw7 zfcF#5bP#Sm)r6gHujV3goJMYAQ+sW1>|8ggkwCIgCM(6p`5NVR7b4ikz9C-y3YQ^(LyX zIEDZ+JXE{-fU1j7qJv5BG-u7VS`AK8I19}q^<2irg9l>AO^{%~1)cjL*cNBtsq+F- z1vTb4Xv1>{!r2l76xzf{y>DoC6uQea)>n4-dLiw6HWPOhY!kz*?I=Zm96F9L03Jx_ zu9*@dU=)2{Q>nE~DLLMl55%mNFoRQ#>thW+1IZ}Dtv6^OKDDCE)GWN74lS?lqA4VS z#57mMc6?3&@aQ=Zs&7&Vh`h+CAbK1?MifQ~ zwiZH~U-%XgdurLkX9_KY=UE9!jKztwBh8M3X}Xp`#|r2S5HsUVeI$!=U-EkY=o~S_ zJBH8L$(E999CH+(q?EYRcV}@-ZA84FhJ1EMiA6NW$AKGeFzf)5jX)ZOZ8&t;I-_or z;cg802>4}IT0!p35t2woSn_7NyxaDMI&p;??}X1-E;i~Iw7*XlwngJVoAZXY-Bt0A zULE_+L-kvBH=$sFJ(+H#4pG3C%8XRo>B2=x>e>@AtQazGn<#hd=-#jDHBIhHI@)Jh zl3|_tT)_gp8!&ivb?8+Z+bkRIhS(d9#ca$bY(!kjTxk58>8GHy=g3OHiBbw`mMf0$ zxF@@;n-h!ns}*IimzUfICU!-wHm|Z9CG1?M8yMFE8cSaT7C7PG+hC~UWH#JAXkbF{ z`5RV`1@^Is zl+e&x43oEX%)d_k(rS6La@JSrCO)57=^ZcKBwL9ni)^PLod~!y93}iv3(WBu9nWkg z-0iZ^x2X%WI>O(+8_#;0Wa|H%Arzoy_>ZzHWLG zMDG910{FlF^xq$i|9)>HtmOtW)KyfpWtUX6Bj6rs6(D9Z2Og?t#7_AmQeyHQW$(90 zyDWEI#8#Jpzg3WS&$R+3LuAg#N2UYr=1%p;711NI{xYGbZ_)fP8F zyRKsYEf9P-+hrNoQraZVt6P)9C9-OZ9(Piec{F{*rLQrrl&XIVS==wAMvndqav0Bk zH;}>0A<=Rg85+wa@Y%h%`v30%_&?JjNg408f~gXA+|#wM_ix;P?_|5&}h` zB4l-@ZKol(pICpoJPwc%6Juk>P}!ok@b>yvq*y!%Tn=s)!j;w#kZlpNG?T|{N(ZS( zeJrzr_8lpbxm@J>R?q5urz}2|1b5br-CPghwhxVcG6rsoTd3!kWYu7d&ceVSW?O+e zXGgK~4mpx*or2PjlhrRWlUtr>Y&2%ziYqui-n!-UUCo4hYGlgXhi~O7DC9G=>?&Q^ zVES7cNg00_q0&P4O!?J4#SN!1cx>>@HM(^~f)w?v0@9Hi& zhjQFZWepp60DOa0kO8UP$?gk1h_eH!f1h0{KW<;@h}CbO24Wcy!FanLKSen6mU5%d z^TJf zdWD5Rzv1oxv-3?fG^r>uioiy7srwfB0u9kTV;-Wdi7AvBTJ)~s>KE=0Zc|VLaavC` zWBcRji#9QxG8USkd*oyVyhG^%WM^ktdt5cg(xN)AZS@v7I~lJU%E~9fV?t5xhxFCNlqub$I{z4G~Q`nsM zbdTh`?W8d%RN~nbHh9yjX-c;|XaiEFaGj=H>LU=<7_~I;)FMBn*kgVxB=(azWU63i zyH(g?$L-F(_#?VwgmQ%#pk|tNf%(rwH$6(xfs=s!A8M3nrP)2zq}oxQO3h6ZM@DAO zq_^q}hDpz}XK}H$PuK(p>z0{`MVe(+7_y;ZDrBo}M5{%OHqT*z*LB)Z+=H4@()7t> zUyAQ8YL}=bNfi)luOD=-6G{TBMTmcfpBb67=2omwy)QdJgMf&+@KiW_(IytA@Z18R z`Ku%Jc)Z(A6d>?{f`GZ{PCJaVT$19d(Y#GmD;0*m+6m`6$nQ~dDJR>6qIp9|gmgwp z)?dCqqXMKY4flD!DvOcqgl95j@7BF17|nLwn8(s?a3{a@^WXv^OzFkO@fY6SvWVF$ z`vu9W2q@P5xT%fG>=*essI^(FZ;I5uxY;}SmzWUd(T)_+ zC;{5+`66^U$+OE|S$>MG3$@DCOaRqM&m6&`O~F?58CW(Sfteo6eULyROPL8YqSA7K zpo}tC=0qyZ(iY@*2hHr2<$_{?hWCDgjA{xxi)B8m+Ckxbi5>UcH!cjeS_oGDR30Oo zj$0^mbvUFTp9=Fvgip{lOBCj9E`nl10ff}%5f4-sow{b$QM>2^1_$+=U3T8hV27Le z%qZSpMvMx7=;P!~-%UP`=(Pf2mA9I`e^N-IPsx}*w{IH z7qlm%IEs~DmhB3>Kv@L5cZz#l2ywS2lsO6*2=FCsDsCsAt|N4V*<2#Hj2?SnWVQhU?pE7@_m|yaA=)Bnb89SChKf&( zZe5~|)!drGwVo%fI7Vgnlbox7p3hdX_rc4pI_X7f6X3Baj5&M)#NxvAal@$Dpz_m* zkU{#%e>u#;Uj95l zKqx@Xr%7qfqsivkNUr_w;utsyw^-MfL=B92$MpHongeGZjs zbu2FAvpa+6ZVHUf)#6D1;-r#Rf|2vekbBGc=u+2yR=%HL+l4ciTCtZ(Fj6Cmho?h4fBvZPLMM@KmY%w)%lfRcmx3|3AOIU1DqKrhQaPhQAq=h^^a zDrj||C-+U#UYZxMHp-Hu!;)pBq-~~~@oTvA>Jf{KgSe&8g94cmejOR?I6WHj>6}}4 z^fM+NdZiObVlS6k@Wvb|b)#8$2uco<7oSBn0SX}s@RpAOzefjnQjj>OySk3;=D_Ua zm)31LGJYJ&1qPpAuEFUTy#I1-%~6sMWAvF7o-$0W;S>`V%Bxe7cHx)G6>uf$tV(=q zu{8h2L{>pw@K1xDm@x*2IDCD=J$|vykXJS`j1QaA^D*Ye|4P4te_500xoLVudn;q6 zhEp_qo5MSi zi529;Jvo%6*w;PQ6FPEezXo5PsPi~N-(i-<48DMEjP z^967G5kyOwvaB-E0E7pxGU47t2przGwyllv2d!yYh&HmT$0Z(Pt7sZQe-RocB(9#Q zTKW_mE95^%|Ms1-wuUqgC}YY_l69o#Z)j3vXw-kh2_2%?43IrI6l7oayx%h`!_Xot zm-=^>*o#QvFrIZD?>?k}aP=#i;=op`9~{A?DUwagru{A}uY;Wu3d6YcHixW=RY%E! zFw5Eyoa$3C+O!VEOY8^*eOFrOnw9rpYg9+z6H@sIaA=px;myGV8U%?ZYr?=lMg^69 zA&SDMf9fzmbakGCClZMjio@MOND$`>?QO{?CdpGkM8l0k)LzEMZ7iQ7Al7h@Bfz}C z0qrp=x$PDE4Fy&fDEJMNkb!>tO{o1jbMC^o3csJg+iw1S`DezOa6D#N2g{``>!^zx z@yylvYP^;LrMey^*}t6$YS}xVeo%p#I>)X`)FB0pAUTq~*`+hFJ*L%Rwp?G~R&&R* zkMkV;^z#1CZL~}2db3aYH|)Z5+*WRb-f7uA&`q<#AwXS!k8YqHG4pX*J;wBN4sesJcpev|MU- z$`p`w0NG2HD85Pz#gd^Zn9iy|w`UMZdwz#ya`A23Lop`ow_e?WXGef5zgr79d{pfH z5>>PMfHW02Tuev$J()EQ$|2VVOUv0aF8ilE!-u?CJHVug*%GGqgziNR&LUo)zsSpj zl?|DWY`rWa_aDJT8k>S-pkcg^HNweT@3o&g#-(CE?luTc-iI^QSi>g?o}Xzhxk;0c z^9M=32;X*z=@#6VKKgupAHvaLAr`n8cqcGpJNb~2Uyha}Lt?a$sjv}{ApjM`EfZ6eZ6@5xRsgKh9d5TC*1m{b&bc6Br{SPKwQMps7#(d4;LWgKsq~Yr{p8vepXi$J}Oez9vU;r!)N=lRjm0 z=yLPdTmSFB=k9p&X>jXHieK;J-}e*X&7IB^?mhKC=R5wFjQ_GF2lkoH$vp{T`Tx&$ z@BjD5$nSS@3ACQ~-#_@(=XOW5vk$lX(!sAEg!;dCtpA5D`u_HHUG&Zg8%w^kQ^zJ; z+A5ce+?x9k!j>etE3)hm&Rvu>ExTVmGddRA8^sm%5HK61w2s;a<%;UOa< zljcxhr?ay{0%di&)mzmaA#&MZl*J5W6RY9ZEXZrzkkaw*#H%5xYe~2u9eg(y8R59Hw=sVLSlu<3hY;$&%=LcsL2s9P9?6UD+oe1yk#&#Hj0)p(RWnR|~pJZ7H; z7q4KiZ7(*jDm%p@qgbmcD5E~C&sr#jt4S0mk)7~Hkoro|k{MQ*4vCDchk0y?G#j^o zA?4mkM}*UCqnOvYnyJ6N0G}_*!SYf!E~elIyWL$%+|WWNqlPaLpJdiL+gk) zN7D5h6ikgtTb-wdH3VBl&`NJD-Ow`fnM~h<_^r}q-QlXan8z;m)5p7aUQCCF;oQmx zp7eeL+K!|OD5GM7b9SUT;7P~XD#~@Ba#RM6m-HI94PA}%ZQuE|C}D5Pk{8leak$aS zoBJGAJ>$pr_crtVV&f@!AT7r%Zvd1ESgBCeYAk z9F#(U>Ml!gUB7GuiCFci^6YgF@hO`hK4h8FM;N}mDO~6%HqnhUw)4#u*@niwbh9nN zyDIjAL_7)*52e;5g7A~gwjYIslwxR$OzLff1&|3qx^dv)N0Rj~%FYk<-C5Wn#;^|- zG~eN6oTbcpJ5?jJnU+QEJnOXaw~J-pmLR~pRcJ6z5o6Z8UJQzAs=MiTnhiddY69(O;=33 zErpvh_UyK2`d{8OP}`(5drQ%DpgW5SP&j8`8}aO6t1`(>xC4{7IMPuY%0gKo06TO{ zbC86X*k74YLylNZ#%@8+zzW~~T^AMFa7i$k#N>73nWJqs9S`*JX%z+CaQh{>ZJzza zwy5QwDdXap4tooMHJfYQkt5mQDyVNyGp}QiTP^ye#V&T%m)#$X%0_>XU_~>H zZb1}@xbg=z9e6BzvvKcX?;Q0BLCY#!(uSpooUrTa%Zl3-vXaT@T>fJCmmCjCYU4K6 zMKZ_228o3cfbkF_lyZc|hwB?D&!@J972CqqO{`!7GC>wmnPd`X+i7OZtfoRVM8m!l z4Q|hE*NN7iQET-T($o?B24??C*7A1C_A>mMBpxft(NQ0djNs>pXVu^G3erHj>fEwL zO#|h{^wJXAccW)Io~*pFJrCcK#W!xUwmU=C#y6M~TS?n=(p>boQRZ}`n6_QbEXa{m zjG3)tS6U|`$9c!L7kjRebH+t?O!$IfrwMZ~=eh)YrPy-+G3wE*p#F1Ksw>+(=5_b* z^{O!dYJpq_?}{W_^Ka&c#~QKNf}pdde1Mb3S#O>{pg#Ko8m0Gl&6Yl!Ok8hsbjqnI zHLAdQ(-7cxDQo^)9d;`dHhG;I+w>WF(SM=TRe9YJkok-rM_@TO(v=?SF`+-)15c&Y z0~bm1_B+TtwSoGZ2#6gN!WqR@9PagrB%Cbtq#@9fmXQJE7Kk0hGkmLTBj;?wPa?($ zO)msbCP4K1$K-CX@&nDg{Z5C!gS`lpq2Q)08vX*3-C!f+N{IQOyps_k#I+)c5}4A% z`g0k~;Cm-BfEd-v#9Om!>2ka)*5iX$)1w>$=-n2+O##bd1Y5_6{W6J%|s0h<-m zm3NqygtnwzGa-Ss!g}DDfy34L)33g$f%CCQ)UIMe{4d2bT^SssBo{23cY7M!q0SD9Z>E#wR%8)Zp~mu zd$~c{L|6Nv3pk~!Cv3wCNc@p1ZWBDnt_Poa6#j@?uBlM=cylz^<30e7AdEhX?NdZ$_^*DZf^u@r>)|43b!Pw%? zQ_Idr3LaT%#zqC)KQfr`F*5wat~dsP`lzl|EB=VL`e7xDC{a^MW#zcp)r-l`nT#KV zPuNF^0VdqDam}aW_k@(bj+*v2u}Yq4PKO2L!o=}G6YRxOpXl-JwV_9w6cG<=PVsD{VXieNZ%am+_Ro_UMwCWvQ1X*^1MSz#We+CJVetq}A%g<@Gpz>*d|1su%YTyK3ADkfp4qO6a+e(w+zxoNm? zu=1V)FG|_;jl)4i^5pG){TrWstVWK}&OcOwT39SqJ(fx;D=T~5?K-lu%S3Uw-fGySA(v z`0<6TtNH#oLtW3QfVn2k$VgJq=a#KAjrFy6Z5eThk1@=*RgWFUgA*YCU_V;RnwtUf%WSZeI1Il;KbMB#er(NozOLzN7NYu1yIqUzBXYLUvUGc(uceSR8nj&^9(S zYA&%2B{jR1{Q%P?Qi_R0O0SML?xX zlM*7K1_-Dqh;$JUX(9q5y|)mm5PFBuLQ5djP(ukxzU_JM_m1&>W8C+i-~Efh7}B@!!BUza)E0*=ME)IlWaUcqF&TxbDoBmucE7|sJ+cLdkhRwD8 z&ktSVK<0H(B}I*8ALEth`jPF5l2AE?PIY(Ri`c(aqqkbV%)PKiAP_GSL^N_gB2V%K=N0V(+R0 z&Pv3!+X4SDPt3DobGeBgRppyrRP@M`LRiJd9I>u!`cYG(*ygkLPMU3OZ0yq`s8{09 z$Vh;^=3?!5!}jC~TC;*P)uMmsY4!d6Y-u{c+} zG66$agrn3bCkODI5@g{-BZU9JDe*-`&@Rp`%U_X%9^jzqH&X7V#e5)z!5)*&$@MO?rMR8IUV#z~}t*F&Jdn`Ayy+g8V?FlhpI>$_K%1 z*Q{Ct_TGg1?yl(`3SPuS2sMUP<^yhV3+{djr$hDm~e{wyL3sKmM@jQ_|+VGRg)@vW?VjAtVOl zgDvbtrhHA>Dx1|?uBng;7fj!&*kCyO-dH4sVwB|N(0QUh&n^Xr<9t@<@c4)Dj=T_J z(CAXxSwT6WRkX;ngHeyaHdBJka0%f;{L6uoG?TD+5zf_W39&5P>3oUk-K8sR5?PpP zc{!vf-3jZm9U(9xfd4Y^fu}6obbI*5tZnKEjP;2`XXmumjx#QvTw6=Cc$`%_&>K-D zTc<31;aAOp$V{bm>}Ny0qh~}oxwwvFK1{niCT$t4i9|fznoVVsz z+#ef|m)n^2TNJbq;3eSFI>dt|OQPj{LC6dCV2%iQS-7|F{v5}~6)+O{@#5=SR22LH z+g!h6a4=hI3$J71Zgp3L(++Q~IjGO78splc;=Tdsm4opQlRDKA7;P>FEBDIefWEG;KugdEg9b5&q4RG5kt`}0|CE-6<7_mfc z-9Bs-CZJFt?B9w%cc3dXy7Y_-L~@A}*Nf@cCNSPQbwqtd1X2jjD!1a5^FbVu?v|5u zb8krT3DiR{zV{qnfncgWn8g{GgP?N$8*8KpbPHiKIu&)%6UbQ?yfpnT+X@WiMD#{+ z7V|k(D3u&2u)iFBUMB3cVwUf!teGb~Aw6j6|g~N ziUe#7GxKtmX~a6_JS=vuM(E)SohN!-ky)$t0pCsLjg6ml1nf^RlZ>Be3Lt6uVBE{g(^HAVQo<$Wc@;qLVSkF zk1<@skMs?qo5I=lq;7lY24(GZM`WOM|B~>lHd8biqVzG8?CQsASPGWh`r?P|lr_m+ zTp|P2FI2@oB!syP*#9cA^)WCD?ObZ!d7mb`*|aqk>vdqR<&53J2jo*E7BfF6pq(4k z%%aWHdkQHoDqp^&1tyBIX63g}{~%QTX#JVr|J|T~r`nO_p!fF!NX`Ia=)?FI8G(ep zn)f?)Z*CV335(|Ll0A3A??|+nKdl>JejF5!Kls|>F4BxE&41bJJ^agTHGFcZr|)UbYUX zM`(Ob0ZVlmeW)~tMS6bzJtJh-_Jxu0d4nj&CDPq-*P$Yl)k;R|xONjltnTab*khFx zke{wOW^ZA$od)}yUB$jXAIQY2+@KF_y(AK3RT$<9ZS89wHIS|Z~Bmw-IsJ# zg*G0!v_zMiJXR-Kv?t75f$8v2^#z$8`@^ctzShI`{i6_E5=BvDh?beL{3Lk=Hgp51 zB-vU0u&s|59eDT5$z%5}ojm^L(wXCL|M&mYdx}k47!^$_?;l?spkaSx;>0NnGcl%~u_}V^jw-HAbb1n4iURw0rRJoS1}f^uB$V zu1S;Z*^bGx6Hi_h&IzH`h=>lfFDy}X9c8{(sK-}_+(DrjlQ2EVS*hsGA1X0}B|0i% zi#viDQ=~qx0k>{2cj{2c=(5Q@={Cl;a-C$PppTL9%T&FXVI2jFi~eG(m)0{~a#Zrf zORF@$)w&vg-9}Rhbz;GadP~QtJ8iR+x_vg868Rgg?&7o)$?N{KF>Qqge_cm+wprq_8*E&ois~s#K8;@(Y}#S8z3t43^A8TOtZ*Z#f{&)75Vfl8;<>}^ZFFDslP7W zsy{{k)e0d*d$WSVNTbrKiofbeV>QHD6Q<1vHTQOAn_P}f+}&6}(C^J2?$24ofCVUV zvbA8`vNB8)+U&T;>oLp3sHj_7xk^rUHs~FX6k>c@K`4LSj@I7u?pb5=@I9RSD$Y#B z3~zb02_d%%ruKO^^9di;*Bi1tj6Ya!9##cNjELsr+SWAs-_bp~jX!m#S{y8Yw#T=e z7OUA&|6l>7BRX!Zj;HZ3`u>L*_5Yh|m9@uTH2Mo-bKjW{8ZJ~X2Ie>0ou^_jL%VMK zYs<`Ay46B!3JIdVi%m9^sT6wp?M23}FcbWk3GuP`oQm1Czq!ScEQNk3q_qCztGi$l zIogY6J`}R{+^nkh3b&y5bK`05jw!OudWUg}KIDXw4>{D~JIdMax}dM!8}`}oihk7A zSPwDMf(dhv-H#F&_o!1UWV*Gvh!UC9mAbgLD%br3mOO_$yUPlF9bCth>{EtDXzm_& za#nnI)Ze9=ABs=jv%HV>o`notg?*{ye|?@X=i{7`=Fh~6_5bn~jhb4bqsC6Z9wO^p z4Oqh)lSmys9q&mM{qLBvy9~QeqeG?o#@_l_A+8b7y4x`>?=000Q?U$vkKzLlvsE*3 z{nS05=g)d%M~jlrujLk3#IEA<;W`h1YN%kC(yrKMdHxcmKlwP;eY2uJoSm*<)uqME z-hZ^;!op11WZZvl*nL$QaEsNh${&b3?9gp%XJ$T9Tg6t!e-^nNF&*e;a~Q&^w7M9j#-=L@Tm)zi{Wo0l~kBey)wpNU(3&&aKXW%r;hsVU&(v-Pd{)DrZQ$en(aB;u@Nt$W z5w@O=`6T6Gxjj;JV!r8^r}l*$u{koe6@Vk!9Vi$6Fn;4_6Y6| z-9~jgy1Re3SEE~9NojNOyfVEYQSCu*YF|2AYnArAVvI6UDP`4jTz0&4fN2-5F;kdF zYlz^f4!r04e6dI5$ggcz*7%^Q{zT587-c0!eM)NttT`f&`D}k`v=lOVYol6k7&t_B zzX9T&alkv&^QcPx(5^Ugl1*q>b3;sJoh{#4BNDxwR|rWQ^%tt0_<64>^>-|)tVCwN zGlEqK`x?swO%@7laooFSd)JX$nESPLGoQBqVZ^hv=K zF^I+$Di^1VntC$Lgev*p2p;!omCVG^`jJyNzQezBTQoXh9sQ(YxZpADrH)+1;wcYNl>LRQe%DI& zZ;OHUX(NV1SqtM?-H`pr*3*S|U<~Dc`Zqr##8dh2Cinqrseql4iFx&A;O#0j&&GE8 zspJpy(4E~?n)7DSDvK|#d*WVfY^;095%FvRLDUUZ^H7N^u$L; z?NTJ@;K-z*r@EPWo3M0I+j{&nq* zwd88mBatU7WsoYX_g}j>jBOP)4Wup%xs|l#62k78uRWqxI1*PvCb=^Jt^oDc^yzc+ zht}URe~C^OzRrJ78vRmOcfNTK_3xsVyJz;^oos$r`_|?yRNUF&P1a4-9y6L8jRY$tHGnKlo>X|CRL$vGXCnN#&_XyQB1_ifUG{Z7@a zb%|>Ao&7IiI^5(O3$ZVXpX4E&+lwC=3GdmVzkq|4vc-SoNvE~jz+(E`LQAM~iC8zC z@<#h>)J5gUti18Xr)fM(xnt$*waTqFeRGWEp5vQ{;oQJlGZ!nt&QxWxqO4!fOko@PB(UMZZ&Uqy1Q zI{N*QF{8=xWwmj|et`1i5vgCfRnl)J**+}yx9{M`-h3&wQ)7&^grD>n@^FA0^Y6|- zUU)5YGB4O&kHv8~AymnUL}utg&? z!(=0%FiZ8F@@PcCRI*X~R@nZH+P1rSr51>N-weXTM_`b_-0h zH4E?2J2ny4n`VNSP2k>bt6J`Ht=m8A2g@5`_egvKVfPe#*{2y`w2#=8yFDQG&vxAC zJ2e8jmFd3l&TP?@EjQmj{O8gK>k0cQUZbCBIP3d7 z@`rtAiJ+268O(2*8F#OZjHvI1J47py@$MvQmS7HeA(lDh3@wP@;j9tl*RB6ZTXIi(sUIC2yH+| zE*hKk=N}c7RYI=@u1N6X)0ETx^3^XyP0bnViT?wJ`S1R%3A(F;ceEw6=*?-rY??1iliX!;pBZ*{JxwK+MQOVso)m$olX)HEqKvp3nxtRB8Ct@4wn zd0;CB`JHwd^*HrL&+wXaM|b!U9Y%hxsYRK2M$8Jp`u#N3em)c6K!igH9s$p1x{u45 z#B(o~Z*x>j9Qfp^t~Lpk69N!qU1(kmW26FR@%O$zs&;|W|6?<)+c!oy+-K`(4Bq*@ zN_q?x-&K0}BmZXrZ4BK1gZUt&2VE>%{>eJkz@{=ANc?u~7cR z^ef$pHA-S-Qu*4#Q=Mt<#j6mLcCgdhL`chN?(>cy$nEuqtS*)H!-;oRl6pZ`s!g~% z;`JHIwP|U(G$R}-+KXf#&v4Y3aDOFRH*5cI12#*sb9?xA$}wMcL>EfWB_z!uY%N3; zKu*tzveoAqg?i1K&O5@%OoK@orkDHU{ckEchbgxc=vy5DvE}`+{2otx+Z|lzYbEBI z@bWuap&w&!q`!nbgEN{lR7h*TDi7B(ma({q5`*`co1lU6ZaPnyIk+S@DmPk_gNZze`6n}opZQLZgw%A6QaAC z>s_@^X1b==XDdf88Bt+6)m3lO%FVmMw)h0m&nITpe-SI;Pf( zp9p-?c_!06lawEz zn?~FGyI$X2MOYLeK(#;WD>qh1^6kOf=uQEDBeQ2&kkDJ==^5LVES}ch7W~N|Z{^8X zo6>1}_ka~V_2x0J0VrMXgd`u~p^EneE}d9o=`B)rPUxBp#Id#pD?an+iksJ?n!dzo zSCPn1l(#eCgKt~Im9(%NLDvTFi~eB~3Q@-%1q9B_vrl-x`;>Guy6VyLea<7~n*9 zrqade-S0M<*y=7ByAQcey%$%AJ`~wMEV76RTx}&a(DgNY$S0uVyB|#j(fY=PUKtBx z`kj%u!`a;YHGg^CVakj9redgsSN=M2b)3tYy>|4} zqkvSMoDR4z!^8>9JRQj=k6o7CZ0@s&PR*7;n4<5A;d_48p0AF%mpC|d8`lt#-VWc4 z)A}%~Y0JG3pRbhaQYzo;sS~so39?+bLrrrvYU-oxaO zxr1PgyR)#V6b3dWzZ?W@%4Tu>jd(dIfDr#Dh2z{&gn*;lqh--Rh-hB%80P&=OryJU zL8=*S4)u|ef?%DJdt&c^+^owDIX6yIuW^QJ>*+NF#)%ss^}tpyH*U^E&&3qY)!xoR z!r!sUK#{m!Wa~AVDkUu>i}E!j&e>2;8v7DDv6!03i|f@QM$N=pi_j}RUao#Q4$b-? zTadShc`Nkv^Exi}jpCsjDQc$75c)pF)J*YmmgN_d0PPl%9xnSFD0=i}5P5VBYKYVe zzFd$r#v1&|yFsdA_$v9pBE&f){7*x%cze{zc+)s?M|L{-QdVz_h;jRwDde`Lg{Ju> z4e@A+kWnM)=B!VA@$-KnF-Yt3($dd1NTQabNj4fsijcq$AZ-^B(j9$OhR`zppmp3v z+a9UiN<^tLuD#E=@s+F+(mC+W9rt-$9KLdhb3>w4?7$k>k%@9mfz@#a<9y6g0saGP zYst{ZQ1^}YqN2}MZTm;fN==1KZfGR+@uu!S{@IU;EI=Yp1!0gX1)t|m>)hSAmSnYSCds3bzmRoW*P5kg<7wvghs^YhrSTfeWt>+)qM zavvjp!ap}9XZ1JVF)!6UFSKy!?xwS|^MzNmcRC-R+a>g^7=aSo? zle^Qw`Gdu}NeS9Nuy_>W&66MOOwPDcgqEb**6N>(}S^J%2PEgYV{^$_`6(l zJQ@>QsN|Z#I&jinrrv>yINMl6*3kH2gl0fF9#+1LEk#a!Tz{MIFPsX}Y@Lxl%I2N{ z3t#9S2-bYz@>ii;R?lnU5jbtPOi)*mqp(=mWFz@JeXnzC0d?yrL~mupf8}`nO_EILNVy^gp?kLKq z^^35tSeAqu9DI1axP3B^Mpa3fHA~N-DQ-uzh9BUMA>XP;OpMbD3D?&@jG^z&8G^Zm zyk&MOtVb2IB+7pYCkyuCy{;uvm#i;jUcjTQcA5?wJTIab+)1?W2N-=+BSy`*i(7Zs zQAN(FJ1NR{Ta@ka=g^_!@I?02;6V+%YspDP$yY2aawP-DAZqJ;$FT(k+4@yEN455m z`n&d@bcvA2SC_=v>YRAuA9FR4EosVa<^4=P77+uM$D_s9cLZY}=YfuNc6V#(>V^*v zO&++iIqkSg>xbXZFDjBIjf_oAEjc-Y?gT%dqaP_MEW9+5Z^q*&;L+tPa}flJ+1S{) zYHVRaDR%WYZ;fi7HUV;t+Prr_AR3J}pn1ZKAg831v~<0iPZ1;MI5>sHVw<^xQl9sy z2~A`V-A3eznZJ6F1?g@Mabr=!iETQjx3uv^;4HMbdwuZ%%!%QIn3PL+-^{27Q%T2*jQOV0a+*ahJ++B-kIsH8!e|cW(AcA z^7=YDS1;fing;kRaM4XT@7WU^$mp>*Xu?>Ka#%piDH)s(@?%n~$_sk|$mVHU@B3R+ zAGoRS>EVe9O!U`nU*rX-cYUvk#s(Wdp{S?n*!!^aFM@=;y@{4w+(;Jw$nZTi#^!fV z@78My&ZdpM*22;+l-%;dh4i~kUj2j+rGb%_EQ&k`G6%Fw>l{JNUl~FmD3AQ`;7R? z7V-0t9J0v-QTQ7fsI(Dbp93NORXtt#s7VF+aU)EUvVRV`T~9C>FFXMPPKrIS3af?w z_Be9rKyW&bjFJ}#4ilqTy%vCm!Bbo{;UZai^|_tt%`I;H9l zyfQ>K6q!H1InI>mcRYTgN1SYZ%R04HJqNy>QI2Z4cIP#pK~2$!qGSkoC)|tA$NemL zR!6X2B~sG4QJwD`3j$su9}`Oai;Cj=#FHfaffq%F1_LSD(Dgo${Sv2ii@J@{og)|U z4+(QIS>+AKKy7BT&K@6A5retqRv@r`QXA?;Sb)@S*!h)dQ$4-tR>FZ(nGkx96wbTx z{<0~i>@+Bk@x0p?d1{^rE|QH9aHUP!`p<_%{A)$IjHS-9 zXRn<QoK6}>lKqXvrXgQ`gIKRNsD8Os+CY7$R z(ROn$Blh=gSAfKL@KVu4Y6vl~4Sb5z_iq&7s{q`T}zI|4B{JT%_~1Q%^p%;sUEwo?2<}_QTm=V`z6q`=-OycPepcDEM07-h z4lR6j=r7}4qP$-+4Q{M8e_^Kvu8Q^d@N+mpU1g8(8~_>JBnI}z@8k1XyGkjd0|UqD zY_~o@V^(lF%Obo>?#Jp%ToKjxh}gj1;3NNUEr42m+@so~Z@%<@;5Mx~`Y!pe3m$B2 zo`{FiLsKOg{_*Wd;OID(SAlHhqm+SrDh*7nmg1pA0oh)IoAFK}bT`}ejAr$L*qk1s zPC>A32-@?o3%^hV&r2!8lG=S}iP*gE?7Zm@h&{t!;u_sAJZfxj#%y$|gIw9!2FjI$) z=sUD^iHcYp5g+H&BQ{jmtZt*_{Zx-c{(;a7n0tIB&^bgvCUM3}&X>?<_#%A)pZY{N z)UA6%i!Tmm8M9I16`8zU#-OH%#EUe!R(wob+}tZJkKDJWI0Y>1D;VehfXP(N6+d7N zh!!s^deYrAdho?!+$?@{8Qq#%1}*?buH|WnPBvOJevSvNP1Fh6O)BRXg8Fn~fk<%v zz*^?oLDVR_5)$39ijck;EpA(BI59FGyuXN^;^Z#iTCU(1!Y{(-4I(&OyI1RmQ%s*7 z#IIkEz87CRJcc2RRMAI-I7UCkiE9LS6U)y9k#S{+Je~YobZwjRj~dzNDiu=8@IfN_ z+CljztF?)ueOv!|Og!#NOh=Q5L0ZI=lv8)b$@za!_*8DeWU}jqP5~$OdjIoU6NQV_ z9)`Z-UDd5U<7`oq5BWj%v*y|epUi^=LqOv87 zGiVJ!gM4{1CqEu>RhjZq|Fx^DJ(8_38t}`2OGt@aO>L5iGrTfaWSd33G4qB~qN05DxO#@tn)-!|jZ;C8PaHy!&FlMr z;Ht#`f~$Mlj)mVyDXm(3jK|B9yM}+}L-O7H!GgzC$@N!9S{`4>Kvq%V9?WxfQE8;% z@;oX$XFFD^x%_ce-e%4d{Sb?5G8xp><5hn^ui~2%a&db`S4s@LKjWV@ra#DRE?2y4 zFl!RhnY<3qagPIffAnU6TuxSVMYxShc_WtCnDfA{MCv|c?Q)b0ci+&CQU#;2*TjLB z#S`*#06xwpTA&5s)yePvT3ElOapYZL$eGmv-EXCY&+Txpu?+uM;E&KXYAo>n;zh3F zwpxcbqerz;%by$nd6PV9&J&9e#Q()3rf2=>f-Zb@vh5!X?rhI$XOeA^fy~@>@d4XD zx>+$f8VhuF$t2Xd1^ZYi1DROlA#S7nr}a}7Yo8# zX!(gpkKRzyTDb*~0|qa~lcR2Iz(o=wb@5Z|t@nY#2S>(7x14vt6I-E-!+3-22sH)CcqSNR`aIDGjG4|Tad z#NuD`xBu0Nm1f6(0i`k7EX>SetE!+0NlBl_#-3;AZg-KuKxm7@eR%TZ$wNa!L!Vtf zMQQ2i?aRhyW~*nT_xDi=DJgms+{O;R{zs?j`Rp9$PeETrv-9%31jQBL%5O4a)%a7^ zVj8l|pFJ~+`}#{EJNIY1+`BoQ&T!4CX4_|`&!U#vpQHDY*G-vVNE2tY?95{Y|x zTwUhS-_B07HYzL5XKZ%%yQK)$N~Fv^ot4A6Ngb=+J)W7F`AG%90MnI&Qvjf9j@$~= zaIv+$<|<}>8<7O%Z8)0@Xnd{YzPC1=Nh~~D7q#2M0qurirnxw{x#gc?EXmNGb0*tS z!uX|d+3H)o{Nh5iUNy;xhzL=Cf;V6yz+L3_FaHzi_dWG?wlPf$^*ZpKl2HM~<$_YBe7=YGyIV5aHcG-TTE8>xwCbsR zrwsn;WtwkGsg;zW5`!zst$0?f9&XLEqOI5T$Ay?CB*ARddjt6_Kn;p)US-TpHUSFepKOoAw(& z2L!il^{MlM;zU-t$6iqzg~1Tydc9nMBr&Q1MyYJ(&4GJLkY?^B$+wVPM0bZ2Z4!#mTU%u}h-SOK1wAgEW`{k^C=&1TcNc?Yp zkaG33x?c1y{woc2LJPizNv?y2vdQUHHpyLd6@b9N*5R}RXywKm?8xh(s9!S0rVR(C3rJM2c|1;!b#IX0l zmpOrF{|f0Yykv(%ErY8IqmvrJl*+4ET)k{?+K`1cdFEbs6N_1ktH>H&V{O(XBHU=% zYO!`$J${G_tXeX)1eT^2-i;_~41LW5v- z7H~mjIl+62mq32Q*5#|G!IhBY+O|;?U0o~G#4Qf(<$nqk7hY`q#NvIiH~L=O+a)-k z`sVCk)yiE0_ql5OJTJW2&w!%5rVUXWjk~1T-b9%S@7kyZAL$&Vacg&nzNDXi_tHk@ zh5Xbow@4m3Mc}C8i8<8Em)Fnx7F-)V<;Vs`OitsI{_g#)_PRpvk#*F}gJ$;5ars9t zgw@8KM5@X95<7rmcUw?Mh_U`1XDxzE+TOc#grgvQ&~YX3n%Fh{bYY+AH<*e(UjCYg z+qksQtzN)h(=HLoiEVn#5uD`h>U4CXRrKw-($0mf&I7 z1~(LFy67$2CV>@E(kHH}XJrx)P*+p#>P*C)-rFjxkT&`WC5FT+FI)kjD6ncY%DKlz zXcwTjo7DL>QwBQ02$xsRIdEc7yz&H2%-kHb5vstViuVO!Ek)*JTEWf_0B)eTRn+ea z#SMANb5Bjh2mT|KRN!Sg%jQXn&?Zht0USkNw-qpm5B+BDVQN>}L^^KxZQtHJ?@9dQ zHRCPmOK&}}OgD4$rZ=jC;t4*1DoDaoxH0bp?#7=bk9aGfYb2bo!2!x1Csa2`_o=}< zvLU$9%58JDOR9{^8o9tEg0-9isu^n&|1l)Es%flMmX4`+FA)C{D3s0;Z3IER0mjGw zaxiB#<}|dVFL6s?obS^X!St$m7|$OQi~9^WXY|(C!!SPa!N&*&(z)d9GlXgcV~-UW z#ln)UYd*^1b0*yjenH>B3sQ~|#=P`f-R5R{fEN350pRRNF0zGE0Ar2i$pC~NFq%!$ z3j!@0ZTFDv9cL~OQ_8`ZoCxdOZ=v2OKvGYXtOjJ?zPQsReIRgULy}uH z#GDLJEKxg4_ag+Z^13<7&GoyZNS5J}-u1u7r^7kreCFOWn@93PG1XBe2Z>ElMQ_pg zrLt%&`E3X!t1x8mJ4Z6Xt}JD2Mlhb-DCwx7hbe4ZDlq1i0cfT5H9%cG5bFkaC=;R* z)J?6pxdAgzA{Gaz`A-vZD8ProDzj(1pC~IS5nx4u3rw|S7zZUaw)EHie&y0;^$?)n z_LpM=Aro$RH)r>6KvO{ibpCkg4l78TaUl>*#6Tc`$EDC`uFE$9u@X{I;fgKUSc!de zSx%+}BR!ADKUI)y^IZyu*hv*8ZOYuPL&6GP`Mn6kCaE*V?zH9|{_3^xlQ+>TeAwL5 zqCPTd$QNjEeb$AOGHHlAI=&XQKJnhBnp4hhmr8B_En6$I_x+*Mj?3apa@_oZT($am z&~bG;R$>j}Fa_hkBeqH0C}?vNtCKL(r4>s6xgJq)Et6$`3&OlB1l`tA=uV4>8Es{M zlXtDKwD{@*t^45))?rb>P|TMCj|a{x)CR%>qt!y5sNMYgm>=aE_eHY^e`l?5SuMq8 zJxc!>YBZ%JGbl?ejD8MS0ifQ0`__8Bw&M1;vIa=iR?J{9&#eWvl{v{ffT6}_CE|X8 z1>R1eYOb?M$P70Ftl4`FP|nm|5`6D{IMmCoUl>{w+C9fhtrud3rMIdB>w%s(U@0sl zCPi>k(>lg+Ud|6h#UPdsF&HoH7u*8geHhoEwBNTXJnLDMtLFMwwpuv+F}?4@G`f*2 zW>rwN&ZwRcS?Z&#pAcvnOVFSk0<#`GaVgjhnZgHbuZ2BLX8;NP_HN54zUk_pn8V?j9Y){OlV@ky>BLQi{HVY@@$hWQ2P_}sp(8Aj{C(4)yQzE z+d@*?E&R}@>*g|&`Um#P6M2gaC@W2{14v^~K6c78U#`j}6myj~2cS_T}4bg#2N;WbCZF zE3b9~+diPRKA$1;EsagmOxl7p~8cFc&=ZfE5_P?yLcFtN~Z&W@FCO1zEa%~1jN8J38l5m?nWw}oV_%DDHHm#v)d_AXGr6pwO zIzq4EEpL~iQP|~!^$x&xiu@?Bz0@d)WO+7A>N@RLT>S2vv^lb85!kd5m_jz?Qh}iLCiNVHl%%&ut6m}rZeOGy$W<^t2}^B9XuDrl z4dC-Rd@DG2?wJh+`X2V`2Z{I>@{iwFp`Mk=uli@DJpkONZ!QZU`9ATIMZi%zg{@#4 z9Z9k7=CC+f$(e`=qi(?kDbqxQSKBi0MEozu0P6(Bt(Bx+wi?9l8`dO^cm=<8iRB3Q z@tAwO5*k4aQH^uE+uP0Wl>c6PW%6`*)x|@G(ce2>ZCFca$TLYDc{0@ArW}dOpQk&F z)1njnQ5DSdK!!d*BtE#Oy{(qJgM|io7O6^-Y-5qyc`lJXrw)O<$PcfTw_!aI~6r-0`vW7Krx*9M=@M%OjD|4-f+l;N0ZJ=WQH05 z3Iaf^!aV%Wh_1<$UQDxaGouNLQWu{v``BspECpL8gZ_+nG%B6Yi{U-8o#C?H9(^zW zM|lDku-6`xH@4*2yu70ruaRx4*{Nlth5YrPWiwVnJS0(jsYjUsCPJHfXV~iPT={+~D9cX?vBk(o_J@ai6vx zNpc@7v^xj=Hj-@~PCAqzS(TkeLr+Eooebfn%8-H9Oo}Kd#z3}El-h&JL=1}Q$rcs2pUk5)kp9oYgIf!EQ?Yh4c-Z)WfHNtR~VNn}0!;-@F`g_Dwjbc#G{ z5r90NIp6Bxd$3C+?WV>%S@XWve3l|sSBd!+Lop@-;+PHwHGQ#ahZj&?L@)YVy$!JZ zt|7K*@u@s$vVRgOrMp@8D`wrE%{;oHEyw5FbIqPiK%vmD$wbTCfk^JIFv6rZ0&I!7 z+u_Sw%Z9DJPf`gd*MD&?|Dy5K2!Ep<(F4}hfA}7tXZ~Y9SFA<>+|A9?)$M3?vEBfm4Y1mP z(buKt+mf+mJ*k5=jeA)!n>w=VJKK%#4#|h9I9mEbMs~R&m#2Wf-8R{gw#(W>KF%~; ze0f-4^awo>z8+rjA5b^-nd|_Gl6ls@n!BF5L)osqRW`JVDH zYyeS7sxdPlv&LZF{q5nK+SHNQ?O+>MhkND})IDPY>{<_jwptoXZ(k}719L5VQoA;Z zMFnj@_WQbq0!7=>qH+(tpQcBwL#;0K!}hWH-vj<019>j$9)VX+((gcPccoEOVg>P4 zYj$aM3Il9@=$7#8%jbageWtn|1*J2#r0DU<|2bne57w(m6YcU~!R;t(z(#T9aC7|`bb$6ftc#N!=9EW? zqP9#{t1+UtcMj}~$y+NqKeWWA3KmNir`CKmGZ(~cU2VnY{wM$LaR$yTod#92km zTGllFk66t*I3+fvZ@1ru-oew8#lF$<)owJsr>;}m$6Yk7tsL|!YU;k0 z7cZc8(q&nyi~XPdy7|k)*5AWNoi6bnwi&8s76Oxn#in>L0Uug_;t@cy;Op+hIQYtH zmTIwoc5zSc-?Ouq3O$ais3cZ-RI3h{-F=-$)~BFMBVfC)O%p3`iO8b+VH;Q%WoA&) zYQUYm2#a9}N}c;`h0Ee9PN(j0dPTS$oPT(?VgPojCuvU?+DHG}#|S;ud>%c2$W3eo zs=-K*Mi}3pi;>4cUTrh^2Ae|zQ<~!&1$1SL5u+i^nEd(zrX`3T$xy*^{|9WX^9g{; zoI2I+RTOd5j2|X7njSM&HqRSNFPZa!&-Oye>_r)S9mJqUx z)>}h8Nl~KSF^itc?@29)$w^=E_)VXdAo1=bP;-A(v}iHLmK0{5QNzpfd|2umwv%Y* zC(i|FrJD8Vp2*3`d2BJBd&Rj_SmHnE;#c;67~A9-|B;ugBj?w_i>LOge)FTYd*l0m z)ROJCl*Iit#rku^oig&PrT0zpdVUu4h0rrdYG^sS8Wgfut)k&?XtZsC?%Gi);V_U< zk^P)v2$NR4vcPK2YcyH_H;gsx|9kw03B`C(mI7`|9Py9yNB^8NhYq6ddk2*4+E>dk z_it-X5qdLBR>1A=(<+`&eEa+G5`X}<#hw2A=Z43>UrbE&ri0jN85!4R42_NBQ&iWA z>}+k-E?jVbQC|K0*RNj_fLJVQhUC!E(a{*%a&z&eS4w?jKZbMZ6wS-y1=`1IGmzak z`sn0^RLw(RH~tLxny#rX{{p!e`+0qJuDiWmu40i=X^bNbH;qv)SIK!o;Td(M#g_mj zyv?gw$LD8zngh%{&^|(FHMRGHhIhzX47yLQndWW}m|iATHMKu&4h6J&GAfW?;#*TO zykLKe{D|ZcLn_Uq(O3JMDl~+E2vT$xDu)Z$* zB4>13f$PM>ps9kq9l$1i0*Iic9tfj8pb6fW1BJHJI+~90AB^vxLjxGcY)>QLkM^Kd z0MysO0kWD=hRm_ZK%tSG`v8G(f4J#v!1U43ab-!XH)woC@=#Bm4$x``F-R$EQ@c$kGbH4cPX zh|spC0D=@2uNG|w_|`DUeYCQ3qXWr;01la(2lm75lPGny8}Fc`dwEQtLn+ z`1V+lWg>{5#Q;a^usT2wuZP9~Xz`_zl3ZMb_S1DhUqQYP&=@V(pkJDLXIHvV-ic^A zEQWJ~ov9OH4Ilhd*Qg}{8)xvRiU)WlWR$4@PoZ}f;_W@FW*T<+7LsqQva{_* z4!jDWLrasA-m(<*yFT+VdmYJr&K-MDz5M`_+&u7Zx3MRwy77>T#DY_qlxHkjqWBPA>4*}qmUpF zGNYk$=jPWhwV2o8*nwgZ{?WXcmxQk`0U?jORaVwz`#XKUz?m21dt3=7B=sSRW4f$X zON)EQy3@#yygUhhTo1(Y>gw-M-Wb{C8wX_9gM$MbK#|Blul%Mjz}x$2W>yx@_UPy{ ze$bcC2{~6hVs#t02Aw=!K*om8l3y;xDZ_(pk^WEe!%||rXco~OO%&WpW2ut+`VF)Y z9f70MQli>V(RA>k6rK@)SSvV-mQjW^h#+sxM^gOnih6vn4cJ6OVe3H{$X-yV zMAs5UucOZ_%cBu}FLDc-{1bLbVrRAAXzJRQw(P;z0r+mP`}pBe)Zw~YagKw<*G^a; zJ<4S$z|eT8F2QJ#o9qhm03H5OA1a}HAQ?4_ujf-JfyCiHOwAqFl4jiC>nG)8;SY*8 z--Yw?4TLd+f>`6X{z5qp0wT=P^73jMvQ1fDQBfZnIo-U@pi#2!Yc)_)>ebcNKdBHC zzv9_n%!R<0uFFw(cg6St&fxFqqUaz5At>cfE(p+jwI&0ZF9Lz^7nt(?FR{yx%4HXo z387cqf$~Y2hc8`Ruq1ZpygE~Aq-6H1%zSdo(-Oh{TYK(05i*`Oq7xi zdLJx;YVgi{1hFiB)C8_H2yh_C$J8}*f~xekZzE5E+HUaqID%T$xcI#rftH|$-{YR^ zp@45lLG5&dihfd2Bv2hEIepmoA_~aW0C^-fmKM@`4&F~f2*KY%jv$5b;8ykg5|kB4 zNonqrt6K7vtm*%UviAULvTN6cZHNkjh!T*dprTSjQ6SP(1oTzmRX!1{5?cP{_n&=cpS@@Hd5@EE!~ybTWo140eYKkg zbX7035V5YyKv_55v4!cCefsRoHbUD1K?>JV&KiHFp4f6){mQPdL)b>;hgRkP~I zuQ9e3uO%(+pmv%iap#UoWR1UD23Yz45Lfs!vc`*hgJu{%_yF+o0!W|%ogohXq%F7* z@lMu+Kq8IKQ~TSadeVWPhp}H$q*r1s_Ky8$wWm>J{Wx;6aV!)UyIvEGxxruJ;pypD z(kPVgPK=C`XhStAJmws31timpwe4IsOXi4dA2pl~2`aOTdNA_PRg9rPn())hdBz}YPxHFgMAn}&reGSd>qn5-&EtuYY_@)GlR&BsKaq~JWvVH_i z-NjmmC6G#P$e+ziJO?N?Ya7*piNKyPou^Sc!|qu%VZ7wD?b9YR%-B0FqBX)Qf5@;w zJ;VD&SXJe-SBg>Z{k}a7wks!zg3+{@ zG9Ezo8G~uo_fM=Pf%6S_MCnGz1>+J0TTP1>Sr`+mr77H%g=r4~ z%!UqXrsF6@7`?$2=>12Ih)*&G8#vms*eR%KFwpWQeC{9uyt%?~;tZWf!Xn|gny1HU zT@P#Niq|n&qfH?33ZQjfFWrmrB52A9rBA8T+wuo3gFHYM4GqXz&sB$kJFDybQTDtz zI@P%Kk0?^`N|I~z>t5N|I}5dDV79=$jdOii8T;ce4_^@pYuMF4S&PuffL)p1}@0{I${6sDJ zC@N2T!^QPQ_P0V`xZ&8KpX%amSyHwJd(3$t*h*&3r@$dVNUOKh>D;bh6e#pbo&&T2 zILERsXldpVwZ1O8{t=DUm!mTU?kw6=HV;2V^XL}f zT6+(p7L`hrqPR*iru`|%rby04gb_@20L`=4$|`cEu>uo=+Me35_)R57VKQr@!$1HT z;{JZY-iXg52B8XX>y+^V@U7gV)l<$aT|QByx0f64HXo0@wIN_xU8TKLMT+8DqcF5z z`P^G8+%QC^tP=>TiYL1(o~0qxoB6ifFhF>mIDYMsA!!f-L-ddR#+gn|oov7!jaOA* zkB;@}S*YXZ|UN00Z#%aOwI?i8c@u7x!iuv+TZE=M6(yB1rWBAFrUUi+{+e zt}kL>AwhjL8U-(=ZSP6ISO6{s_iD9G+jDn-e+{#ag|*S+g86v-O-Z@KvOH=88)CYk zic7}MnW6lc@zXeLppQ*9o9b|ghq7|f;2_Mc?D2BX#m)Pk_!hBXmW&CWrg%1VF@Qfq z%|%Rhf7q-mslOC|xFbY@@d%slM1cmfFfQ7A-lh?LVhd2jaS-qYcBMx)3Mxbv zlSpOG^t^RUcy$S8*a+-$)=VQC%r3{RL|@N`Zpk!uU6J zyMO>kWg(|+MyH6bJysRrmPXE2Q!7rW$KTgTT)pgl#y}!qv5frK9q?NSphBO{7I&@l zTL9sEAWRB6mBLgz5T5`Ac}wUs2v#{DuZ~hayJHeSGEzOUy%w#t)5mqk_P0CbfR`g; zXlz9i1_%&o?iNfyOUZ14wrI9QFsl;t{AHeiop+KkK2WsXK%r7VrM3c8f>U}P6ylSG zzmT2zJUV@0aWL(EH1?b$sM#W8Pue3X!m^(RU1dVd3;|V&xa4Gk2ojziQky!!k6@UD zJbZF}^QkAw5Rbj}+x&3Q&>XXU|M7oggj_>1B2Pj*%+( zc@;L^C*QML{}3*zsi)x9+AzK zK3S)73Dh>D)FA%nKH`j)Qp^5G7Oi#3eWML^L59DCA86B?=OL);vS11H4N*Rmg1jY4 zP8L$(@t7FV?PhJ^X(G4oR7!^Nih&Wq^kNTCYHPu2gwcS*AhyT&Pi90(laGkJjq73c z9OR^n!Cu?hbyVGabQnlhMl7~|+rYrijRH2djzCsp_&jofRq+}$IUD**0bulLV<8}t zzJ05w{(w*EeXE>e9w|S{SG^`sHjcT0qafy>a?#fb>_d@1#DoLLZe->4?PtN45uGaa zGv;rN!STQG#yGS@U~OeZbrZBh!mo|1hut?`e2atf8*G{zzK7=dzM*KTg58EDMiq|F zt0K_CWONjlk{*P8o!Qxx2w{iuLOo?+d1%E}8R6;pu5uV)e?-y6H{phh7dirkn{_HH z1fcea1zjLlk5J3|oJ%wSt=49XfT+!kLqhy(FSEyEznjY63~Ph%LThfnA`JD!^2$nS zUg&L-G+4v=o7+-DFu*Q`pF!ORJk(5qTCvkzTbU3lCAg#9_pt7UjU;(M)NWfUR6ZN< zdCwtn7>26-iu`&|Yv;|Wg@V0A;|F7MxS23e`SIEqsl?A_9NkrcdA~N6uoQcZmB~Yh zq=F<;wLY|~BsJ8c0G(FM7Xdx*)k8_CH`Vmprrl&9;c_bCI=r=H;s zB++|7WGsDOi!%Crvu z>*v4zvNJDO(D-@2HooFkS7E@Ulg5{}7dwWpOo*or{G;5VV);Xg7j-nMFL-j?i&*t4 zmzaZhq$CN0p&M-##aChv?el|%9T6!!Hmjp^zCC*;RnTZTW$4$cM&~cE)b}Tk*!{z$ zuGk;OPCr+@%}9*3$b8MU^B5@UOLHE4&39R?bJ0i>Ry`0_`?hgz?v-4TLdO&%N3L7G z>)8^bzwfbZLGL0MI@|tYC;rGIVbOp1vcwO`LFKwuQZA+sK3pq(aK`7#>Hvv&?q|xC z2k!;f|70?Ouq6|pJ27okRC<~?n*DKv<^Sq^mvO0j?b)9`4~Uy!5_a%KunWXjU`E_vzu?~lrV|(+&p_lSyeJqw@4{ue~(Uy98SLFWL)BRQzF#RdNf(GM#drOPHetSLowPx(Y zMLw`bjRl85KyI!10>x4u2t+er>RMXzz#BHu*LON4rM`N*txZovSUB-s^>iO^>t*$DYM4|^0&Wx{P^*ssz5|s{N9ykSE=qlj!@?2FA`4{UX5^XX=w>~ zl&X!qHUGZye2yznd$LW;&F7T?y!AIEi9Xbtl5$c+Ozf^|fRD|wfMUT&(pxPJjSK=m zNU)Zurcxb5BqbpP!OQkqaiqf>cL1`}^BX~$~`>=^=Ecpe-Nn_AFNTn6$a z;qgYKmi-M_`i=NeTopka-3kM>B!A>nAURxkuBG{JAPe89legOp?1#|Vi(lMZiN<9e z7Z=D5Flv$=fVBE2?4*@&PzsQ)ioje%$y`9(*A8{j4-F0Vb=T#G*}~d@l#+4~+;C0= z(|Oh|1F0o+?!r~jpOXi(k#Ig;pj#aP6%;)}+qO-ZNjcGNb3OA0?vG0j=p)U6NeMR9 zmJck8Y#N(?41cjuUTe+kuD4fxYdwI?xXRgA1C&L8+Kziz=hi@ry3?{!@Ay4XbFuZ>Y)_7hl|=@LPkjNvW+-MqJCu7h_*bxw765_InL zW_?CDW*s#4ri<4A+IO5b4s>P6kkj?pKlmd3M(Xrx%&sDZOV?;OhX>|zaSg?U)GjcO z_>7HxcHh96t^t`aXsD+IJ3fn8TP?%EX?UvAZ+;BC6NGB=gBhB}v5 zF)4_kaLH+kfg+1EyZl9s*j_CnL5T)Z&};E&P0jf+7B6f(-0ikoT6a|=egREvIJX=S za|FkRy70jR+mEn$bQ%2_>x#d{nw+#*{;qpMS`vjRAlkfH+Z%;EvSpp10K9EDXl=-| zm|=bIV|FY7CvY3SS~2IT)G8dGY6Ntc1!p7y`twJmglhj-0%c<#Fe-&@^}fmHY0{e| zfiI^C5#n;c5c?@qba1h9b7nlWROx0@le%XvRQbeb4DMw# zgaC55FR(ytF}6J+1l)Yx#P{M9%foAu2frPp;NO5KcXGIuN9GNPsua!r$NjQ?^RdK9 zAcgel=O0UCTD%$a`qBVCAV9VkB2x&Rm4PZh<-j1ZT9Gjl$ zbas?n{xl{HS5~lj76${Fw23WHw{GVQn`kR&LirlXVa7@BFPC5t9{@(mRr4aJ+Dho$ zYcnj>KZdQ=^!oF(pL#kJgosqsT@QDlmcmxstepwZw5l$LrL-ifn<)hNq{mecr{YuM zyTAfDR3|0Jq#FNQB0L%`#tTwj`?fn@qT{+S80E;BE}L6$#3hjBZN60XVlN;}Jb8Pd z(Dw4`$&~AlzJn^R07$WsUl zVhzPFcS*HV{o%ZzTu-NL+K0U7;b3`Zxp_>dG1A|LkOSTQ5qxC=8FdA3-jC4A`w_29 zndNz2ZZVJebd$*~NyFL;O@*(K0Um_huBnKBXA}FQMBXZp@#|TUh2vfwUP<9LhDWdQ zMP7#TY@P!4O$199!p=xGULiO}gbr-q-Yzp?A z+lQi&Xb(>nOIXPJd~b_popxx~(FLHI)k!A*8cPhf@d&r+sQQjf8-^~F)_`Zn%0udE zW}+})a~-gR0RiZSaX?R)b`ktjjC$^%Y?Ka5hF~?ZK=d{2wRr+|@WEO)i?>U06tKRe zx)86so71=v@3c74k&3nTYMQD2pT?5GxV)KOC?+zc#{p9%w4s6C-{Q*JW#)|^kHtob zJLUS>9ZD9HVD*&urvcUZ0^F6Xzx!>cj;hHZMZ$4?vIaWdXEIv)a36o(PZ>u<9S~;5I%)I1_!18 zcdK6eG55WBpyqu*>i8gx%nMK-(Bh#SN{JSkf*dmPruCW z{0^jj8z;RDpInPKGF_iNdyI@=dhthQI0+jZ`p?9A74raY%aPLFz$v`T*ZY#RbZ#-u zvNUh;-L8CVr8SKZcueR(Sf-J+7wZ0bM*{~SyPAeLISeNWp{D(oQZ`NM*k2u;V%sup zsD$2>mH!+|oBh{5C9?*0F?UOkO04vOFr&BEV!L_-kN)>-_+J-P8~#U?BJRZt{YTBf zf27}9M+p#11lQEYEhf+#77Sw-u7|- z_`~mIcY$HY_`tVs-wMaMYz{-7H~!KCYkU2?qrN@L!R+gt-rU)91m)xX-8eA6?t zQ`qlMri)?{+t^&rk3Dv*o3$l8IL`MLRsy$ytbXbnU~(+Usx4Gd|SU zYkPbf(Y85q=O4b=OMAk^wQN9fd={uKlvPzP<>yV?*P!%174_hWKfce1y~odqpFVX7 zw0Kov*BlCN-OIQ&MtbNx{^*P)xB181TwNCz7mpdp#DTDX*xVb5{&_~DVxq}b>q|l9 z%j#!y;{*qYTCzOMJ4$98=Pt^M-`MfbeU{6vb6-DxDY$;McFd==UTZPF#Qi8=%RhWw z&l`mVs%)A=yu9xFD;*}NOli3T`2;YZ4KgYD`JU?IHB(F`^ES6KiDn9gLV2L4@{uJa zU%&ok>TKld;&D8_wzxR4zLHa%l>J_|UEkic3I6DjQ`G26u(R{U*-o42`0DCvigA2! z>k%{I6>v0)8VCoBAl--qI!+Fkc_(|FMbdV@vmfe`!3j%?MUxO&68CYZ!(K zPYG^=kaV@knXeB4_^qXQCJ%db3x5%z)(-IK=(0zAmH@YgOFHzHL?5nL5#}lTa(S5^ z14fu^w8?_-3PD)JA`B=M60bBQmxyiy^BDa(93LRM3faQ4yS(T;nK0gh3V`DC1(gOd zHXX>v&FS;Gu(n>$p~M!ou9A$^>XBaSTlKWqu0QY{-t5=PrruJsvP_=a-5fy)V4_OF z3<6^Q8oF}w(aT`PvsPGuM2Xhfee|}F;?`vWd3@~nN2;Q|1>*+494KtH?RP`hNM#)a zxsGLxYbbu>Jx3&d?f<^+=<7R9IE6r;a;a{zPJCmlr+nf~{d3ZiOd?6~hUbYRFRvfe z6}4?uD1S4A=;sOYhiOz+ zU@sFBp~jZ1wgIHxOC@&Gc$l_PzQZk#(}8(*{l8eYk|82IP+f}4bbvSgd6WUf zH#Y_|B|PmAZP7FC=;O?*rTW<8T{1bTQXxrK@~RXQx*hGqO0!a_ypJXt#GyVtF7nJ;Q=%yfxnEK)&n=4qSPCu!7HULoI{1TvO|%<}`B%?)WNgXG zW}D^_K}6>~;5+$&-kv{j^M}}=D^1n^>D`yzq8m3=;;EANcAdb0B~4AFsP4c7L;C?5 z|4tf#ZH3y3!?Z7ta`P9#0pIy<7>YOov)Z8cz$sum4reqJ7uiI+KaCwV*q!3%tqU2k zNGJgOsg3le)P#?s>V&zz$WS+MQUt562Ov5T;pwgOK42;qj_-Ft906t+)s3Iv>Zes% zeNt;p{J=9A^?ulIRdBBW@#?Vzmojrfobz9UQm`tc=MPWwL zrW+BVz{e($G{D^e0FZ&*ovmMc_1jnHhr4Xg^SrfM+jvnV_BR$DFRW|(Yhq&A*7Icy zy!8moGb1hS0=KEZzbG#hr~)1%`Ib7(^Ky&KtZc|LGf!;zcs#w{^9W(yu~t`8z(?UY z)c$vZJBiPt8#`|zgo|$1v=w-HK&F4D^S8MCv zsWmr$7)FcQ9pc$knHaa~y7%KFI8_GDDT_SD;^A}o{i(IH(gdQxz2kPvhgE7;;&w^w zgO-YztSkit30Bgt?@1s-*{$OOAaATR9OC6Zlokp!@iUC9?HrzXe4uMYJ;?hBTwCz! z&E2?$De3JJ7tuaoEv+megm1*alBy31Wnpqm@JDU$#cC+72IB&NhnyE zyCKOuu~Vm{f3Tsr-y1dm`f6CS0wFt}`~3jsBkK*K3yXKpWI z|HRC4=Ddv&A$jYM(1G8LA{Kj!seCiO<|h9iyhs$4U<7<+GS)_9|5>2XQg%YdzN_e? zg3&g)>}hIPoFZl8So#?L=t6E*T@9Yctrg5y0d73|L!PlB$sFF^GV@>33K$uL0wyM8~iwq9@aamNC*0?vIL|z82oln ztbdZaSCO2f1NlpB8?7-l-+Mn;MYi&A`lxXt^is@}`h3A13a}z+ zwwrF}BpKmfi<0!Q>fE{s>qKtuExA2B32>sF&+Tvcqx^wD&84Z%0qmV<(itlfrX4q0 z-J{L(uD2x|Q%Odh4NV`$U#x2EYiXH#jin)TDitR8>TcSBku8$dGY(Bv6nR)1Rsi-X zm&Xh-JP+CF!PNz>*N`2M+}UTOd~Rc$1tKY$$Q&)9esfv8HQ)+2-3r_FJqt{MSywDU z3X#FaJ~xsO+ISA1DP0!OEWcqnYLQ^n-IS&8aoUL{E+N|QK-2qUeXbyG&r{>YU`AFW z*vXQl$0evYH7tVrjFKhk@tM4D3iPh7{cGjfkNSX#M+h)GQ*IN|{$TPHJMo}y5xh8o zAWnHBK4>%Z65`ubKV2)leIt*0!tRPI(WyNOZ!zI!v157QrnT!~qEf8g0Wk0dsfFlv z?~d!3o`k%4dTp{5eV8z&ctpgWJu>z*T2PVi_{`d6joXvYLewnpPTiee zvn~BtG+Q#E7U(3#qPq-L(w8@yUc_nc_1GT!6@t^>@H$reYqz#zC2*4rhD}+Dy`Ucs z-p2NCtf}ED1o=pd>pi3-714}U_ic?pjk`4Agz2`$>h1x%Jf#=h1zy;=5S*mFdbNFQ z+~(+n`o*TA3%a(hHEwdf8tkr@Ah8KdqGB8~TUed3aZ|UpJ4^UcME#K|%%~kR+AZb@ z`Jr4&inQz&_bsKR=tj*l2x>r9XZe(_T9tu$1Yy6Bn0`R?KecwoiCJWT+q9NuLhZo0 zbr1b0K16KA-^@LL)elLJjx&)V9)ftPh<+)#1%!n;U7}8mXwYV{X6(T1AqbeJ1-wfn z|MxNK%)K%X&|tFA40G-VZpsbG*WfxNN9(7R3pB`C5og?IN(0t5BNPxxnz7(>V`iz3 zI3oUMa7;Mcf1%Z#ME;dw3^J6gl4{O!07vZE0!cHrv)U3Cg-xq^2O#&u zHt3Aah}fSFFSOSBB_sQ5E{parI&UQK3F{;FlK=uM<%BeUQTOwlCf3K98AmOO;tGz{ zLL?2DqDh#XY$|pb>Q-7E=T}sSWA5?XRaOlKuN-(Tkq7CF(RQMuwMR_18a|1c#N1;K z_GeE<1LXu;G;?f)XG;p1G4jM`mKqJkzmt7!toU6$&<8M^4yv}uvV*#IMwq&z)idKb zA_cleUs~m{EWQS(sNac86jh|pNm+pvvW&gwCJ*Eotg`C;k^pA-<2x*W9{03Se`?X4 zbp_0dWR+nU1b1S%SWS-4VQ&A%Hdkqo6oz38o#UM~GSqXk4@!<{?Q%JH9IBoZgGO^H z&omd^GMtS0iLNy1i{Q(r-LiZji;>Zv;@Z#T`9;zX0xu5aCRl@b1C_P+GA`2e920-k zqAsNRK+7$xwWH7M&xqJIkxVO?U9@&lT&z3&)zG;eo8^L&-bsSKye&qaEi!Wh84a;K z8jSGq^-=uBMr)S_X9`l&tzTqBeBr*bGOcMR9mNZq0t|mNM0E=6x_ZSw9{kN8H?JM2 z;NP3!`Ppe&){|^%OVhIw*;MjVG;;TrD3&(5IY03tCmQldaJ;_JSFKIG?IzxKB2#F3 z>AcgPM7mW<+kq$Cr&%QzM!rpEb%2q1KzJ$WL%DPq%*zx>2qr#P5I)6IBv$fVX9vje zSnc{Gv({CRibuZw4O>&pEj05=@xwI&M%z+oK8jD`F1FYwHxh_ncdc{P@~p3iBrb1I zI|8>$FwF`@h5Diy?7vPR&85-C} zX43n0)JGq7OGpr-6znFyob>)V1jKaUHkBT?Qab^v&;I>_{JI%xzipYKI%=*aXqI$T zU5)iE!=^rS^vpXGYl$y+GREZcg!9Z)wqhe>2#RO({PP;r2;hE!S|5$(>$+%qbMAO# z>tV1j;fhT%LCjW>Eo-At+l9C?>WQnsqRNfHu5<}PgWB~oLZx}#J($y)HQm}>7x{I! zpPxAzVkPPxe}f?E33vSw862^c}I`ogMnQf5FF*E!*hjIFA;1_VaeXz2g=|I^RCqW6;;5ctc)a5V5`!k1u&KUqoX<`9bpO<26o;#qZ z0=xUA0tk`=^t;;DU7)=Ub*m4HKx-jdc`ZrgbKS)P!QHC>H0!WjU~aS#P0%2%L6y~y zZ6Cp5Za|<$x_pt}-`2#2K=$*O^>+2m9bd9)_}v~OMwf81xgN@pet<3pg@&|`Gg5TC z16Z=X-99PwUylO{rG1ga!@8c9s-wbR?o7oJ4(Tq0M~wglT>bjENl8=KVq>*q#Y`j8Nv7oYkMz19~+@7AS#F zqp@D+If*T}8I%C;pHRkyKZ_1l1&^XgA+2!AmlxcHgPJxi%@K5~<3Ofb1wS710qiJP~Of?F5{kO*C7G2 zD29?O0Ltm(1$Vrq$USiy)E})WBdnd!t^03Jb3ftw}s zK&lBt&jV@a?9Z@$Z2I<2c>C6XTIl+}<`#NsBzO2Xy$nw`L;DagD>g0n38^}+e#Tj~ z@>i*KLgL#)nosswWcsdkr(ez}I;N?3newOum0ml|A!Y8^yLun)qUcR znz-v^pncb_YLYZildIn-lbO=N zoMo-@4=Q~D(U58WIwI&M6{&S7_ zU&=qq+pmYuXe#+>2}Jc4)}ga>7+tg1Ci;K;9Acinn?!Io1eZ6ac}>~qAH6%+1CEfq zfh~0CYm)kole7KwUjdFY<=-p)E-a3Zy#7RM9}HxBqH(=%vW=Dk&#e0Len`zEoof+w zzButk=PL4wWe*R>m}%!s#l0H-J{O{+!5N-in2DPGC_QmYW?hT(w;VB{H_m=Y$B;v9 z;iPTW_~z{rv>q215uO>C_n8MLsB)oX_DO+t{l7i0KXB(xklMXJJ@@%0Q-*f^T!~mU2B7iTF*!A4Oy&S zcUAPAb|NuHli)q4Hq9D6-%ON9>&%82+zDC4Php2n1)Cr_qO1mX?Lh8~>qW~u**{ZS z^$w};V;ih7ydsZjpcDLMJN@(w=l%LdEE3u}k*fPiAcsDM$A^eJloBlFH zq>#QlzBHbzh}CjjnO;Q&Mfbd|FXh%{SJS7CZkv*;UHqBER;0UIvLz=sgwF5v*(ATggx*~=7TH+n zZM!nd8iH-Z(2%Z2ore1t)K{7(i}JLzxQ84J81Z|TEkmU1kdxfb{&Tj~;h~Cf$}{;Q zNArVM*Qa?-D}3^Uaz1N1{+2b_37FY zA$fBCNVHb1gl2}M?h1d_&hpTz>h4OeN~o2>_nF1YM)p-EwgEpS!U+%WjbK63wo&657}*-R{?NRnjP-U7UaWiK)jg$={F}vN0kFk$kze zTQ6O`-0{6i6Yi#6v-Q$#Vp%9Xf*KJ-Z_k^hK|<5Cs~Cfqlc{6Oh~ryZe0ffMby-=2 zg!51As|s)Fyg2-%NjvuLfBOKfJURCJubq^XATAxhW0dU0&K-jm4b++c`hDwB*UZh$ zU7S1oo_V(5#Crn+`BL4Nj1sWf+Vz{hyX${ng>3|l{;De)N7WUrTbuFHP-~e5zt}KV z0o7xBotd*BiYf_K2H|<%R%T2E}l+oy5@aYoqkidHDoYm>JF1REKlzrA(f1=~K9}^Go|d%n+>{DQzk8Wr z-qGiO)9gp*&(?4H2eR2!7^n};JRwzi9mXRz3}SG-79h~R2Si#7_QlZPFdu7-B9#Ej0{_{Cu53FqCAYL(21 zII0Goe=YlhA9mfRP|GOO>8rU0I-9>2Ib}8z!k$qZ?AUSbt#I<%ipt}B8#g0}kF+zz z$J~-b0?DYghALJI_UVq_Y{&kh4|z<>g}^|5iu`1Im-sB$-$mn@JKasgUC%F@ujMAx zHp6@Tx>OX#zGX{porPZGm{n^8zjIZbNayshHtBF98dH4BO(s-vnRg|?iltLJPscvP zZuzQosxlUM8+}=F^}PyrKSJVz>g2ZJr#Ro_z9IKECF?@bmIr;O^}G}=`>RYuG^A0p zn%9EXfq2UHYBJmF(e^~pcJ_c>c<-B9hUD1R-C3qdy&%VXZMKz^*J%rb>?`)!CbQ$W zTWTflXfW1o7OWLvI&TXNF-8ge+8J4$R}&Vlvbi!AAr@7QZO7H7^#|#7b+a!uhu((? zI$s=Ptf7Pmq4(W1t2u1M`$qrzJ(HU(s$9Kvlh0?4|E{5}Q_WjH!Hu%@P@Q&iToAp< zx`IX@NKhhP57VH)HFonI-UnZXziBkw<77Y(GDoLEVk9-6|NbS%Y5Ezo_j-MPmN-9{ z7}8rSb{EnR8Z-@Mc5i88s=N!@3N`*XxQO^yZIO zA8HvDmNu|#KGQ4_xzbp4C~%r@1N=WQqPgRwT_nfpG7mr^Qn?5S_L{sNtVfjA<=nEI zf^6pSCg~=q;aa(czTYww(RF#7hMGT~?GisrQTW4FxNiuR^TVdNTB6}NrPs#zV`WcB zEyuJ3EN9;93EN6SS^mgQ5P*cgNwGGHzv=cJ^9o~bOLm-Ab&-25n$dBQbu-?k9A#vv zG;woUb?)_s{D+_=sqI8bjSbYenRa0x@@*P}_)aKqZYbQ}`1e zgb$b-u)U(8k_`NSok0zWNY{(B&c7UmP+8^bz_JogNiSoB`4h6q#zTdp zqtgpLxKk2Wlqp>%cz0aUW6}KW(Pm81s;ew5{!(NUrgS`-p;&!MG}jAPrRnGPQepb@ z_*d_To$e8Z@^QoWA`>1LBtpanE2XSH#jHoya|_U-gXcr^XnTI1n;A0iss2cfoI@P7 zJF0uul@hzt^0(~P`mzz2Zs1_FGk?zYX5}IQF$xr2t^66RCMyO6cbE!6vsmn^srXCiNB$Dnf=!Z4YY+ZW17=2&dT z5{^y;#RssNn;cT@Hkq~5CYe$|=1ej&Ai(o81Aha~b%b1m{S zh1Q1|;=iDj0tTm_hjSGwQ6yh)M9~;Do0<0T_vWX>93ckmLYb)yxxdTq?eIi}R4?o) zF5Fho5gYZ93MNzh20?`Kxn-%oR7e3drFPfINvGYhFoi3)!r$r99-+=^#ov`#Tfcr} zF8?b}yvv-8gzOkR;-I~4wv$WCbz-I_f+yh)I@*Rboc3R#TeFfWwe)K1$^3+!I@Y3b zL*_ZZ54!BBL%uRqifhg=ZE-R6R6|hym#JIe>c==$7s4#}g)q}OpDctMGuhM|5#QGN z(VTTnlFY0Os+Ke1KbM`lxC7VTEMRRa$q z3JiLLI_ElK}j=gDGZdRV1+eRx6W;c@&bLWCBI?_ z${WmrKN*STKYH||NBc?Zv&VNP`+>jJdf7S145q4TVxJ&M`zkRp94I%`OKUCPUjy>} z*w523bBl|s(H}cHI&NCskEImnUATCOg8#CgoA1N!?(V0Wl?W)I$fEi|Jxj}f#oAcp z>S+T4QwlhvT9Tk2d$Y96gPOH_Aw4rvxYjuJWztTY?BJ69(4OqJtFYE8$`p0#T(e>u5udZOPT z*F=*CqB@*x+&ZGd-S`|JjLB+N#I|u&4GnWfU#;n17^r2Z%uN_bCX-tOL1_>Uz6p>S zIrYvhs)!?*1_=^AZ86mnFvO9i?vGgqb*of#lw<;?+Nx5SV#X^y(gvjFW4=U#@wZnN z?CKo}XH3MlG<&84<|3Frry$dpOtzoj1)#J zfF~7z_b$m8Ru)8w`zareE)q#Xoe*yo$;SV2P4-R0T~N8n@Xg24v@c3iJO;hncM9Ic zY&Od*Z+De65n_H%zEpzQw0paVoClmmB%DY~FB3sOJ~NbRoW+y^W|TN2H~5*@j-!Yp zV_2FV1**z;{(cxb$Jk~wKi1<>y)czs#YzDgMjwFf(%{|3D=S0i0KF3)yy7_|r-l=rK}m1Ld^F24OGjgD}U9maeX+3N-G3=#xdz z_@LTPZ&FL$W74?_q>*kts(^E;#4zoY8b%p(yo!^`dB~;4%wEZ?@hcj|N#rB#?-3y$ zp~T;r>i6ODM6^f%O;sBr098Mhr_o09E$!0TiWSTOh$7eD38 z$zHL)=ajc@#(Amb8bEks;!sQP^s((ox3h$ERzW5mrCI7Km0lq)0e1f5i0W}@ydDS) z6tPs4^Qw22_VxT>9X4)Jk!1JHkI(whsN7vA>&^gkLVNjr-A(;w(`ydPe#Zg2&=7W7H6~nGK9^QcITQw=!NO?>7jqH zNN+E`NYCyOlAVfXC#t^(vp#bp((cISUxrO9e6#|Vs~d$2@dKg+k{Oty(*f~aVD5&x z;V6n5MupM9@Te9T-zqhItXqhosC$7Kxvu%_v5kl9Y6`RSA$kNl_aYKAtj|ERv(!<& z^1$QXEE#FlkSLv(HXL0BG6T7nejK4)#N-9Wqc>VjNLR7uNf+fA0)S?$}h@ZLy`{MiaqH@M$gv*7pshb3`?> zZ4{vAZ97)^YX61SC?B!Ps_i$sTZvn)-&$z6U|!@`&K?T5ySC;hR9t=R^u#CA-5*Jn z{mb)s4xzf^*Mgi0vpaCx{2RGK4_d`w2mOeu*zPA3kRQi4}bZd zH-C*3@!Om_vZQl$2SVlBC|U?q)$;n?ro6l8dvo_!TkZjdB|V&7a?owdXso*I9H{J5 z7#4vQWs-ziiQ;?O!2x63j52B-lk0$_10gqJ70*h!jDgIdRRV*4?|T-5^4sgHq5ihA zJ#?~fm|3{THDL#4aBYpBqs^PO}a4RJ8g@6ATGXop>I+)0fMjas4NE1Zq-)o-5e zzfvc3g^iryIwC1nG6Gh>S{Zlt9Q}E+7xSRxI!TEh$Tf+QbI~n-TIlB;2WSUe;>|1{{T&V zg50AMn9<{&RP!R^r^DGiIU2|kjuErKvcot;)0RZop7S$I#CLzX#7h-=PUr`dQ%qitknuI>SdWiBFBYMA+Ukclh{w#Sm*TatM%JOvguL4y8K?9Fw;!;P zEPpe_^Yk2C-zD2NU=N|7{8Ile4 z_+K4!ff8rmn7Rat+(z8+WEP>h5ERY3eL!DOB%T3C3m-9?LxE-eu96`((!A0&i7E|P z=gzXmYV9* z^qlv-$uTp(#fAt5bxOMNP_JBC11sv>Ha29EUwI=u7UWlUYbP^{)r7F--NN~1xjIU^ z=*ldcCrZp|sIPF#>(DUFRF{CTaDt;Y-LEVmyPsx5oO$%(3<9(;)9zE~&VAmr0@MFM2=%qVYZYWD5b?UG4=D}JxJ$-Sw0 zR0!Aa#_F7eRG@s66qIMe-$!vNE56_kz{>*P;$2o1IXkWbZmWI>wx$Pi`RF%b~dvv!7? z#~$ld*m2Q&WE)s1;W$O*%E>@?OIthOAmEp{oGiZTUAt{AV3Iec51L^S%&9h9Nayiw^mh5J?BiD8u5n z5oU)R=>cA-Gxw-^zl@4FQYAZD1&V~2kwtlNDtkd5Mc*()A0R+`s7`rC=!TMqn!N^a zSBuN3(B~sb!?&tpv_s2#Gsk(Vb%OmB0?bY~jAU?8nye=7C8+5wZQ`m5P}ra+7vn-J zzkidDD-G5%-$Jg#cz#l3{oXB$)%z4gbL}*d-nUNaKx*D#32ybJ%WOmxY^(!$f4})A z6Tg?`{;5QLA{{a~rtF#4|NB?cYT2`dkc#~F-FcNqoo&LBmccU6QDoV@Lb_bGc-Ic2 zW;kB+5iOf8NRk^@QGn=~+PS5bJ!?R=zg=Xtdv4~=Tht3%13n4WHtGW`Ct zwc2;LpaUgj=dbs8(E_gFcXMuH+j@ygn7Gwaf6IzUyn@v7xve$aEiPB6Zw}OS$;@)( zHSfZxy~yC6@%t*3KZpK`D;%w^$LS+V&J(VbKbU{iBW8^Z{s81vpPhWIqAT7qb4F`o z341pl23mY6=qh+S=7PwwGrhO_+St#o7ws}0hF3T!h%(q$c*-fHS$q2C(t$~*rEUi? z+nn?N6~dJQP_OkJ?XP5%HwrursIYXs4FIMyGS?~#)ffwG%U@dz zl^IFdm?j6>>unvOD6{Qy9M`@W>e6~pn2Xi5`d@tWe$WYeX2eo`kj?(F^$Ak^bOD{8 zN^4y1wD=}3SGFe$dZbgk@_WS_F4j-cjD~07ZJcVXX+5s(d(+v8i`mOx;kA9I;R_*l zuw~m7U(#z1N8Yc+j$BxX6%80(YBk*Vh>^OQ%r5jF9nA@%y{9!U1Hc!Djq{T%AB8W>R*wRWtokJG>h2hHsm!iV*AC3~+G@u1VXLgfwhE_f%TcOZ<+D80!q6p( zmHGFH`$A1cy%v{x^Ow8)D3W74?B`F-hI$$6rRCEaOWZYUZ#3HAfKp_StZ>|dZBAuZ zH-<&{3zt*cPzz2rTY-|CJZv3j%h}H;YfSIiD~*U;*CNl2i%N^SMx0^+Hb zr?XCjra237eUD#9g3~maeIxcG_UT0bsBs00b9y=HG1Y{~Gu1sI950CB#s4HpCcGQ+G|>rL|jaRh1xyBG^R{LsV5z zB2_g{RaI3r7e$CMVy333d7fg7DG1NUKF@j9I(vW5TIUaVev}mwckbNx=X1TU>vahy zheLVi+$WXSO4?s3Pr;^i!~~jHcO4jGZ=5%MDz?nzhqKx~mai{4ww2woOb*--(%+v+ z^G)2CWL0a1{mAWBI}{N7YwD!83<|FPS^FQK0r-(>aad*lUD+%~ioeZj%KoJvk+rdc z$+*2mL~*-!+UH?I{6$~<^P|q4Jo*FbrKX;3YA@kHNqwq)`ly#m}^y$iU#i3OU%rAQUu4E zU<>b28jLx7eV@5j9qx2?P~rrqK2ndg-|1^m)ayGrlQWQSMtTPmFjL`aS$a5cH?P|! zxSh7v8bA^^9ze!zNeH&Kh1hWIRc~FM<&DP1sFiNJZ2&;mA8#F8S$S-cT{&v97nqbwZPwfABZB zqc^8*>gavX;ZA|rsco;|vRuNFkF2Wua`bAor!OXoIYaASOYQUx-3yi0_`}gyOxWX7?h}Bc4p75iEUR_>*|V^cmjsDr#GL6-p_v9a1LkL1n;+ z-6Ym4NkX25PuJR?X^giGbPxR+ieh>f~4qCzRFAD%@HdrR%3UaTz_cZ<*KexCxl858|7GQX+H zx#B#^4oiI6o|h|+)Em8b3hlpN>lf|3ADqDLn*>IHZ(7s3>%2!R(=3KFb~lUAIp7lT ztO`qKQ+V}H--wFzVeipux%qlJea`6nG;64zpP#&vQp`5JjAu^x@v~2l|7m|HO871L zkP+Hj({_mm3&tNt*fvoG_bEbK*o0&sELw85 zWE_hDl@=pKU=+VoQu|SbwFBLx)?Tj2E+ud+WECCJW~{>N=QD?IUtWDhEHq#&Ze*xT z)vf5$rG9hz#AF82H+C3^xnfDG+;L?VwkhR7_sL`XMNu5kkD-JmmenF=8GVB(@!jQ6 zb%PJ=lVO-o`hZB=VWx-aa#EWcOrqr+!-gJkD)6|Fxbb9x0@JdheWU@*V>&f*fYy7j zvW{)Q98i$U2mG+lK8PVNoKZHD5ZDB`8LI`H|eb>vM?(ZsmGE%>BmkJB9+XK=skYvtA@PrTx`Go(RQy( z0ws8marl%}6eO`cs<=*A>KoPMrOt&B#Wtev<|^Umj-l_9#gWUt9-JMGCbfou3n(GE27ATHcmJM| zW^x=<(%n2~-S^UPfX@esEw`zF#mk(;r%8ss60o)6X|3lxamfv=REb7XWN|1-e#6$3 zDAd3-Frxa*Rf=P{nC;hD7ejs5XWUoEb{mL-OwLBRF8V2FewuQODaE)E#S${#Q?U}c zWF4P;)qSVn3id+Ni%H2%CnsvvqjlJ1fJ4#5^j%k;54Hp46^c&G{q=g^0*OkO&Fj3- z57Wg-3@7@>9eXPoFOOAk)7)g;@72qA5;#`=~_$^qiGe-vnB2-ayQ5>B9i=2k z=0@y!dEp#`!~4=Ul@3W#>tp}8`L8eVF~79BI%h-Xv!PSI&uuf%zP`)tDP0|X8FkwZ zTq1n`vNmvr{nlTu9?y%>U+~J1Am?is8+~YXk1R;rS-=NuRwZdLT9;L=RjHO& zF=N8f0n=eo#(Zq%x>3fh$|rKgZ|Dw*{C2k}sKsPXMQvnagtbmviX*pYIIm24^dk$l zEpg|2QKI0a(F)RwR{Ya$s*XMpuRs%|$bdVu{uHaAN49F-s6|LWl)%0<{impy-QsX)Pj@yo>wZ{`1ffo`bM@fZ!2_1eh*!w zu(!C8Z1kt~^Z4F{EcAMkPk&^wAIYFBK4IO;FK)WYeYk2}9CVg%H|_%0_-=@!T0->k0o|8l$)|`^Z4-@ZeB{`h=N&|iTjqdkL*$}Gs3o;tlW<#>9m<_q7(NAQgR~e zIR@m)`6+jRXV3A$+?9Xs?D_*MpPGJ4PD;>|7;tg-K_ZcJt?s>+f=&Jp-kjaxqVke`%fZ8(Xq z0Xs-|Cg|Nfj=x^?lbqGnJu}YSE9uGMFX4~fS=e6~@1;H#^B;Or((f?jxSzd}sXb+G z{&?IN-|W`p?`5mcPT=R8?8>^AY2`70rJs&esBnmEe#9N$#=0R_P%ue1-mknd*W8x1 z`CTV6uzpi{z3-Zkg6E>;5_8ui3$Ng;AiLSQ$)f58{tjpyE$AU1yl~dkz0{oldJp`` za4`cE3baJ^G&+z;S#Jk#lZx&eN|y0M?JaY29%c6wXT8h(@3+D&24Y*$ za@}~LeeciPJ#{8G%NgCFCrd|K3zR#?Tdo#y2C%qa4u>WYFHcLR=QgE=j0Fg?EigW| z{OBJJmMNi_7;H_A28HQHOGS!^0!eK`TBRIhhpJCUQRG9;X=i==c6O?kZ)zeZ-JBPG zCTDTck#K8PgBat|?yttFF{Wp-8Wx)V=uc}2k0c2hy8PHNb#MO^9bQC$gY<+A=<5k& zoW9hRe|b7wq_qpeT&M$}*IK^CT2^TJb241%W9KSXOm!E(ozLJ^9V)$$ZU%|%Z?7M~ z;lmRwgf6DHpVZLLYLDIyJgHLt+%aJuq>ki3%)z<6<}^=jlu|@Jbvgs2*BlEFjlhBt z9!Ts2K%b3!$eEC4QWr>$vZ=1zjBXK>-kefV0U5W6vlu@C&%8D;lT1Xs^!&ZmKDA{X zjO#)uMiUgTDQMfkaaQLJyJI^lC4K@}F1fYJ8nOD_qZ%M;84fr=p!IF#O|#&P+0czJ zR7|~^g{FyfHan|+Yy83^af%11nWUkx8ZZG>=dN?~vw5FMpinYhy%g9*ye#y-na|{L zlD_CdehQORXyGWwn!7+5<$rOK6WGLKm1pb`0US;y??^MRPI}^SxW>VC=<}3{FQ_n| zp%V6ZGvCR^$ot9R3Hha#Y!fQ9m5_m;+g99a1$0@Fu%OU;BqR8&eW2NHr`E$ZNK=G~JgAlC|9 zK-g`jL(B0z3+J}i+`AI|?)Z=D0YN75mB94<8G6^L=c1g!e$Q)%t=W^8*^SGmqjC!p zZXWk(;UBN5YJEs7T@LjA__@4Z7oDQ6plxcF94PW!8|Mw;r`g2_C}MYYW=!Q}$#J2H zh=Vf(4?4Tf%;TzYnj0d7M^^Fpe?3Jw4PqEZHoXo?w*$OqJ_y8)amB9yunnN$2HsYk{O1 zrnX>w=>2R6!hSIeZ=Ndfp%XsbknfbK>pj2>%MZFKx2BZj15(_A#Ww z8ufrzKnySO#@WWZlY50#bvcOA-V4#DX>veSONJ;-u38SEQz?vqiZ4iORO%p<87k$f zY5y%;MBU5iz_hgj->!ZeK|I0PSr-TcIV?GxDRj0z6W)qn9~(X4|HQ=F{WUoTYXiF?ga*T(@Btj4Cz`&@iP+87qwt&L&I_ zG89>J8nJs5{xg?z@g7O+dww?ipI90Gss3#-Wr4x(_zkFfaN#& zp3z4WeRxt!h0wH6k788eh{AA&V9Kc?chJ?w}x0CA4E?Aw&k`~ABr1O z6}^~=*z7w_l7lsRgxM^cEe6%H`ds23&n28x1I&Rkbn{~?+GK^~orPZ8Mcy^X41?CeC(JPjFnth5qFRbngFRfkW6D^Hj{&plq%78bV`g5 zczcB%@ZBh6~d%0wWXlbbV>to|Jucjh3$P>T=$95f<|MQ~bLXxW7+p~f4Wc`U($>b225=0>W%YY#{TMgA6N=Yq zDXWtZHAam;lKs}eN>{(8>x+Ukpgh{u-wDr*y&zeIYPERb@iX`C`rPezz9u_-z9m?w zgsXw`N2jPPRVQS^i|flS60e2|LUJh=zY)2TD`OEqnmOtBRKB&@pr1ML^M~e;uoiBM z6q~R*C3^jb@#O7_GPg@eF15GCHR_(U$8!|-Km~iswRE-gqnxG$3zM;E#q`iyEh{JZ zOpVtu$Ba|4-~#9l`6;?x^hU((UQ-7OeaG}e$RjaU&Mhs-7_X!=2hM>gS{NF}zwmh=|tkHq;iBvBV_G13E|a_qE8d`BtHUj{TH#^@9*4U2~gdd2;R=>pHKN! zhN3GNW$E{Ae7)>!%e*9enhl!oeV<8ia4{a=?U-4Rbf?N~4PJ+z8r*YnUDd46Zd>pa z+t2Rc^>ZkK)O@MnH@PQhEw-ePuK*WCogDt+Ta!$0cycFmB}1Fg(6uU!SyJ}=Skd8| zIWKD`w%05DZQVW-6i}|;W!iakQ7WaML@#HWKNdrwKjyFHG!TCAXXe4Kp(N~vIv%Fx zTuKLrR{)$|*n39Xy@20(&qQG$^?Inrf)~DGq4hM60=By`Mf__UM0-+bGT}`=^){}s z z`1!_pBGY{FP87`--4n;JfAswOyp*!EgE(55^lTj?@gpohIPgg|v9AaYq)) z^(Q#fbtKem?(IZChHl?gah$wIhsh8lwpUp`Iw?VlUT|F}~;c+d|f27bqvFI6h}fBUQdl(cRf{r_L*;JEo6Y`5y227ZL| z@bLUxTXVH@a?)3Jqy4q=*vRM)Zf@?h{nkX~L1(btvVHzMZ|0e;?ep1>`vU_{!!^Ki z^w->+-K;urBbI`l7@Yg6FNn~(0>}4^Isy^<>({RX0!ZoGQNd&tEHDRH-~?1uWQ@4OA^x+nkw>kZsCt|=_TBePlOsODlYOVIW% z1^^TrAlMq+2STqPc}yNs(!X;jGJzm156li=L0PK^(|BCT1+@jybS=(pQG}N1ho;9K z!1-Moq|jn44pyZ}Pgdq+LCI40F`W>XPf`w!ArtQ>Rjy}ngDn%UNi8dYuL2OOXg=r~ zxEVIiwM%3RmShVDoy*5}lv?>&^%F8O<4T6+exfyf3vj^nvAbFuA`|W}vga`#kkH}~ zWw4uVRHFs~4JeRzI&N71gkCZkkRnK-U|~)?fdG~h0y4K65N#T+&Yjr+okt4eVW4Vv zc?}REjyrc*S?<+oht1YrdlC(r?wi_a{)bdloWX|SBiI*#yNm?b7dZh?<7*KCvmkvg zp=QO2Tt&qZZLo5_UhW(P-O{FaS!G1fxJ%pRa_tSxtJ|Oo6Eu&LHP%cA32M!0#t99J zW8D0uDhR6*E^~sWA!(p~cQmug}ko3c9Lq}(d2eIcE;+v`QN^mZLJZb7if zoNpcSEGqq;aMQQoxbj#tXts7W=LL+>I6%|4MtzbBmYe3B0VOd~)s?L%kXM#i2CW8N zk&|cH$^m*vdScKxdKo7|zcJzQM}qk?#)SDTB26VKx%_!i>FblgS6uZQ!ow^zP(&h^ zl@HhzKoZ)?I=NokweqwvK%|~SUwD*zqqXU@3H8=D5HpsbXrXJ>at^RCyLc7=gIGR^ zdlVsrquQ)kcpp@7ajsH9*nE_m{xVtw&UG2w?Ae0P!9gIIy$NFx7z+Ba+5^zwq{$9U zb6+&`j#pl8f&vEUTgdt826oFY=S<0S@nSj7R5rFm=nJx?$xNrc8@HN*rscr|<^w$DxwxEk9q{5Bu@6PQ2fv0+o z=AKs*y}T|+wo0Z)b;VupFEwzoA@y^RA08lw1pK<8n7zlX(@v}mm(4lnxuNdxu<~5R zLbr)Fm?ZM)QV|2kz>}ZO+2ZSsmJlrCvhIJ;`ezbQOsF7-rrxSy_S35@Ckf_j=GEoi z4CnB=Kb@dZsBUU%s?|^J;&hoRu^J00080&yIJpL}MRnQ)HXo%wKhAHj&zpw-B#fAr zy4o^7QN%*=hB>B^Rjb=|KaTS92vic;5F;yfl;!2W5L*~pL2W=v;#<#&+~6Y@odF2L zeR9eV(;dr8F%tvaDW#QVxU;`fO)cP59B&SeHRRnB3&hX7k?#!fF%3JbjtP=rOxlLx z9hP5)6j%MWxP>Dm;b2{I^gEioaFFbPvC{Iu)_b#YlvUWattC5M^Z19Vof97Z@pmuwUErj}KGh9H0$ zo4mEhg>tS{?v;a=ng#g|S-Yr$lJ#S(RHKCQ(%gM9tfj!ovL?n6`8hOlQW?JW8XRekW2{CV5Y>Gq<%=e zcW#Mfb^8>2wN^Y338aT1rE%VpLm`yirmYzfYe2@~bY@luu%=+nhD}AgbKE!%yr_<* zPypvVm6U`-P9VY6CEo0=iS}-_b{OZ(+SGCEX_R-!#95jdWk4`PS2p|!em~kRLF=IHqCcistmKd~$MlzWn=R>ITq4>b|q3|;DYbg`6q z)fU`c6XGn9PJX*Nb#}eVJ17x6FL=uWHSsjSqlzF9)^(8)s0UtC*n~P`<%g_2DFoiS zXo*f@0~z#ciyd?O{3Np8!xgqlH{<0>YxxQK3#QMa7SD z!}*JpyrzHKHc9{@#9lulF!~t$m%t4gpagTt;DB8Y(yvC>vO8u%xrYMK1Ct5izt4!) zF`88sRUp1YiwLj|FtEEELdDGHqh9xd{mIlsIWRAyCzRAq+knf)h3OS`OpX8>tsopw zm$i9;wowcelr?e)wl*5&dBJqc$t6De7?wJqE#&3!DJRiiYfR7atpOn5#?J@yJ>QVu zt(V#%j)mu`JrW-pxY61gd%(#+<_2|L#D+sDZw-gEpT;JO4%bP;+0g%WXTpp{*1)K8 zEMC)|Is0=U7@qs$Xv`6ZyP*lZGQ-lvGMt+HrL=@`M|#V);KtJzwda0+S~V~@xKOdp zg2+BGv#=n!G?W?ry2`)}5hI46xN+y&$q7B=#8*eq zI0N@&-aIZOt=Tiq8*IkbFVCv#VeqGM_Rt!gm1-55qv^*)m_&%H_PVF@Il_`o08hK z9AkWlUTAsHLghtXmY)bU{{;F6yWbe{Uf*8j;Or=UEet!LG{q*QOPQM&p40#ZhPS?X zVB!IWXvHGI8n0FxQ^IZ{M1piWUjqal_9GSn2>_Cpnlo!R6~Polj0Gh0!nUgp;iz-w zL2BEeSa0{{aa?Y(l9wwV>)NQ{cd78;u8Y9!%NSi}g?5vDQ|*I$18fsm}q9uIWI zNq{P*R@H#c!1h2kH_tIES#YZ?RdHUENgf%sBIr|62m`C~uCY&BFkC9c(ohyzq6#3~ zV7D|)J~lPIt#}kY^dv9g0A51Ni_r=*$EyUz*TW*J)H)}@YT-KIi*b69FWP{P7^kfB zHi&=(pi9d7P-nUU2V5eZSQ;c(q&4?*^)w>JW9}WNyBtHema%KawK}9sTdA?&$%yRd z^Tbr1UYuFoI_AFJr&3o2szP+PYdVahLmd#uyqs!$o4j(6c(#sn<|ovZEHZUC;=fgZ zw@%rDlK}S2i+>$M^Ou9CggjZ<4iIpw0+lSX*1`fEvw0mfxiDbu&z?NHk+kc|4G_G>A$jCnF;Hje?+L3E5fmm!Y7cOXEL4aL zR?hfTBEw#y-4A@lI|A_(3{w^=mTVYm{#^>nr#*M(_#^S8(hEuCa1|g0I+IT$up9EO z=EB2u9?Y9$3q9giU+1#0No{O&y*A3vp`+B&+p>Muf&8|#)^ttL9uU02ixH5awrqKT z?-e>20}4vEZm6$mxtgaQ7^gLbSF$p6X&ODo;^f0SW=r_!Nm2SsLta!TFZ4iM+|Y96 zf}w(>^b>-4eZoU}E((N?PnzEnHl6Vgbmyql_Vx<^5v_HmR zmu5?;`VHDKjgM{sr$`g1UkCRCe)`)VDaP(puaB8(qJR0G{5JIX50{WmTQ~DxF9KB+ zkaE3Z04dfS3>Zz!SeHfkFpYxT_%=M$dQR`@! z+1S$#)R)Un3Nnt(87I6Z+F3%f0HI0rGL)(V1PZ-oJLg8oD%hA(G^yjfAiuoD6P|(3 zlc@z#Q{i^eS8xX~l%ED>Vfy>Y2kbR*#NlhWLYbn3H`1>Y>m=efmxF{l?IAtC0u%C< zTSjsBR46Y+c&j*{3&fnDDe)j8lfhV92ze8De|KtPq#=f5JaW3^nnrC$qU zwfGz_rML5D>3W>!Mr3#OT&&Rgp_u;#^Z^O3uwwg5q<~-&{`AvQ8j*h3z4R?qhal|k z`@AF)oXq$YYv(ru=Q)9sne96G5RnQlP+2NZ9?%2kGvU1UQ}-=%UBrQ^Qu{+DU6h75 zzXMQj;U#CWTiQY&+d6Xz2{-j?bK(F%oS52VFNW=!P?cyum(_GdgVsNna8p<&MD~;K zg>*Zxg4bHj4Hp5|L%Ze-puvqoF^~qY)xRXec!U9&ODgHO7do`ZioGW5o9?bCjL0}DuC)A7 z%lVYa0bx_fqpRzp8pw%5QK-CgK<1wScVc_KkAXbqK~iXRb4J5x$r^ddH;ODZIXJia zS=D4=&^Fio3IVz!^$C)?4+fwQxE?<-)kDeESXE&WPQj%8mA$ph{hH20or_%Pq~d#WXYU@P!2y+Ae|lI=Hz~YJ=b!F|e{b`_BNZ2p{wkwq+rSp@ za2wimRyW^&V*2Ed2p1Ic%+loN6KLVX3~ff&uc3Fx3F~S8`isBRa0tncSLNLAOaJ4H z_@BnOmt1B|3xk1Wof%GSX<4}R*yc;9gc-1nAfvPeZ)ZGd1%7qFAdiJezQ;I>DAL5Ne&h`E5h^R+WJ=sbpE zTp$%v$hi@vPxOk*3#w)OE?pZ9u6ul(oSC|=THqESI-RAvEvke4i;YeVWW%E%8(J=> z_CxD`-Gc59?*dL4#^F2S<(?@N>hg6XCJaCH$T*tH*MM)CdhlALq9*Bw)-;DQ?2WAE zG_&<;6aI=9I@5O9AN5`YaZKH*6&2ro`vM?ihUZuzfv(@`uuOf!xD;pbsIBlt$wL=t z$#as+DIT)F_s!(=ST>(jL}6^{w}y-p8oM9i7(&-YrlOoX+7JiV*RrdFh#E&20Ip+l z3rT@Zl01ZY1L+h+STWAFT%A~!tUldu*A~*wq5%sbC*))_-AVv&m`vu!<$i0q>fynU z1q-{FV?vLRTfN_m9G4xPTP(M{Qo)p<3nB}ckvTN$+kT+ zPGGJtgbOu_H@H+B`GRlMe1ngblM{6!`qtf8sfmvWt>z+S4Jx0G_rMp09>dq>sZZaF zC=^Ay71L#-*ZZ8m$tzp?8*ZrKI1fmPUqqPAAS$JR zrzto%c=hI#W5k10(uJL|F^dLv^upqX!?9QDKp?P1qb1nc*yK4%M%#c6IQyhk!!HP`?wke&DWiI6fAXIW&z-egZqT}S0Q&0R^ zKLvO0RW_F^HmUF~p`OxMIuyG7Q7SwTc=|G6mdor1CCo8+B~Y*#UIs!|^=jmcAiumS z2s(>ITemd7b&O|#{@P}Wmc2`DFdLGFt;LnqYKFk^b5 z4ekKR=akTJazY9ADh1Sx2MEw%%<`3+9H3#D0jAqvhNW85s@#hbX6X8K0Q8=3!)5dY zm1YY{&JX&Pr{G`#&S$}*q3?KJN3H6@I{;Z%ImUbN&LrRIgLSg<{>LLYqS?%P2;#kq z!)xZIKs4=0|)Fc(<^uoTF*QUYr1Nw@}gm@d1XtB4wB^u9E`?*NhQG7G-w8);h8EuQKL#J;T^gc4CSPa;RNxm417yY# z0S^p_`Wv^_Bw!jl+mGqQyoCyxNPj4zxnvmf3N+ME5G-mS#0;g2Y+wL4R{GOHhf>fv ziDj@Af%kITJ0AfK574re021;7l|MjXqT}*%?hTv^U_7m1#+sj>G0FWzng%4s)~X;r zdgLbLxYLgaP%qqi=r7B?G|adbata>vffXkd=AHD4#V4-~3ZMW+t4&^P{({pS)EQ>Q z37)QtDwNAkCe=sy#@IDDS;4snh|#MxVn9MNx6H1GDZMqIV5X7EIf3eIst5wI>tZrv z1VjOyC#3eOhBBL`;M+i0fQ)dv{MKCl{4>eNdL@b1$V66Ot2N|p!IA|4VX^~pr|Xp3 zJ){s~yXJ^g^Sz|fS~cf4yud8u+wuF5m?0e#zyRUE=WJs8S!A=WZ_fp)|0Z~@@{lN8 zG z{rwZ0pnutsOx|&n5QSSnRYYZZ#)AZ8XDZ28I8jTJW=y?kMa5?Af=;sS#$DgcCTpLC zC2}r29WAg_#;7d=h8(QcSTi*W_{glBz%E-HRSK|DQnCb56nyNUUdl*Fn<{pM^=>V( zg%Mf^g!H>A#>3Rq&lB=kYTYk>2jNka*sh(nJ# zhg&1+ygr#jUj&;vrRJcT`ber1 zX_Dk{x(ixj+~!h;HC%gA!jwx$`cq$-&gF3(7qG|xD_H8zmyMwfY|1=e0 zC4OCf+wx zzCO^7%);18i7l>LmXQ86%z~U(dyTlLj_)p5;cbA!xTTF;)8wy+!E+(DMFX>lM0UPT z(7mLt;(_UtgG+o&W`&|smS|)7j zecyDqzdu>}NgGAf-N8HN>h-np|B*I4yN6~T})%`^3bKHjKJ25%DxDTH9denLD4)y?&ca2*KQ9GIzmXO*WP}7^Za;W2l>)tiWWb-TI}8H89ChN{#0&Xi4>wU z=;n@A9sd=QX+UIa!O}EZ`)SwAFF_ZxHwVnysMH`K&=4Vp0@znzu8E+VW_2oE8t6D@ zE3E@^@nqZ~!;G=wNjZsvjG7~Wj^A{Z7CLj216t0o->W%o5^l{_4$}E7V1qad&z}}kbRd8ZB5=%FKj$q(G(8HYC%3W&Hc<|!jufpbIj&TbplCWH zv-9@%bnvyVeia>9j%W3X(C{tjxx&J^<1UBTyE_b)sw3w*0+19<=TnYrIvfmZJnVi+ z1X^vl<28DM!;7rVqN1`QYaE)QIDMzpg0|9RIb-h(K;@hWloQHf&GC8N<++PLbH%S0 zr*NunS(mC_hq}~==?K&05S$v8dK`a$$=XrLczZUn62ET%6dbZg?E-okbQa}2R4Q*m ztl=!V1!%tm$B$T!EUh%rWN@Y%q4;cIM&j|IXg`sx0SX82i(sGd1X&*GwS!t1`iz}G zuLaO2@Cq*Rf-va1)2CjGP(09NS^37fV!7{dkbwAC)GHg>WtHl)JUyJGO(G|(HbOLD zUTf5*)uGg;Z6N)y<#pHitF0;Q0I~0_*yY^t=yX2TiDqp=!nzD}VK~VhHDhi*9t-5+ ziDp=5Ktj5R4ie=0Kj!$b%ywA$9F(0jvW#dS|beLQcF~@{pt+iaBd{$ z4LhyfMTT)w`M{M>Se0Fs8mLcHf&UC#0eipCQuxuq-CyA(TChnGEhSY=H236T^u_0Y zvx|#Um89k0<2ZyQxG9%#@4x(y9kGh+(H}Kq_lMpkI#9Ppyw^0VTmHMYf${2ul% z-Qz@1<)sr(W6I+8mRFHcy@@!+#)$o_;-;@^dFf8Lnik zw>{*45e`^%MlO#prEz`+F}jzF*aU1{ggvwus z3&iQO3k`&y`t(=KI`3%a;IFkt^tS8NQ&0ER2;v_VaB&j!GIMX}bo(o2`RJE>|4~Ew z_k%?;ei@UoYs_%P&e>R2TTjeJc2xGz;}##O&&i?X@zzn(KX309$DJ5}fm{R$X{nI? zKMwSNIGF$SFH{zt&-F%K#7o`%tf=jqM2@-?|H~D#_r%j{ti~0)c>76v&(T2GxiIA=x?ef?ahV{>kT;Q8$UL=ZOD_I?tWSAYX6Y%!u7YN1M8N*!uM0x zi`>!;$}j)CVeG^x-n)Ew(;pDAmslR+ul^sDuLE4P!wJ_#w0@xSX8ID%OLg$a^Hi!r zWHM@>o2K}%o|&ayxPU%VQeF z{%`V=U;l~RW$Y(<<+f9O^yi-?B~2MGbaXKbj!XJF%7OQrI8+~c3%g z?Em_4{`5mmflcQ6_3J;w1nt|bb4h~>K2}y2@pyb%RFwDRADR0& zCg{ZZ{{8!Vx5WV?Q+Ft7W%B^`-w}u|AV=}6=?JTXnIR02Kmv;oqC{ZZEjpe@cz&?+ z1$9C9{E(&_E-o%7eW-JhVy2Yy^PmCp?wvaqC;ETw_G2sma&v>faC76?SJ^kFH0x+< zAIMj83-jVI^x&5&Gf&S_DvLVI>+$l@e!vE7H<=G&Ns8xWD%! zIY@;ooR4Wy$tkpJ=`%{vX20f>Vc6X9=JP~Aig!NSWZI*((fK!@rO%?GM0gJ_pQe;K0K{E_6l z-c-7kmDjPd+|0F8>K-FW>j6G0QjI;jQY$~$H__!YQG!xvS+^ASIE&~kIc2!rqeMZ- zYJ^o#VBe*RQyL09s9egk#$C(6G-e-U!Nzb_7ZdDc(q_1prZoA!1Gi@PjB$g1nck3 zNvLg}yR$NRNnJ&j+03%&T%+0~q>RbSkq(^}@9vx0J+06>aZ#<}S#9G~8tD}{w(Nc^#b(2jd0DnzrBRs~|Jh(umJYy?3qT zFTw`$zDS1}Av0XoE)E*D&5E@dS`@8Bn)`V*lRC#0pGX2Cm6is5#%Q|Fc}& z=Z__4);s3k?Phgp-O9@IGHiE(Wt4pxCk73zS%e{RDUlVSeUC;()C9!i&Im8swMZ$? z=W&+ZPHZW9p^8jlFC?pWBG;_dajrKF_>_OW4E`2za1F?Mr&tE^oySP{o zSFhpiocYYo2pU;oSuZRV33-luao2a(+rD&bYi_M~6DyiEyA!{3N8Mw)+?GCL9Vepf zlVNi$?mkkH)-9fTRqZbu8{eW1`P*)`XB6*5nT!OO^hEm{GQsv8yN*15!)hgf zMXK+jw&;Ugv}u?F2_lXO4taaxT9Z)7GfOq`CTzvEc?L}%y=&d;9Pxk}_*uGP+d!;Y zoNaP+>-npeDJ$ewBf0)KX{PCoAjiWyD|N>S->`MHVT;`#^0%BbCD>NJ%-EH{#N){&SdBn5uXl{}!)1ONr zU&;PrnAR%hGrL98-j+>Te#p0KXf$^&u1&Vq_^$WEIRPJ|;OC!UlWkH{P5~_ChVn~P z&tn>lhY(!OZR8jpY2_9aOz_BmH(4d+dC?w)tku~!5?X!Z;*tiFu`!Gl{4SzhVl^HS z{X@kz`&m+=={Pp_c?On@&V>`(WsDnS>pDXA}fUe<7aB|(J>SLDM|$u0_vU@=bQ4P zQQwwF>@ADx#;Iu2jesff@{fUi87@7%JhZVAp8%caus1Ev<@qvS9SUKB@j2lYp^yT( zmpNvS7n;7f1c=;;Ipl54hnr%cqD`87Vhg#r%f5c5Hn8qDzUhmy8l^&N1zLLgD#}~8 z(V-gwpRcXIi|2c2^-Nv4JPLpBX=*{mb>&y(aaQvITFqgN0#>{$Rz|rg7!`1)JVQ3W zyd6pn6daDNGhDOU@Sgoa8xx3H;r6NxL`5A_W_#rW*H-w5+`Gmmn2e8m$g_bZyv4WW z)!iza0;dddc|2NCp&^@$36I&q;G?H$IXWenGaGLuT)@gn63B2h*g;fA^H z2@iGmJPNo_R$55waTj#ON0_*qg-dD%?MafN5ptvB{oXsI+vpYW%?^R7xOMJ^v2JiA zVb;l+zvpHI`SexT+3W|k8$qaI+E6iJBHr1AHJ+onUu19*VpxnI=kJ;VcU;cHYcB@)<=<|8KwL-%U`H? zYEWV#QCs0*a5(864lOS35lVf4dB8{H?+jvoxl`9V)QH6dv;-wpf1Z=ut!RAFP0GZI z7C)>v*<%FVp|TLR+@}B?tpwqw&@;CeMB+Ez3LY3!irvZ0*mkA8pw=Q(BXntftb}H9 z{~G;)oCx!YN9MwCE~hEAFyRs_v_+dKy4p!V+y&Wv>jeqI!#h8XQP1ybNPRL-MNXOy zlFYVPv0kJ1M%BkIhaEgeU0n=b<#Rz1es75)=OU~o=Y`Vmw!R=$@oMx6AxP1S#EUz3 zX+ztwk4nqj3K|cizFxSF39T*Rjpw@a(EhYqtr5P^&R|ejA)EZr>NIv>oGb3h9(HQp z4x2}cL%twU`!uuUN{FR4wbL`ZU$*l}76n972lP|P-MiYmq0}J4;1vQT#}m2G6h1%=&E^uC z-&MUCsY4^|+>_!f8!@$3LVmdJ{R10$ZTI!CUg!o@-MOM!$QOgG{jpo#+$-jW{wh(e z-`xscR&TM%t6HbFWVhfUGAAD)FT6YXX*tcfF!p3{>ciG3fA*D!G*Pwsj9Q4>Ii$B{ z=tCqeU- z9c%s;PU3H#PXZ1YnWpi8{Lq;N8Xb4KctY|Td(R%X*V}_tqi*` zCTw*NN4ePgvc$=^y~o}_dbyBWUu!9s#-8eg&i6c+QC;M1SX?D>;i?D@0U~9KO-P<2 z>MGLBJ@)TMZzbxWYl{ub-hAFruK5zZGneqrxDH1!Lz+`s#bb0_DMe%AW6&0H`}7L5jC%1R~!cE9gP0*z=Nh`;9T>TMB|@rdLhdu^bogdW8B?U3eh(0 z-lA&xjAI{W)ffhtFK5bH77r)qyvB;Fyh_>CwtJK}I(~k`YGSgY`pC|U&B-rtZQ39q6w5C(MXR)n_+%vw^N%CIO>Gbs-CB=q0PV7HDTs9Lq=-pRL6s_nWes%K- z&U+Uvu=z7OlK4s_ITBKnJtn@&zYP3Vd&u2~kH9(iW}(y~LHPHF)t)8p4l6A}EB$S3bw$Z9zC7DWOo4EI{oE7Vd);Tj z{SKb2Xi-movs0&jW*@pTnLGcDe%&W4qW(JlNji+}|Do*7!`V#R_wjjWrqfPoRXc>* zrZwoGB9_>hjzOo2x3yK3kRVD>rPh#X)xM3Y#%|Qqt`R9}i&|n|YfG#ZTMdo0l!*3j zb$K~kIOQ65U}K&qKl)J3EQ7*%+TV4#%z7j-)iAJBxs4wo3D1)^Tr)&H7{NDgC9wsV zyVKGGU#I_c$@NQ;7$&+oE+`2$Mb_!c3e_IK{`KL;Zu zIRIg_zpu3X2V*?BT8IBL4=cENtnD9;v+aziu(Er}-2%PhRYM2I z#MbUH+IEcmj{`Kd5x)^iC5Hx=ciM`U!%z~pamTHEa`_F_-`);sA{+euW$?k);L{?j zcUdM;HzV|Lvweum$=Xtwww`f3rc+`cx`+B7bxMkm)@C2N2dQ&+X3^%%AZ|T(fIhWb zAd!8J{5l@AVWb!}89xj%Poy>~G1;^Cb+hooM28*>4V@|GNvSKMnYpN-SI-d5i* z=a-BSVEMXQ?kfpd0b>~}chU5-*InG(5*lyCB{Zn^&&akUkwv4;;)#5MG%{A>E0=>9 zICb{vtlAVl*zx^a8-k1Hp(T|Nd&MJ)@Uc5DogP_cIrv?j8)6G(YI*Q@UQ|w3v_#bf zZQ!MFM5N=k&L*U^{|hs3wbngIla9ZI&HYjNt3(Xp>)5mU#Q4vlFK46jTO+^ay4XC2 z&qG8*8r)6=AIPd+%ClX-c8+ymhK|)OmLk$m#qt_4PZ{}n&di@Ri3fRCADp-BiCgh~ z(xA_N2L_c#wlo=xtgjfqH);{yaNnTUaKB;;E9HAnNAGKWc_prt3|WyV>xbF=pxB+A z$Zrzg2iG1hz9@mPvNUfZ78e!T80_Wo(k#&;B8Rf$X9U%)wvg?JN*AGG>o-s%Awz76 zkNh{eMQ8Q70w5Vfs=Yqoj~K+K(BPL5<%K&^EJ@W^A#cKG|ChN!57o3tl*o|sj`36a z4`c&@Vg$9ri8ykeW?anE8dSVTptEon(^m0>(t_PIAM zBCMeyMRQxKdPO74qW0qZyy;#39rv}=y4CZxS|J3L`_mPiruFm!I z8;M4i@c{M24&R#|yp}(PO`d9rW zY_22AtVYPK+3`Z8SZD+8fVK9qCT*$%V~$`(tJbwb(!0i^6XC->A$~=7PST@MSZU84 zKiEWThTT?E7@^e8HB7f6WSPpcI#8mn5=$2s6xx!=tv|;JAgBiRHW9h!{8BWR!SAE zpnauTA-P4j2!-aYsb%;Os^dLo_{16gK*xK7FWJ;`XS_}Bx%;J=Ezd%p9L2bUblx&8 z?{R^Y-wN2(JlX3ZPqEoxvqYpb5LFc}-(gckGT$ZU+38)61jAWLon_)Rz~k#7G~lln?}uD#)(-_E_O@ z8U;ki;G6I>byz2L+Xog|J5ya{1Bu=Hc?|>KWv5Bu{E$wzHv3yZbQPPMyiiz$hawbo z{_V~9Y^+O?+kX+UYvMAfErF5UjS%YIDZqRERq*;Z@}vfbS-`x^UAR&Xep0@I^W)AV!vWF z_>v|K-R~VA&-YFB#W>v zd^ZUxzsgG zY@HpeR@WoLE2qrAF<{~6=Wqq?=bLFKDTY9JtQ-a0*i%^<# zKc3#Ai**hxZ^^cM6^<$?BU=zK_KeMB+XYcW+`A@)62!qX9R={d0-5^ODN=0#JxL4K z3>$8X(CqomUBwSdp_(tO7dSDsUEK_DnwbDxpzE%T_N11fOLP+av+RUig2;A+-K*To zoji${hIvQA5nZ{i^_RxSK5>{hq0h$TilsB{)+MRCG@;!`RA=-Ty+e1wcWN9*FVS9n zgxRM|_xQ#2G@IlUKJgnZNu&-NPgNK)pVuPKlqVHra7|76sMuhpQz($&PQF7X?RE?BgZI%V=aTDj^ zpq}DJAzBASLeKs{{bU6!CK1XjM+!>-m@FDCf?yPr&hb^!p(W?wu!(tm{2-I~sG9{a z+&oPj<(KLJCv-+Vl~P~idbTlD4cR9jxNLk#Vo=(xE1|u=sUuvFBqQ+^i!=|$Bc5Js zsu7{k_Em#DuZc4_Mdn~aImEZRHSj5Fu}%)pgxCIb`&RH?BSe5vRjxeIQ*;Y4ZWgPO z2KF1qGOVr~B8i}}MKcn1lqO>A0E{dfBAi~l^F|m7g}LD8+QGgk$;bKRxY+n3L4TW1 zFu6m_2%e)I_?XH6g)lnCc|bzTuXM-z3~Vw)^cD;CRV$?BnVy7sxm$GNIor?*31>=Q z`G-@940`+b&X***iCwRtl;)qYY4xJ!*yrNB$`S^SjY3lrdZF3^lHahl+aatp@VRS0 zS+QtyBDR0xE}Mla>13P9OW3_He_vKCf=~&6su`jG?HB-Q9}5aIScAr2Z$>x|DS1^)H-WlS@pE?7;MfD#YW zS-lt{es92{DrcefRSt$wX#MCrR2xK)9~FM(o>0YtT+6d_=PF`rlF9%;E0)j`bfg69 zl2MT&U=VbnZ8ck{_HbI`z{`!Y`z%ycm@NSA>EO6GDM~xQ#9^rfm-pyZDKFlUAHg{h zbQd`CUpGfH9iy_?6;Bpvwo>{FJ`)-^^D93>u>%%ucy1Ga%WXuL4)UGe zL28&-1QwYZofyyFjH*c*v>1+U5F#~P4C%D*Re~X}$kbT&En_=MlqTj;M=|DDj57FO z38{C|?7u~9&B@rJ$*oJeNZ-;aK|yeaBQ*2tw_-FX3bts5>z%j&rxhtEEGY9H_}uB77i`gB(+Ql>$MtwR&rNNTeWN zPyP5{Xd{a=D8g-@jM7MPl-T~pn3vfve=u@8t2pGxzAntAJR3qI_$MB`CTMyApPbeu z3M8E_H-Tp;iY^;3`SQ;E$pOk2V`rw+JwISFt7yNCS0j{W?#r(_4*PdpP3RsRX(^WjJuAU=EkA)2-S0EGGJlKLNC zCx{NzYnJ`}tH^m@i_e5=>(p!qXmXBeZOG6lw`{o^ zhx4vI8XGHEvh5$cXsq?{%Q}8GSX4l0PW3o;$H9@?<6o_jyt>?fsD46YC_f;aL%*DL z=kxo5tkH~W{ic{-?5zm%X58tt_A|%);*#*PqO1{N=l9#!4z>}GOWxiQmn0Vx*Pa~T zjLU;d`UUrTiLoRDRctT72P0y46L>MK^?7$4SE)zNV+iZBE9{AzKi5}Rht-WMT zS+{gXdv%Rh7H?y(Hi7w66- z@@I@u>LePg6(Sx>Fe^P>Bw-1kQLQdh@`l{9*jZQXYL&>sop_RJWis{5UX~HH@%VD5 zhaLO?KMs`)W-=)ASvxsggkILVeZ8{IL=fCVZ!-IBKZHp*YA+cITjk5wH#%?DUzKBG zwyv92FNs@i8in#H;ueI+)odCzccyNu(Y-`t*KG})wjHyv?KD)8w3&}CI-${*Rf zA)3Up(uN;YzWf<$sRbXYXS6?RwH|3ZDmEE*ZS{P9TF=s?_t$d^Qe-!KR{M3T(}vMu zS!`}5fL^z3T~v`;&7se)WK^7Anl}_qm=Bl2ji_pR6_e*JDwDdS6rSA}fg?-Dv9^F2?s0oftIRLRY_zg zwHITWYgenSHP%>Opazxz{*E$f?W?La^zG*hu@KC7qAa>_rxM)L<@uF5H|z+v`-vFQ z*}QUsr-dJ3nbA??7P)Y<*Kl#;#^~^w7Jic=uS#fMTX#q@C&hs;mtMRdQ(w#^ zir86BINYmjDR)yYZjtq8v+0#nROQ3G`tvO^OX-(l9l{Nc{vMhLEUcPL}1w~^x(1nNMzfVrlf zf@9E-JEI`Mbc4GXJT%BTV)hyhRW{aZ+8qrU50y1G)S&vj^ro{tx$H$>PQQYQ8t!m} zF3Bn(Ha*mY4v%qk>)OQ`Y^B1JL-yjj%y3_`qKCZq#CLkhA8of4JRW0Pq+y(9O?o63q)mkhP}(9HZ&`dEi}5P0f3Rv;W@u z)N)(A2u~fVJ;B5%LDa^%;zDbdmVdtc*xT!@Y;|wsoA`4R6Wel_w}>3Jb}$Rm)h4YG z!dbu%btcO|n4MlD^i{Op0qv@lhV$0r+wz`MwUtr3Z06jL5quK5ndJSI63RJ$RohC} zu&I+Y_Sp+t7k&u@vQ@FA*;_&Gt zQ^Dz7rJ(U>W2th4Fyb54I^dmp{c+d+T{Ns15+N-3`1j~O*&@wJ)nJ>Rm&_+WTl{bky|;{K_wp_vlG>JFA(db@Xmm zS(l8!57H$?pN%V%t=k}ezMtEq=en7+G&N+8Zh|xVn?jHTuphydGY{zX_`#P!6wb%c zpq|o&_E&DNqLUVB%P^SBj!5Eln97oNc9*=L8wIhTfzV$3@N>L!1G0+Zql-WX8Ju5{ zGu#t$S$)&j*Uz4P@8-x4OZR9(rDWyI6(Hyurulv15X#10Mfyd_-V65&+g6JI{ zW{rGj;^2B7eQ;4t9t6x;jf=b@m3_Jj%|ByYjQpw`9XpN9P*FxMlsS=563O0Pydbq{ zUr7tMX04ofx*{?5^H|Ld&y*!O#^-^H2p5+LIp|UOqi!mV&^{b5s2uW;9GMR|Z)6oh z4yhmDsNm9xa)-z(uimACtZJCnEIHJ_e^LK(6Ledsr=FN2mOY5a=R8Pz>xIZ!*Q;xc zntgH%ngNjw)f(ZSmgfq#!}lYH@;J?)XSUb)!L>u&CgQlwckuw7PgvyP;C6A?+V>YW zmi)=@b*A<^59?+)8`Cp&zW)1%$48jjttI?YVqXqUMg@1=b__zY&~>)&&V1(fpxWNd z;%x8o+?-yjf@s}l?SSCS5MDi9Z70LA5b9kZcR&jH)m*6fMium-ew;YZ=k8MY{M_Cd z7P|6ZQSIkpUaqhSyii5U)d_qM51(8x`6XVXe{3vdEuh`Y^#`-h!Z+lVQjgJutIkc~ zS@UzPzyGVx^v+lJ0n(XJKZ%BD$!Gx-Q%LUh6HLvgY>&1m8J0EGJwZJhEfTBu<=*!y zLu`JD2!2#m8)j|6%U%3y8pRO%xOhH1>uKIMfTV)3cYF2lu;VEo%nC!@T6q3)L|X)Q z`TbV+cxMiLvm^5d4spKat&(qEn-D-p4loU^@A2fgQ18s1yHFxCEgq&~nI@Ld>-jRb zR>Kh9=5Vi!lx7mfsOYwzlrt9RN4;qWq!u5G2P+uogKm3oN~v{S58%LH&v4WKjGnA#v7oOT{E$MwE1$gkEdy#SSVpuZjUM4?oKY#;|eRp-W()dCza9 zf?`^LT7}uTi9mkFf*}hccIRqb1pI?ZWCb(cch$K(zzce+gx6}<{D z+6MXcIWhpT^>4#SG-rGRL-xt5?#EfmWW`5wC9lu5TcLNGDKI5DA^OH@&tt}C!YzP5 zZzb&t1n2dou^G`qbE~CULE(!e-Vu!z%jJ{-3t-}p6kpoG*gsAJ+qRh2qL-8*Z?kn( z-yJAd_4SYPpeS)*B-DmV09)q|65Ej2(yRud7*ew$)iR(ED2GLI>RT<>jQxVb`(vPi zrxHsA*XFvrSZ`9xQP$B=#%9N;@dM;b>ytS*#xkF!_Jjssh3*MNymQ2X8_~V*z{!B; zW!#?FE=sGXcF18}+^+*{zp8ImKTaS5>9k7UmpR@_UT!~Ln(2`U!#Qgn^+hkw;f|O) zeKR;TIpEld$z&VB)$;vAxlv!7l-Ih>JNC`P z-l6S{2>4dw3}vfUApM0kKrkH>5mivmd6vyOKY_Y)&p?FLhUs3UNO)LCxSs}Rv(Q>> zy=*!X0^7pdAs?ttobQd^cz<;*M?*DA4Em=$oy>EEadP;cB{{|}!g zSNplY;U??2q-%4npS{}u=?^9C!oU#j*??_+BjJSc28|b7W^!2inC3FPCF`Bh30Gn# zsQgIvBo*<J43-g_PqL{rv(B8f2Cva9f@9P9N5nDv8dQQ3wf(h`hk~@%TZ#RIAe|vp0I(vt%(5S`QO9g^0A}T?cMSAft5Y zR0h#J_5;iLJ!9FrVfwr_3y6Dff%#32Ev8^YOWy!YVcG4yCxFT)g}uFPc| zbFll%jR&1O(>T`APLmf-Q7jAEI>R|+?Ldwva?Fkoar@lm1?k=kT@-_UAdd+ zE0Bl?48FgMZ1~*}vr<&ABiM^Vm!-Fxic225MMw_&jf+}DoK_`djU2cft9CsaUeD-t z*)Y;83|z~)eRQVxkxa=+qNdd#UW$tcj~-}S7(Wm4%c@PGXrJfpQN1@YGZDQvJtg5Y zhMuciC+Ve&9S18Cn%+KL^q)CgYq)rN>CAs7vCIAcJ&E-m!_&uQdj)H!ZHHKSYs)=^ ztStDV|A=1jmT#^9!url*&ucME&Z~Nfuske1OY5n#UdSB@^`6 zMiRFhw`aI>%dLBhX6G~au#nLxT|>-yrEW{@(1mB*8xeyw{{&-zFY;^_=xHlAY`6I5 zCx-euYHA>!@9nK`*w@zuoIV;dGJ|ZqKAt*e8f|Cb3zo+VtP5=Q8dMw2kLf$iMz#~N z@kjVmLKxs|9BW=EEZt93*^5 znlpkx#?`79{doeRb-&Kp1s zIDd4I@7(YXW|h;r$el?)p5}J;hTu`BcUTv}Fuc6hG`Idf=wfw2-DkGvU0{%pH~+Lj zp!;?Yak*h86vrg8{h?EKhm83HnWfmRrF_mf_pd<3Cnq!KB)NOXbq&_kXzV@$%P0*v zUez|+9>4!~_zVE2E)%Aw{yrXK`-{VN&!X@AJpb4%1c%rCSx+1Hj?45(#@y zK@Rc=I{nnEe~^>K**I*gi%sm9QLlx0h)$dtB71#_5CO>6ab+&!tG~bM%4g2JSKMH& zXsY0JkJ=WoS3=1Ro?_$bDjwM@iH$h1jHBr>qfgc;z;LH7arkdP%zx0g`lnzq;=W2f z^85Si;V%%->&3|`mR@EN{~-uj|MWwfL|=EmeLZNw0{9EHf!m9|^w#L*&DqJyLqNz` z2G)~9bT4fT=Fc^DAbWH6YAIm%D6Cs^HwN>JhCm(-@x#Dfa3!MvmJvMgnjtrIc>5^t z`AeUmGgCD_Xvg5K`A$nu`=bY@pkkYvAf{F2@U**FsttH!n5{3o?CgdS-{X#tnoaG6 z5oX!iY3oyU)$heL9y>j@t<2WL=@RRntd2J0CzWFJTRUco%nOvHV|5ajG*2fgqx)_b zEKSHtBM(7YH@`hm0;=w`MUPvg$msDZ*A|@fQ;~Wb7fSp$qIL#&%p^$YWiMXQz29mV zul!_j4iuGnB#=Gx&ZNsj?9>4!YlKIy#xa!{ZqWL96@!RL_Ji>wB2I~HTPHg5tIf=KgfEU5$EDeO zYhP|n>L1{Gh92I|LLO};r~L~VL%TA1R-e>+ebF!?PeUJ2HdLhyLx)Q)&TW-iqmQi0 zQg;`jp|l_k1&__NGKffeqJ}J)SHGbdAAF$vME^1OtNEn^PA8Tjb7zu(kOFZX!f{G=XqH%G@3ZpTp_y<8@8qVL$VNF_T`(~G(3 zfTPru`}#+&R9`9l(WRXFoy&s4ELH?uR8m<>v0i!aajXCOAqT(oLwmpwDC-)_g+wt(Y{LB{2#eg8Jw#kGjsq17!4o>gQv7=mdZ(ZDh*B*(B+@y3^4kYB6`u*EaO$AK5iPK#t>$K&N=jCAz$#iB>8CzgOzDeL zssnt50R>zdr^Iv+zml1zM#m>l4l5{oQ60q7ku5XD3vBgPR0^ouAxpIvFT`@i-Hqav zd3)jTdsZsWF7F4uSxl&e_xVdGkpG-*=|Y{??t!b3>^+6WS410U@(SYehhrCa@jlNd zmAXUIn}6_a>^lFI-{KdcHV99JdI~ zh=Y0)^MW}}2Y0papn(=&TZcV>mYFHsLu`TXq`{1woqg%_W%DW)DbRhmjI55Lz1J= z($(>TrrUAviv&z8_dk}RK{-$;sB)7N$!Vka9Zse6B3O@05!)7T#Nm12L} z%|4Sax<(8MNfzYp{$+b->1n*wP`UpnC%zNT0;kz&99@RgMN>94h_F>wjK zrpmFfI%;&H9nj_gHl4A;d+V*on6GT(1Xx&Nvb9v(1?U8$SrO~(&Y-dskQp0t#->76 zTg?h8De*9&;#TzfNL2#-1bv-Mn(Uj}Je0gR~FnxtZepnVlPd3$b-)%@}qf_DIGH zp#WzGsQ2Jd@>Qs}3vB|ehe**3{_{cbJNf2^xd`nFCb8*y(3@t(MU(K{hTI}d9J1N8 zx4cKeZ-nfNA%oO2AdV#^zGylM@l3LQ)qt81fw>5et9`^{9iyDV&zApg`^FlO>q?;q zbnR7L64gOszn9u7u4|Z0XhkJp2%C@-VpmYS>)C@;v82`FU{TI%3Z_9cnc#+?3#J3* zAc6|8YP|g&QcNi9!-ZtVW#_0Ht=5hb>4bS_09*iy72w8&03y<;+)58b0i;FedeJ8= z2k)O2MBA);W=f%f23aIhe7w-^*#sLw2C}NidepN9N&p4Ot>EVyYh<;Z;IZL~h#}C8?JZ3-bUlE&QK~M9%o{-qG6Wfi& z@Iz<&tc#(F7V}+lrW}VkONlh=(C36}@(F}sRYOMbZg!Ts90Bgb3)@N#q?of?fsUZW zoEm63u%yRbrvKV!1W~x{DSY5?Z?LTpsGQ2Sy1uW8SF#{1QJ>P2yMV z?X4=IBPE&>hHj$T#UaAl^Ono#ZTuoQHLl>&$oD^F9~+4TSkULf6zBpdkXtjC$S?5R z$0+l(uOxYi7f>mVm1dg~%iZNA`z;svc;%KP;VZUl#x<6}pMD(QM0^3HGE<3)K($FX zpF7(!5CLQvfPoLl1p0tv^fAn*vbg7q$_iOH-DgftD~}DRSAZWE!H`uY;XsjSh2!{V z+r9TTs{=2EMrwmh3x1rG+x>`td>@}bpYM`ppBrlofhtS~UL^xMgA+5_D(#S+WV_4h z0@O!2US;p=zo;C0LDMfY+rQ~w(Fl15B^Ck^W~@T7Px0%b@MI1y;GVz~X|`k^>->E7 zW;|XsWtnj5H^iYCV2w~b=Q9&>CW}e&(8}Ugwc;N`nqM`>2vV+*W7cVfwt&7XOt&v% zg87YSi)a*VD=SS+`T$24Mn@1u26ZB^7UE&?iZAi(V&1SwA(DJtQ4)Ec zhp*uQ;0ws{?!K`Nk7Sqg**O84RZ9r?2Bh7r(%$3&DT=kD`rSD&V|Pp}QKF9OniIvm zYkVe_ZawF5B_HMTY7t}G0?f{`k1|4E#}?-s9vUF5Bp~rU@bPk4?banhA2;vcT8|&K zf7Mc)&n^Mqy^xh}zaeIXU7TJq(_8FztM`@g(K4v8A8j=uOn`;8OmX?q^#V0h6WpVt zy@DC7?L0NjL|tJ(R|HS+E%KK=q!ZXG-q{Y$mZ*kGzJDVj?_Nxo3y^nrTQ2uHyc>tL z0^sUW_f#l^H1{mz2TPNvYsK-ElfQfQ?7JT}MK#_L0hpX5ne{AEPqm~pNyrGZUgvPk zSsH5Lj7Up_?%dgb#*HG}IwGw9CDc%{EMt~vSRFN(iqnhd;l-fin{1E$los9Vq?p^` z1vDIMvg1O#m3UB8i}v=7CarkcE$uu}eo)ZJ?Rx|bLTbG(TG7DR@h&KTlyO`lH=`^f zSLv|J66Yn|TW6x;siTzNp)f1ornX{gCwk|Zj-KZT*$HL&ScGV!E_^ui*N9J~EONWg zLb9|!2Z-^WUU;V{ve5LtIa{KdUtBlz15vPxOP?RpS6a;Z zVMt~0oNIqAH&kx-U6)u-Rh!ss5(;1O&M9;N5-Bq+0Owifbr6b z38orjNO-61?<|JA7t`z+AW5usd z@($XMPdJJTELCrE)9{R+FZ3m)S+iLtfd3y$?~J>{v2z(;i}`XvOtYQyF}sC)}G) zx5mHy+XzLN*@v08{nD)JP=OhweVW?Az4Pq>V3R6&fPOp} zB^Wt?iJVI%G`-LYGL<-6(tJVhwroh}X%hepaNgd}4Lm3giO#FHFY8^!7UP9>W4Jbm z?k8_|-cW(%fc7vTm77%c4dNN3Cv8_%DAIF0Ji7A>tEJH0e=pr?p#FkwYUUPKJ|L9a zI?pX(qx&CpxSi!WV0QGu$*mpvcL4{r_z7GW&M%Q;BhHG*2d)JI>CNRA^XA$ae>9as zYaFjNf(W?-A}(wa-7jhhl^c>{o#J89>}SKoa4DQ_1#yU_(3buIMoE6&Sues0@e48;w@m|{XAz9{ zA6GBQ#kS&)PI$}|fkcf8`S*Voq^iA3awHI_8{!#NBW zp34>5%5xDmaTEe+h(-DD2==8t&+h2~i_f9eTqY@s2tu%8lJLI=UutUU{el&apwnZ^ zQp%JC)oc5+hd6Yj-qsUpasKf4!J)tNgg%bFj1Jg*nhPRM#Jxg)xFH#2SK(JAkl!I* z<$_9`bI1wJbC(WnQB;98g6Q?TcqjB}1jF@~Jsr?|n+HOw>=R0?QlIB5ky|5kQ=G58 z{cMatug2NA!fes)=*ZK0HW6EnpdX$Hv*uJwT)ykK`s51Wroh5lBHvYyn!(Q@M${Xf#Ve_p&dAzkX{!>CCxW+&F7D}AD4h;k>|#@ilm>|^jN512X*&4|GE%f$S`s~P?Ut9gR z7kkH!4oA35ulD>OEBjXP@ST|SK_?f-S@zglJKNS&Vzka|ERa#1q{D~hkACh~s6<9K z<|f;$P|ml+Kw-sr+8vvqdEgSvQ|2t(Wq?(=okx3PkKB2k90P5TYGd#|VnikNcilzZ z?`d12zB^jLBgoi+BxZqi`5!=h_#6tWc$3oeQR3aaD63YOnU8XC5HGfNruCa9*d);J zqAL4XF{tfugExKMBweiudQ$|=5pD(GA|NSw#TS9>9ks!oC@Mke)AcKTZl)b=ksof{chQ_%P2rgy8JB-$*+kK~j8l?<; zL4Yw16&+Fpw0vNPPcV4{rL;n>TfNdu+wIG+F-ZgJnMG3Kb;=JCyfkOwmEZ>Cs~8wb z)Yb##=Y&C;?ZQrLg)AKYBw}OYD7Mty<)cg9`4dRMiXaj~R@m7NK0BZh`=YXYG%aD2 zn`xHl+%LCvA&-{ZmtwH(X(O1>8_6Eln!rEyKDtZjDqS+hGAdvnUvk|2oY23aNF_i3 z+A6J<-rpAWuM^?GSww(Szaw2agSw2zG%$t|0b8yA7T8l3y- zA<`UO{m_-b3MQOTD)tSvR*7 zHr0#PuTVEn5ljSB2-E%zX}oQCuK%-gesr5cKzXt?yp0NY=i2NScq-j)i!eTwcDgp= zz32D6h`Oom-yB1KL)ADCZ0P7}9Fyls=_^FarJ8NYw!)c`BYUn|T_H@`JJWja`tgLg z_N3yUotCljshfWQS8j-i?tEr+zc=mPNq^V9Hz*@4*q9t-bb&Sv7~3;wg*RM?%9AbD zZPKhlaO5o@#jSuSWm%U5te!+4QpZZH6fKl_foBaqp6j*JJ;ref1A>it+(?WLy+tt@ zkE-A?0bS=9i)NTy9aU70O@fK};ee&z?2YOauqPeSvBJ!^TMrSgKpKVl*_=jpx>O45 z*}vF$T2`nQgha?z6m3s782+xrUf86w^STN++(f)w7eDwgf%KDK5h z1928>T^Stmf`f=Op@liv)*REbc-h6r$>x@F&_o3NXI;BZYXl->Q-LH#2hGpq?%HT3R9 z=SI{JzaASc+@?#Wx(Ffys9f$sJ=h60p!-1*tuUJp%1I*e!D=E9^GunXwZ2)bnNzOS)d&i2F?IXvxJE=mbM|- zZ>`w*)}aHBavD|oYeSH_=M*fZLIKKDEUl&QH4xGrF#nAvN!C%LpnZ4{B#ZEAI9>!x zWtoa4c8+QUJa3F6blfOgprHL`pGqWnXU3Ij8Z=b+J*M*{-p&dOZ-C7x5s5q#g+6V4 z;=H@opje^!@>1RH2I{@7*FTb#{F2OFWkJm1B61YE9PK&vt&4mj#APB7KB@baJ7laG!*dM~Xe-f) zP}OdQz-XUpvbKs>5T}ZlF#V`R!PtTirmnW_3H;2 z2pZnT$R(@cV7+cgM(=R;D$BWz&Lh}8Gqd*oo&(Ynh|WN@(Ye36CJ+HYiWnqXif zLx<(X`5hy*1APjTr`xZlU{@CX`Y> z93ggjWW!PSu`bfw`5F{NVz0v4iu11jOy#5;G(gS&BuP6s^t zaKVgXu~#WVYXJExg%Sh9pQK#z=pbB{xc)JjU6J1WZ%<2;@bwbXSNegL=nSraSd5wd zNDZv`$D`7&!v*6q5OivmDv1V83XVq|b-BiPQa-mSn4^OmGiPo*_6fDDzKlEUl!=w; z6SjqQd5HB}4})VqBD|rgd2Fk5{CbY0Y%N4GCj~&~DKu%_Nu&K29#$gn!}D9CPfai` z4>s8Qi3F1GI$k@?JQZ6CoUtgTfKa$x<{)oa@>ut5#0aeoFMK~b5qjA%c_e}hn<-v8 z1KR@ZFaVZPtabD@o22hKsZt=eav6B;EMJR!u&$xFdtx8l@4Yo06(*KZYPTaLEd)Xr z0KSUo$|h>;#N4;;;{&oe7cV$r;3bz9+NQAhG95(swqx5V4)@~uS_X!q@ciVEWGcu2 z3YgzFo2o!=wh$5gic*pPT8)FKq&hlU0%mOGTP4vf4|R*VOQ;3rSAs#5>+jyjAPzo*dTdz3@(sC4@LFD)FgI3h0|=#y^T>en{+<*xNpelvv*% zRE~|^-L6|T>x1mcNpwDbbnS`s-&bjgssKgU(I1vO*L%>YBO$Ymay@0l?=#rmnOcA1 zv=+0!ccWA#i`xam`zWEtGTvwEI=UHCC0 zO_|W}MFXBS-rzrl9<>j&*m^N^GfNUwn1Qe;INnQaoazo}sd>P>&nUnG^Ens_ zLpCA&;y*u9A#YmFHsLcW6Euy0^@b$8_ff&md3|}~LX{=Mf$v;g+H3dk#t8@(U)V3B z8Z-d6oLsgnnUy|vCNZX0>EM=qo+fY-0~Fxv?s?59+S|){ld_4|_q6oK&xi{|Fbqep zB{l&23;U*@Jth5siJ4(=xY!yfJ?D`Xi{>k>BeqU#N7)b1h=i@ed06jNEi1O<+0TBa`?{~| zx*wr{HVXi4W-*-1go=eVVEAp$y*I~C{K#|#WTFiZQ2a1nm~wwK}s2B zC-9y;w0;CeY4-jOnMHKQI#4=K8Sm%7GE# z61(<}>~Tz%r=qAHfvuQ zFE{1S81Ae=&1;mvt55r}0h_$CfC9-)k@6l?k0tO)z+Bx!oko}SUJ0#481!RzUBmSH z1w(@zeaqgQy#WYFJ_TyFtws(e4*&))&FiO)atIt=5_TRT=cFyZ>4?^lS)5A+$Wkng z&RoF|;joq32i421{Tfc%+f0pjJeu!l4`*BDpm=#;WJ&Arx5KX}0Cmq{`le3tJ~v?I zj{V^Rx*M3I%oUyF@sm87ZT*=3Ss<6LFH-yccKbLgozR1w`Va(91iXohD0KrgO?an1 z%zUbFl;k_xT?Cx!lL zr>dYdXPEq5G4qV%jG@op896^s7&+4B_52f`-~bYVTT!7Fj2G}Ta!z4?*_;5Jhr{eM zgH@cdb~X7|RS|g~86`LP*8Iv8r1brToW|jNB~gfyK;V|#SgjKcVJEyk^KP{lL1q2D z(%V<1VK*lOZwxUxaU(Ns7;^okctMyFxK}GhaBAuUr%~nxxachfu>31F+NCz23tsRLVthAV2CZ8OlQ;WjsdlLRI5VcN(w;PPdDbX?bY zfA0)ljJp%-jU;eLLK94H>;U?6zD{T^;EwXMHEDD6Qy}?iM#j|}fT4=Uket$dUy_l2 zjnX^-o}inrrx!kUN=d!V4sZy5qX6)iRwFmtY?8gBJNkt^E_}Yao3N(0A@mo2n8lMz zWNt1BHimug>9tI~z@&l1$4=V2Kut0wO|p>obX`U_7BHW+wf6G&kBeY@C z>(F{RVARx>rHhUjOBoN=)W(Tx1*`8ij7TV~@BC^C?*VTZ-;0V*@Ob5uP%6Hdzo10 z@EKwIN6(p&m!NK9%S(Z@HOjlVrCyl1;(wy7tcsRBSiif@36g^Dqo*W{_JhOFB9QQmc4 zcmr05lM5o}`rGl{FNQ4e$XJSOzYO1Qi$nvq)YONCkph$T(9QOGA@XoZCkRq+vF5n{ z8;`0Pn%apk{>W&Za*}|3%8RUtO=dySq8DZ4XL53LVgcT#ye^A|F_0z~#ai_N#;EO> zhqisvcvlqa?e&$`t#`Y9>IU!+&{C&$DBxtpYsBIw%GW(nAhw)i01gon#Ae`Qeukx@ z(rkW^?HSZerRa4>707mj1rQCHEfiN_H&{Rr;$`kMdnXiNPe22xhBXPWtYzNh6+i!l zH+CxIUy>cn(QTx)F~+s7Ax#j6z2OWec)T{3!+JI9NEP>LQd{X zK+tb^@T&Ckgn`AwJNBuLB$e23dxS$Ue=DY=EbVJr6G;e{owA^v?7z0BkbXkh2(w5?}X zW!RdDQ_b9%1^w!P#VRd`)u11ez(4qv`ll_R7Oro??wUmd#Ifs2LXJXf<7$}=O&AlH z^G7toqPz)aDICUbeVZL~Hq>M5m1)h5Y0rSIwRTuanYh~-mzLqCDd`wM{Cq}!_!@aL zZ}4jlH8=U(z}GDMvIw=3&D>?5`>L5hT(yR^D`&i{#$Y0#2=?uB`2+sZlxqs(Wut$V#qkNN#e~9tbjaCVtNATPNRGvl z-3Ltm2qFp;^hhN11azeso3@?5NGOufm6wxqGH3|n(W-|6JHT@1l(Y}fNs$)GA;NAX^dgd(Q zuD)S*^Nm{N+^dI5=^8#+pLvv`70*kACImHOLI_QR5eifm)`n?syRBJSZ{avYH3KqS zz=jZ|^zFy!0L{^32J#F}e+mNvG**|MxyX23beehzx30*dVRG{BWWr?9jX?es88G&Fq@6>LbM%9u>fDEP^-Y zzo?aRd#jPt>i~X!y)f}`f+l=yShj{)``qS>EuaOQNEK(RiQv%VbqcL7qc)$O#@tI= zP&;}}wF|rYFrv#Aphm`7t=`i!MHS`s(pZ2EpBD4W_S`wf3_IbPZeUKy1qTg#z~!!= zMvy8i@AULSDVdjp@w-Ci0A8&;G!8HeKYpqPh<&QyU(4PrE9&l$9|HaxBZS|HA>4oJuI_HoapaBarR$?z z#d}YO;Q#Aa|65r2edykIpb4L>W4sSk1mojq>mneQ&d%kq+r1?BU-3W!ta=v8>E{k2 z@9S54|KRRfW}lWh+O-RK$q5K9R2MfJ=mtoeR&DPAasdoft-;e5wky=~s;S7w^*^J+ zP}812GuadMvBr8EwKQyn^GJY3|5%Ejmmk0_4p)?HRxUuq1`VbwLpMf{f*y(s$f2c< zicn2`fGQE=^(;^O_hsWi!TB>xKkVWSDk`2V34sQOh6ESr zapN_5cI(HtiC-;FN+6bw`bFiKdQYzX3P&G71WSC7p) zT_Fj_LBd`~)eO)=2lg&r`RwyA?mPc`L%$E`eqX!1l)rJJd$WETIIUNqOus(65BN5q zXWLYA;$=-hqi%Dx)oOo}PE3vxPzxp+8!9>2rY%h2fpTX5!o_vTm19*_qx2K3hf*xH z+uZjgJUp(D6!+)e-`J^&AAP?HLJ4lb?`&CZ>}Bm5NT9z7eJvcElA2HZ-y8IA0L95S z&(%Um=@Vx@kKVa3@WnbHbUFWE;p?fe9%PsavF}x9D zGox?N8z&|vTd{(n;LDJX`pSjiTZ^`@_FVp>!GBisFm9b`^f&%)pww67KxNs4eU;k# zAi-+P8eXHCgzBtZtWN3;@sA!$-1|_19IE_#DLG;O5rECv9#{ocpL5%`IaQvB@C=l# zvMb+Fn@7_rVGbvgF&w4Ms;y=vo7L&M=c_)UI`v9te%k(r9Y_~Df|uZ=u)PSQepg|^ z+12xRK(&?Ci)TK!ez5+wk@FL;R>6sHA5EN#`ij3>!P(m3uoVhzt`BVt82x=4m=n;a zYoq60l)p#(w)NXi`4f+?!uGc43I6-7p&||mN00U%57g~$2$CRpT=^?{c71PqX+v+@ z?Be$Hf7CwpTgR-iE1-a-unpv#4c7a5^Yy)(CG(FGChe=acA^(*Yo1w?{n_J(Ht>eD z4H9x5idq|80v)i!MhbS-(kqZZDfGMz;vYbD+VX(o@i9Uy)*6UZ7<9a6{^iYOZpc#B zhW#PR`i3s|pAZS{pl|AAs7mz~(ZCj2wP*VuJ=NszLq^|Iv}`_a0ObnF{_Ra`mqL>{ zR06#2)E2t~Z&U%AJbnn!5RcxO8~iYD8?3SC`NTco;%{y{V^x><#xFT71$2;4*XJ4R zESwOuJ*85KO4T@A(zf0hEwIpDS!nX9g>WQ`utpoKxgZ55Z1 z{ut~D+FP2JGD{s28UV1-K}y9V*R!B8lbKdC#Eh$uncpP<)0AkQ2sE$FM2GbFAiF?>{kYzs51si;%%Pes*FAG zR<1Ka(JmOEydYRpfa?QTy27e5odA?d@w`=;t#=P#G7q?#ofQxkAFm^8&bz!413c;{ zUjzPX5K{J_%*wJ%y`ZYmF@DjYnmZquhnDfiop zp78Ca0)!&pFHTe_kh%41S53yk00s@T;VhTvgd>nu(uc@R8<^$)vT2F_v z94{3hq0O6}(D%3v(D!hPBzS<7^~+D&K&2?NH20NSTQ^Ii1EoXl@4>7J;N&|7oU-QJ zdg7&6ggWWg-_@9$bMP`KK-OLag6nrc)?w^n*W;(U1xjt}r@`bRfb_82Qs8YojXB$Z zEx>B#B>;Z7?eg1`jzB0c!kn0z2TH8uCRt5-@dBzk87l>{eg}_569y3f{bg_3_hEM@ zXCp89T+LE&I`c^NGgG`_#n;8{QL&|wHQIE$NB8Zg??TmB^(d^4SY(4=kvxz5Z4o!+ zzdpok_JjefK6}0!Lr`JzRcHFN^EZWtk9}HzajvC++eejzQm*#p>3P?8edNb*?Xn@a zA+IkFKcP}z{VL}AO=AChnZ2x{+8<7WhwLaGPP}+3_mvG=L$tyfb4&aBSHA!ggW*#E z1~qv&MM(F7FxZPwCnb_e8r;TFE4Qy}JtyH5yoWA>*1lju?@v{D z?_LJAUl(boIgqb`WZ7>{hS>7qluf1*@p#egYh)SrJGE^Y4spD@f;#LcQW2Q|NJyu! zXy6(UM6V3o$;`9XNt(8=k=`do4b@MxzFN#~oe*YRqNtHr?~oji0ewpU0Lo}QE_H9^ z@c@Kj&-gg(WIg_6Uc{FbaRCZ7u34c+nAjojr_SViB`x7o$&;od z!(-IOhER?dQ5dTsqvqn4+9)j|2a;uSj)^z+Ia)yUB&@*g1Quw2q3N+inTCYlf|P+^ zSUgHM8Xe$E(F==S$pHnEUsQX=E~$Du!lC*)q%vEdUnU;J2tzFy&Iu2~OA62%(l;Uu zL+Z&c*2%IJQ3W=z`Y3c`zZkS#P=vr#C5OKXOyBgNsdqKV>?$#fFV=z9jmC?oxa#ia ze=v!_;`iqaC?6z62#U_}=4{7BlIWVF#NG(1PG*;;7Qs)ErG!_K-n}X;@rpZ2eTuIl z$u~U~SE0uTW;tKLqK@i%k2K1(O-A26`~6As5Vf8h(HXCV@9s-;X4JkQH*3LfA(T5U z5@9$_l}WM}Yb?SfNrBbY5A=`2%v?tayyuN%1wsyY4i1d46dNl@VWOO>KLZ1Tya?aG zM**zo!n^pw^2-D6ZuUd{opO9QXTBlN)XfiGv11}O>ysiAGOG44FPh_5K;?iEH1(p1 z3EHR&38vZLYAaE$=dhK`S-if-WX82#C}EVI8?w4S97v(OTL4Fo(Fl6aktln@;oYC}rdV2BN>(fu77y4Xo^?bRy z<)2I_IpZ4uZ)dOX+h)wiz`Xq~sF{ z-cm%3$9=Q+5GO^QU1!;~btV%voj8&-W3Fy}5&8bNgz1Z_q{|GHnaF*iue!|3=!oj;n`MQ85xyI7&gC|6(~ zqn3Dd&(x-yBrz7XQs8W95Lgo74BK~q0_)*yzh2{8W%?f`_lW&rYOa@rXI3R0TwH7S|k5bITnzhx8G8Sey^48WIWk zMNS{9HM>n%btFqcgcF9VWIN$k(>6bl8|uWqXhsaU z&QG{eR6$#t`U5)k_mr5nvc-j{+-D-&@7_cuoZP|pwCJz+BGHlJEOk2%>wuqKYdI}y ztDK6S7PYc^C}C+P2O=aSXi4r$Q)9mwtIuwkR{BnN7>-6SOK44gS!@H z#W+X7gF@=n+Juvg;W)3@`NmK&!QjHXdF894V%QL-9Byt-j_z6A)z<(bzmQZ-o1!+5 zMWpyaGjj+m);t1bj=gpU4TfRftQas=wv=y1UWWwzE_} zni#F9+DvSY0VUnnO~bxm+y-juVXi}xq5#J!)?>pXkN@0HX+MRQ%iGhYc^sOPbKYq z=GR>rslX4L<2ah$juJy89DP%NcCO7dDg}QXV&-*~$f?MS2Wb+PrmmtnVg0-Kc%z+{ z*3cLR)`U5z2CtFvJuYb`rpBV&Mc`B9=!dU9 zWy?X2dT}d@iO{C?KHi5yn*(r{y9&5jR|L|$>~4jjttgHiBoY7+;sh)rC(RWD??#^9 zh%QV-ls-guymL2%XZ|9R^lm#%AH=H=wby$l5Y9wb!RGvCC1mZJml-`MoAL}}vH-Wx z^rt^dmkj}?p`p*uG-uMyU1EJpefDySvO|g&p%4snm{BT2%Nn5b>@3(SEE`gjtBpu2 zt_eXnSfxuFfElD@gvt~$M!Cw>C6}Di)hgS3tdzQ`wH>VuLb@|M*ydqVv}Y|vsJY+0 zDjKrOl!wPn8;UG^y|uTST;j{+HJ0Mpyf%&gZC(|9+3(J5Bl6uG@8G=+paUh1lOMgh z?&2BYTOar5=-RNj8!!i1NNIwk&0xQm-=se}vsL`nP%D-fPb6&aI^zl??t$f-jTtMJ zLU9YxTubAGgxx;{#VmdXHVyZ$omn@_zu#!aUG1UW>5qy)4+@021%Dh?b-aV?Pw1g@ z?OzSn%BonfWW2x6NVZyzHdj!Jxwj=F0JA_8ZcJ>F!k3H-!8gk+j8>Nx) zEe~zNCQYO1K!4w(Uk1&pe=re-D%EGS>Yn`>$=Jea?>~7cn$5mXSE4vhJ|t5eEtqRK zpj(IIz(D^g<6W#TU{zT;_J818AHck)+|FTlkcNoNaHa#9I&CJe2`LqUSyAKZ=dgTX zR2@UakTOVLXgin z8plY-E=x8)uz#G71f!apT`%QXGVdhjKbW1QR;*zJq|`s%p!_gM$$Z&8_%tFDjMsLCpm$o^t>l~t9t z;Q+bDtZQJ=g?JaL0DpD-sOkq>Ad|dXQOOR*k#}|{r4=hrF1NNyN)6q*bt&%koT^mh zT3m*GMW2;_zg)kGd8oT0Ly0#JA%FJ$TXd|8V|?2F1uGT*5_eLq8-It6p*u*NdLt zK0gV)kg$VLcyXBw374Q7FOas0^kr?_$a58wXNGRTL@; zGVs+vXa9Tm{OdcvW{;h(aTh8rIY{`=+u%kPXo%n#J%8k+?#Ke?rrfhn&oio$lZHB$ z1Qv8Qf4vF~bYr_vnwLPbFU~U+rmJ*%zVcU(^fUSktmX@rr2o{BdtKT0wmyLSjpyBm zFGJ=Fh~gv1Z;d-uB8gI7R_E^Cy#AjwO8wN8mva5VG|NI&$D`Rp9eGA#b&Gy$SKb71 zopifW+GDGI&!#HBJvbz4t-FvF1*AUy1ywz{h z*0Rss>5jk1zgXZ9_xrs0qzjB1B;fq#BnlAfZ+di{pAExksilKAKP0?51TL#68s z)lJ*T3ChpjMy==yMC{VppL7(vUGqfBU3ZjUE2?sVO`U=#5*P%@vf)Z_!WCzTB!5&hGtmWn#s$@-$J(+hy$>a0d;xRCq!(+3EOisgv)EPl=u7L|6|Pm z&+-4?zkhd>{2Rfp-8v@W|hr#GYbb2O`Bxl%8f>um2AW*Ic}QG9XiT4 zAGZ<|6Ky>~Z5@YFG&{~SWBi<=oA5!6VP?0^WlaCDYMW?nePGi5s|=ISzj2MFPUG!?{Cxh$)l4r_vIL` zqqB>tk9Y6ewA?eqeT6_9W``*j@2m90nfZN0PcJ+i!ncOda|Lq@M*cZ3>=a)vSD-oB>X4Xs2 zB&xp89lP=p_Bw6$Gz&pn>T35JaK)UJSNA;mB1UH~U{@o?Jw#Y*gVy*SC_Tmsp8X&#L-Sgb>?_Yb%)7wpB}%0Kp~Xaka@LyGw%Oym&kn>!n4CyzZKHallq#R@Tc#_d{D z-C0+ar{-P!QyU+LdYj@d`Fw<>X0er}ypMGlFnU!-& z7q0W!#O8chPdQ2ecL~gj(pCQa%#~iU1rV5{4cCpPpK<9b_ho5iZLMOnC!!FyBC<9ZxwfZ;PjV=x~SfKJs;LFKST7Wv~l#=mK`ib)0P_ zbb(6*xZPc%;$>a>r*ZF=gX?XDjMWiICAz@QM`>7o578UAOmug=v-lIDA}n}zrMMVE?~ z+4YSDA)&k|*9%yx@IbF1a9;di;!fb<3i+x%Yodw?>aYAO3JeJ*jy(!p6GaC53r=DN zhquW7-6GlT#Z$TI&pfPMJw}woTe4@APR2hwyW4Ek$zz4e-8oewi*8x!a1FO_YD9Z+ z217vw?@GNV!zDt5v^Rb{ImSK1w|peVOB$-0x`q$se_TmAeFXj2z^A4PGfni%uS2Uu zP5oO0LXYC*Oe5pADGWKzDwN9Rv3W_S&IENgjyLcO+t*){h#(-;?APLexj{3f(BXqW z_S-@jiqOP6??~z~sgjQuvXA`An*!gUGshwjZUTG15>u&5IBN+WUhH#zz`LR0v+!F^5Sp z0cL#>7zDor^%;(>`<_@ePj@tddfmk1LBI*7wg`2)HNqrA}7uUAY^yD`fM zbz0ZoSg5$`h)fWP6e&r=gOnQ^$Pp%8EP3D&cGncLthn2g0vERwV6{I1WW;?narzWc zp|#>IIogE46D+^F6m1yl?duOet_!b)*S;4OF4)Pes7>}zl5$diZkB?bwU%*X= zqcT@^A;Rcpp%f)Qx66#dgs4^Pz@_T-8%s0e62R#;%n^pIF0X$dWt*0zBQQEU+cfs0 zQPW^~+gJ)Zz1r+dE~q$z)Qrk${fP>6u>owv-by8%)dirC>?#u=SZkih zo1poU=Nj|%LfcJG`N9(Bn~|NoTOUpQgp0W-En+rd6N)!sd|}MYJm$Qj1HEMP{Kt`J z{G`ZF9DC<%e%<1p^7`2O;m567vRh78dV8N2a9aSr$$JwiT+DlNU=WDLChD&0uG?9x z`vnLI3In}8Ap-PNF+oJmx2TKS<^vV1*sAL8YtUz8c#NAi>ftSXg^Epf;`?MEdL4Ps zq--WlEqsNG4arJ$Uq6vhXBg+PLG)Sg3n5}*Tgh^yCggMlHKhckOPZlxz&g?$tOFAB zKs=g7iUSTLc7ABH-?XqUlXEf&kGF3kFQV)`@b!`Ev&y~na_XStR|O3?oBgpoHMM7U z+!VOY++o$RAGmkV#!V~942I%DjFY(=fSCD2jtopg{UDJsg3%xpYulOwi&uRMN8oS*rFkNe12^&}T=@mLDr zX*tF-KXLDZjRx@|672nA`1;<~qm~7Q`zszH>=biP@72@Z(Y@$V&46=9ZBF;5$#@C% z0KCotGsPo$j4EF=t3hzSS-1|-O<<2uj|qy4*~Edu5bh>Z&40rFSmjLPbjJ00#Gr+? z=$rVnF_`Y~PLdwkiL8{%cT9XLa}OUA|DYj~zO>g)Hv!OlQmuO`}j*ll%E+=V#7gCngTdhT%c%ANsP&$Xhc-iYMgy ziUjwkDt!n**%@iNT9zz)(#5S_P{12L(lF+Np5K^i)oQI7;rF#ruV_(g>}hwOGDpOl zaL$K`MJEh-QjtF%FqAvXF;_J;3AY7ww3^1ck`w}a6PLf&20Fs(juAB2~lov#ThR0jHqi!<+M|kqVL({;r_gIx}G?tL#w1S zab}!25{S3fsQ|_4G)UuWi&6|Ym%=WHi_w2#b($r1YEgdU-2uyr^pWzL_{hS$)-AM& z&5UQQ9p#%J%6AwN`{h$#qdVcBG{TcSe1=(Pl$k!_bsZ=_x0|481$nO}>RS=|dqY{T z*{YWkXd!=!V~EmCnRUO$HB!3NARG4DG%AL>96!b&oqSCd(fB}ff?@x2yZIG5(@sN#Kxe6=;Oa9&> zDV`JI6Req{ixfKT4v&DC2*(}m{9_5swjNO1X+a^`9+N@-{_}FaEUSTtmA}+t#(yB2 z<7PKcB(=DQG-mK&JCJMIx79%H1nZbL8s9T}CjNjt?P&6)IHp?*Cc#B3A<_mcJNYL6 ziB&l=ZB%XTH>au6gQEVUOlK+o31M^i(3^kqxc$-93L}h{fo2U`5g~7Lm&eqJUHk&0xDN0syA%OJs007ao_4xAq!1Q`)fN@El zEdl@`#$3~E2^b$?GWKB4aG$J?4OGR0bScAW&MRqNv2r%38Zf@w(V7pKR}Yx8I(eMG z7Yq|acUBzziBFai8}(uYgTZG?T57DuS?{Gt|ERZJ*uekRw;48NpUkFh4Gkr6;jLw&2F^=J89 zrI2@87A%UZE*jL<5=x`>m9~<>FcW2<8sxAqpFL+3oMFth%o=v&fjgsZnAu(~ z*4_q4a^&u1`mcu&N9z#(RCIg!<~bX_^>SdCX!X3&7WegM=6NMV0mrIqARvP{)$HZx z*6WP4787Y^7R%CICa#$;Bi{i>zdggzeQ<%V(TY)2tCk3qqQ9P4xNIbh zo}*5ee=*GesbTlj+SX>>E!Qg$ig}26^t|kCZDq#S+N|R@8b?``ARK%)oZuD6vE;Rp zv}GsJJ2z7s&GuUkQ<0pRaA%H3_;DM4IbTj__AK-uV^?uOkcP-#&bffyh^7yezl)%o z_%3XDV2`i+rm4UR<@xsKb=@@$jB(iL=e6H{5rf(R`03%;sB3`n#H%nUrCC8!RQ3pC zUD=jF_FoumKERjB1Cc`r?}`~M`_TW!@?L*u@p|rt1k+sHVheZ@YWA5WUD(eQwG4~% z|Ax(YCjCaOaF-;3b-o&2?cJs=@vHDNW#5cqS`b9Yw6R#^tbg%P^)8DdQJCVw|DjTk zgK3fUSHR>R8T01*>Tr?wuj}X<9Ed`CkRnZr`aKn=4`a0=8+fyd5Z_QmD-k(-{u^3bK1+)zf_|Da3Yj5wc5se zRaH*&fV=qY(c`aJ(E@5Lx0ZzN?ecfvdE7Bs$$O{-(S>5-xZ$JKZh6G_w(+oU|A+zs z7r;(^HLNIM!}hlZ4E(MlQRY4bxHZ8xO)qM$SWPSUX(1v!#lUjNW$8;m;nNuCV5{E6 zj)*UNkM5}qG( z>5D@WB?%dKU*>}auKa>0Hk{pW0o9!P2e9n-a?~;A?@&x};Nu^*1lz-*8(nSKn3g?W zxmv5Ae}_y*(^N-;P|uA%&piy;n9auFF=CIfgPrV4t9O3vU25yTG5EE}4O)Jw1Up0u z7VI?#S?0vs?}jubT&y+>^BbOvD!#w6P>&SZ&37gFF2)}I zhcW&Ai}c^`^KI=7HWy_c`Q_vmRsjP09NbN92jWwYp07a?A45Ed=<5N?P0v(vwnEv< zv;Kbq<60-9IbbyF%Eh5mh~x40)n@nOK98(Nw!~%hX}H`t5@$OSdCcV9NaUZzA1=;U ztPALVeyRgc8U4rANHFGB-yJRF?!nMxyjdvTc!YTr|H$g@h=VFIa#;Y}3s2GR88r^B za=&E8)yu4>XvC1yS=t{*_a7Azw;V}F;FvbTA*wUwBSGw*6K=tr79jK1?EaeD?=OIo zXMOSX-9_Z7LG+={%}ODb&|%_t?3L?bo=4&?w%4R>;Lz(dQcsAQ##V~|TcM%xaA$6g z_1?FJMqOns>>oPQufoO{w}>l=jh73oe6X>k+dw#^nFaEJB<3w3`O5PS*PrsJErYZ zpj($6@yX$AyL}#6m-V*+c)eWfa*VU1-HpuK#eYR)RFgi87guWK8GcEu zQn@DHlB1HdP`%b~H4oY@ByYKnc*Rs2eUS*=VP#UzXc_gG*XVqZfV3~dw|4`V0~U0! zPJf?{N)NsGd@k@eu|uL`7zMILd~)q1!fQo7F#7Tnl{cNnm%fgSPc;1`7Ft?ncosZ8 z^JIfFj-c5&YucFa`ljpwOc2|(qQV;>)zPXzADiQF$A`elFLsl;?38!tORJFF)eAQ| zrh11-MwL%GI+hmQ7MCdWb}V01pzp?!r4SF>w(OcDk$pA6t;AP|5~1(b{RNv~h~tl2 z4bmc-a{ZfUwa4`!GmVjacz$nG?0o}4{Ru7N3Aw!?7jul=Oql`@K&gZFk=Rp#uh3!| zUI4#IM5G;_ZD-ffR+2bBqYT@OfGVenK8dDYp>HNxXD2yVoZ*8skfWnk6l^9<5}D7Y zgpoc*9>{dg#b{suX_{7Z4JtCd14TFmW?YIfAU4;yedfZP)eV`qPmo|x)25RZv?|%w zF}Llc!qPak8E*9^RlXOZr)YYgM1OWomM09!K&0_)*u+oS;LaT}F7J^YIe=}|T;v0* z%kVylo0)n@Nf_E(uFtNItg>6&G^Oa1Z7V{~VaIvvn*DfTva;C7LLex&)8dy4@Eymb zS|~X;WJt;C*fkMJ!0Byg;2Dr7<toTpz?fcItAN+qT7UFVKT$&; zXyX;|R@lbNBRgwD=&Ag{)2#>uoNL`a2{oYB#5$v40f&WGm*;od`TBW>mrs~Z$aq;Y z39A}8L)4~hfbMbw#GFti+ua9{({PUfLTLx@HZvb!XQd;oS7ccYjfM=8Q!4yZB5hz9AD~(@Qa%y77O0m#r|R_xcbq!Fx;h}b z^HVf&x}zL0Tbt!mYwYWqyg)$3(vdp@tmeMbdmEY`ttSt0gV%+zqQ8bI3eKi{EIuUqQq>xwPC)H74jrI(!vQw(O0@OFyD#;Li~N|bh3xHy9?R# zyt9zM_jArAS)N}5&4=BtiM0QLKC#$Q5|GoW4irAVZM_1A&jFFv=tZ zq4K~mx0}$_K28$)o!*DS+?Z1^V2r#j{Z!#l&(_PZZ?Mc7fM3BO}k3YGlu8j;~%tUzE2+=@o; zyt3WWRK0)|y?7GI=w@*W=fm$>ufWL9+~NU)E!nW-s;&qHbQA_arpKO3f~2Q`b}(vR z!T?5}$8w+-ZMSNClOH=W-01J!!Q<9Y#1NU7D7yefr+e;($07r3tp|lcdnAe<^IOlU zWe-sf&VqqK>9IqNj02Q$n<*hvjn;mhSkxKP@*tm;`D^jv%BwYxsg5tn?Q@;PM4dNcFlUDb+c zf1cD>8qvqQFD88RUw8wytt znV$ynFlZThQ~9m$wN-%OB_@Ii+&DIo*pLzx&lz;Ipv(?ha04OPJ;7}mRe`$b=s8_z zv)_W8rH=|B#(Gv6X@lPCNBKF<`ng{= zB}Fm;k0Y4w#3joU{?a zgjXyMeuZ%WLft@m&lV8-ZHwyCSE3@U8=4%f9VM#yDpsNZ9+H1I&BaklfKMDC%DlS1 zWim-MFvc$_-;9n+S^18-rVF#N@=t_H1T`5?RiIv_LbDU1SGJ?yK_;lwNn6VGb(`&b z1HEcZeb!^$eb9AQ4!|#M2vLSfPgm!)mfsurfL2OvO2FIS-6xv^NP7lmxR-}k+Zcg% zpqi0nn9WZCB5YXvJpDTNGf}tt%)OcmRns^i-UW!Iv(x-eMeadp1i4ArY5+oNB6umo zqG7wTq^8ICrIuMJTO}f4grnSHECUc1UIN0#AkPKW~jKf2f}10yQZZ0wtW9HVefnS>=eo79xh_9s+H-jYHaXn{;uH)~;s>mg{R;d9s?6nt3y>nn@PzrMVm z{i6RxKLato=D-S&DB0O{J2E9?%B9hpc|Nx9NMGjM=$~{yRJE+R zTr`xcZY3rHe_fdwED&bpKwS3ZtwYA7olK7p!})(L|LmMGe`&MVT!gsQ>q{={V9g1M z?9nm`g^YLMhVoeaUHQ6Oi1A91pW5eWzyS%OvirOwK@!f>4A1r8($y?mre4w=Fvle~ z5f;{qUw!r$+6@UjZ;Cv557i>SJ6N{aD=EC^I=hn{Vba${+|(8($-unNZ_ILDr1~=h zU0oRhVZLrjh1Wy=z?SLxW1_4AfW&9TS;aD4AZt3H_e5QcZ)#8y9T~+3CT@M;ByWxB zUKrZbzN{-?+*p<{_ECwyI>(Ksz7})c%aWAE9pye=*MYHK|)ZT%XfwJ+NQ&{o0-Rt_+9t*uZk)GTkS0Jm5r{8gTy^OOAV_-EZO zuHhjW&+`Enh1{*jgB;%PgJRcvkMX}Qw65^>-rZU&m03H89Z~}L6I(aRHYozaQt4ag zg-PFvk-ME5dnI~w0da?Qbj^mvk3CUJsxD6IFyHknBkOvxqu1}8UoaxSw&=9f`S1$I zco4JqbE@{zDOqU+Vi3Wi*3kQL6>bdn5k!on_x6uA-53u8$Dtr4Ek}bFPOm#Su3fwzApam>LVAhseRQ*vIY5 zKp-L;x#ns<_g3}8;^H?Q9#D}F=zS*%Kc{e?$q}> zlh`5UtAoKC#f(uKp38c9DOCeL3paGaak!`d$nZ$yeUJ}*1u$LmfPl3N_%W(y&+*S^ z$KVq!V@I=jAABP|sTI9S4W>=xMSqoXG#&=gwI(}GX7i$0HHJU-8ajVaj~ov?4vTp3 z-Lm@fA3?@%ZuwMl9p7f=N@SuDN2pd^mk%fNj)W zZPdW{KG$Tw`TvB`{~c$4SK=+=eMkUcD}WHQJwscYdoU*<&YM3KOX|hoRDAx{f2!-k zzc9l8hjvTr5iTMHYH}Y8I+Alvd;pc|Vl>iHds+rOC$0Y`i1@8MWOpm5l1nZr?tEu` zz_O0!!-_L)CnZpxD+@%xF8p>mQ1)v}J~*`GP@T_$^w0kQqESC_B(h4pohh(cdbMi= zw=iy=j}%#F_Cvywf`(6&W|R1cLyV!Fyc~(Yp4-7)MkKeqns=7;cme1^FIHlXU(3Ai zO0OK%@LgO6J=nZDZ7ooI>-V`AB*W*%7E{fgahYcKr+#LhY5SB>ZT8e0;z-jm*Gk52Vn(gGPt!)EZlH5-tNr{D zA^$%Y-Ty>%ieJ7v{_Xian?TdtGA~P`JHquurzWGC)YP72@LZUo5$^79KOs{i zUJE1QkB&<(H11uWMvS{tX{!!q2Na@yzdp8Hk_fwdLR_l!vdBZpoOrR@Ezr(Ugz5P! zB)3#RFCP5|rv3lqr2o%1WW>#?9{nRWl>-JV29*W8{FDOs;^*EBP=kNV`qWa}0 z>?g{g^QsjKsR=O=MuvlVO8%17xOADAZp_MU1KPh}tfwtD+19BKM%JzzEb(xzr{y3p9z z!S2N2?TW>I*Nj3P0U-X;w#Wu+ILb#}cP^R@o%V3tdAH(wEAp_@>DRLo<%Xvku{!OVDl!}4L;V{SIc*;D4Gt3~TOS}Yb0tJwhKu2}LGt8F zq1ky&Ew+fF*tC4ed}PGd+ph^L;88!wCKJgJD=f!=9@lO{|82ey8-flAr^}}jNF-Hq z2=|q5$Xg5ZI`mJgTLKrD9vhmTAEXRnsv##g9%O~tdalIIUiP-~^r3umbGx9&C7Q23 z1!tYXpCC@>Ut5eD*YiPY=R4a^y;SmL$ie#1(4LVeYXcP8U&13y%|gL?_lcljTb3%v>_zlnynP1Q9?Hjb zvzl;?ZXdV;k#Bg7O!WYTtknD@pD^j{1<3MclJ(j|5u#OI)g$a{)eVw!TRAAd9p@$k zJbiFLj%06bY0Pg|t9!hjlSM!7>pQ@RveODPApb1?brCz%|6Wg?7Rk=SmRMu& zlzyoH09+p*Q-+_qfEJxIr`O}O%01hGDVD5|Mh8keSC|)~@3SY5U?s%>Iq<$k3_?u` zh7}f&O{Ur5JJ~^wR)nZStY!hwZ)5_rFi;+Iju%ulpqJP9AvLc23X%S4*&Cdze1xf# zd|+u>3sVdv*}mT=*q&H1H~o)qk2>5-Fl4?SGhJ?f0bTQ9*S>#MN+le))+`XQ=h_Iy zoHgk#-FP*aUxMQnXi5>bK1v78T|O;6-L|np>rmvKj*J@Ficl7utuIuAO_ymF7*70U zzD_-bzJykWH=Z0SKEt}y2YR0Q;caea^fq%UNS~6$C{0Qw~-j{(>NFL04 z!Ud;bD`r-6TJ6dc#f0g-8odYaX}6Aj4^}!T&N;VJS(ieL$;;*vl6Uv{^(2jf~Fp6-v&C6-hcAr#IzC{j^W2+CRN)p z(vU{OvgHA6KXvYJj1b<@z6^8Euyvm`Z?qAcHC@-t7{<8AN3j;1x+&DSd<(S$=muxg z(+dKsl~GnxrTNHcYta`~@TuR$6?g*8J$s(9T-@nXHoh|2M-aY$nx z<{hnUUuJ&6_$&Yk!sf(RD&q+k?}`sHIA!?*S_d z+h8`!O3!kj3uGA0+Fc;g=IV~KsY~osI$sz`B}kMCB%>>pigG$o*66dJ?Vz!e*C@@u5USdW1azQb|)QpxUhe;xgY+ z9O%;ULIE~;Y1h|BKyON^)OTt{7Cl!4clpiKNFABp*kr^kB2<(FrNIM0ci1&O0{WO% z4O;PqV`d}%zAnf%zDlyf=Wx(4S5d0KLgv8N5()GIkpOv^lj}UVQGk*d1JMBOi^_wcHk5V?w3XDzoRr5VV8EM0@&*$R1r!m8&CxW3#?mkj6)1|M? z)5cE4OEzTk=j_5|C~BU917TKE#{-AsyUOSflp(?a%96f>Z!vzO+$isb%4xqR@^hVK z>JqR1OjgW99;iEpUOaX%SV3PX*syGa!&_Xyb0l5}NU$0^!=gSyEYz`4_8-VIr&v}e zEUpra_no_b+ErYKalh7nRcEkG?>ax-mHp}l7iZQwW4CD+DBAp~(Kru}(Qeu~ibQo3c6 zuFO&fF{Nj&1X&-Ww>yF~kKJL)HrhsD#sUi05{qA=eBMVDEGN z=-w|0$?DVbOQ3{7JboDQC&7T{E|0CB!9_YtM$VZ`ZZwOT_Fsg^e=F$fmw+rMPbzuo z_||=Z@tZLp^zpR_42ks#vUu@X>|7j`Dz6}c*}HGgv3|=ZLfE%4HMna)T009xE0x?6 zNoWuc@mq@0cO!#OV2-h}3@_l+lWG2B<(TGyuSO(JCwEw0md zlGBKonnr|Af#^yWbXR{zygqb!MrnI_DbfAo(8ecuqxqa+wJ14`0ex7XE}0^@0KFzz!79pf*x595$2;RM*n^9&2hhaioiLO zm2sjncD`Leq(cX|))vp~&M1`%46P`NRX;I~7&}P-f13kWuaaCNApK zZsXDkv{}DnbX!*MzC~sz>0?(oStNFqSc$t9#)D0X`M%1-F$*?j4TPO%blx>7USV|I ze!ZeCJ5SD`iS?DS&h4t3p*JT)ca}nZYQyaEb{YC5VJ%lJnw0-i%8!`8vilnARjv97 zH8dHpmpbFT=na{W>>^5gZeJ-=frU$BhQs8pOgRS@;QIkY2kjFF36F}Yl+hN~7@m0n zGf75%+k>K6n=9QOvwyK;E17Ku$<5#k(=uoLqQzyrRz-sEAMXSi=?AB0s@2tQ-oI)b z7H{H}6t+$H0P2UO17T3RT&|lRGaAo;B1822IeIX%9ka3Zxm-pxXB~;%O4S6?r|@`L zrN1hjCE{hB7;*R0>|5VC%6$AxOaREb49+XLli`rXgPs)Fq5w7+CKVroX zD37+1@kGTnV^1YvOXBQ`5ilY8x!B6LD-%G&G@=gJ7Ot+c>>bUQfn~DUEv!xZvJ4vC zprG|r{c4m!k%`C?@;N_#loB}K`Ma)0<$0P2a;^Fw(pHzyTs_ZU*p*mIFXZI zm44ja-IM&3NG0Ftk&=vfR%+?xFTU>HQ(fiKft`SuMtx%2sG6a-?= zPYxfCdE+I1SMnC4gRe@=ux#Z;owq{#S;?}QYi7Y-OaIDqG9ma8y$fA8ZsNr{@etSC9)n{hh(Rgn;cuS zcOXKqFF$;3gBe|r*o*5LJ!Y-2f;e($;Mxv@jEs#IyD_NmzLP35;$Iu)UA7#%(FBRz z68cyD&zXIP%;9SzdHO%@NfCZ3Uu>RsBWA@00BKk6HwmZWi=r>q-z6)%=le*NNWmR8 zVsdIpi`28FwP0uD{oWr3J)DNIj|*lQ;c3T72VLrDc@gUI#uP zCXN#o{3`yA@uw2Imb}~ZrKq!1A0SrniPc-J5OPpx{nyX1_(-N zr>Q)SR8xwhuw+oB(81?f)sHg2OEj8od@%a9Nchs>#K1gysuZWGg$E>si5U(MGj^G} zuOWEr{>rYW+rBY@lTPOf4Qj$A0&U}y9iEj{r&p5QCq+ll}A-Ty!S zjEZ@sw)XNcvy2p2IeM6+d%yC#M%J+I4Yf=js)=_s0_V~Do*66*e674b+4dlr`=aJ5 zF=)qYD>*v$Ywi!-0Yj>K?Q-G`fv+J5?A=t?c5=7tCse+@_-!KB zxTJA53^cb}q6C#qc|U%mgo>%ysP;9#`cFXpe}H0Rup|A#dAEZo9jSU{^<_uz0`*$} zkA2t8F`4~~HDik%bN7|b<>*v62S+vY24}lJT8Uko-&1t?hB%7UMnB6jS{$tonCM;i z)s-Ifa)@4xcMMJmyBsup?yS6O;-%k~OSR|}Z)UiMRI{ul0VaQSnGrIj}FQT@4= z@!odtI6dk@OWbnbpHx_ho1MADYN!`uE6nk1x&mn#@5IE2Y&9rYws=pys0kKArt0zu zY?bxj)-yxlRtiB=D~Zg>#WACxnMXC5f+BL-f+5@O0vguV^XE;LCw5a!IV#swBv>0? zdsn5%gWzmg^<8PA7RPwntRyr$OTP@|cU%1d0$7NASrx&Ye6Qd7r3R;77+s4 zQ>8~^ieeC`P>*Uj09Ru=Bd$C3-aF$tolmhpZ?N8zfq2M z#SjF}hP7qiM_FAcl0(-yaHFP7;>sOR9CQv#5r5H?yei!?0UEZSZzqasRa^ z=1lk*)Z``Onc=?Y(bdk@K-I02-}YhmGYLAKXw~jzhQd5>OP&Gs!6Of=T`z(bNbp)=cC?f|Mz4Z=GOHKH11Iok?9m4XBH_J2hhJwmYoF}n)p zUJ$kl?!^`Y3dELjQ2O_W-k2(7to@)L-}&lee~6V-%&GnIcYNxqvim6FL9SU9T&A)2 zG__BvSxfN9d$~Y!Gz%fI12#uf5_cKGaCnZKs?`gZ2T4D<;cfcrwCQ9w_2QV6gifr7 zg@Og;s^1R25x9=jP)F)GsQ4pn80hipkQ47!ckCU=T((UOf31EUb zlzPbkNNF(Ikd@pEG4?g-UeO;Ikv|{_Exu>0{gzJh#R>~b_mR&L;Y8_crh#e0@pY{a za*ho2fyHHu-mym8iCX(CcWH#$MxP6EuvZ2P*ft-_cw!vY*7yL9YwmWfm_v*vI;u_{ zUw&t+Y?_k1e8Smf{>rlWXJ(<*VEevFE52D$s@z)hy?nDv&^=GmvXy2}qT|}b{HChp zAf_C8PwmpWy4lCfR)i7SX)VwAeG%F)vVtSwt&Uy?^PslMH$fDk5nZ)4+>YP+Od`(= z7J;~!ur{LBE@CwbkYLHr?aCslDMlf#HasvOACrRG>gU^Ms7wo%B$NoE8f~oT_0dE@ zGo;NAfH34pW6+L%RU<3A_ZoXfnZrR7_yqU_cfC7r5O=b-H~ZRTipiua2fz(!a`-LJ z60VqN_c3wYjQs@>;WfljZJPBOLOaO+F*nK%Ejr6qC9UCciT4@cSBn`UmR5B#M zLq9}@g$2c510Q-GU_}KOy+*8T*_vXo(?Q6G?s1yqArsu}s;OqcY(-&0(CImXD4%Tu zvvov^F5`(y*YGqVOgJRfqw|R&;rsFrW2@G8{4Z_$TZ9q8a#%tqmSW%;ulAkPxKTCn z=vV|`+lm4-LU)uSqhv}^6n5I4-OzIyzS3mGx@fJ}%I`lI4oR~>-s$&<6L zRz2bruHSXlI+Ca>%o}Zq&iehZFDoT@#!gaD4Rez(5D^|zi0Qi22Ue?!a8nWmNU8MP z>c1gy_4jheYT4xHLBP>um7ys$rG6#Hil|G;h(=BrEY=)|08R+K?+9E*1V<2GGNHSD zl_0)u>{+cssEvU7GTg2E+Jv_X!B<2)jf%}q9%^2;F0!kLD{gszYNxi;%_>~aUZHQr z+eL=-BK2CB!F*#e#z?E68!o?#2U>MQ3?Jzf`<9WqKQp!bwB)8Q zB@tY3(f7@tkb#SHe~I7rlqgmzJjjvhZxdpZ>Dv31JRo91ZH=O8*iyL9chZzZ z4IWwB+$0vZ`On$U$k_{S`8L(qK3aplc#VGUjPy}DHQ$}87af&4X`%RAmz+}CDm=n2 z*W{Stm&diwI+edD&X|@;fV@QflVEive)#RVfc|F)YgY50hyK5$vtY~MF2>pjK@7T1 znYg4}mJw`QuiRW&9}mula3R~}MFC!0t2mL+-z-_!S2%kG>uaBR!2~&zw4q+nx^EP~ z)pzcO0(MPktPB7I&7I1z(t=qY7JC4XQxjS$9a^Y?_SzMjGq_JrOcrwo>Ucr@91V zYNJa<38LCO{?~QWjrrS5Q_J=?AM5gbM_)b^xWlysOBNBc2BFNPjZYYU)V1V(5ToZ_ zW_z1U5H!vLa-ckHFDr%Qvx$gF)ja;qVfke#Y$3Yni#^q+bJEIT{wfiCR33^=h;ph) zbROQhFrR}pb6$B>%F~knua2*R<5gHckgO?6nJiueqDYm`sh!3f~@Jrs90JXB@1&C)!UN^n@SFYcutV`C5_Z+ZL2Z9{e9Q@ zO|PrAibbdj_|uev)7x^`KR|-_cd~G|9+u9x0zF-^v%N4i7p{LdcJvOX&P(aM(|2co zo&W7ns3v!TuwdvB2Ybur+bK1$Ihba%N`gWnkuZa-_vL5;Q-q_@%JN|U`^2089) zLH!A`BlxJ0Bf6;NC2d?M@S)NDfz59<1FSM_Y%5`wPm4#|8ZEa5yjF!5I~xB4m~nD} z*uudX;4;JI=YIqVSoV!~qI^hd)`Yj`n9)&BL=*F&u>Z~9?8l7d<~r+Qo+TC*g` ze^DSm;G)PA^m1o)!K{g<4yjvLvylL_ld^&3ZH0=EwNH?uI1gJhAxZ2^VuQG#a1gup zS}b)?kB`+#sr20BVYgC^GQ@xbDIp@dRT#X&hm6qF5nbKgFru0g3#4LdKE#bz4+-I? zb90>g2#bhVk1hI-d?f#8fJ zl9I7kGN!v6i(MI7jfzgutegh|IVYLZ#}~Lur3Z91@PNO)TW*FfdjE}A!{YabjQ`w~5$KMaGGw4T%%G`1KDj}>qE#3=NdL8q$}jdD zl4k$4!=07ogq_4w5GkE=nRg<6Scid&gFcodXdxUj)lSR}NTo_c>>++oW&jHtVcHT$ah6Y?-dc054jxEmYpOnl=7T;*-G z!H0#+NxLG2O~}m5hfMRuLgtB4>k+y!JZ#xG_cNys4Z;&!m!=GD_GS z@ppe5m*&P}>ydAT=zq;5dLe(LzuoH4OTo3O?#%9td56+UP1%E-Ml~aZo)nogKY*(+ z{!)LA!H=v#A9RzVI`S!i7OKgLyLBzOkBgqS7~FjDtL*+;lb4YCNsFV7m)b@{)>0$`Vpb`uhJ>e$&HXG}T#y9}Siyj8 zrYVO;aOb)vb&uy?xMskSi@nmn_b`WSe~un=;JbRVUQcFGP9Rsq7W2!&?xgNa7k-~m z&lukIi)?DN9HrG3Np$YjobJrP)TGn%K~FI2lF|veM=&qL;w)Ok?%fHuIhO8`U(Me& ze&|Hu{gz^9v0KtfDcvJL&gw15jc5F3DUbSH(A?TTEqUo*kl16?D;f<6hc&+Rky4}b zEbrBxJ_ml#wg43CKaw3!H>#ZNG43(LgUE_im9?$DH*tc>Zn^KaHpqZGapd)#Xm|Yf+DipW7q=K}6q&!hr>{$gBAD+C zO+NiMdac2q$fi_p+xwe-MVGL(m886@r;zfbMs?phf>FIms^ywAE7dK=MKU`|tnDUDgbO{}U?qCafhB<-w~#ejGs#UDoB zY@X*H+oa)Gi;wK9tGA0GAJAQZ%gE%`T0jN_4v_LtkG(LNQvEDvVngnRfJNd^tY zS>}^JySw$ux!b^wt%}N(p#q$lsa(xY|L-$^ege%3!Da$8WCa0MS2KQO17~utE8SEp zAoE>~{HY}Yq6#^AO7Y9h?U71&pY^KmAjAHuRPt?x1uel*zLaNQ;G-DVY5ZIcVyGk* z^(cp?q&|29-!{CH`IwgOac0Ky$tzO2bI$MsK1s|Q{hPn$>fIb@Db2}0 zA+#t)1m`zcj=0BI^~{nJeZGx&#Cpk$EE+=S`nwN!+=Ce|?z9u3U8@fGkzMzAkH{`U zNx-~hkg=7fu>*U6UjO;BciX@yH)w{fatEnc3ZdOT7g^zCAf$HSY+1UCO?x-qO=u9e z!cihewKVkxR%P_CC zr9Y0M8QYi{Q0b2sPj~ON1RuqiGjZBj)&U80FPWXs<52wK?pZc zNvq9XCAnvpgllNRN~C=H9z6gIP&gn0*Z>)SR?TZi74sI#OX_*6jJ-OgEX+GIj`X{l z9&mC%#CAKGKI%V|+rubdZ|Qk1W}lH$DVPQ!n%w;ckF+dv%X8^(bxP^4j9eQ?z=zg+ zJ$m~(H`KeHN1xu~py!F29VcPqXfY^;u5Xqr;0 zLTKkxPWeoz8>At{VHn^VED#%J<;wT$h7HdPv1~mYs)iyybkME z*J9%g225e!=HnP}CA%2CCQXIu9Go{enge%RlA$E&&%@o zw*V@I$Fqy7gcc3g0e;T7jEzw9aZzDBAI$E4ZgXa8`M15`tvJS1@9J0lF(aAtMu&_xB+G- ztwMacu!otNmM=Qt5#+B?j!(HT>c^Oj7gt_%jkxa>qNPzFTi|vf!-rxHi=qDT>Ti7^ zK*Fsth;&-Dk9vWu{oDrOr*oLR;jcwzt=KRgO45RkT6KU7{>hC3p`}f3=@Ej~k}^z^ zbuTwGUTty2lHN{jTU>gl*TgqlW3Bjo@oLkYNra}?(jP)dEXW1h3U)UXX#zTuK}Ja= zWwh>tW?v=Oh!Z|4;+-UyO$D5eqLNa!*5=eC`||O4O+{v|dgwTy+<}lM$qABFRyrhr zQId(95Yi1z>0dssd2g-7gx`BBO%!X|X%+0l_y=7;-E1il7pPbP>WCDRD;#PvD^ry& z4YfInL_{!$goBlqdYpwGqNa1<0q@ z^Abt~C&-CP$)|Cqo+~~d&-op)-bMa#{wSuc^O6>A8%dD#SJQ>3SVzzD?GKe0yJ)^{nTz&0ch^JeoG<>5FTxXYy}wWn0v~w5nSj`5TG6f()xTiXP!x;Y z%olY)ZFM?b^NW=#WlhdG+=8v||BR&bBpO48OKOjD?CN*HM6Q@wBj*)in`B6+ZxZvo z9**@j!?R2-k!fqj7j4`+Iy+a2y@uZSUkFn9hDI>=ghhUVZQg z6jR~5z9MADzzhTt70df)6$ES-1_*;BXx08_9TLnCD{DnVzXxviFlEATzK|(ss`II} z&L_B4EonS$keJo%Pvf1R1wPnbK73^pVvy*PGu*pv*MQkjM}oxmLc~uhbRM`Jm#74G z5tl2aEq0>6<>x)+^5^ZPS*jEb&#Ms4sh(JA(pkJXq{StyttIsQy!?DeJmwjHc-$g| zQz71cWsk~Ue|8a5)-s7dCA~{HRepByPA?Q#C3l83b_+IJ!ZT zyChv5!FGG0YgNZ{Ycv7d_szSwvtL3d&+a0zE0lf2MF}-A@oSBuwd=pC1qN3+KMvBYj7lC68Yn68fnb+ks0^+(ANj`kAs zS*t$#@u>i*ZApL2SP}woX+gRwfO0B7r&p*U)D*p5tRNZzkTY+4_yy&7t zby?y3uO2NC84|lpV-K4VOT=xm$%Bb=>}1S2Ygo@$1kuLf=ecZXNadT;wF;(&%`#jI z{#P7=0-jq=Gk3N_`!28BAUCqnS!*NAdLq8?`sz+jL==l!-llGTZTWEp?$HLm)QSb! zeB>HiS+-QOH2QXYyAi^u{@pitW-icXW85PMyq0tu;;O?;`EJe4H=&w6)owsE?fkl^ zzkI{Vr0-0!Nl1jxdSKC6a{LpQQ|Onj%S_n(0gP=1e??jI))Gj_aFgk?+eMUzIgyw* zw_1hsO(gpEd4{TA-WIV1+jK>{N^V^7ImLYJD0NyV@bx=gqF5gg84rHUx4Wi7Th<;P zP})vQlo9a9#ZgluQeXq&wxJrrgsEY@rZ}a|O0q@vn9yQUAG3b-D@o1F?ipev!A4SM z_ifEd?h}gnQ*)d5(h9HZM8216cjSb>=e17-25@%=;YvYN{HX!DgOzHplaXOjVuyZO zx{E3Q%wDH8zh0Qc$|ZUn{vxiM14F)G*y_|>A#9#i z@3c;#i|lW@^97>LEQ$C>ek_7>`TXoskp3->lxbF=B&4#%3w2hbl1lHw1dMM`_ z7>p5nE?}@6?JG0fY)F{em`o#VIAON}TR#}eHx#Rt77+vXW`9U1%y)@u(c*MPLrj2t zTg7QJF8PXY?6Rh9_U@<9J-(LHX1?MhT=?!B7;-ZB94#N48r_x>n|wtGq;MfyR63tn zZ5W+l0b8MWe*Xi_tK-JeQI-X=S&XYuWH8;pUO@?I(BXk?PuS@G*14MNG0@Y51GI>P z^*mpqLYoAHSE&VgK;Sl0XV+HVF@aIA@$8SEdO0uJ=947npY~bq^eobr;$_g@7Uwm) z9va?OWIG{9_2UyPIU1lkC$v17fL;p{YDaMm$LaQc`bv%n+LRz)0`A8dOU@@YY@r_3W<*{AMBX`gcLE|3$K>?JeP?+B~x3ZeG@%C~h%&p0_qc>gBe*G6ZkohLD z!##)<(;IzX=X=R+I&Gm0591X<;`Q3(f^7IVMF@_a_}!^M=+oILulX`|mgw&KcxH7Ssl*IUl-fD3$No{$4rYko=bfH65}=?8xI1$}!$PHoE4K9uGkxZR%lVvUea>j^#Nv2#xafyT8ucOl3dmY#w`64-{R zm959W2|9h>1fbRi<1|*I)-DldjN3t2l>0j}UDnp8I0N0WtWLQ@^DVW+xgn^sqj2VW znJo4x>(EPIb(#$*gw9`9_Z4JYlTBQh91YIAy}ht58HCR=$4>rL-u=aUovKQ`0{`j! z!wr&aKO+Y>WDJ3!EOCft-EC=Te0%Ep#O2P=eHBL|iaez2i;5K`kdvEP$kLaC^1cGb z1cK6L+OOC^3P;}?-dQQDp9cP&Qw(b!D*CBOpy+*Sl41=L{oRn?@raSuL)qEI2ASNJb%71vZ>K3uKPkt}QHeMSIrzdx@ho?u_4m{u?)p zRdC;Zfb(HYx0tm#@~AC9ozpC7NehFI9cg^H~m9z>`(v2~ikcPNwYFuBIfg3rP% zm|W$8h}(qfCxOmW`%!5%)ZNlyKm`<<)kN{0^ih_Xu_kVL*`j<)=Ksk8*wirB+;xXX zt+E;|;m{q=_2f9MZG4+sf#|4K#pVzfG{4Hrm38BS!2WZEVXv&$f_8id^0vrmsBu0L zPOsf%%(Z0g2HID+;YLR_@cJHaZ+^M)h1bvDDsS^sI+6#1lq`6xCCj1I{oUY*(D8lj zKC+$D) z?#s@qqB|5Tj5zYCEdwT#exk6FSpXuYCaFVODO;<%mTsrEwu|b=DN`N+1vsC3At%(d z$Lda*yYp?Y(w~(Td>%o3oecN4Mj2mk z{pch|`KKq_45<4B%>N$}Sab5!67$xL(W^;v@Hx{(-|IN4N-uNXv!R%;%lGI;?l`0I zQ6Zi&(z0HLa=f%l1IW61ySBxB)Et+)JU_e#frC$+h;ifFUPt59n7&eH5OC^_C;QHn z#hSYo;>tJL(ZACNQ!;4VjGY!}5KP3!gFf9NSRflC;CHpc)mVZ1+!EqQ)$$_2Wt)cT-=b9O_5CI4X-9+ksrx%l9{X7G8h9*)!E^!(jLq_W4+S`OR?&zUVekrV zrqSz49&xHm{o*RYb4pYBCmG9*?e|L@%B;S>^18tx8F938PF%ke1*7uW&~TNSqP52H zzfP1J9^AmER|`LyXz&tj3?W4%ZrYWbYB2vlzQ-EosU;3_kfr9AN9wbizkL#&|yX{`4C zx7{X7$=$8lrnn$PiA=ixx~QZUVx@lb&<{UDL*5@dl{AyyzM}Qo#ZfckqNT4;ISuWx zlp``2zoJ{@;3xLm!I}Sv>4Is4;VoU*&1#|AmIuP-Tl zb&BN{D`$*wTj5q7l@(j9Zfe7RS-HJ`4Lgci#JN6Ai|1bqJn z{iNS&PoMGVOLA%)soHCMl1P#%JG;HpH)7bK$PApHN@he}{4YQ`wsg?yfMwxxMm9=q z=w$`Ox6NH}rs(y`5`#OcN#}Z}%?%r)R^4~cCz=eldnZ1lmN!@a^2{U8jjhG*B_~N* z-dri(j~gNl<$c`m@b3DkoZ7yyPUDyI=Wc}aHNIRFwMrs0ba0@!&?o{WLls^1b8|8G_aeDvWlzTJJ~L|_B&>|Ce% z_Ofz8Z{*yIBDdUpN^hU_U+3Dg&3x=9Jl;iZLGEGgDBJO}dmm0q%Y%iUD#e{Hhnv6S zFJSgmtXcc9^gh00mBin%{Ebj#%XE1luhxGvQSgEkgBr*9RpZ-jC{=gavNr*)7LR9@ zm~umGJ&eqR4ABGo(C3UrWIp@B7GLcyeeP^}Y~ga`wZWaoDtPrRO_!t1utn}_!mM~> ztnL5Z*{ETh9kcpblYPxyF(q{sX9;h*0*4lx^tU-y?QIC52N8JNQ%GqRk;ZL(h-$^H$1V#U6e_%-(%zr_Kcr0X2ZBosYm{R`}WkdJ?1`(Ra@H3j7tX!Gw;rSyU_Wc_lBtibIEGaIX->Z}k1! zOyVkc2I|{0g%Qh$MVWwzWKbO%dgYe?G&7EF6jOk8pB&HCU?HAVRC=qAK4kLv|h6_ z7{@_wf*6sExV>pT>~JSI58MMCn}F^EqZ^2m9;1*#lIgZ6)#&Dq`dvpZj$gp)>v3@wJ9>y zbJn3OB_%{OdL-mrswc8cfR8n|rl7U$w>uf?zT;2tifMaZ8tTM^ajr7xW7_08JEZ?L zrwolxj6V%%6otNqtWW6Q7^D{D5wE*uKdRjN)m*jNChLn;@KZNmsg@txdAv_&(bh^v zv*&^DZtZr7WJub=pi|=#MThdU;Ky-obsN}TY)rXaTve;jd{VBiv?84hA;!&@4Q zAIt%fF@cWvSmZ#4? zj=22DbSuG%w>QTevR9wN=qY~u{v_&~+m9ZV5ADAt zZtOq!{`zD@3Zwe=%6e|wArY&z01QzFd$#vHS4Rb9K7O=%t+LOCwvNn^+)($^B;*JB zGzbElCI@||OV~SzHsVTwDQX)U7FwX zIw58>UpaZ+xZFmNVIJvq`2_r0{?*!TM5Ok5;dlGM!eP@zTju867^3#x+(nY)`g?@6 z@8zIZUpIB5XI`mI`1KX58_9Qz`mab4AL>ppPC9k>C$KkBcK$xzAn<#qRY$^nl#GJM z?{Cx3C_XjpdhR*&qS_hzJ^Z@PSg&r)s@!vxGLtL8UdFSwDBi@rJ=i(Ptq99N|^@TJh)A6alWv61z z0+J*a8o1JuS#(_~^536@bsCq4wCtyUCq@Zoao@al;)vh&!v~jSXtxbMW;tbfAD-dn zBJDKuIjLF{e}r0d(_NF6LJ*A%{88v^aNd;{r*She?wh6iV(Pw(b*-=a2IUxN zR%J&Sl^^9T=L}h*HoOEANxLeX-n>&H!~6_|w4_#3-n;6q5q1m$pqXA-Y`pV58Jx)H zmvY~BpSA3kBDB+_(R-9V64yuCmK}0Dep8&2}~? z-}<<%OU*X{k#qThZw_U=v4vkbb@-?>Aryrf%7yR~_M4B%pN|w#J^AN|?PmAUkXu3v z)LUme3)H{M99AM+j(ndEBZPMA<9`t=7H@s%Bt(7YYQOV)b66uZ|DJ_)T*{XKGI=smJc3HKS zzE)Qw|DP|C!vZQgHobQhgXZ|u4JU0o9d2@Ir^ptN+x3&h?9Bvi>$3Bl@ZATrKi45? zSqFNkxLo97}llF_Nf%WU=4<@E?INhSTk zXBz*M`4ovuh+Ks$vb7P5k1r-}W+2P^C`p(0^Srh^wXf>+f!uc%CQjJtX{@s3rQyyM zrjh1gWA_geK`%jvwyM1MW=4Mq)q~m4w9e$;vLyvCswvYFUIwnU1BD-w=yLNVok_32 zzbu&G0uJu1ZY85N&b%l^Y-SoWlk&IE@NG`{4^?Mhg;{8c|2;+3Xj6ku<2p_aC+Dg? zvor#4{(8W{Rlx$hdz##z;(B2Y=rR429@+xh43o-E&YkdUsSq_m0((jC&>Or%>tLQ)!~r8}izbc`6IMvWXB zjQXbU_g>fc#*gnOT)TFio#&kM)cxGIkfJFoc@;0`a{ooK%vZ5T2PgVN5x~sCp7U+< zQx6Q9rhq3wsv!kzWR3lgDz z+$&h-+~auBU+*x!a_D!MIdENnOk$jTr1sNO7R%d%eZk)3<2%Ras99>qzD}4wEuHb@ zt!EK(9>R+`NSPs@eG~UhPWhHRD&KCWZpY+UWx8ZqoWwO3`DHvnf^C`Y`N8i5r&!z@ zPGjR$GLJKTGiA1XjU+|i<37c1WX7(9J}2lE*PLH=`7B90enPywN!KoO_MPUh0}(6z zrOtFJ8o1iuY4z;QD%Z9B>W3!dc1>8S&qL}x+u_czedxVjF3=A6ajwjqv)Mvu?N%*w z84!(|z;#)k*fX&((ec~*AgF+(A>sU#@u+DEXuqR5JYV_m4g^zl`Bw5^vJ=uqDBNu< zW#5UFxt~D-tFu`wv+^hf=6IR;0NKRsZOaSjV}M_*#Y-F~y=;wsmtPAfbs~84^A(<^ zs-~oqDdieI_A?g5kQ{iMldZSI5s}I8sD^psPxKIqT6v`njkBKobk9KP2fH94?lIP_ z;C**^bLGdE4ITR2*74#|i6t&7)1QdEMY5}TXb4KxnH}wu>JppYO8l||obP0AoL8v2 zwWWD^O)2}X1uJCE70I6Nm`o3~$$-7kKHlh^lp8x##K6=E&dTn37=6z}G0e_7;bU`f zJicA_FDcvtjjK&>YYwhv1j3Zy;P)3`=|fv70$#?V(c!O9q;rYH0LaHY<}sy{ohd~W)4210hQ>;a!(y{EKObb zj~8AhXllezKO2ro!x^I+5pMCFd@)epP(B)U{IJ8W$I;3u%H73ax@E>-TPXx8=9@7} zF&hHJd`@%-&fryoQ^xUh`h>@Bcu>;rv}rpLJ4IIqV;L7miUt32UM6+_sU|$wbk@~C zNtpK-DC1?TzCO%UqD1v6C|aVDDzHfA44T>PV$f>SaSJYUG~VlW)VyqTX%Em`(rYp{ z$T)TGCcoWI)dZIWT)MP3h{~Frrii}v&2S6se!cVnIShU89JH@gUZOKuKpP;)ZX2EH zHT&-Gm81)PF8+9~b(Pj@J>&8WW+UJQ{w8~5R0eM-D7uVY}2AFUS-uK}&2=d*E?$ zQ0?Jz_0wbTm3GUXT z*L}CE;rNXl;jBdtpK^; zA6bmw>savq1Y9jbY&&)nr1AHDig|&@pC`aqkfuMT_MQK_$k!^Sf}pgJ?;OTs%csij zneN5y|MB$jV`q;$B4#}07JMh`1GX6m&$~~LOnp46JxTBt79gdbp>shLXxrC_C+vZX z8Q(uxF=Y)@dJXCP-}Casy_fQRuP~R*TV>%&O@SUM=&DeVvu0SWL8txc$Buo`yLOFg z=c)8OFXD+9y(7hH)2%H7`5snf0oJ*HCO5$y?i)N0XEciWvp!g_*2TS-xjAcpiq46# zH)?5*57P$tHgRX~H-GJqWlZCW&rB+g&!o8OTRm+|vM14fR>kgOMBa4l>cH)We77e_ z<39vs^#8;xmr3fKt)VV3-ok%AD=(KzXh`gg79KF?O%?2rS_%Zj(F;jwGBN}Jf|Pw4 z8XyW+XHkmq_eyKqY%W3n{av8AQz;q`aYqK33tMf$WtdgwDnoqw!!b_TFwbFU%EsFULk6PZL)w$I(*Z>&@B)kC(%BlOOd&8B@e;wWb{nh`u3HwnmDDY4Ri^=so zTJ`_~@husBWNAl^W2u!cW|MN+Un^dHthL^2IQarLEq4nd@ZN zZQMk`E=$Cf{{zIw6&Z|s)gAh&`Ld+20rM8^b9_b+qJ(Y6HB{mmhw_43T3Vh<{swqt zQy!M3Kr4Wi_GJH$8UAxY|80HIUGUFXWPXWO-iOP+b}`obkh$Lb=eXT@hkyK~vr51N zOJw)P^D1T0f^a?DSPYjW?CS+G9`;VH&O4iwtMDf71tOK|;G;raID@3i+6#Ee_j@;{pX&=6ccESD=mprIT_R*AMR&|2gT*h|l(4p|OhEUSp@#C902{{qtpw zK9v?3M9Y3wJ>c~ZA>U8r=;Gwgi#%5#*#YNZes?6+F z#n>g+&>6sA>--O6{2Xf6(5k#GP{A{_|pOB5Hw`!!pj|j^KE*K&5tv``b$jeCMh>U4H`# zS}t-}&wF^WRS8g<(|;i4K?cpR1)kHE;LUDstZ*9m)ro0iP5aC|7eIu=UFNBQ0G% z3e`lL^1R%sH9@|{umxo;fWtAbq(ayE$DR%0CZVC1v0;quOD2WkB=nzbDjQ1v=gt2g zMqD-~Bu5LAUub)OHtYY=0L0wi)yp-Xi5 zB$HnW_*7Y<(iNyq?1GUMlg36^eUW8PlH#PxPAZuWX#Rcg7Wy8v_p<2=2KH(Ku3BAX zC7fi&?L*re9bsv<{FG}J>gtXA;loZgT{>Rf#<&2y! zWOvE*>|4NmKBPVta~{^_6io%&Y;zWNuM3Vfxfx~BWyCl5h^>g7@tNz#F&{9sWfP?3 zXVPG9PXz(z*^MHb5xef((~Tzz08K_=(auKM(jZ@Tb{F))kjLIZ z%}n5-M~0A@F}*B%mXwrF@u&IS7#0%^oAGS5C9(=_Zohag3y0;>`*vFgDh1ayx<)~* zQv2Rx3}mD#H>n6`2_|5TB|&pGOanK9m%ZKzbaWZaXO zqYFX*QT6&k*wm**pCTH7UQUVcWOl5y`hY8?)P-g@iay+=7Y{d@?tnQSN#}BFW_+uH z^H!)x8MjQiR=~dv*t0sdYE*kCvK444y3qV4-Q`RA1b%V;>a@=OhZ_Z6g5R9_t3**M zXa67ATG@>{m@;s}6h zkC3u`izkK4O4`?#m5x+Do(qvDh2}-Qe(ZD+c$w#-!VL|7fJwwX@lW5Sp{1c>p3S!| z&=<)JHX!V+cwztP2=0bL9T=!Eg1bT^W&otVVR3IK9=}AF;iL+AK7a?0o)qwvhT;%? zumPdA?RBmHBab{GOs8*2b&W@HxeE?7r>TWOPFZv>RG2O__d=aFyLGY^6{h;TT{44) zV*IaGrP2GwyJ;Kk8K$M>J?Gh`rH-e*o;{s-3ckmI)0V4+fycJss$#48jlXXrLqwP9 zC4Q$bl*Dl2u_SZadMu`xnA3v^bd`;^Z)gPiWXn}IDqS?ZM~l3fmXhWU{A9Ba0AQpo z_Te&*`jo7D&a1PM_InKZ$_uX-fa z8@)daJH1jU@hZ{0(k%((_9~`Hy>p60dKbS5#wbAL(CQeeh?jH*+(8N}IasU|zT)Ym z6#>zI{xn~gi4rztG?3J{Rk)96UEq^_Yc`-dba8<4oMmdkl=gNknarw}w|xzk>Kcpp zW3X0%Rr_hXql-GZKW(@BeC>u1{?qw(ywext@S`R*jvUat*PR#39HveiQvFPtm;EQF zzR*mrW&Bsm+jM$4#hyd3@7he;j+*;V;k=+I&C^|7s2_YkbbppxG~0lh*wwCNziB%a zL!DNv+Bx%45b<$E(Lsv%WdhgxKEI4_qHwKoo#}?<@(qljSIUmo_^grlN(x55>Far; z7hLEAQ>WungN(IqxnAP-SY}$-t=GW(k&_JeIET|$sjY7e5HN!QM~6EcY|fLu7~x-EW0O>S@~%U@p2@X!k;!(yL!?WKD04&y>k_ zwhNhmWR5q1L*r%S~3Klcf5&#l^ka#kXxk2QtT2bkPpUWTyU zn}IKZ^NC}-X)J-_aSv*4Pm~0TTdkKN4e<=;TR&?DrW<Kbp2U(fYoBp@ zTZg(7fc-f3T*v3(zS53>YB5=nyBNE@CLggDweBELO~vkP!mX4e5!scm*-ni|f1uq^ ztpD`4n(1DN^}T_ExUMbbnP8kz$;a-E#qR3viUP|Pd7VT0`I5Aii;d1E+fUpVc{xFy zVB3oBfJloM$uN_xj4MB1CuUo-`g)S4v9>Yc7Lcfs2`-XxDQ9&Vo$Qexmq*P}992+K z*b0-$xILu0bM#q`==xB#vhObsP$}9dJ{`3&whXLNbz9jWx!$I0y5IP7B#Xp59SBV* zzTfCuDFU%akJ`lVgjJo`CX4BF)W@@?1n7R^xDCURshgyCPR7)SPO?Z9F2q&mq`VFm z5@93Yg-u_B=0l!Cft*ZP$bOkSnSn%#CqDc&e_KZCO5utEl-6GCs2yc&%qtcSoj7FP z&(dc=uZ58PKC~SDC{A-f!zM^moaNqpg1sjkT)#-Zg<-*Wx+eBLGXQSro3>A+i-wKGt%XWKkO} zzcwsCZhxQLz(B<%K>h0zI)HTzEI!#S3uxUnI2Il{?coWv#f`Q<{`XAAalKk2QCvc> z8GHwGCZN2Mjm`aawRNRjkfwW4)kx0lXu(+6MkfV~Bf2YbNB1%hByGZ;9|&SnnVu2i z*r+-tj|9re)N?V(bn*gFX|;{4^P&6bzT;%Oy|ABl-QTI~4tJ!dcgzZOE~1b>;nQ+Z zYc?fgAF)5K?_7>&x`H6bIB)bG!R3_df_@P$%itPza)c*DHcwvky9>7IQ)7W$4S?<4 zxtu1z0mKdSz?w-6LsOky66m^^YW#zkle^qxRq=9hJo{(SKMGgNX`@wAyJ;4(h}26j zDj9?VgV|1d^1BA@Jr+ie>1Y0ls|R8$qiB#y6Z-J1vo7wF?Y+G-YZmsg~jeX1JnwBpn6T% z(|F;=I{d|VUEPjFai`+h9G?m#Gd&5fmqbjUV({AHHyAuM7EMm;1l6((k8CZ%-fp|^ zGVSSdfTiE$>fGj|n??nMlKq4A6J$_YN1W(k1Flx~=at{6?*pCP!U*sDytx zc*(_rdq_C|W0m#Zd83B#>^bTF6LfXODVkHbAXeU_pOuLyPOIMpDZqg{nWR+JC`wFjaIn-(eNF z{zOktC_z*AzW*Bl5{Ozkzec!!G90-mcfqn?+F)=p?r)WJlD2p_uRPg!#nSN(oIIs!m6c*3bHzw@UYCu>6etW%ef~gOK0CuD_>D6wTcDibcP6* z>zOy%>2-OIt36LQ{i|(feNwxk*(WskuwyXAh5P&KZ(-SQnPbG}EQej)pSxspTl~?S zh@18vr>lAY5kD(4{iS~q1D*N2v8T_xn7H2Mu%c36TV(r8!5xE`!=U36Dyj`7fZhd@ zkX;p|y^O%vsSSOo=jbtc*)G~FNf&|ceI-A|ywEjhbK?=4_B&QPcPnMMPre5W`+TD05*DqpdxCCGzz!QKym~#mgqVyz{((Dgw%Rc^grccd`p7Y-8`B~R$qpk11 z*IUd)n0EjbU(iku z)Z<{Y#$&xFs?Ju!$5h#u1OIqJ94W0?u&(b-8crfTV>jm)@V5w>P2OeK8zMb`KL9P~ z8gdPRzG2*(RekPFOMJJd3%r(oeG{_Y&~(>C;F&a>Rhldl@0q(_7gs$2z}DwR37&g? zGG){L+u7-wQ223FtCmb{Oak#3j%re6kajE)TW`=&X|)hICS+PFv>-|NE@bB9=Yna< zL67SW1+ot$Q+u0|0(#Q?RR#0@!UWKuui| zQRMN-Hzd%}g$KL=+OG%tT_9FHe#3mkY^%e%pvmnyZZZP{5euGN#0g}IEbyfufTEz? zX5?fr=nLPwMkgFrMmb`#oCT)91e+DX^qoJJ))`_!<2F5$ERyYVOj7j%M?yd$WCk<9 z!IIEH=Q$G1-6jK#GYhRof`#1{N(XDjv*V5&&AP~YB^TWT{13fBVZt?!x=Lf?&ZhV& zPEq|QCHWOL3Nsc9LT(r7BEs_9+N9dP2KVdKrAeJNGD*uxN0CYp_Hx_JS?!I$;RQN7 z?{7cvWPHL_Q6Dozto2*mPxBWl*8%{rhyqXvw!3>o4F~t8RNVK(Ng%X2#eYpv7a*{q ztU2iKcqH4w(LZ?^xlK+KX;C~Nq5rKwnO0%N)#=(=MkCJ6hYWIno6I2F=wO6{k>9z% z&~u6ykzo8Y{Tt1BjC+J`u0aNF<#q;fm=ioMsqlBFIjwkeGw}yr71Bf>FtD8&!wBi zVOlm}3mKZ$PbHXpZc2ge63TRvtwkPA(o$ka9`i>D5eHo1|t%$YB@#RX+I5Gj=oCh#c7%|CR4r#k}hMf!Y z8~g1RspG=TpPGeUYhBE5&z9-hjDCG+Sa*UMm>#*|7dN8ebO?pmL=^(k?Gj`S^*Fm< zGKe{^W?`gr(ncuZSoheC2FlK2Os>tx6CNum3g#FM?geZns#N;8rn>t{w@F7tj*m^n zEz(kdz#TWP4*99n&YP$Y^fBVSZ6YTU^Y=p*qqNJl3_uR*6n(XwPZA({rHm1FC#tJb7=D`Ex55+dfs64Pk45UIeM4)2W%@2N_T znl5Vdi9R8nsnd^S5#BrQcB`93b_0oVcC+JY(^p1STY(3S5k5e~=&j+cI175fZZ6c< zY{L6eUS<+q0hd!RL9&ZA3t&8yN{J&j!A>v+QnLWK$lMl`P znq!P+uv40mlZ_=>P9R)RAPt%Pt67lUD|f9nKdV zC`@kWM3Re0#T;w%U!D_gb&7;moh;ItpLLV1w_W>Yh@V&Ppz7+l(1R{e2C{3|#~Jq2 z_KtG`I}6$;g7WDS)^7UD2H1k%Osgj{BHh53;MC6h1<_2+*FJ%^a*TSb-Y_b= z@jfNT`R_sX8#cS=60iuZKdFwBjV6wFA03*jIiKaYYSWUAY9Ad2@Gxk^#(QbgOLGck z&{E_>PZ!Blq-|{S?s{cA)da0~*x08q{N3AM(PZQPVhk*TF09*kpI`!0b2$4w5|(Be z40$NXDtg1?^q_{1o}VoEIcoefI8V&XGN7oYMG(eL+7y@Vh8@*KDR5z1wSrwerO#u7WM!3VPRq%HigAi$c^?J zk&OYx;L;t!5{O>Gri}C;7!;Uf-7ngHKG9NVJ-?4>((ypF&ACq<49`s&?*^}7zZ2vA z?}rqw)Qt5Lw64DLIrKq6+q@*?nhsZ`JHN(_9M-U_pXD#9U-%@gGc? zz$0LQP;!v`M187r1^O(fd}Lb1mIJ}Q^swOp5<$9umi1&;GCs^mH3%hsz^EV|%*iz3 zceg%~X{!XFaf}gDg6MYy_|An&{GdeU`X4rzQ%SHO7>{sWdU!Mk#3ns5f@2MLgsR$+ z`f~}kl6Ck}X}6Qqwx(pVh}TXDt_s@ujgzHys&WA^@fMIQYB{;lcESsta%zexZMpER ze97TtlnuNeq*Ptvyfa|n+@5kSx}m-ptHX?$A1T+~Z3RfB)*cpXX8!KS^N#xAlt~EZ zAE3T&x2GbW)z;RbHcD{qJp;+*p4paoO?2$o)BytXJNXS7_;NWMlTco22z7}G>^K}4 z)`#(WSCU-wchEK?&6c;fAVQ?B&Z6n*Ufn&l15XMZTplcJHtLu}UQbIwPe`;cF|jxi z4RsQmIcAgipVKfzm!zZ-yRg^XE#d7K!*KqW6Pf`R~n9g!6EFV&jDQxUg%w73-5%6}xbl60_k&rrt%PV@aK7bs!KL%Xf9tS3SEA-; zF5KLb9!L{owjw-pMphqF&KYw%^@e(J8D;P9-hBCjX9Rg82;e;H~n% z8;~Ze7P5xgU$>~1F42a^1OR&D*T^geq`A>~SxeAx2&DPw$e%WKe`Ty@`FbP{;`8}# zOpRciNyA{=VQl?kHu`-2HyL<}D#m1AD=596gh3bY%KpC7v$FqMzh83?!^%Tden%A@ z0gneh#%`SiL9(@rJ^dl1!P^5LcLF~R=}w2fN&amLU{gGsWV?-jaO7Zi615X*0(#a{ z3~Yj}kZ(7J5o~?u8$AWXwzS%8Xsbn$FJY7IKIrJ425?l&%<10E}4K8Tsy^>$4;L;=>{8!<`w})z$)V8|k2g&Gl4YNJ`@krt(Aa1!=H*BzFl3d*KD7t|KMrcab5-f27l|#K6wp z9V$d&_YC|r=^h`1^dw6HAlmj;iAt}2V~Z~!8{oh9+v(^*@J&%1@OwfoRU+kfD67`D zC0b}rXX7?mLDkYU#BZ}RDzHKpXx#B+NMX)*TXf02QrdrXTZkMccMbYIa5YDMxbV@0 zuoue;B5CF7lty@8E*bzN54@6y6$r6R99}%~h$anRw*r=~T>= z{^%w!Gfg;LsqXRWvGfgpDef8Sq zrNomkv$*;LG6e)s_98=QEH=u0s>g0*3aWFZbL4qtw!M8NAIm~>&F41Q2)UG-y4^6M zptEpOT-VEL{IJ~Q>@W&dr^m}C?3}bD9IuEP5Z8<%#b%(L)K<&y?m8imyLdT=+_coCl(Uvg$kFv@f{oqH={&x;U1r~I~k!6(-xw|t%6(Qn5V4qxde%@ryT0W^%@@*HK$mF7jb1bR`$1In6E6XLTNTFNZpYS-A zQ4OfZR3}590m7RSBx?i%8U zG)$9=Yw~>jT2)D3LDENU$*$YGWAsB|Rm7Vc`6@a5e&(J+r7!Yj_UP^j(h9m54aO_+ z6aGG^+GA{Ky1zB7ppWkk>6;>W`P3k=ynt}2JosyR3k8X}FNO@sT{ix`oC4NHuY_wG z@+z)AnTAs3H8F!8b@L_Qd`9Q{yIH87<4lkxZsU~l!bzdc`Ifton@ck5GRJcX+uV*` zb635np^fXN`(2ns-}JJOkp?*;(3!P_Ej{;IG?4n9xD`g*+2a;^bnU~v+Pl*1MzS31 zv3?!J=y$t3kmm2Q9NU1XNY)+4B(!o&%&31TlmF6cc2dD54W~K9gj1oVEmyw}sicoc ztB`h39)KBQW+CRbhCB|)jD3>I$0(5D-RwP<{A7BD0u z$U-cXfhE8wb>wuFH6X8y9$vZoNL^&3sOMw066Tys2_0dHoY+E*-N=*vaT*`rxK3la zc^}zWRh&}GJZ8K^ApgpouWQ8Q)!hJf$5%W%Y{3L4<19>IBPm<6!|SW<$-L#SaF9&{ zU1!O<^C0X+!N_+5d!aZlv-_aaHrnB(butXxd^thh@lRijLYnT{$;(MgRA}12Hd3ZD z!{OQq^~i<;FAqMuxX;4^wNaRuV#&D~XQ(=_B(Kx`-t{)G@L{n@;*!HEl8njz`2r2WwwPN!qP-28D5}N-J5h ziR0BrKLK>>*{muJhs~Vz8ch9h5?IT5MwtbzW}l~ zlvl%}DxMg|RJJyonVz{EN7~cq3Iot*@9(7C_qG?DLQQl3v_%fPip6?e-`ev6<(z$k zr^5z;-0ZFgq#WGWh%th@GD9s?v#?oR9h=I-ONn%)P-XMBLAKc3R6OTqVB}1&ic3#; zpO`{Q*y_9#USaIp9ORHJ%+=VY4gQ^Z5_MsNFL z1gRg{n8Lc~;lhXR9He|-G2WzRG{E9nF0)d|2}64%SD3}_Q%2_5mkjLT)3}>&#Nd&u z>*9W2H|nE~H9>3+ReCk-ja5g!Zt*Fa{_7gQG(8?e&JJ_|f&DRa8~%aoR+WHtzBu{L zppzJrO+TixIE2K-6O$`jeUAql11J~Mi#!cgIa8ZhN#na1W|;K43{zBUi5%d{*uV2M zrOJ{d7Kx{Fp;~lsJ-~hUkPT8Jq8nwwA3|_5Nkegu_Wrk5Q{&=ozGwCv5k^f@wN9zX zQ&n%Li%k12QSJQ@S>kLJjPx#Lxoz>hPHl(Xm-c&bdGW58_e5BR<@h1@J}B-m1bKzK zgyECs(GNAHujmWBG%rrXkf|8pyx7(;vy0jUz4&D>%2}Gt~a#;7vMiER%_P z?0{MtyNw@+JidVSj1>GsNdLcK*E3T?5&5ANl_M9{I0w57}a( z+HvQ+y{=^<^11t{y6#U-EexqoT`w@?lyD_jLj*{Knnfw}id7c1`O)=^hgpLTEbeD+ z@&bvvbSD$pvO~T*M1i9P*~j)|h-|i>!w61cRio0D&7P_ zMG;uk_`(n8O#LNTtqmUbnn#*K zyx|rHuahv)sUOt(5MPerI65H!1LaP^4DT^2WnV|Eg%ufCUE;ljm3WVqtd0wo{xaef zv9JXLHY}8NAZ9fc8#U@Hqu5!s?RWIbyYM>|Oet4IStMM~GJX)RXJ0^&i+$1B6xoNj zJ1ICtKHDv69%pWGSW{Ulr-ClsudoOOThNZ4CI zMe(R0ZJ+-!zCz}g5W_OEXFg(Qx21_S9kl&w1rZ5xjv9v}hA{MgJp7fSK83G>NbH>A z8jaG%iIT~<3`~6fNqjSTxK6L~pYXV36}>wX$cqIU3#|b ze!?0-^kxa%ip!BfnH?S^ukuL~K{?QyhOa;tT(aro7j=!G9M9&I%n6bQK zLomXB6#39BE?jQtsrnR?Aku7={Shu z$#q*%+&P1eg)x)cgslh`(&D5II~_$g+KG8unXW{BuH4dj5$PKfcal10agx&-j5$SFDv!9DXJ5{hkj$+pH;&jcI7A0plS0n z(AoF3em^TNId2RO8?3{NVtqudgf8rGdx;o>pc`8fQ%{G-x{j0_P)zJrPo^>qZ3SOu z5WZPK?@H*=XA;h74%NUZ^Jfm-g6Rid>1NI>MGLvjmfQ}1(!GVFnXXL4I%q;&Kp#K-}my0yJ>DWtpz=4kQ{%WTMXy_u2rmk=4^k-3L zqQaxj`g88x)}yHRlgM0qqnQoyO_6Xrarb>v`z%4`Vf68tQn@aE z;^6A3;$jsv<`b;mx#|glBzKK~<`jL2q0l59_RMLoqO)^!fFqTHHV2 zjEjQf`Xo_RiS-2`N_x(6Z9hzXBX9MoPPIHjwnMu?tS>GCpvRop4UP}iIP2pkqbj<+4iSaA-G40^8EhzEVs`kd$>RAjY0M05Q7K=n*MJu~r--xm2XY0g6Sz`YIf za5hegbw!fbutdTe;K3+#2Y4xJW#%kTEB7jvTUn*k;pK}9206cV?tZ))pq=R|z3SRD z!+3j42y}FQD`J*pd<$=(kRdh^A)hN8pc!S7K)>}UeXEu1O`m>NS~dJhR%LkJ%STGN(;2oYLkkn(*%-YPp^m$^F`q749Ope? zJmS%d=TB3W?1Yt?rbV^VW=Jba68kxZ;U4WnLLy(l*{JZPd4yfF&&Q)WE}D#cTVJ>$ z8z~Dmh+h;!dla>t%yBqjNw&^i?7uqh-)~xAn&J=r^H- zP4+3$o7>5RAjEf~K@>lLCaZ;xuN>32H(=<-n(HqVd-Rss!QfLzH=AkObB`B6{f(+2 zLL1I7%hfjDMZMmIivMx-FvCRc6nd07BQl*~G)|g*D>ZCQ#|Nng1^~&nDwu};H&kKc zfb=~tFCTgBNOGiZX5_1$w9e-92+6FQTXHuQ;sjeKvnQ`(@zt36=-JYuHjvWH4Ccpi zYA^w0pYe&!R3a6@6ifSg6?V}`a{Cl_M2W06GKmm|3}b#0#qYvLOo4E;^hu@sVL;VA z;!VS!mGE^|#cg444q_meOE#H{10+RS?HV5`Ya9{|6Vd-bLGmUcu?H>UAZdW z-M7MW0uH|i76pwBH;*IgA=QoEW#mg<3!vLF)OEXqp$B`u`NClVZ+@vDc~>r{5c~et zTtdNZuPaN_@=z(G?!S45GL_HcFC#f~rivqbFvWnIX?$CXxzw9LX>LD~1P85rnU4EI zb5JupmYm}{dTbASH)4{=j(IJO)UY?@;cej5lcMlSiI+vR>p8-3& zrK9P>`Oq(YD<~EnztbiJ{xNAhg4pGQ>ZgK>`Eb58sXK5SQzjdEXaRzoeV9@0SW6Gz zcRs)+rk#5+nDi%(NSD@&mWbmEEjo$Ap`ARqdn>DsD`fPuPD2brP5j9gZS^bkZi!qy z()ro6FWht$AySybaHOY*}(SHHjvwhA9MQu)eHT7 zs+I|HeYnpYawu~^IGpQEvMXdt>7J|O1qz>$9RkQOm@iNH)$C*AjLv1m3M0Ewrm^11 zm;+-MM*ym(q6T;);J)DFdECPnjvMNqgA#ulM=IcC43P&cB^w){x!AT!0x5sz?2I+u z1Q5b;ip=I7^+^XsxITlYeE6Sk`2T)o_M5*k>>)A*CP%Zo+b{$ zHLUqA8ZkF;B}-q=Gt^Z7LVkf!3rCn9%T{V1mImURip3IX4zdp!Vph&U8A*P@5*|F! z6q#-|5?e#@CUnP{m$V+KZ@q{8# zGx8aOA7@76Nf)(O_S`0(v{pffK+X+=89Xb(_TtCEA;UQk--HHg?tz+PYR5_V22Apsc&#bm)}Y-cF`XQzb-Nq=ZrEB^#4 zqNYa_Qf3%n^+VjzN0uM?o(N>l_WaSNGP;{q6C^BO}lNw+7ZD9H)){r~dCxQ7>m#_Mv@VQ9ZYi%D zN|nR{!E|MYSJbnV|I0Xv0o^@+49(!V{;Ia&;E5H*?-f7~3)2PbSk&>Jwm`FCHFwqN zkHdW?2n)VU&pd`ukcsT*ga8Ie$uU8WF-EVP9C@}Up+xw7@2|Jr-0wiXl%r)kviSoT zoLVGWp=bzFIS}rYJZfx{K$2TJ`c#xXrncbL%S=ao(G%E%j(d0eMQMz40Hoa|sNdGsjn>&=Nw5 zvY^^FZ*LX|bvRxLN5EarGE8fvYT)0@j?(AGtBXjr-|TI>292v12Z=oJxMwdy3v(&k zP&+*|Q<$`K+aBSdNL~X9m`1p|tgkNkGvASeSqS6hz++ErRmA$yw_q}NSrp{ba{GrW z)YF+ERf*#CwkFJ^rO%t&kA2fEbX|_AU%ladbK>Ot3}ijCE=AKU#ShVmB3rh(o87l_ zDJz+h2ahT?KODj89XiiF=^r>onwqrj`Uat3FSQrAB&PiswNW+70ua#*RDKDxt0S7}au;g04bv6~ndmB$-&xaQb6a~)EXTG*LVQ(Cl^H__(f@t4 zHkJ`FOAZR?3&$nlGlmm(ugNp(wg^%bpY475kQ%f2!=t9P3*xt5u?+CfEG-bFW57qX zcDYhyoJ?b8jE{#zIC=vH7E@}r&!_yiJXsr zgdLCTXR`j{qF$?D5o8unR38UlG62S_q$?TOKK;0Dc3z{;ACCjG=+VB*$vZUY5jkVs zs(=4?&QQHFsBa$670Xrbg7|heW}u9@xrQnE8+5G_gQ#PAj+=N48^^E9^! z#BhE~-cnqDpbH@d*==lZ?%?iyY=yze+tLc}vz=Ou;FNgTjK+uE>#lAhdj*pNq-!pGB%sb2Bv91R@48j;%$$?u5TqM94JpU+_wZ z`%I&I<`h_pG^}ZHphi6UBs%bq^_m zqk1a9y%oz4`$ey&<3euz>3|&$WCOSLw5Gn&+FOCRDE@QweickcgifC@yoe=i>hh^yeDygcNi6<^7QvZx`wQ9OJhI)i-#dyO(=?yI8UeYn#=O&^6(&)63p>WunXRGZ|@>k+v z|NZeai!vobM=FPK0$g-Ad@Kn%FIc{Pdzl^l`NZV-T|>iyvB4c3&9~cM$tU+Ts0EAz zcCT*tnwUDpwbrx^`Z&kMW8;MT>4}A+0e+%wZbo?iaiTwr7Nktc$=ygYd|d8? z^YBFS4*%!#D=s)k3p;0?3^_Iq77labCz^Z@_X+OY#AqUgCkPA!=+gJKde0wy?j}sb zce~oaj1dK|y?N!$G8RG8`^7iq-w;u(4(0T%uac~EG!<~}SqwEkwbot*X`6}DQh%f zdhC!PYZpW)RQvn0*n$>Sd}uW@3sG4uH~(NvdC0ReyAHUDnDWAmNtif%6=5Fg#wj27 z;N`EsF`Vf!VL%sx_xNSV8hI$8PR&g)6uw@44})1&*^cD^4*{WgC~>EDqT ze;4c>xy68y#(L13|A(fxj%)gR|Nj+{5G19fK|%p3ks2T&APA@^AuT1+%@|`eDBV4z zQMzl?=$v#jx?v+nj^AGI&-ZsbfA7ENIlIoe?vMMG5>Hjf@K9(*I4jmGS}^O($=iEJ zcU)T9SWOOz-eR9C?{}w$0vEMN9mZtgt+%7VWfIG_jrO^HvsUN3zyGx{oV|9nh6#vE z?HRUDj#J4CWK`-cKiU5neePutF#C#2L!0zLwJin;amcl@_2 zdw1rWrtl}x*QJEhg`iD9Qp=RJmR?N3LqQ~?S>f9!Ucfca&SrO&r_4EZ|4t)l>0eNs z1D8&m4g@vGP}qES9~?DYkI6ipX1!_iA4hcVL4GXPybec|0#zO}W=1wO!NZ{vh-S!A z0@uD5X8=6SjV-A0zr?~}IC>%%jz0Oyg^Auuf`~Gb5VhPMcw!ML^)Jagqn#zw+Rr-~ zJF7jFq4guty!Sb1CjKk$7bNhVdM=hT$vxUq-?|g1Hduqv11rlF-;~_DX&)xfG_B{} zvIen_6nA7XuC_W;+x+;09sx3s!xW$SFOr-qfh4HwZ33cJgb%@N9Z=ga#xIoU0^ z`7g7gceB=6!#%`xwFSA`g&yO&&+Nw>y?*9>aK~MfdtO`lm%D0!T(FVmYh8`6>U^x8 zIE&qg+?XhRYPrAD#V8Rp>Q4Z&b$zUW_qRhyiSIHf&58Y~;lL@!Bij;c%@i>r+aFPW zB!t38vOKU(#2}mVp`KLbCj~qzSW5=A*|CwIlc%rI1OXN09Lc+WPaL_vp-Wk-zrw3% zjK3M8^XX4I8!BXLL(d%U2@h>(XP~JO9YSZ;aRS!f`UcMFO~{Hrd=_O9A!tc^tR3A} zt1nFv`I__nS>os24+IK)_Mf9G`)0Byn@tQRRy-SuRI*lH$c+WGNtu=b#7XlEUMc}M z#;q*o=UBX6s2G^#9*DU|-Ls3dy#O){_QX_fRSU1TL?pxQu}NB3J}yh)i8pomFM(tk zk?)LQQBV|;3aw^ofH+2TNc_c*eki@W&8kTltdP$2pE!8vp<55~s~s@l?<^R)o; zU)cY8I~@x&TTuf#oTF$xfGx*V6lDK6Y97Yg3M&U`gTZ^`K_qi< zwvniATD(UE8PgsM zx5=KN>2KJf=V9okVCK*@x*l*$$HH0!Jx=#cC1vKRLdf$^(zY0)q+FPDowtFB(*sACdcy`#U~Kfb1C*f>PGjU0L~d;EiEy zZv2PDj8O&9`14WbMDD0ap-gt=Gw-KKoS{q7S|j!Kx356&Tajl!Xsh=Xhp63+!j@=d zdqcdsRc(cK{v&^AosfOT=d_=sTeL_EMjl`5Y{*i^=!AH64Nl(LVU9}KUzCo-DiM7Z z`#ada4UM?I`&*VgeuRz@T{3$;5x<9X^0y3xcEj9n`fAYG3`f3~C-+CtB0L8m8O*eb zkM{A(>jmfhAOpZ1S{BX>txbEDv5j2@n!s~QGsaCA8iwJsf8uehfq#lH6Kj0%d$=RD z@4msyqidxgs2l^!%I2S*_U#nD<+|giG}-gknrX?8Z_t^SzZwTPPqGH#2NxeH!7q)i z;Z7=Dk6Ge8hhcKzs}X?(5$O!m)=HRPw=;=iJn%HcBY!K@Pv+5#==cw3lCUctyb39_ z?Sj(@P?i~rwuYm}7gV>+1p}m4sIf@22OCQz41Ihhj9v!qJ{+dFMklVL~j9k zHQSN!=B((Jm0+YUEoTpv{BIgg*L7;)`cwL6Ow|<_uIJ%|+e1R%{AkFv^*ll=p9x|K zx8&kk$jHx(5ePj9k-W`K$~*s~Af^zkT~H9WS4Vg~mW0ojMPwk6@=)1JVX>$8!3p$TwBVJ?{__?Q_~&|)3c5zW>>{4t&`p_igl|1I{UIrF*s0$M_Nuq{AX zqEwg{=|n+qO2gI&Pt`}2OZ&kNa*={?>xFn)t>$JQ@FXf{s9nb*_@gMI@SUs5R&BU{ z1EFV%dg{rFSnun1QN7yG$>DsQj1HLWLtE#p5#Lnvj?wvvgWZ7rmFZGJ8FX3MwT$E= znI>~l=`Qbyp1ln4)J>{I@c68&fnJuqZ2U#vYX(-0=$#f#vR-!)>4@M}sY{E!DD1H{ z_K|GW+wSgV7v+@}KouJ!mj;Wp%*l}=Noxwwpuro%Wl%6m*+pfk(1UNd!u_W=_ozh)A8| z&O4s*zJ9v#Abn@n%-7H5iqE?rIwn|)pKu_mHR8-SPV9a;60C-QVu9j5`^z*JgZ}W` zp+9J|__KSLVVRqtu|x?0)+cw{xLEI6+w;35cYOO(Qox4*MNcMA09u+VE$)(wH2r)~ z1}|iU8DR5iz%Ta1?4?oHmCj?^->(;6wsjC%ozg!IJ0kPMSepme$q5U;emtoL~)63H@TIJ*VJW`K88u zUPhWCSH||AOHhfYeWLGJBM}xiip~kU=$L;nmw(VxR%N5o)9fxJ^?CN!*Xw1=2=7s7 zO)u@>++U`(gX~b20ecUa_vDvXJh#WZpajISBSWQuU5^KHg?P0 zYfX!&W0MdV^~R*lkKtT1&DRP_>)hQzASAj{+(wQ+SC!IV z)!&H=H%7;76hOni5|@~VaZU~~F-XI0)sio!=k?~dWo1(qACgFnE(w^ZJou>;@pr$g zunZi@z(2pdZ@~Jj%Oa!CMCtz8!{<-?c17wE1j?@{N<6`~8gqJAd|B_}y?x53&X3srA}&7jO1iT}NQS(f=TV|hq?mE| zyc^||F^|}P*9JIsXmZ*#Y_i?IakIGSce`7+v{B=yjckbXVy-73NmG=Z#Y7fP*c!BE zVf@k=NQzun?@OZ07@)HN9!#kCaG&0LAJdufuOznf64-m1AosIAg3N)H(5GGUa90;Lh+ zxk?~GPSVsT+^#&X#mE1CLpvrWiOIxw^={d`OCCG&vSfy89gyVyY5tE9r*3^VC*708 z1=9r9^(2mdqy|bJNHvK8vaKvfTXTm30V%oy_d|6Hx(yFJ|qLfWv{_XD1gco42#?i=!d_KJ+& zD&fX6+~ge}9C;8$qAklJj#77)2d$J}^se9qqQ1*mWM~V=T-E)ke{$%D{}~gBBUsrPAwX ze(X4T=^?Nd<=%Lrv1yetsB;wyo_9uFVD`n}A)X7>i?#mlHd>T+uEup0yWja^QQ__Q z)9Y-CKV}mzZvaaf^c?Y*Uv%lip|T*6@XzKdnyTtnOLPif_}P3pJ_C8Es56K^bQhVr z)x;-n%b0!kLp)2N&L+~?rQkN^D7gkXnmE{WFYZkC0`3&q5KP3*!~H;Srl9ISI;>}Y2P@Ujotua zGh{;wBLcI&-b~)?i`blQgnW+x(6f`$n17i*HoY(x0%nAE3^}gvyvkZb^ftkT!)Gg} z#9mDOExC&br;Dm)`MhaVx|Ta3I)klE`XWaj;n&BYC4l2=eruEZ5DfUMvocbG`UsOUnR)-@8H(cnc4x%n|g9hn6e?I*b@!Lnv zawq~OMrb_iYG*qEmO_%FpOP6o$@qFG9#w9$&9*GXGE{5m^*bI)`a0Stv&LEIY+I#J zucq$L$IMaktI^Sa37SDYrW&5#hMx2ENJ+Qa3zRdL?fzERW2`ICihn^+(%OrJrlu!M zmfavuJh}K`?j9I%V$IVu4vG37n!dRVGT4hXeb($`3Atk`cxm^zL&+D3B-&4>G!XTX zhqqmrn$*^xQHsskr4$l7num?MzsLKg|Ms2^qa5vBhH##NYTplTPY-hUwiO$r#~Tt# zje4^4usB!4`dY;Ev}g;v-O$$wYmsuje7ZsRH|lk}iT-Bqf%ibi&slFuXVe86ZkHBGb;w z)bcU6Yya#5U0%c()|R1ai+7^v-zPtzf5;*9K>w{q-2Pj%K%^)IOmCUukTSo+Hb&y| z|Iw?qKkwxzhB&;s=+o%c=xKfznjV*)r^|y=1ujwy4jZITLk*K@A z5T^riw3c$cF+8VDld+WdxLELhHmXE!-JK1z^{sHT>Z1*>u5KX|;l6{N zYgIf!(X~6el)p}s)3GlqlOVpcj)jDs+6B*yKLZxRhFC^qz8k!%Wz&lj!7ei1iIAMP z0g8V}pb^$- zk#(!$4-SlF4f%do>yu&5Pf^1iK;uXc{&$#?q&z!XxR4d5Ot~>QeLY_&c_IS6p9=PxV=Lg+<68GgZbxZsB?sRmKiK7rvp6R z0d8-Ng&aIJ2P?#|#HC^L$Q{kOEw6IT z%9z6CqD+nW*8|g6%O3eKuy7Dhbf64naC_i7a~6Qe`+MdSa_HvK+&ar9cCsF9t)-F> z%*-<`ja*EMI*?(;QviI{d>q(KVd+KjxH~8pqdXBM2fQ{3UKweEg`PMP^pc21+T3LDom<3MD01#%xS}kfZ&w@oJU5TBqf!PNb)|Ic#NR;_x=N?;QQD!}q^{eHB_(1g zOveWrz;_eWJ2JrNt-;f35c+z#ie9|e~ zC>Vhq0wR`*vKF-0T@j;@QDbyouhO+gr$XCAx$!rHl_&1YX1&s7iCLgQx#SZmy5ofF zaBQ-OX$ezK!gad0>9*_!mQAa%u*n*Wki%}RWP59Q%fdfWH+n~ZT-w0A-yjQKhyFwW zFTnr+K;Jq_!-P~eOXB4Cl6@&=;M*IV{6o|D^6OTW?m)D3Mqum0sEI6I%iBUDN_VX) zUSTfU85qC!a}{9e)}%9S%g`cbKTIeF=fvk(=y}B;re3^yAtt;DByHOQlqI&)|!&^T%E73RE5+gytgp#Ip>yA*U!Ps z_gA)#x2?E`&<0teeVM7msC*flg@6;Ak=wO#c(ZqRxkqgyv?Wh-^OM(O$g+RyNtVai zg{I6~3C1wT>rGbDdYZ$7Ao5ARtcmB-oeFnlpRHDT4`3SIp0gD|~uW|ot= z?{6GbkdO&QAx@{UJc$F~wDDf&R7g5By47;J4JR)P3b^Ya_l=3fYsG=ahT80NQl#X* zEZ6EuK@UDg2-*UA8m%+rZpY<@DTC+*ebyL}4QK7e0*6HRx0o@@0zc)$fH`7PS5J&! zPUKLLEGAV*o1Odg?ibTD$ZlUxNtm^H_|v)Y1M|>s<1idF=p{Hcrc2G-#xUU@^U$C|M7r&!nQCE z|1BL!AOkQ@Aatq#7nveH0_mRD7&dQ}_5pAe2Ca2ldv*hVx%J zPfdL$jvjCpaVqd7hv2>hjQxCRZ*g$EA&ZU$AEVL?#HaYtq*wxJ+e_r9AaQNt0EC1+ zql(>09EN1hcXEb@RTb>j?svP%cClnz5%ZrDZakjPqJueQk#cw?T$K!azMYZJxk(_h znR_YxLBgquk5t$f`c`_$ptQj*+85j)ejXgueNlANG$?dI|IQe?}Dxyip}N+08hBy>d!)PB(nvO{>~B*P-% zVg>oyyjRMU8N3|92>hx^f*6jQIso%UgpgUHbc~O2`P+<^J7Dt?UC09%S1@34$UxSM ziB2H=_Pwt1#X8(xrE|}9Fxwpn+1*M8^MwW)x9pt_?Q}UA-#I$mek5xi`WL~jfOJEz zA5vf-ah>w>=IQrf&%KPozAP1u!>e^trf_fDEFfh^4Qn__qGxf-7dnmRtvGAv=hKyS zOqJvf2=lep{_%L-H1KbgprV&Pa)>z%!X>S1_=oV`7ZKq{il}qmVv+LWp^a2SoS0Ok zr`guCg_2TR(*iLgxzf04JWO4eu;LX$BOPrwq-;m1wLe1eg#?~E+(OhEalD{?KpurS z$QGaq6spJB1~O3P0FdLxEBdlg6M%bu@Tf-vDYurbB)~@P35Cxqu_-+I1B?Jh+kr8S zfTk4uH|sy%!k=;*1Sa=6Eo@u$*W7v$JTH~mW@BCmL<{;CiRoKx+}cz^wd2l4`a9gY z9@l}nAmDPG@+pK2uWU;bsJM6ZwGhZG7vne1`XX?puEWW3M;CHHzkS^?Vo7`vsz|{V zFq-%XS;vQ{e8rpgqyzeq*XS3f>hk;1J=UVkv-)3ewDGse_NL3Va09~~pd>Tj<}OZ2 zYiz~~VBm(nQDZSMma-kG`lU=pOs3BiKg$DP5a}F@@O`!idN*XT}jYU5WDr2R|ccHz~Ra-x4g) zzSb%TWF*Twc6O%B5G*Gb>OYNef&{gdTD>Q0q+v@tz)zBv^AA2A?Ca?qkmxc=*Vg=XeP~aA%iI(?cUjs{pW&iB|=4X zitA%!+24tc33FJ5G8q-fJGjp(=huFE{$k_gj)c20cz=^cuHgXYeY^cP9 z;Psa_Y7SO^bvPF(bJa2}=x;0i7J{WgacbVN&m<*qZLH(iagUziZx-QrJ>N!$3=2^e zM%ODQGs6Cx7X+xE(J)EvTeZcv!W4Z-E)U%{DpQ9pk_7*X`YtRHO20H%8j(9?5tt1! zHyP)cp@xeSY}pY-vBT>hoqMTvCAImWkX^ppDlT9E%>H9QGONLbLfCS&g(}e9QL^DB z$V6P_YK!{qgVnnMryohSwQyF-1^!=vp`GqBD!LjwS9g-?}`-YyT(M0GgTmybR z58qJTPkn)Ruk$4(e%L6SyHG3F-bAWY!K|(75;Th7qWr=(0>M=JlvjlQ6)Hu0bHTe= ziADf)%Ad`ySno`RV7^)lj6piw`|1&yIw)^}(qEv!m>JmBTlV%*cu2EuQ>chr$}8`$ zOMflE^vXnA-iZBP!Jh|*MiB(`nv%M2E36IAeG^cv#EjpXj*kx0%BIIj2HO%J*U>{W zBd$|CL}8{9_so?UqujMz_#a5$x{RFuynkIW|3XFbAg_-j)5QkrTnwO{1%|T{1o9Ei{saQ$$HWHMDNC%!!fIb^iP# zxzH$m*YiKxWQ)$f1Kl1u;L4W`y?j08D(EXxpC>J>xm7v$VgW?Uy$_P?NfVO!6~?_9 z?+#6K5X3JDB+}PXEu=FXw78j7ffA|zMpjv>pEqEbp6x!oZ_)rU5S(@cvH6*QonHFB z)|1KmB?n9&+TDJpW_ZWYf&IZ|(Vr)5Okh2W$xeJzUT~B4pYNiGh&1S)OP6$9u!3O_ zy;i0y4YVP2>~K7EJQdB`NTs1Z7XQV&J2G74Sy%{2SsLPX*F*^N6hQMo%&E1r3k z<30JJp<0DFZs-@nJig*m+ITTmIfD3U6ysGYr}eRI2OVu*b>WCr_8odt(WYzWBkt|a zbw`2d#=Ibc*h~&TJoZ5-|B{=F%6_63kTsfeUCTjBX7sLgM=9*QP&4ygGlEI=dW1!j zki!D|AU>(ExkpfyI)tPRWc%KZ`1PQ*Ttc04g9XRXh1PjbO}mz13jGp9y>k%u>>J!) zySY>G+FIYD4wBKb6a0cTK!yul!a&wwSV#Uyo`r?1FWXV#$7?d71j?J zJSgwqi*Lx4%?<~?ppttAjR-n~4lnA@aMMyZMh6wL-A^mT#G%=1?1|w)Y9t_ z)X{OpWHoOL4lhJ92VBDWVPS77UMlh7A(qypJ5R8+Ok*kXfpr~jADNIe;7S=kR8nx^ z%_%yCX|k%&)<&A?bx1ZIx$n}=`Stqjh(Uta!%msrI%~m^xI!4niF2XD-g#efz&xqp z8LO6d4jpHt%Lwq#VfgE|<5A35W_(iD>O30#YBA|;S-k&!*lBVfp=i!z8pR1+h;FNF zjW;U!HEF)*$xD*Zpx887BvC^f;gZ9OK<-pz`Y+(=zp^X{tEk8B=R}afR7MJ%l zR?_Iu#y`e*w8n+>LO86^`%sbBwDzl+bY6Qhd*8#B8h#jh&99}z(UUMs(quOTqV|aw z)6i0fnE)$qudH$+PXE|ct=A8uf3Yc;_Vk~N$@u($&{iZ0z980=lf^n=qvW4-J_o&8 zOa9|5IL87|^1;k2o@7PZ8zgO9_xUdadhW?|r03iZvkEAW%G_BDXDA;DN738Ys*_GM zallDsb&)0SPRtz-B!Pn8l?fXRG6A~>mBZsEr%yt@*?P9oU=lwjAF9A6f9^Er0Om=C z(q0J`JZIhd&ILF;k~SP6*b<4NF!TG!;Llvf)u;j=m3c@7DE0TVP3hbFTE4T%!RC_Dm~*XZdkhBF$uPHB zcm7+RKVt8Tj!fP%1ZJpQ{yd|qg!<`pU+KJZ%tD;!L2y~^@dyUdWADD&{YGnsrIR3# zU{qkw@5*g)O30+6=7;fvzaG^spJ>T=p67RpKxnjhCL)}Aq#u6u5N>xMZ=>0;f@yVwH|5k(9ElyM+QC-sQ@W~ z%j3AyCDzt-kCw?q?jdMQ!OPSns|8WfOljcj1MFm34KABov$|KQYrSs+dDeA#25Ee$ zL6)86)dpF_o*s3h-}~D6l9eM}Yos%0z#Nfj^dM>z`3lG_dbj3oh%wncHWog+?WyHO zT&ErsF-|v+Vs}D1EjJ>;@5nNnq!H+d677rD2wkET8 zT!Et1`Y4hXTU%jgUKGbqvqXVrS2%aXJDebhz0>?|S-cJ_aXWc^_ZrluMZS{jxJ%3q zo~{(oH%*~s@kN9dX0?QH;q^#M`$DnnrFUW^)LqojEJue+GcVIa;_^YBlOysFdXARq zvq2)@%^LsBNrIv9UiLn-YS#`K<;^I1+s9$nam$?WN@YDr)LJW)XN}I)ASh@m$?s5TJ@k(EB*?0*y+!sW3c50s$NXwA9=v_9EC&J zkN^Bew^uC?aZ=jNV9(vzegEg5$X(WpbCMrf7m=LH2;YgYuMg{uZ!z9CpWvv} zZC>}TEL!Pk4+sHX;wBJ{rRbG3eA(K_380u_IHT~dSH_PrGW-4PFF`ESj53O@Y^mk9 zSf00Z1i@7l28_S`SoTVKR@Ltff@sg05v%rLqLn&)_EhfGA#6}N%s%m5c?j&(lu@hGl3MUr*g z9ruZYPZ;P9Z3XeOS{jZWgH0jUvg?J6SpFkFYl-u33Ds^_;8esyr!Q?=OVh@6V`)xn zm}$kq^3_nb!VP3^1wuujNmBh=v*^yV{BBSy=&H5WgaOPP9Gnk@*7a(`jOn8`VqK zngZ3os^t^^5yoOBFQQUP;$PCX=ff+unDbn?xNY5&q^M!rX((%JBt4n)+hq4RmZBJ` zi;snzABxV5a1N7$s8{glB)8rx;o-YRQTP2!t5};&^yKAigDVQuYcl_aV-LMl1LMuhk5J{6nGh~y}uT#!+s!By}O^pf)MVN-vg z>!BoSer4wnf|Q9m-!Am8bp1|An8?D9mzpv!~zcv~d8KHW7uh(0eE5xdfhlsHn+q)g}H+ zOsYv-!N#u>bdT~i_^E>>IickCB%{q@UM**cZ(0v{;A?TZBg#RX{D3~GXM`cSpfn>% zU?=)O63TMb8=W_*tz2lIPL|hc&{=ESFue8jZuOGHW;^Vqgskf;Lz&EG`l(^hJZ(aS z(dFeoPN8z_kxkZCyBSh1nx{}Y6OK2)tJR&ZoK~^f3pJv}*0_uFLcxswa_(tNA~N!d zSrUv~C=>;rICSlRt*^_+I@UmA23fEax7N zr#X^THg=uOAlsFw70&9t#mo4lJLL!@r@RB(vVL88+L@|el3v;H;t4bG% zRe!$=2&DHxlhlRvBSNj^ne!wi)i+cWdCoT_y*&&Mdo3-2bGKCdH$2Z#0BMEDIxSW& zMsoN39q%hPBqghQ!BYIYw6Z$S#&?HIuesxQ_)oG-jxgifPM(MymyMy&u|d{5jk>=o zhn$JL_uezyUK){ACt0-o6iO3vQS*2V#tiia?6W4B(~8IT_KWsb;Sp3hbVm^8wvwAP zMRM^}Z7ugNvd7zu(xt;W+_md^Hk8wf_c!%Z3O%zC1FaXpqJ%?-B*2*!W%{N z5D$1V^KDU*ddXfd)1#-zm@DdUnRyvPR$X1E+c{g+y7%@T8Ey)1KpV9zw79uY9!rQ`Sv%B`y?^y}6=_MQ z;VMb?w{l?)SOq_+pv2XgUc+gD23v2ks^_4N#2i$Vv*MZ z%BhC1v8RS|dfSgwmsHIrs{Z2tw@k@P)zV@6Q5H(pU5n5-tkbIJc&Zk{3SQOrY0lF@ zYtGZxQ_4G=Z^^yY6S4cp{! zmPFaJGPWPx#aVJ9k!d0?4#19%Qk8c~Qm_N3U3wY7Fw9XWt;Wk`)#dKTj=DB0vA{TA zEm3#T9h>25+tVqG;W8#6q`LzXFj_tSfcj7k6J-M3j3PZ|S6%9eV>;ygt-rx;*T8Kh z@3p)Rtr6qz*I(q^-r=u+G154=#@^$nLe5=%^58+v={Ssa`^hm5knr<17oJcOUVM*v z{H<1-Y=*h(qpgcva$9C+uhRP!v;##R3R_XO)oNt7NToh2Bg;9}_=V-Eh)UEcPi#jK zrFj*TPg0D;!YE&EjN+W3v78~s(KTXqituUKs(zamDc%5pO3r(;;i1{ion4)jwUOZK za}0)01$=0NP|6(q};n2TtDhTpoecT3>+pI0M>VM2tIgI?Zx zLFdoRFFiaWMnq)Y&z1BX4mDKsl3!!lT3XKNbo(+3AK=UCj+Qz=aoJDtRp`f+FBZ!C zon5ZQJ5x#bxLyS%d;QUs*2|gXgYK5DKcYAqPD@i5`}@HyK%3AUy4kSp-&r9MWi4C% zuhoUwPU^)n1Ln%`Pp2&L^Pvry@Iz|len(u zZkoS;-n%o-*ofa_N^l(@$UT)U2j08u+Y3~PT~|nM2YHQL559c_T6vKrLi;wbO7oB0 zs_&-~;j=2`YSvP(XA%3=$=YDK4}Y%+gP z|Nkz4=)f+>QD+% zUUj*24`zW-FG8pbqG3 z9V{+*;s3(FgVUdwnrBny(cnRvLm57RwNlMe1|qh>0~=47;c*oV)y#va*d^uchK%mz!d4cs_$3{}WT@+gS^hNKUJls5?uI?Rta0pIa=W0; z1FJk0mkwK!B?47$u|A#N_*)-lgGhbw{4@SoS|_T|d`Dz-8S~LuNUqlWTblR}ovt#I z44r6`q)iV^txiHpeZ(iU9R)c*VPi8LUy7|eadxMaA=5Rhm2Q`#m90mmPq(j15gQu? zth66F-kB&9-u-eKt}DXV7xj_@GydN1)T0fi&kZG6#b+PgSiK#9C4`%rjot#|9&jps zWZzRaF1a>XlZvO~b3uAGMz@N*6q#kS=9PN_S8nBJi^cZ+T{+Sp*m#&ap4An61g}T} z^Q(e%52V_!&4UBN-z^*3gCs?Q!qm}Z)~!@}!dOefkBB)|G$U;N*%)A5f}Rd~HW0pMC4 zte|ZV#CjQnnU>!hXBRyW8syEwz|2gN2KJz-gVipuc&c8_v_j2ql0An-ND4oLKCS8@ z(~9<9HahZ0uB$8Kpmd0_>@tg(BnuIo89U0X;%aw2^3LpWJxWQcYU4v)yBtFnxuZ-* z&j?ZEXfXA7WBk@waP`WVgt1I)%&kp$peNzAd58zWM>$c|tNfkOcg5p3Bl{-gXY7M8n$>+r~XC zZZ1i-s`6K6DC3XarQruIwvW_!s8xC<2lUDF1qJR7)RV%?%r(Mhlzpn9cV2f_6~0U~ zQ>iMV{eBH9De8u0%{oj-XS0V(YDH{)g4*1d$B0|QnuYjNmfBD}(bj6!@!~T_L2*r-lDojY>JV($& z!19$bFS(lHln8yB>DaFbd7+;mPiKT6!4W?@^f~r|@#VXU6+5qWKi9>0+Z8t*G9q$D z3tJfpw;zr=umI)?pXEX}{hTuX!%}y6nUDPjYU6{zx-xCT)JQPX@^YV9E+>9plU{X* zB{_cu*rxB#vnnats#;_BI@5e4vs2thzJ5WJ4#PZ2B#ZFt@}KcML#igmNAuZk!g3%$ zKTru<`0a6x!BboiPbW``+IBQ^1q&Txevf2zpJUqVY+^1CnY;x70Ym9E`=a?$jSEsg zVK9QXzXB%@^>Pmr$fN>>p3;RbQ+HM^L1F(p|6lma3;NH{eNcW%dlB-gYHM}n(cxmm zNr=24V$Sb{e>%Va4HJ!pBc^3Yqio7vA7=gQP;YnERP~hHk{9$2q=ru){AX(2F=b!u zB{VeF^2x$kv9-nZ*ssx2s^YBwHcVMQK3c4S*XJUZQ$<9%ga45kV5qjb$50#2a&NAr zsw_(u>^;{8fk@$o#m%*VJkvxh)d(i!UC~Ygppwlm{}pkIT&CtKpekPC4vt4k6+}Wy z{QC^FmHW~?ZbA7sjp`!wf*#<8Zk3s~TNTHAk`%0}Sx6xV>7o+|)o5;7Y zsfs?W>+D9WkF8!{Ywg+APD^Hafg|!HdTjNN=avt}3x2BdTJ`fk=GMW@YYek$ zvGcx3f7fJ-!vkus^%}0XlAwELQw8Z5N22K?2xTaWb9`JKtJx5a%$l;hw)D0?iC#lF zstj!gC(j*r2D3SiK*Rq%A#fA?>Ap@#nXG3FVE8UxKd#RC+O0PxcIV$ua5kG$k@K~* zS9sWUu-uy&n+KM}?=tr4>ssmEeV-4~J3lKp!D%%qvmbdBNXr?eD&9-@>sxcTSP~(` z&acIz4A^!m)A9X_`hxdB4KE>z>4V-$2{?BxCg)nd*YB^B+=~Zm$f(BV*Jy9$E+N*+ zCfpIWk-VDRxgQBa_FKp>EswQ&(fA{994j%&t@*40!CQ`*3YjPOOg~`U_!ZptC8{Jt zwNqbPc^UKRd9+pEChw?HOrcF(mbXkL(Ww$YT#P$;BlVHI*+&@xesVrPOy<15+hTn$ zPp_+2$GV2KtL1C z(zUhm=l^ZzJ@)%*3KGvl@+z#9_oW(e?kvD#7$Q_tF!kctRgu$DdTI-;8B-Dw7V< zo)5OQaAf|ZKmRu8>W^ed!f-bJBOmiUG(Q;&dTXT9q6nADvb`N0S1DxgytcLv{+%V{ zWA~I{{diUQWNg3*4AIh?L>y$qF0OcnaGjZIu8xM+OS6X&C!yu8ml3Y-PoK-bufC$D z!{k=&?pfYX3z0LvV}*;M##7tru6!o3LW>zI?1;zc-N;8_DB__>l8x?*`X;z{6jSYt-+;UoJ zV!j{03IX4Ktv@+CjmO-*DtR$&4L1aqyg<%he&zntRig5%rEP2Ybg4|Y+L8-%hEXyE zR)%4i7L%4P)~&M^rG-l*dh?0pZ?u2S&^R0*ktZHcSzksaYe3jE$W|d+3Zv zQ(>y-y&#rj2SYu_rw>ZLKNo~m-)^|=Pps^Rq{hLQ%%!RJe4zw72q6D3Gh}OvO!uEb z<{st?-htY0J%$;*3_kLL1}i%sq3#v-!XZXuhn5<$=dl=1wrg3jSr znd_tXPd&+H#vJ5~54_brDTI8GqG-Ftg~+%CQd^~lGb{3hS(0w(f`hN3oOtezRpitl zjps-K2HvGZ4x4OQ?+koY0Im`B^cZ>8u)nR=xEYn); zW!(+U0_$ogw$~=zlO@!$cKip1_|b`xMZPotmvU$eFxk7ByeW&uM=xH{7KY{q8<5%* zgse)zm$p2Fx2Vie?P{AuZwsz8L_a+b95H=R&^~$_*EXmqC8Du3u+r4!t})mkyUKLa z74jQ94Dv}v{>K&ZSkAiytm=rcfVbad&Q*&^;W@;7$ZTb7V!rS z#KR=r|IJDa_PUS3@hJJglTAwvrjw!qWWt*c0VNq9b{_J7ufM;cv~e_?O{ryU?z?b{ zeyh_eR4iwsXWf98DT@7Npg&yQlm$CIR<1e=aO$CP-t8@Pb}fgMpQU-od4|*;2UO%h z9OiKC;PI@2VCL#E8S2Agdi1vQ7Mcg(7Z-qoVS!kxRG9ZJK|9wf|q z43e;H49(CJ+gmwphgnyJyT4qZ(=bY8$++vb>kc*bM55USqC?J*e=3TebTYGd{eq@1 z3v-q@W@pf1s<7q{!zO$^xA!|E?kjcD^6|t7ln8W~-5Uy5u=Y6d-SC>!DO*(u z8Z^g`Y2qh!*@Q}JS!KlT6kNRi8iwN@~W_nb|tXL8=IxdRl4rdbgD@Ap#wj7QzT#L^*xsK;O6I! zwIM0s*NP4exR-ni>NM{ts`yoy;GYr;3l9Xm`1X&S#PZ&w2YlADOtimOTIJ?y%%&M* zQFV6j)D9C^SW<9rK~WnnsmBjIFZMiOv7B1+7Y)vBH0B5%@TxQ-0v+PlzuE%!{f0H1C%{iA%mhgty(xYO z_${6*viPPg6#7n)yW9Rc+qFg*PT9KK#Z3%@r=F!>aG%AOyX;>s6Yvo?5(gIxEz=wT|1Q;j+bu)^E?CF^S51jOFDs4{e&^{H)+-Cg~;?n=$T&3X|>4I zA_F}+xh7#h3AG2|M{+^a`?ih^OHUsurbI9Zqj;jAX{GJYu}=Bq2-C0|RNCbSZvXx? zm&qADx+c}NLEAq*-RAYaH6ao^P1c`-WK&a=38pH?0tE;1@i%3O$zDR6r24MP+;SW|_KC7>}tv|DfR4j2Quon2U_v zE;`w3r5?fyx2o}IxcCN)EUX1KZ#Qy3=$W=?NtTEnH_}fZ|59XFYi-mr*(KSV%Oz=x zOC03ot(ZU&p(J+UdvH@tp})%-;}lv>1XiHX`xUw6`18vNVt66&ByQqXFL!(^t;j7p zTP60YGLN<dRWK4xj zB8;fm5V&0V|Hsx@21Ob6ZJ+K235f-1=>};O0ZBm#=@O9!VS%MXx)DT5lo09erMm=_ zW|u~CsbztM-FLn3dFGjU=ACoq+K<=lm-G6c=W!grLk2e@(zBXAD27gYBYaRrk3b3^ zSNOd@?q>-nJ%WaCi@j;{7TsLw5=^O%2*3fu=Xk9D_8vaO?6Dmp=rh71*20Dn*&74l z2d@06fN}7j6A{WodSx3zEbMP}u(ff5Svw+GTV|aIIbG?%Z4ag1pAjyRW*_stsJh?% z&bwP~axqc*%jl(HiYLi_*o1UtDP_CSF?anshP`z5sUt*W*dAJYIO&=MxgJ>8Aq(FsMxO< z=zsnl`^lU6O-=ffoWUdYhXDpK_qD%o2Jq4&W3Y^`N7Ck_QdLo$bK@LDz*9S-6tsi4 zLaWEv7>VFYTfB*fgVLjq_{BFo^P=P0;s#D!zcI2*>PlFuE!>nwZ3u|jg5|d#%F5;ZzB)6byvxlU z^|}E-e)x+!C(N+MS(Pd{;Km~+vFt6wb>&X5eu2e_GV2D-3qBl&SszTeSim?oCP;Mk zFJMZXF&i0)gKtLmdplebK$NGH@nkshUE{}gu&=~Y@WrCJ`7!_M9;!5|%gf$l^e{bU zx0G^l+0nzsV+&k5TDlO3g;Zpb(prudHN?dDe!uKs!OQFqEA{1)_vNA$cde&Si+++` zB&D;G80?a<@F(4`O#{&3B+Ba`qP@?1au?XV8*-n)5D^4jelUe6$99qHh*)dr=Oj=d zihiUOl;V)WrHPz(aI=uRNUJnGea>eP?Coj4 zel=D3Y;bOble93UX=~%I1KFtGmDr_=oPV3bE4aX*-zJ~*P)<08*?AJ$ZX@C)=77|y zEXv&C(=n)(_1SoJr59!)vR}&^6*RLQT%f}&$R2zS(lowEEz_N zj)f0qIBdU@02=@-uFZbzB47fWPQ94GS0{^`YSvjCzopJECc2bzA`6~2p%Ie~rrUGH z&D(N*g5xoBUN=3B^&-cuz^e@BKNA`k9LnCzX(+6c5jI z;*mnufQ7nKfdzevbQE(1O0^kBz&KTqC=EsV@lOB`eFP&-gJsT%59`7e_h|MwNDKky7RGrLEi5ZDG< z*xIBLvZDy>n~jz06b*J>@<2+Mr&?v1p$_6h*{n`2Qqj}9;@*Yotb82r2yZkKcxuRi zW7-!|mNBGHz3;Z7&lJrpr@AZj3tBI7Rj)$1tGiP#ehDvdz?!tr5sS%j`)!V)GM1)+ zN_%d&lYg4d&5kLzGgs(LUtgK)9=xx4o5G&6plf7SsE8+{J62!1RM~8l<=1QNc)Kde zKdva+u^(LhItx+i+gRwkcvw&%z25Z)6HDy({^1yWaJ+op<%hA%GPOgM%*~E}*Ja8Z zK&j8@oQ)^^MW>HHS2J;pl#>#!&pReMNrAcOQlhTH@G&w-2xVLw;A{4tf`!2Tc(_@r zsQt4$l&innzF~U($EY2f?XtXdf8VnX1(NFPBw%vr^&lwZs{d z&Dn#_X2m>YO(mtnT$7${ ziwp1&5r16yFIh$>k=-ZG@m(hZpkq!3f8HmBUuuL;s{fC&?Jrd&z6<=!?0zH&Fr@_WDnznhWd*Ivsrm#!-89PP;4T9)UW z&eWH-Ixi-&PHbq@Fq3*sY(YsZx_k$(>Bn{DBMUipu^u1#d4+G@w^@KK9I0m-K3?JM zsfAVW5@q`v5BQ=o&_XbTw)yfSx|VppzW{QTW%4y$myiAEc(dTyULZ5H083Bl{ME}( zg6xN&H}4yJT&KvV{Kk&T*;+P%+xCWY6AiVdAI5sM^^duq-mfngY{xBT^14YBj;>*) zIk|}ceTOt1AX5t7ei-#by$ht4H`efO!VD{kIKgiJVji?zrdxR^l-~G~swOOv)c;s; zYqW`=yB$-xN*)LYzwrjhTpW~69i*`EGTbm{7^aE2F^-0}LKPL7dBiy+;2%=j*<+eA zSURj>%a3T86V>=ycK0Q8pZY|)n$6Cw3KO~{&j!>oSYs+E;tR}+Wp1IIT| zg$&L&-umMGdv5Cm&b0OpA>r;8a7Xi}9_RZixX%!pJsZUNxk2ZRN9=U$sZKmX4eQgm z#B)A3Uh2yR-4+ zP^aTC66reCQS&nR&uh6Wr4tukv;O5Yb@>2jrOT+NsejDAGX(LfEYU~NMniw$wpymr z1#t_ht#j#$74tOHVE4S1sjJG=Q0=Lx!Sj%hx#OYFMlOHKy)}ou(NV_W70Nc||B>e*a{Fyt(_>eiH5HIo4E#kaBB4+SqlB-u@D9?zNtN z$4gNdzxngQ!5@by%gbJ(l@AJ?!n&l63`LPqUQbwI--AP(4|&SPeM#`GoU0&*LgO7Rlq3epM{sd3!xT}S}<79cI1bZcC$2uV}I)q-ne8C=diqck}z1i($^6B3zt1Bh0Ao7x< zg5A-j>D~R4?}Y<-iWyxax^Zo9YI}gSFH9w6#oQ+Hru()(G`%;8l6=DC2pnMRSAhDx zv-j7z%pDcXLcx)LUuXd0xQzO1>Yfaff3y@w z^iY@cc(4<3QO{_C94d$2>vSX-dN@*C!O|v1T=0B2Vn&yG1rBYWRVN?wGl|;0P31K> zGkBydT-!wjJ?FZpe>bWRz9sP-i6o^I`xBmvXCvrIN-sFFaNt9ldzU&^EoJa$uIb#W z&D)XZ<#>eE!Qm6c4A(gYSsDWJsXOTA+yV&yBtj)^bysS!UNJS|<_ORHokX_gG?9iI z@N*nPVYu+m>~;yOtmhV=n`-mfdn;Lk9ZUv7$WpF|tqx=uVCr?*>Fo!`Y4;&JH0PtO~j_NW>K zS{-u*Fv_>s9Z=5q#ROT)EJ5PW%iVs`FbdLyeW?8@k0E5Bs@)hUcNU;X8x0Q^_@4u3 zT{jsx!^-cvwe14b>Ovv>N4I$A>xEiBYPeEih8cD0F6Mz=uVuz;K(;jF6(l~FA307Zl~BM;o`zKtFA2e_uk@k5&D>~qZXt^_+A zeZ!0X+!q|j(dEmnuzarVv?tlp?^iHVoFjL59VbZ1sC=tBnD~-V=C-3naB;?62-zTV zAkqvktMtQE7#;@ni=nMwpc2AOsX}Y8bX@@E}(z@mt|YRmFi23+BC+r z8F`tXI`1AT7gMKt$@zWKMONG!^+Qr>_b2#7;8^y~>rKNwL%6LMib*8?`N3mB3U#(> z=pX%Dt})e-ohEs!BRX$I-^*lz|S_w`utu-hd2&9WJ(%w;$D>VGwnE4-7ujNb8LhT`)|k{|$q;!7@20 zf;E0SVrt9llqx3|ZVD;`M!sv-$*PeHeF-u#79C02-vGQI0(|^7@X?eZinTrTL?Y*J z8C_Gz!l9fbpoGYc_2G+#0LwVvSVDorFeL`#Q%uot3K%Sq9m<~Bbcr1;RCKehv?Cr* z5h=7bg4vjo+>WmX_j->|M~Mr1>u%%d(Zu}<1UjeEg6QFj8$$e7udk>FJ_E6u`{j%)ITNEI2LQ;BQdt$`-e z2*Q5l(K*Dqku>;cBESOA9S8XP%c(X33Rq-_5{BR`aE7ja^x{PLA7|?^G6-i<9GbZpcp-K}EZs$9>(S9go?Yl76bnu{J z?2{aEg4VEdr)p2Iw!iqKt>fz9Rfp zR+l)xYOLJ1>TO_2EVECU^>uS!Y$B%tq7N&=Fn`o&^kCkbv6pRTl z$9u0GUr_)iut{rCqRokMcCS714kQ2LnEJ0(_P@{SE&^)bK8PKIb2XKWY?3Y4Z4J?xIEYeIE}xi|5hp3g4cHS zoa)5d8je&22Xn+`Eai|PcQlkWVffTrOyj`V7G2ePk45-%1s_T+O7$PS12?;ThYM-E zE_yc{>uLL%6>v;rDhw7N5nD8+Gs_-Jk++&}g}SbZ+(rk02{*Xu=7y0n#XzNCIidnk zJ)Fl-hi&B2I`r=Q2lf#)uDShMyQHm49Zk!`^Hj?QUPum4%9pgNGw9mQi`xnyn(siA zC*%1PAs>Au`K)zU0P*K1fyT}2!JrlGeK(B}>x!ifc zTTesVW+l}7toWvv7SzCi7259zi}^-5l2ea$2g2MK&R#_V97%y=pXlo5VL^&+#N3H{ zS!g|D)4x{qj*rEc8$H}AS(Qgg-cU}U)vknX1XEWiA@?%-27D?86YRY8oMn?32DZy+ z@gVd?KkgR4dDlY{cI0>T2w9s)zTq%j9y+pR|E%&sAIsX*-u)W#D=K)4Yu$CNO<9~= z>(FCwtd(Lr_dYdSA_%%5>B((yjjceA!e@9bxCR$XCiFNx9~W_&B6W%)J!b_Iwv?ht zou_}DSG7RKpj$3fH-;Q{_sNM;7}^r5@NSzofQ3n4R($m#8)40#FZ6gzu-7zJPeabH z%(^_4q{xGa*AG2^UOy}DIt$AXM=_>uD%p-Vz1ebSwAH_SoUA$jV-|~c7*FEIxQ*}H z@s|%l9J|yfl3wvHWL&5$A@g(CX1tFTVpE$+;JNd5^-O zKVMgPv(e#Z2Ee)yrT=csD809OzQxrNW;ulAnNYsK4g0Xx?i*0#ODS{P#r;ORC*uL~ zU$0dH~fFxD~$yiIfEZ!itR{>DHqP3mN3 z;K7^oyZOH&_&*{1-&*A(uC=pdaaf6{El4;0EalY@`2E0Syqu<$m8)m+e1`o;ck5Bh zY?n`=p2X0U_TE+p8kU5Xs#<}+=P3@^D+PGNu-<&C?F*8i zbrpP8*m}Xdk7-&MEtZPd|HQrmT|>QS{^zs$S0(CKt6ts+h*eVm=%Bq3N z!zGP(KeQiI0e<4Ss}g^f*m?rc2}Sp!9}8}0mrWAN6k88k1P71nH;9wB99ZP#CWLlu z=+DJ&a7wre85$&Z2yMB$WyhBDt(GtE*>^*~^b_%sK``Cy>;h8kyZk)&Cr{BB+^!eZU*1PhseOIO zE%d3_0lapkFo%u&sOycLVhyV%`i#1Hy@V3|NU`InW53q`Y*$Vx4_#7ydC0p|(L)Oi z5mId1HZQUflLE;WIjGpW%>LLfnhj{XR=2>>k$k};LT_yUgqgnoXC z({qQp7+1Bq8oFHkR#U98H5eJ;NvI6H-dh4;IaVO|lyZkC_ep&FNXKstZf{R8ycA=q zEdsPbl;RDr1|{OWw=r47exC3FQ8}!3@QJMJ8|MK)MhC~AuM-D%K&|nXc_#juFB`0q zEj<2LmGinbmREen?U)HTU5;fHD^jbIKGLXjxt~%wFW^CxqEg%pzj!y}$NFj~vFUiL zADewGUFTy0Im~@$*KFu8Lh}wkH=dJEqYaV;wAN?+0F-F9C7Bv1oh^hIlk2obiTmgv zR(j2b*`0-Fl2sRRHOOV0 zB^y(3amx~_Lv^Ws<9nuNZrgICQ7wc;x{lOp;TVL)(8q!jIs)!hs8y`0>)5fSxWsC4 zAFlQ_yz#42PaOBdiqXj~49t3R@N@x71Mjv446n*->0MFWUzq(kI}I6+C}+bWtE&Qs z%{UlPiYdR-o{2v{NBkiIZH;dST+V1*;GMVJnGIE3q;qT6Tf`@hK5NUQ;M1#JB^aCj zs1|W`%gk7j@Qmr}yTy?d*O62~K8+vDO9#V*Z2s&nmZRM*4jU?q;~9^zxnqqo$##FP5wS>&N@=Gn^m$q^0ZqPnsVzt#ULrgJAD@ z&W(?#B1zEQHw8dSRL9$s__Qa8X%Nq39IUMc!6GGMp~5)~A^isOFjab#G{E;3n?Jw9 zpQloplgr$+xVqrNzx9LF=kC4a0)gH1*4V3aMZ}_Z#P#OK9Oh%pS8tlWoVSnW^1A_5 zD&o6pEmRR%cO;Dg{_Nm5EV|g@-|q5?e8fj*znE_$M@)jAjMVPDW1W{j%gr7^B(hFB5PM~{a%DDL1bsLMBsg6DN;B-TiGT0U(1h5Q?jID)t%FV(py-+opB^r$e~5rfQ_`MPpW|nzzo_KLNg= z2X#_un@rC{2LmY5+1Qf)#?U04G728G`NX_lexz8xe)Hk)0N9m0Wp#Im&6!f9&}y~B zV!h!52a%^6O#yCcpFxo(X0S;WiN;rKxwTj^x(wjSq+f?ZYMZldXz?E{2%y^@bZ1zi zeK?e#pun65O`>#k6j6lO*K-WE`bA*^!CnWWJW+Ho>X1tbCRS3|xGlD+~4{NlR`^N{o_mZdD@J*jp8>xP`|I||&|o1WV| z*jfW1MeNbDp2a-G0sC4?lup_WGongMKy5=cVi1F1)lRSzWWqgoKl!b4=E0C7Fw0r; zUm_>>F*Y)Nv#2SL8#!gB$SJmmXeHmfmm+s1UUxcGBjXMpa@wE|kbV0#j_~P#MEk&8 zD+PMQyb(MkBuGSh4GV^WmarZ`4X!y5>#5r@KL(H2L8nUo4_R`MNwh4isIexiwG24k z^gt}FMwu{~ftQjA=XJ-P5gL3((=)wM%;)&mWh=3_8Z0xENV>KCrYzTc6NgBONc4SX z__6%6$Ev?%v6Fd-A0hl+IJ}f>4~vn7@yMHDQFJr0=n~=rS+Sy7uu1xID!BtVDxIVM zQert^J=$0!d}jJ98}D~&>Davf!Ny>c0?umi!4DWo-SVrtubCT`Oy`+8Sa5-3dRjI{By&tL6$mLaYI;jp_Lk)LmFT;L{Ff^l#2j&6IJ}@Ab2W z!qlr>Yuz(-o=4?M3C|JOeJg>;HA;}DEZSi%{r_k3_}|~vCp3&o#hkR(n$pjR48M#6 zvyxWDIhWdMMIa)2`23s&5;WvR$(L{8{GEfSSg|{+0S7i3G>6^5)2o%@8HtcBGpG3| z{J0V!J6#%vH=Q8kZx(RQ8KH%N^|z16Jf7}@IkEStn{Y=K(wx#P)eR`&ZVJ;+wJB^L~bHnfikCiBZoTu*7cF zjlb;fZ$c0x{uc|(nuZzpw-%pzvP3D}tRN-Idm?24?ZwlDP`?XgYzP{{%^3iHxRAOH zduOu8wDBI`ml-8U|1=+T?^a~+HN@zVD?wfa;cBl>_jI&E-m{QG#n2})mqfWYEY*?U zaPmguv8a=A-}XY6Y^5L#oN-J+}xEh3raR+Q7+2RvT1B8^x~=LDw#8K_0(c%wez z9`~%f*kC8MzO)IF**v?!vpEfM`sS=Xu*@Te5fJsQ4QjijFaTg0LL3>bvk3Zt$$`E1 zK5;qJ)>8ZKnGlMR*;$$$Hntz!l-WLoBQGmg4Kjs0N!vuZ=5fT!%U{@F1S&J}SxA65 zcwoG(>mn#9R>FDpH-_l?E36DvR95nD;GZ4rD|?PswB%yW31cx57Nl zTiICfLWcZLbAw2|5{&HHYF5RSI=903NG^wrHWr#)m(s_d&0`uL)^fHCU|$GI@h;oE z>~wkvgOgLv`}8-S)tI2y8ed}5>5^oO_dJgcbUIGDlyr_aHsWw0e2cJLd4o-7ytF#U z8Kdj>Mpn~&Knm+r?TxgS3PFxev-+Utx|#}F4xH>0 z6hPkbHW=GG>X_r3ox5SE=PS2kT3Z<76* zI^lIzLzUL*&C9gun;(>$Jm!hJqzrrAT-aynUFz@}7irE8_Dr!TDF*p7Po@rm3S( zJ0@|jaRl`84-J1(C*-M14>DzK5UtGP#SA4zQIlD=?`F0In6&!rRR?cDn$i}|6hf)T z*XoL-YEU7i#ggKlt|Ly{{-cCbvH8i8%1oa6B+=O1@Z%wJR?jNX5Z z9c)l-8wP0%G>9A;wS}1SeYr{c_DUCH;?u%u#UJ_z$rq9D9u5#S9>D!52k*WhVx?Q6{4QcGvn?mS64HpJ~}-6xFBJ9L-#>Z`+(a6xA9XD z51vSISGcQzwFk$gVN>q8R1X#l^kxd^O?JK30dz`d>|swd>*Bn)hj4vTCv0==kf|j>?WT%8|fj!MnxI`%9%V`es_Z1M5BN*CTPi@~8EydV zz*C+ln1V_ub4YE*LVTC6flfU>mpiwdm72%)x>3^a@8XB;yR=HJ;D&?QE(=PH1dX+v zQnJF;`?6agCgc8pSC0@E_Vz`2En^qseAV3Wq!wnt2TmKFTaVCE66R2;r<(y zX4GJdY%xYV+1}psA`2 zNh@+2S7s8m(`w!<`wyw_J`llDT!YR5Ep(tVd;2UNf&B$Ck4o=vYm1?DUWYBkNg&8* z;XkV$`w3vWcqXcr%#j*tFwl5XaLzRMgI`s4@(5*g-+{^eu0N(}^*Awdem(F6R$yA0 zLS%BR=B>$q>!VkgQv0!Jr2jlG8XkUn;Kd`jRHN}t$MYfmm5o6!4XYIfM{Jsf_FU^G zKT@FHcv7G#&{w5=M|Zwe1G)cxvyW_Mjt3^zg>9q#(5B^%KB$V28u?w!w!lGS1HajQ%pi-}j^(H!>eM+wC=KN69-9 z#v4)+eAENTBjXR=6Q+pJm8RZfCIK>IVp$(Q0@xj3;bkk<^SH{ow3aE9K-&}9Kt{sn z*iY?L9v^vC4DZYv6NM&%7He&v2A&NvG}!XtF5#2XZ!2lcgD>=sL?TB#bY)5NUID@J z=`k?Si=hX&mK(jGt>wV>nh@X?_c~K}xQNyA-LUG0bC;(fSu99=ple>LtJ9}-Ualpr zFg)Swvk#AAu}igvbn!{wW_;y6{v;jGD4G3UK#1*&3IV=2i)p1rn|Pc@GLc5Sxoqlb zFn1);Oq@Y>88RLxDtr$_N{!VZTlY~$Hf=N35VxL7#aIvuH>;>%{7P^%05#~gzT0GI z6I^J_4HHnytB)X#XoUl{mMxc6pC9vxW2dT^fUrvkTeyxhyF0bb97;q^+V%>9x}F3w zH8{LBocZCcESG7@f7_J$O9kp%rFA1XTo|yMlUJrBPU&9eusUP&NZzN%KZ^7gV%n%R z%UiqgmS?%w=lPT#Wyr0{@&|9DWgkC}C%$JgfLV73Rt*Z`o%9W(+5GSHCHR~S#WPPe zpd*_uX8+v7|98ol;t0;{MKNzr)qdS8@PB3M24OmjG?RR>Y6eL0hY1R%Jtr!a*wc$S zymebT&%m!dG%M@wf|syNUuSrFb&JGG(cf^pDc`+(L*+14>}hUIGyWTwxQ7n2Q>4U~NoUre~J$Osr8+;I;Bxt#!wW{N^fGi@a6}5Ap&aagB#wMBHSe>^;~rGnQ2}F1pt*(g6=&f@Vwp*xA^@#);g4D#21>MatNbH zVbx!~nUs|1^Q{fkFhCYO=%mvqkXt2iDh#CF!fWxNR+C3?i01Fs^SV4)#RwYW(fRY~W>F@MAg)o}toZUnc zl_Tx${%yTceEDK_sC&eg6y?`nSnN@Q>K7zEX9W0DEI1|RXD`Hg!{Vmr>Ei9aKN&)Q zl*#nA493t41ZjFaWHW#mYEi9#S6cF1PYL^e-b<+a-lZ8;^V2+Wb?p;6^f&`nhPYwgjyT6x!$ zEv2X(_=Ophr0d#_h)xW{H#6n>LPkYO;h7FVz_5m(G<{!iux=g(RZB6BvZZ;5wzKgk zxf+YCe=khEhz$`_Os$|J5wszwk=8s|B5}VN&kEhHVCBKKdFqK|E&@A@rn@dxvo+># zTaT<7IxW!cL}s}n??xiA@NwC&JE{7X5%{f5-^KS5(pn zTdskA!X$Pnbs zk4Lh^>?NfsIT`kn9&)uxZc4Qrt9>yaFQBS@ZfKJ4yD5^SRgr=zX%4bZXKN3eYqYUpdT~1x5}RR z+`%!*^)<7TQ{@%)M++3-36m}Ik>15n7i`;fYD9KmJ_fX~Ejx#aM?4vRbArKosbdaG zLfG`bH4#8NcUY_^9RL<8($U1wkaq~Qm2DnIB$QQ;y?7eCIvK9v`POcrz8iRwAqQ?x zWd)p=(TZ(5y&ZS+q1itgd=UhzV=a2uz)w50g@+on7Yf9Ovgt9``1Y-wJ74-i3?Vr+a5&=|( z8OI2+xj$El^RR>B#Fi)F!x(vHj?(IdpL@d9IhKko@xHS7fm(qC=TK0_Z)(P6YAY>1 z?$qh5cg);aMK7`l3)~Z6e$n;+Xh)(|n3xOC-_i3f%bHyS{#y6mZATM2b>|nBd+Nyb z&E?~65{#-aW_&eXy2fzW*Ldxwb$L9*LSQ>Fa%LM0-FdLS6%11k4b4Oz?{m-9jI^L$ za*LXBuU9UabvHQep<7e!1dWM0PoR|Z&5$wpki-TBW66QcDg~Gj7**ISAIF^%+2ghk zEaVQN6UKo>?)7PGaBH6{7W9z@kxL>)Q`!^W$!q8GxR|QWK7z5-M&+u&1#G64DS4Ef?|&L z+kJ&lARLE!4!6n-kLGuptIjx)+GwdKkSj^*-VaJUG8L|=2AB?r=86Udg_QTV=}(tP z6#6;aS<6BnjNv{fXPuv54io!urluhIQLynanZuGYIJjBlBV^F0a42#&)<4#zy2Oc1 zpn;X?P3_cRu$O(|pPvl>QDqp3KPNFM+l2&r)XR9YR-RF;jImYLJnY93`Dd~VFFA33 zlhtm@Uq~&T;b2`V< zDV6_eLkB49!v0a|U1Ob(cu^DLZ}VmENsD3SfipVJa_J50Ow-OfvTn#)C!w@@BFe}m ziRuD?HzSlRB>)DaSKMyE$08T3}odO@RD6ExYIfdw*oI@+{kjOg7<{tfBddvb&c${x?xiE ztkat1k$qoSRUN6A1dx*xB9AT{43}7m=m~bafPVZoF(Vtv$V9(tKFdp}M7DWr!1pVv zWAU?z;Nk*-*d$5t(j{_M(WhYI*^Q+Ho93Y(nC*oG%%S9q*%rgwXg0>@bxtnZ7sjRy zK4qZ`81R|@cHq;T4!yfea!f1nb5LiJ zaiF5WsV5XY1M!tB4c?asn96OH{FlUSG3wICze=Pey-iRp04TJH(C&@+Yng`ee|K_B z2PlEr920PrC6<>;22QPPU|%FX&Sw*cb%)iRMjY(+G80>JuPTq9q!2T$F+97&ICq@z z<6By~GD9%I#c0A$MoYP4n7h~Lh`AMK*WbSIqv1Zakk`fr?}ReG;dyW&CPEZg<$d`& z0e6qPTF;O(HPkp#W8c@mvSJR-;via;Q*Dos^9xh+W~W0GCcZKxl=9t?a()Ut!uS}` z6v=vWF+uo!S&sBXq`CK}_xzGWbveK_ODH`&%D%|Eb7(wL&m2He~L3 z=oY&t+f*etOPrr%l@Yic<@1!wMn9~!`>?u0JbS=%R(9BAgVj?-7GUup0k}$YLb4aO z!zx#e@~w#Y-mG{&!pWqW zdW7{|#|HP=1Y83Ud`Xh?USfxOBYpKf9vZ$263vLgurVJWzIe74VQ)L$uZpJ zpZO!_PKCkJ|5{mZT414Qq#U6y&-YK)}4`VDyy`D`o(m3Za zm)jZ|zIHvesHw~-(<|N5NG@}pD;tc2#zNxT?}8PO*0KGimPlYpf_xu0dDjs$GHK@IyduG z(<-$8GJd3aN8&^qYH?rq_RzPwKs(R|BC(Wy`A>7D^*t{HoTXrB?Vm22K82kZoy(*`l=jPI)XOCAM1M>#=*I9&**6UA|87tVTz;E_E0=XQ^&THG@``!?J3b7CtkZdl|P;9Jt94>+zu8T^hq?1 z?T;y&+-iA!;y+y@PS7HA)*45M7B%8`{UC=dDSO$h%50@l8S)a?`ANc%c%P$GC@>ZuJ`141!~O>Z*{ui0Z$XD7&i z<@3WR6sKzO^cY0$MULPSMnT(=R>!6F&O)$b2y_k}mXg}ZIZ4#ZRcCeu zl=J_q`%ZC3juFiHbUi}Us{O-0H#fd-%Z7|iX9q=HA{|hOhFK-H#r6zK6r2$j(+}S( z3{?_vFumm38A%Wv-?XU4x6*tc8G95=>Hmg7v7Yb)y(*Uy`$c}g%R=DagF{auTNnxx zN|O~Cy0y69*+Tv6-lW~5XxS0*z^Z^8KvzCXxQ`44E4n>u)L3g;!|wLtZfoAIIXPc= zTW|BpBg6L5n^t&qjC#fj)T%n0x5vqOIyW4;vlHlcI;&ij&;c^3dh-dXwKlS0%)DaO zBZN*RUBZ3thVOp+K=gfg$h-DBx6!Mg01&f?Nmuzs3dMIWST-u*>=)OkLvtAHP>G)7 z1gY?m!)>&G_802>mx;syh6eN+N9HBY{nEwSkAtlCrf8%4%GD&kr+;3^mADv|JxN{R2A$x210fTJ=6P8V_v*4&DQUT*>ijhw-3_)_VDi*tiq$a?q^x^U`IM;;yEAn!a|_xID~uA@4VD|a zYfEsd_Z0$;t=e<`;&Ooa%#>m*9<bs}h8|HS!W&1wv~TblCmur`F0t zctGwc=P;IKY7*5LpTA@rGN_=*?y-yJCifj5rvcMKhHb=F5niTIC1)n^gxm#HXPXl$ z+UJ>Ux{B4ESA(GNb<=7DQ#?Ni*YG9R%f5KJ%bd^P>cLC_64da8C zxc(|$>Rx&I-noiKRQNQg_33?u^(AWviAZ#$Yu{wkO7;-uK1PnD}Ny4J#P80suo--RRu#uZnCF|lyKC8#>&>}&BvKy3!56K7Dj9bN{jQW&c#1> zzR3WezSmXH3L{}6apk(Ns+`C4Y?9g78iJ!W+pN|IIZ|H{MlLmlh%7YkuB`0M61?H& ziM?g7o!y1(5bZ`H5AP_`a|`0yz~0f7D?daGBYCD1>E26r52j zl5@QVFM+iqwDot$=3yg|ER*&k_s(8(A*9@L<}9GYy%T$I97`g%V^8ymeQOI69v=92 z+VA$M*=++Wi?3U`{ak%xzjnv(x_msDq%FU6={Ap%RN=`*zmuwkf8Ie00I{r1IN-FN zzqt*}HwXXNp!kQLptYtZ$q@niq?$H)uWQsR-MXk}*d4K?yHjK}{{VKpUeliDD1YqT z)l~)hGOv%cg!R_u*RMLI%(BEmO)-@p65 zKfn9?`Q5)q{*cHkd7X3Roa4jT}Zf|rHm#j`b48vcu$(gWpETvx4$PY^x@gbFNaT{`~Bg~ zO2A|?pSmM^=J>LGQR?2GN4eWCjj%ZA#Ba^-aXk2o4>8R#v#Xd>>%{DholC>CM1V;I zblubBGE~ED_qB(n14qS_f%?2vXEN&M&rtm}qQje03*OEl=Qn)Y1e((mEo<&FtTe~u zTp&U%W{zL5vp(4rXT3zV14Z-`T7Rf=3JynhIeYKSlz-Y1o4NaI;iboiny*51^@zdJ zk;!5FZbdCstS3EO=VfQ>}EOhSxYxxbkWqapIHxC5e&mJDOdCJZ#tHuPd0OIss;=mq9Tb#6CIIu)!-`Z zVY`9ILe^Et1jKzs8_c(Pj^HdvE~&vnHQswKLZpR?eB&$zNcfaoLR<6Pl^$>4^q&%Kp;=!c?+; zpglnbwbNwuOnvp)exSMw`mLLc;j+v*m4_PePtwTfSQAmVHgKyd4(+`Lk7i$K8)l)?e23c@QZ~$LXF_ zVdzVy>1Q&l4Y;yiF#c|vwI)(09WFs+3>^d>HE zqu>ZXjhC681^xiS$`DB0SsG*x@llYQEpYhr3DDXeqvXRYM(>!sm7LgK6}f~rzqn}}-JA9#-Vpo*H6>x82k=4yKI%E7g;_mo4x z=k;xJu>N9n5M_FKy0hsYA;EBIhD-xTVeYWQyx6&18fwQyP$CAgykv(1l`6+cwcP z-0zyY*pYq#GyGmE5az0lo;A7Mv!?s2{DT=?MTf2-D#6fuu3ICPcd(fCg)F<$aMG94 zE>Qtz(M72iu%f@|m!yl$A_|u~4CEx1q!`fss%Iq4G@jZ~-%q2wf1f9vD6b7>_@HQY ze%CSjl|wTv3{5j4|y1NhI8W{k^A=Wqpww0XTps*!it?~d-(9oe@DdA ziFpO)S2DEj*SO)CSK*po!xeZt#AMFikN+?RN|FIof8lHXV+rZDD&)lK%r6rm^`aq7 zBiSc$>V?I0`B9$`=LL)5&zzwZp+O5uor&4!=T5jrN|WDmYJagUtxWdybQ;P0v;OzI zjb}Auww2ftZXhd-E*6x6svdBn7j?Rgo9j5WJ5b^#gZvAWRxoZoT9!0^I1`dBuhk3Q z186812Va(&VdHPjO=te~T>ZRgf&`ip&vt*tSMTI);<98|{mdx{a8Nm57H0|6F7LN#FiuyRis*k{i0d z2H2!p_?FM=MX%H7Vt)lrg&aDE2fkKn_Rh5gk->r6iJQ|>C%W1{n}wqfoW4}Qnl}gwSF!6A>=4SzT7I>V9|RVD?yUKp{d%iiqye7! z)7fjZ0r8#^+lC73Q#mK@TTRi08voH7m|uPncJf@Hn9aPe%E|KvHs=Y&_yNrSb&`p0 zT_E$Ds@Zozo>@!!_jWVgv1+Y5SZ>xN>T zes+ECZgzjSF%gC*RV8dVdB{I?zuws75W4k2|IWw8v$v5J!_asBuYPXIh%-2A0$3y? z%Ty!>Y|ngs5bBU=kRBI4wsoiP!xhRqn4%b6LFD5S%ViEh)pxcjuU_1I`R8wOkzW@q z)_tcRoVe+uvgl%Z>U{EU$2qN6#VkrS1FycF`$-@0|N23kp{~f!Sr!
)u z*PEu2f>Ff){KPN&me;@Ns-VSnjF<HPV5%3Z5&?W`BNc@bh9V?P~MscXiS9v9tV% zcdvLA*Uud2QvQPP8^2)<&`ZfnuBi007(nDI9Qg{6bIVu5KdK-&{f!dpo1GQiy1%`c zZ4MP@)H-I=VcYHpineS%pa*L!g2KLK@*1>DYaT%uaShObz-ns;an*UA(%nq_d=3*i z7v)%FRC2Ml;No*SOA$Q@@_%DJ8;cu%I;iw-yc1mzRgi0kLwku%Fp?Licz7tImOmd9 z(5tG@MHaV6{Xgth{Cqh?%xrI$xZd5bw`|E zK0Xp}{voW7ItLxHKN)*t-NW&@{Aiu3YFAHL;*}S>Z!T=}XAI^xp8vWq>4(L(ys5>B zmi#l*)@8%vKA(5l%+Z8NTnW-m zNp_ua05a!h4KYPj^?z>Le2~`}`1Lz$XMw36s-UgG_$y~&($cuShw0D2)MopSxAkyk ziqPe6ijY;ZBKUp>v1KN$YV&_4gVdxkX2Zm=O_;B`iGmq)P0BRPl)|TZsq}+cgqV3T4cf(7B0e8IY$&6U~3bTe#`BYmf26w zsB~fN8w|Y)#LXD8L0o4v>(ZT>chB~+L>WA{c{CN3aaSXg-P%Rsl9KJe|Ifk z6@%x}6_4knUMvqYbEDS7{&klB$8%oii?P}yn<0`D^8BBbbxz1WOp_sR4EEPq0zQ&(dCP@Z1R(p*I7 z$@$TtA#a`Rq#|aH3oGJvrG>Xd@p020*v9q7=N2ofd(H_!rOXnE)dTC)+rG>bekbm$ z+8<4Z{dy_)`0MJ6l2&%rW@4n;6kSP0J}paPV)^uR<{<5fw*N>;?$mEn3fAHCE67Md zoiJUwV&hBl zIZ*5snCqO_Si||&WtVLKDYoSq5!aN{nAxkE!-WTeioyxkM7jr7v$LVFF$ob>>+aTk^;A)enH33|+be*9W4WS;JEGS%) zBig^n;xFaQX2{%pj`u_si?P!87n24p(m^AP(>u9hC4mz2xd_t0&%H8{NDopBef!q( zWMWLQ#dfdq_zC)-rT}x5fWcQdcL6-0#~ZP)l*adNvGAbP)R*gMikMNeVILj5F&9AQc01Ky z$6sxGOW$0LTOCY(HO(3u8U~^R#~eOBTskm8@58Td4i<{{)}TTBV)V56=L@U`d$N@MvK6}OI-eMKvQG%X)IJ# zBz0!_=vK(pb6FZU5@OnxlAWRo(2 z-JL9{eZHyEl^HKJ;mT2f>?!Nf+>s33uR}^5T|afS0$1vO^qt1DxD7SOZYBL;OZ03O**R~qUmt| zR^YPZtq|<jW}LzOdhjq5o)2? zz1JkzJwBc?W1kQWBVc&j2%=c3CCfo#bniS)CqJ-;EODWfv_ z${;}O+v{{pd3tjQVze#^^!H1&pXtl{Cv<9&Vjzwm)_|UakW60lANEcIl!NW0EWO|> z2L)77|6j5`kP#(wuMb4_jq9Lz$lgVFQ*D=n{|KArKZL2Voocja%6|F2S3$()jgV1p zA(mBELuERGp2z10_n9{x0jcbN(5%$Vr@3$*jsjftU;XNt8?GV+|JOIKoKV^yu zmFZO}Ht0dk5}Bo4f00ListnG343(!QPm&Si%+0mT=W9kvLOfENe*zeYzfUROJeByK z67@~ixd|C+;Ig@|lypq5l=xFEQA~dq{Nzu*)WermfPtb3O^}G|_+aKn8=}7rTuXUH zl$UjL>l!0vE2wLZh{f=1Uw^!q^IjPi6A*CW+^3mS_sdTNbw50Pa$!~Y_|<;@^`vf% z2T&w(W1ni4labCbeb*C-**t1Gdhc&jym3UeJ7yR9>|3f8N(1aH*um6S0?jnNZ}@yB z(m2qzo(uf2>GfHr#6UumrKReHp1KUuhzSUUyw#8`f*?B*I;1tt z^U+dXju1Z_QjJ7yj_n^oiU@PIelEzy;2mc`=4=%ef9T^l2tM0Jr7@{DG}`_a;j$=F zzBR5HvO5SKDtB^PZsW2AJb|hmKL^|f+tkmK_b^?3(HDrRF|yTzXr}=lbI|hX(;|i1 zHWdVX6Rs~>_6WP^W{auOJ|Yil0M%JpwU0GaP(CwxdZ5C6kt2EATC)DZJ%u{B_TdY} z?Zcotdm9k&kd`xzx&%KyBp&*vLLt?@LN1%vjS^e)-?M%>e%$x8v)lqmG7D<2{ULu4 zjkgQH@2SgkYbHKkXi6K5Phg3JL#l`jmZ#5SyJA| zBZeR*!z;Xd0%3>VhmoDVwct;%yf}7S;$}_{cYY@mNILs?#Y^VVuEFN6=!2iH~+~%v)Z@SssGE5APV9 z9Gj{+>vh*xB#&7!Tt(kr3Fn+7jgH4=U&ttn$r&{t$V=32&3kFF?{(5>{yFn~K?~o( z=YzaLMk`LBz0e2ple$Ukt^=saOC*<)R80C6&f^k<1>P6`+hXC4F|2g%#r52#O8V_(k?*Ks^t}?Gr`62=dVYHE7;l&&WQpK zaR%V|=?z?8fd0^6h1X23>R);D@3I#ZhG;)s7xrP?UsH8&e z!v&5$$BKSY#E6a??Kxzhj63#*)DzLmUDcY0eh0fvaU)*Z{c{&UwCx5x`YruW)>f$jJT2DgS*dkEHBHzU`ZzneAs0V&*XbKLr2Fv39d^6ZEE9(=g3snjq z(Ysx4_jwNYpggZyVDB4U!{J85dXjpoN_895`i`Z(1NVpp5K;y1erw%sIpoX}<%nS9 zL!!sLf7u$@3!Jq?J6SZP<(LD(+Z`9RV|Oh|Y=XI+rtRIP>BeYgU|b9?kFiz~dG+DQ z^gk@~|1#5mzX(15DDVk9w!!yc()QD60hh>Zuez^AwI=&4^5x0G*syb7Up^7m#Ike8 z?!A1nNpiPnGRl|yoI`%yr2qb`(HEiSI7q;bZ%%i+PF2mGjGirUVQOq2ML-L7N#0gj;@k*0>D z$p)|Av06|6mo}>oF>+x>bN+mFzrMGlt`izqGCK2pOs0{lzQ?)2qFHO#gGjC+5{%_R9t4zOV zV!v9`zgC@*Yki}*rnVz8V${H~`(Z(mMJa$s03tUuH_VvG5Y9u~JRpeRx$q)1MI#fq z;RuY1EKvJ~Yo&c}5L7+GIVM5x;6Kwx^)%zrlN^h0#~q8UbAMx~2!!aP7_%NpXEhqU%Z1G^jj`m)4m0n3tqV;meAs+lyJ1aS zkJU8LG z^Pf%4x;$@S0PjH41}0bWOrQcFsxccHLTcOHoJ18;q~KXMKl{_KE{kjKg9H1$$uz&^ zh_n%P;y!%wPeB?$5Px#?*!{0Bph_5SmTV!;LcjFjW183=8OjUwW_~@q5IC8Fz`=}4eg~zGF z@xP9fw2ek6N%i(H!-9%||7_i>i)YF48l` z=dy|-ZLexDE6h5nvNL`ZGSt?F3v1>eIkJERTXOKN{nduPleD>RA2J^pWEX*D7e&^` zJ*c6Zat^@YO&%=m`|?$R205P2i8K8%yZ7_lE9JP(y)xgQ2T!@K0s93!G^(wTo<{SE z7hV~yZE~{gC5fPt{ByES1GbApw5Q+7*cq>ZI!vw0R3;@zhZLg0cCMIZ>Vmk`lvV$o zUw|oD-UC?So3N8jj!o0^(^j&&i^}Bpd91O3G5AH$ibJm8N%U-XLFuuEQNU!Wa>K^) zAz{RCVNnrn`dn*a?{2o42F>@sCf6%PMsUkwpFUhk>xee8+L2c{(pJb(7%e-Ok$ZgT z7jlee^4&)842qniX}#nO$_aDasf5D~O<)x7gQd2vkvaoVfeG|@!Kd#rI?G=`?9+MT zCY31hnFDBuB51?y4tE?6DC6+LN@D9*Px|<9&uxyQ07PYB86w=|P1HyO4%f9j740E_!NLK}2evGcs)9h`qhG_?F*;91oR*lU&L6drgY-c?}`O?`3@!kSp zozT-E>g%&dM@6NGXNX7Zag&05o1=`?QDGgRab!+`!1(dl)doBPJw54iyl@z^mvp@3 zMcwuK&*hRn#9!TK1P^ZUVV6qLMxeP+HO+$q>T00~0=_(e_&XpcE{6x=3Y{QZ6h+8E zG-cVEXD$O#ySut$+kD1I)%ypRlh)yiio$r|NZz_hp(IMg2xX^{p#_E=%_!xld1E#h zl)^W}ZxSZ5kvx>`p8b&#o}@wr94rS)Y^WU3gJ%O{3#Q!ytpoVawi{C?RM$fH8v+S{ z$WUZlmoog!CC$zeqvp=XfuYsUJMuDQo+OX(Y{Mt>T8nHX z?D(kSOAqm$R&Jp1Ga7*~o7t={Hh;3YSCe_@xPPKSAO@yVA9O%)OW|Gj#(DE8{Ix%%8mc&zF+ZPk3oIp%WsQh@0r@4dif_Fq@|fB zkHB&ewcIhI0*OKmZ?|8$y^_@qEup>eIghW;aX;y}Hfht1!My(>J5W3q2O6{PZY6NU z*1a8wIKb<8+uCeOV)ifOh3-w#8oE6IAr%AZ-F&9XBDVs{E}N#d?SP1-H1 z>~(6_Vk^2~u4dlpVkoBU*xmWVWXK|U2$rX%l(e1}?6NosqmJiTFQ9zOy|P43Da;;u z9_%get>P#oGNDlg7OqG&tM*X8hiSGeig#--yc?Ap3qTZ^fqT<`Kp!yimA-UOT*-@u zb*rRitG=Js-4JjH$GkO3n5rFMv#^RMb5R}^+pm~uMmtm!sD@#<)H8Y%lEQ zwH60V>Ya&W;i&)ND|q9F&p}fELr3!clhM0R@(qr?*4L_bBV4%ERVrg~!(cJW-yhIaGkF zRtBNnWq~TdwlX}}c+cd?_>AxNcdLLNh8{xP3g6E6wwu|({hYe~UmR3P8D0|dB1rn5 zj7lJO_%P7(%r+6F4!7ubnU<{-_6;E64^YiB)Y<((V?xvM+zuoS6wxVkP2r#&rZleV ziT&Oi9LWa12=sH!%Kixx2PC*%t%-yntSQ(UxFP%)YR5|hAK7i^@QhTdYnvp&k?G4` zLZB1UB*NEkISa*yAsb{}tSg_IH)-x6%Y=9EnbeKxnABf0-RUQvb_-V}R3aw11EZe) zsrr4b_eNV$_xZDzpe5Z=HjiD~3w8PUpM)3^gP_5n!DzxD{InKCE{94}q-_@Y(!Htd zN6?$nK4zaV^B&W@I;UMC3w12v9{f3eT1^{^l5&pFQ9>uJ2=we-aVoh^6bHF+NBISt zS^bXa@>HwWtek6bbHZ^D0t6bANt@;iPG{ zE-|@>4;`(Nwan$}>{YK>>Dct1c2@9~3Xe7DvAj_l*Eg>}7%MGzEgY29=n#JiTPB-= zE(<1aExcF3Cid4(M1X9ZK_=HG${*)C=4DrwIJD@p%53$m`PhW4e3fo{%|ghzNMLs{G*O6w$mVk>q45-K}X5- z(%8j8$vvIo$b42L`9$T*E~xg16Jp|H4yeCpfndcQX3Oj96loItGCU~?`%jLjrauwryyitNe zhgu`%nBl#UPMi==sJA|cfC5I8r|w)*yw|%0qTR#@{o>BZ7&VEs7oFU7VghLnV#m$z zaxz>8ZFn4#h0-ulkU^lI%LL|f=dX9xN8ZiH6Fh1c+T3j%QMxxeT&jmU3Po#NIy-dh zj1Y1q#U>8n%yq@b{3bRgIvS+{0@T*RiI1AV_bKWVRutiu^qcYP%5}ZcJLj!QFApEt zxIo(EL;(Yw!jrXZa26!GFLxG+FBVtlmgB(J*9CYJ+w*5!=UNK`k{n85BivdY zD)My^>^{5Ov+g$U#Ufn9Yqsy;dz^{qgm`-e>@vI;oDlH|vV0a2Ok1m4QaG{0aitrL z6W?IU03n`x%8SY6vgv%*61uGcq;1<<6K|ag99jzd!@XVn*4zx2GYm;!J}vR;igCft zu+zxacM!hBWwphNtO0hluN-35_hH26A-g@q)z!wnqcr%y2JpcEp=B|rQXQV7z$T;I z()OBNO|I=ct6)L`gikxZl`sM_;i>%SoBZ;{7dz)%U_UF@-0?gu@=nwh>Z?Gelh9z& ztj*&g$5I#XKKF@50cN_z>>7i#6b$VM1H{NY)Oo^SU|}fC4xj75FjrZKTDGn{u9&Eq zak%1;r?xCYXi|qo->iHvK$KO7w@v6vD4{ALhx_?DzLg2!>k2@>ccBN^)!i&#_3_ZS zJi%QglB^X4I$k95*|Jj(Cy6>D@ZQdRYto_5a>wp=WN^fAK5-;14O#=nNu42$xY>Bm z<#j1Nq#Fs=ls#ffR9crudE0&%@W3apCtS(W>nR(c2QTfGow^u#Ait+wh6K~^Xb`FX z0Z_hkc}hOi_G+%2qQ#^eyVFg;tsWPDzBCPfTvNOty_?-J<)f~`6B}Mo(jx2TA-++s z@KV4guA!(456^0@zIfhuCztFy`?bgE<&%I>JwXr(i0ydGJJS9{8PLYQU9ApCi`dxK z-WEjrKh-KN8wxzh1mPjB3GhGkL(or0`!HDRH=?1`4Q%zKxrXzV>tQ4M64VyEF9GW3 z6A*?wr)Sc#J-xjAbVJ=!h6Hux^LWBFC|dJbl%#NaU7W$Ai@t>TY&n2uGWQCnCaS^QP{K zNXN@&i<4;)gt^ZRCKrjxZ<>Jn# zCfF~h$?FP;od6-g&B>=$dKK&2W7oM4Q z_yjH_$}7g8B14x)7%M*`WXgVC5(uO1?e4>l_gB{?Zh6ku@4Yn4h;u(6r-0YYeU{bQ z9(lGsa(77H=D@VJQcO0x$!DloTjk-^TM7yQEf>R*DoUh9b&p4WzcBPs zA@T9C?Sb;(mEKYaGC;WVLx$6hGD$Jx{z|t_!?wFxN+o!ZmfY*;g6`zBB z4cq-z!DliG(k@Pwr%3n+Y^v68cK=e1PR5w>33qa8E&2K?0}>33+mjH63IOH$Gl{4H zKDD|wN((M}RQ3k;C#@uqT<^WDtrliekh~C$t*CkG473^Vo8+^_i{S*Xpq!m2OpJ3E zsmliJ-RI$!51$jBm@$UYRP|?`jlR5`c9-sss}y4WSJ? z@)=XY9Z)hi4*O+7bI7#)B%W%=W8!3IJj9Pi#x%Zj927yqA!{>rGj}u7WO0$$ae}c( zKM6vz+N7RfLsB;v-ovzW;w}&d0;{?w=#}w^qC1oj!l5AWaD}!%j;ge*3k{S+3=U|K zR}0s18IUAlO{>GBG>YK%B;GW3bv=_Da!8)s-CF*++9z$X%xirdEDm1VR@qQUiti6DH)Ifnso4VAN!eZP zuw^2Puurs`M_qzpr^-soG+86V(D*7RylwG7!^w&Mt55+FVoo;OlW!9yeeo|#x^DVv zfp~!N1jn-OY>Vz5qTj(xvZh|`Tyw5h?dBGuWav)XI>WdM)wZT?ED*4i=NuoWT@P52 z#AB1J0PFZQq)K?roiJdZPRZ#d7O0HmHcfj_Nvgabw{&+LnUQ@Bu;j6E&8;huQ28N9 zIk8_!lMmyZXCorlI9@y0F9d}6nk1)Pt+W6!&COnAi_!fu2(?<^y~sA|EDyZ(F8OPt z;(>fjTH?Dr&@6MX8u?J$2eD!=84Q$2XEeqxsY-~=N7>MufVmxJLOmh79U~?Zws?1d z=O%P;nV~B-E*gJ2nJmf)^l|n*p(g)~boVK`+rt4Uf$Z_{31huXa*=&Ap?7J#|MU&& zZj#2*YniNRQIj6M$r)WB7N{YAqq|IYnKh2S#;gphsNNc@r&nNbNFU`^-UL7I``OA7 zh3^~Wp_b3LEtsx%VhIPB7ItAnMm%tsR&>z^5(-gIi(Bn2vDAi*YFjs6SpYECEXqi0(@$iD}`ng(I!L5 znuScE6_e{+0p;FUBFpAfcWqgh9L5x{AVFw>K5krwfg@yJPS*ZO1JrS~#Y3FSy52#8 zoWT)-n$CJ%sRJ>7Z%oVvSFxgj^T{4!^oGM*HL|=WoDx@A>)1jOT)`~&49Ap6$!&SP zH{Av4(I!SD!<+A3yGuu5tIEnJ**sR(nVwA919=(V;p732#JJBqQIoY_S(EIOQ(56E zsLqcsT3!$0_hjLsARG7owb9y{bsRNzkWqi8$mHOaqoyoP%pouiPHx6QqduyPq`Z4Hc z;Zf7n;T&OCGV4X+J@~%6r>_l7j!i(Je)me=P^t%B!LvSIrtLga{qEBKq~+(XiEgtU zqlxD>a=`UY9_0!dbzmYNu^++4D(?U+>+UY+NNOWp>Tez35|Kr7P>>B=(-vR95?gcNy(7hFQ(Z=ieC?FE6yc|H9cgYH;xj z<0^VG?wbd%$v+f{79-f3gaKQBhC{QFsgyns7uqK52uUQp%^KO=23r%4_mIX!v*c(h zUeFmaFt^)r_^1ZmFEYrA>YnL(edPaNxZ{7bPyY=<>zrJB5^$wR=evF#=Vw^g>o_ty za{Y-;*(~kn`L~hUo43DQF8$`Y-O(`|w<=ej!N$}(?zdlfPA;Een>6Pxf0!`x z{Ng9af;X$x0Q$)XGe%Hv@x$q{@@iYjEc(Io;+k6~Ep{nfv-Og8<_ddjjVT*Un)Rml zn`6lwh-}s^qV$yD@yJhsO)(Yi-yj4xAzzwEi;^`aGT#W<4gFu+?v)&ND@9$SNbS3J zaEE6onlK$nXaHCJ7_q~G<3|4c;AC6`h(1o+Av5oNyEbOANy>f{Slr;(qNN6jO%D*! zbFjPk>qifZWQ=^S#c;>q)!&=dkLeY-&E+P|WaV~%D4mLns7k1~UQESb2IGG+c>is~ z|9EGM#7rK9mS(0{W8Gp&FU^~hcQ;UG)>}1gEkKFH!rQlRCZjO4LIx9 zf|zF()O+0+urXBx>Z|j=$F-q}n)@NiOuya4rIRdsxm|508)boR7m7Y|ts@WCid@nl zOI8BY@+H-O8Posd5&nI{n)M~-C#UGb0hR$HMS4UKGc7Bt%*)B`bwwr6u!fVqn@RlD}NjIjMH(9!b|i2P(1&?V;-Y(Svl!%1*Kqc zC4VYvRia=I<8p z*pb-hjEi-b&2L(%ie9)rEKMIU z`y0CK3^h7g{|}G;-^1#EPJ>35lWT@7JDXqKD!)yo1S&}>o$LrzlG-}~TOVUgjBTdN z?Ep11)n0x+lz;g|`NPg=NkR!coNh1BhqWtw55vsmUnDc;JtyqH&&weXN&3%k{$aex zSp2NQXK*ltut_>!gRFV4zcBaZ{a-Zw|GORk@lNx$#HSN4M{=h=q}8Vke5Ge<*#_Jy z;=cCj2kY+UGDdTJ`ne|hN%4MTNZkED-~K%CxNMd}r@K#!+ZLk+=G{(*HniTkG&5PC zt=23Z`COjA4(~j)lyBx2X`{mrp~&2=0WY?4jUH_Hu$)<^u4sSO9vle#T4|9 z1S?ziI{hfQ5~D5N9-%z8dlpK7)uOVSz%0^tkEFgf^3zC7&Fvq9!~)(Zh!|$kI?pFn zLy2D+dtGfhNVLY|rA;2&fc0o0dt2TuU>=4)wWyy*C{N2jy|#FwcD1%W0x}vbyxn#o z3NNsC&L`D7p{!8My?BL1`)UNNpxnz3AqZ>d(8KC_2yRQ&UuR2*?(eRhD0IQ89VAWg z;_`|C0YeI{TdM6#1$^dU7GIJM*aedjf63=>P?J=$RTS&>CLz^ZPMrRoqW93>=-sf- z8<#-peY2}^uyiQD1>{x>Y5dizk;2y_6y4*&4Syyy{+M{ycCLun&moPGHxXlN>ttcA zpC~lL-sP!_H5t+g;MCCd6LO_@Y*nMX@XseM{k;|=*wKR3V(0TYt|v`_ZYRBBPl&|n zDG)VJheU?RHmr}{73;i?(9@m1kBA8>S#h+EU;kA5$E#{cXY%@GqH{a|`5a?5=4)4BHCZYwdk-}%b> z`4qcjK8=?Gf!V{g%^VV*rkhKFmS)+CMM!E6;AAovX7cjI&u>;d=>@iia#0{hoz`0a z^|6a-iCGD$^r}NI>N?m32I1Pry=ab1X6e#y2wnKaK*QO;+#pfZ&3%&h{i*i+#w)@d z^YyM5+F?gH7$ES*AM$#;>s7RhEgPgsiX~Y z*(eB~z|!6NvU!u_arR-U<3&0H5d|yg**1UOTX^Y?%Q7pg4>(r4?vd>{9d2=+h;@jH z#AJe$hdU7Qg6w}WjEh;ku8YGO>O9*H-YGe_+C6RoIj|Cts9LxVG#QDY5}Cb88Aam- zbrB}UtgOmAp@-RR^5^u3hLTiy;!>^888`>5w>yR#x?+2b*BDtcw;ycr(JP2?2<(Yj z|I!b3xKI}XnVjhq9@kmCvGcUEkgTBwy(l=IU{uV@Y5PnW&>;ro6DmnOs+t6g`>t8LGPJ*R&p5vvGAb<&Yy*~F#cUH~gz?yHJAz)8+x*-$La zK^7o~6+4s?Nb8J@<{jwF_wK05(kYo-2J?Am45%belh0((v3^cOi%EsrW1D2+7fFG% zodh)}vEyz|8*6;N#&#IOO)Y_=?Ug@^Mrr zTaULh!F_U<#Dbiy!FCG`JGv6)6Dtk8A(Rgm1$>;4Wn)CoihS;tlsw-tcN{hwRE77y z(a>HY_=w_Y1;ZiIG4UoDrK-Ti+`4MTkG1UcUCTGx9@)fjAB8*Ure%BNLhQa{m9;L^ z-MdjK>y$@1Ai|Fu$r`ZU?6&?iwE(~u6;5g1Oj$vSBe@AJ@~59iE%*e|klo&N;+l#p zE=~xKiQDFrYn9|RD`c|tw%-zF7H4pQ>zUr$I=;7o662M!2)$+v z&a^~XP?mysMiby75S)f~dOj$hAQ;6@l}CIo_2q?F2D$h&d3Pu&WW*=nF#>kW8g!7D z$MG86l@8)|kyqzMYN?ZgTP8RYcX>>EAw4cl3TteZZNhr1ua4#eZ0mn@AdoQ z_e5{GOgUo_ALrYsW-+nHD}*h5>A^&Jv*tb*)pW2KLh4u-9p zC9x*SF*CXr{W^la$UqE>Ew{j`JaDu&ekQp}7GKgX&ZjXE?J%Z7=AyR3DwS_(Y3{oC zY6@(#19u7jGWaVzl)#++HME?fmv{Cka$`>+$^qzaV@7gkIo?B*tcs|8;!jJ4N!KN4 zVnxj>*m~YTcwLuTRbCah=Odi2d%tjRnL}l`+B|D_akV)pUarR1t~dhYrSsxE7Uerr z_kdj_7Gz+yzY$ECKdH8ViiZu7+%{Nj(=IMN={=P1h5hbs?407QW87c%I~wu3%2?t^ zm`w~&snMNeV_kCabfRoisOBw>PF_XxM5WfYiya2$*cQP+V#4^MoAEOrg)AnzI0pR9 zd8jM)PGc}9@Mq~G$Zz$~b`~K&7sy;~cPB+&tWdkITVPpcx@?}O^W)GGJryL&>WCo z<&eADw-!4S6-^oJUFBh`B=#R|NkLOR{F2*l($Nj0lGb-@!V5$z+Y8b`TM5N;N-wRvL!-<4@aU7SgpalO__{#w4@W0#*h3qBgKiMjt;7$QgP70;EzmCJD((zg zVk(997V$X4EQ)2<+}JW16d4^@RDIQWP%yDfik;7V7SzD}Rz=c6(yHh2JL!JwCal#S z%YOMwdJ2LbBpGetqLQjUQO%z=$tTQbN}34W6Br77&*!Pm;cK}^NtWAKlAc7<5)f)$ zhQ~Xok=;UARsl*2UI3%l;;er~7vvwkVp3t!#P7Vrbl6zI6YM=%N?E!qLJC&c>m zi`O(CKZk&{lc>U!a*vZKRJHf@jQ$=yIpKS+a@%-}M{Jjb6J_th*ipyeOXxJ$Ju$VO z;9l}|JpDU8UgqlYCAP;0zH7r5Le9_u3SvLBhZ??<5TQo2fsbWr$XQrwSH=Mt*(ptQZeJgw^*Y2CvQun zX=B zJ+)}M54ewDfFaSFR9m~e9FGJIy~(nx8NItwn|dg{aXswL?0Sy2I|#PXbkLi~5j)Wn zXbg{@g(Qlil-{~GEb6buflNu?nto+v9#4&vU2z6tN@P4&}34fav zCW%mjmfS1nHK&^LrTDF&lwf*CT&b!4ov+b;(4;(v;@qKh~&F zL4Pz|8j9&jD((FNqq{$Hv@_+Co&3IE#6O+m?{K43^(R?uTrQq<(5ZiP#XbC)sk_^A zcc-e}+eUfFcSNzl_qCF-|EfTpk@@??d*MaFb?3YA1ggPuFZECFXS>sxF(;n(4fWjF z7>^#h>z5kUEAM({!eI8k^L@dfk55<9>F;pZn`k!&tD<4CIq3@z+8dIk%pXjQ!%t;M z*iowN^4{)PQ)1|UXXiUd{Gs}Ig{RRkVC;7&gz9n4?oE?m^1z3GDxLo;%p^a1kRTFt z5Y4~P3t1ShsX?EL9EQ?1fsFF^-E6Z$nTd3F`sG>JD7n%)J*Pia`rDx`l~#}WF1xIq zeNDt7?#64QPn5UeEDszd?i+<~{J}ffiWli@LG03T*}F%(T$cox|CQK~_`-)*yI|j5 zxcsByr*Qkv^qYehyy)t&q%m^)Zo$W4-7VV&Sv>`>AA3vnJO)+-oAcdGtsxHVRyX&I zKZTgX8a=DL(l>NBd~SvWeVtz!d{DzN1kGc_piT4bO zd?$QVY2oXWw}*#bJpRXl`SyGC%V#35qCmQebz>j>p0jdx%1iy#pSMNu(G9U$(?58N zo|{;$ERQuAwBN@JeXvi}?|r}i@&QVIQney`E@-EVd`Fy7;757&Yy4DZAarQB(%ZdU zv-$9++>^wApxLz0$N{9AA6;J|JG8?k=(V>*A#JU?aRpVgQlt^=PBv1L+! zzp{r2`y6+#_J=_}=u>(aDZ7}-vpL|*m zs!BNY6d*!1zZJK?0GMxqpD}MfH%qaVvt=`uYsHLsc@sN zYQtz*j|G2|f4(%h?wZ0FMggpLm54~EB>$VQ5E|vW0Y~(?rpUAW z6j4k+MA!FdmqFZat9*?mVa+N}8)5vD`oGxv|GVNxe{j(Gg!A3N?J)`B@poLM3rnI; zF9204-&JAWtN0JPd7+tmf$O;|i?c?vvCj7cIF$wZZf--JyFCLRmDRL&R(p!EdM6!a zYx7U>CmbsM%L5V3r{TBy2$KER(+`ac_7ul63BDmAe|_9Vgg$a($2}HHHbKi^wxf zo=TE$`Zb&^vuGzQ#08{_rlL4))Kr9FGh@7jh2j?M^9k;BLUWU{`0Cp<<_uys>Xg+x zJ#A?cHm%|%Hh24KGCu5k1_p3%@a&(LZ4wPx4KPYoym}gF^LGgC@!2>iwiSJi#gnmo z5s_(TBcVg=&JN`Ovm)M@+k7^>L~3HP{KfTCpp!ZY=lJlSkdR4e&U9!X2xk)U5H-%o zU7_;4s8L=WEMvPhklplKJrfrvHI|Nt41cXJTNltYCn>?W+f|a=G)kaYyfkd%lA1iX z@|cqTHV0@-$0E>{qh~2y=F1mKo7DtJZ>0dXsssv(;lCqraM*HmS%bnD%Ph%;yImgW z56#W4xR;hc0vq2u?LvgCAfk=Zg4@Ves`T&~WO(btPVFb1>l&#=-mW)KN3@mEmXCXc zsDYm5lmd4Ybd&+AVrCD?QX0lS&PnXVZ6Ay&b8C$>Som5M?;fePa#*{0^7^h2c4Y0y zGE2pyHEX=#mSuc|haa5MB~;!n=7fI%DvZfwoQ@;=r&224vP@IxObb=Iyll5UL!NQ| zZe;t$eH;o$kZ;`R)^HkQBt_<*0<)rn*UZ6l*%w=YS-;7-iWsJe0|n)`2D3Iy$l1fo z$%cW|Nsn-kv=#kK}6w@E@^(jq#Kmbo2^PK%-A z@6`uI*#!?fPPHnwEALK1$5;-L;bJGuNnE6XBOU}=DnAUlohe_cEW8iep z(d2Y_Y+OG;fkUJwg}W2k7dXuasCqE4-X4ho z^QiXTX_*AjuN^(c19xP}i;uT`-ZmJn&=XQ4A?dH#zD*OARDGGuh{z3;L{)9qB9Aju zO3K?l@Cj6Ku+!60Hjf=>2odp9cDm6~WuZDx&a2N$9i`s< z_7bZaC1sNKMJZ&V5^@mN@Ff+gWv^w=jt{CL{(5@mtt4+A^5$3!Au764Z&Qvrqg>E(LLLo4m$J$XJJt0%v;$G-QD!ZiUkCiM%{1;H=|gNNWQfB z%{0nj?5sU5(J@+?T;|vIP<7T=3XUi%Nj#yE!@4cAg+eJP+E@!%ZH5?{PPCchytqV9 z`0;2DI9RSk-8x1hu+VyNYOsFr8~d1bd;^MFI$d8-Hu-T~VM}gVSHcSLsIZ+N{Ohxj z!fyih?O%@6=i#HXMa`Il zh`1>UYpG?r?5jjIW&3g<#uJ;hVhNeyNRHMck21|%IeV7Rie%-4tGhXbg zg0{_M8U4`0ZrYh$$N)nxV3i5@0%*CEaB)fZ^!>>84y0H=`wh|MfY(Z3gshlBRFGz3 zFh6D>U>J6Al#wP^7m?~?UFG+xQY+VJemAl<3n*4CkK8h|Mr$_pGFpBLUu_^Rn~GSO zLUtxfUr?Vq!e>n#9fh9}78%!Pe-UUhMJosD!Xr*7nOhy^f00dt*jfH=f^G<^X13hs z6XAwJ@6cucJXbr&IVzGnaZYcM{9qV{ZZuOe4-g2QtJXJoX0t57YVNf3vH9YIVOT8= zLaYBAsoX>eHiijknQA`Qv#fa7VI8|-v-w4CS2#dI=%D#%d`juoJ4~!b096K7vTPSk zD=J($@K-Usl;(bf@p)5adwT4cIcI#r#`m`^bVS6^EjMLY^(`3}i4*iWiNeSz`q;J8 zdo5|p{yW4%-TNW3NhQ_J46Z>*8^Y+N?tGJdLDGGJ?gr5Z4{}@>z zZ(0PPGOSa3iDl<90V2*tk_<&jBi3!%xa-8ShQj#%25AOCLN9H}TSHDO_(<}tyf7#E z08%d?hG-_9BW?juZiZuYv%|bal>DY#weUv1^B7G{T&LQ|_B<>q%Z{|M!X*fa-iE8j zvs&O6X^8ENMG*@&iz~#;1S`6ASqUPzdH9(25q<4K7u9a3jvRPONz29`I*K8-Ue$E{ z5LbaM$u+%<2TirZ^;xv|)}J?CyC>z|DrmCOKo)#fEXaSobT{PgX~37?v>*(dFEA&5-Rf5u$K>^H2RF5Y9*(`#4z_ zu}5%Uy0AG8$R4{#w-d|WHQ~Zr(LEAdaynYUx?tqPgobIwvdW`t4$~jg*6X)`d= z)|%Qyg;$%ly}oDBOvmCQS#jGI&90fljZ5MGy+5)X6prf!Xc8pO9ofAuOKO30ZyX6> zwq1tD+omd(OFdK9E)xx@cqQH#odq*ZPK{hxC{a7OeXqT1o1iQjI{zW&_`BUIVka%L z!@jo@inU;Gf1TQRxNa8`EvyL7muy;`QJw^YbInE?_G0APGV4KY2F99kUHEU|!w+TD zg1I#x=<4m=X^EPa9AkeFwsTpLQj{Bju2kR&K|8V}T=BUZ=n8S3gNj*`_a(yxZcnbX zBtl!cWQ30LiiR#{=SW1aYs_Y1Q(HiXRG%uKRMsH2sn$b7p!6W!rpr7#xk}XAdb!cS z;`6d8tj*RZviKu*AYlX-=XtL-BYIzG3bi#uz1hpq*dBQXcOE;>^TA}>-r=?<`o3E~ zB*Wo!>Pme=+mmw$JqLyFdJg(1nrXpr^ggZNy1Vs-ki${p!>*?bSxbepYCG?g#M(jo zcoT_geEcwQ7gjan5bTt=q^lD z%ZSluOagcAKs`^Di@=^Lw~n7&1-$X(3-CKy-160IEz{&NBM#7xsHP}h>=3lry`G2h z(e2IO{=hds!P(^cG7DiEJ=tFEF*JyHC|1(nFZFP!En8jMY(P@q9A~St^WK2?x_>0s zcpmvl3x)}QB3->I5k<}^;I8j`9WHv~sX}B<#i%ya8EU6G^Q+`kD%g5WMYsLrye@NW2fpibJU)Y<*_u5xc(g&D28 zs5fiuUTqmLlx1Y;O_iq{0iAVrQWtOX(o0@O0&Ph_Oy4JxCU%4*vMkHg_kAhhG_2Ab zdna|Ad^5y+D0fHCJhCNfNjMJj**yX488zL{AH&;!g3o4O5*z9NMHU%OCJq0t6KoOe z+o_Bsu3#se8Fkh)!(6N9_%U_}{&% z0Efs&|M{KL&*4>YC#=F_Qr5r z;9sb+=o)uVbQ*le@13Q?8_N&e=S6k-_GvVYW9JC$eT&t;YxMtK@&2#h6dnKlyV8K1 zm1~Rgynt6P34rmRzhC{X_5EGNqnhI-?26-FKl-oF!q*KmG|ZiWdSh)l8%AIUEU2IR z(!q4y%RR<%_diV7l)m1)3=HN5*jFbHB|6M-*1g=I0}`HFgtsjV0XGhIlC*8^T2D4L zgN5RdKPAB^kHVGzZ@9B>zn6wTvtrNQ$$09flkI0RaNwtmt~$kD`SIfSq3C2|Sx-@% zC|+P;%&qfuXCACKMwuS%`_oldi0RFzc6jeKx2Cb=sdv2wL3gjw=PzmKU;hthHi@F1 zENHB1?)xEP{Np`$YyKIj3lDHzvSf<7VSd#=!Y;s!oBHf+m0!YNg?=G{O|A1kz?{?h zp1(TB;rv1WGe-tx-Ikd0N}Nr8etDtWb+^J*2!J>4eYkh|ypKq@mFD93n+I%8eQLSc zWsRY`!p`YKGyEuckvY+s=>~^G#u}mxonBoQRs5ZY$@N16p7(K5w5>D$EUu({URyv^th!;KdZ9%RXO+E;@1}CkRUwPPow0g$#;Gg7ju_ZfOp|v ze;4E!Ge3(^OPJDsHXDuXcBHEtj11ipIq=bJpt4Lw<+)K_4PbiSn%w{7*KBD8kSMh` zY)L|UyS|I_zmUsL82)+b*0KsbBIoJ)25^`dbZNQOo>pJxDI~`fML!$e29h?>4$sU3 z(?0xrCGz4YGw;0Jt$ryWb5fdiqaK$A-3*HS5G#?J0vIm|kPx#tdE!s~_0u=I2ukf= z=y&8HU~l=tgOga3UdewFctwZ8)c!7L2yH_Lgnw=_~eu~0uNN!Y4NUVY?* zBo7T{^uA-}QJ(jWC0AAd7f!4ypDEq>z2afbj>o4Te>H!TlTj}hcp-K4)@wx1jabB$ zqb4HJ!WI}$G?!zFFzl<@#k4<@$0r`l>kav z`S`E~nNYFssE#wbRujR|SFT^+Fn!t9ZlUgRFDu=0Z(3D&KHgx6UD?YV+4~UV2k;re zBc@L-lL-}!x00dv_i<9xeFzktz?>#(RsNsI=Q>O#`6@pO_1iW{E-J z2YpJrg5MnUqiQvo-j12_HxYhtrCw`=iW;KNPB=BKs8m+7dzTj8E(eW9?HF<|8OWYp zq^sYw`r@NPhGrk6~ik6V&+bO9~ zh!`-krNObbHdJa+O#Y_Rn3`E^mVD2u)n~KWR>gW4 za5rkvv=$Yej?65pu=qEK85ebmSL7|A+!H2+dHB^|zqbEIK}3-2CXISpx*c;@M4dkL zunV(hYIh!AT^qS1?q8QEXKIXT ziVy|e$%S=eDfSIT%1K<^)zUoC; zjz6iDmuk{tR>@(q-PbiwCvPah;42p{Dskev6u3#9vvwpTK)kznt9h~Ng3P@@Zr~hR z6aY5FLInq@5S-K;D=((3L9;aj?~~-N1PK`t^?772iexxJu4;+qVw$O81{@UdyRKio zUEN&FL#i9DF_U6mkG;wMp66T*e zI*mPYi}(`PZj2+u&}*ag$9dpI{la!_(aD+%lHd3GiO+oN5P3;19uMn3I`#Hn7wOEK zQkj%~&`>gv6lG968DEeztU7dU(UaVs=49WvJdWPw!ycIltC{H(s_OZCb#l67OLHYk z<8PFQXn9weC9&VSonQK@|E}-vT?+@8YV@N@uoq)`sQf6Ve}# zJj;-E!!h)tVU$;emlGn6z!-XcgO%PwTg78o^q8&G5qMzAGtAagO2yK^>UohZ3k4y| zuHnVXD865;Z6*4xXJo&lf%4Flb3*uhIb(X^<(pA~CX=`Aa;WdenpDrFp_O;d6p8Gu zUoyiDKwi^=>z$yj#%Ot#iSxi1ju*=+>A5sr%M=PTtxI4-CAXDaPX)zr081>UmA3MW zZ?YBVS9PUrixh z+h|?7C+f@YHPdGJSZSzhhmR;yr%JG!Etyp6kJ}#TvD%{ezzu%UiD)K{9BO%m5*IP> zI!a!N8vI6#SQYS%=g6iUD^jJr((karP25g+*B}fCSX=o|TI+ z9qkq)$5r3TY-Q{G&}xEr@)8BqFwNCm{$Iw#UbAgj&KZi}X>N`hhmXbe(^J?N1|(rn zEmXBa!@1mJDq?LO2%NnB|7OlaT_t(~@nPF&)K zo$+{S9!x2zBqrZn4c{Clr0tYqZ{INmWkpu)PF|d2F-O+6i?Hq2U!SdELKYv-MVx(2581QBPqZYWq_R_9 ziS#ZsYK*V*Lp&=d<@t#|?-)eKGT4|>Bgnh=Q;(^N$~z*(!E zcmMKpH~a68bkWXKnq2Yy=WPp#B6H(o$ww1S#l)EPJX7M&JzDc(!vfG!$S%~CU<+Lb zS#L$>T?x@#%DRFer;BLSc#X82q=FsFY_z-;8STV<>Rm+E`nQLPcW9xOHGM}_yRJUI z746kqIT6-tK;QCSPg7GA+OAe=x`>U8D;qZkQ`Lqm-CV?$1m=y>c*E_|{GUmQUGmsa z?J z#Z|T2l-x;H^d#ROd6bb;o`J``!0#hjuuF5HBgW{wO@7g40LVfoTm2ERZG}~}j*3b8 zWP~;f-={buse-*L5pH15Rn^K9zGn+U1H@AUMnt8wOv_<)T4;bz)kl}LZ5}SCxeDxA zv9t4{&?2@sh`t42E=xLP02x_yrZu{iEJXoo3I0K_rnuCy0LFazIJce$@YRgv6gf!< z@+u!|YuKZsGQc~6^-{l4rA}`fid9W=DMTw5EM?~nA5j-$B)L-W&qy?BqAeYpbZv_2 zmFCufb9c3HRuUO^l=DVjIN#sNUISROU`^axIT(l*K)rSpWQ9D6+-~nii;L#A`{-fq z)^Vr~#F$d6)=Jc_Ie1m0^AyfCQCpIhq6bDa0d;CiQj;ROuILqgoaQ36M7NXKQNA5N zvm_>a%ick8TA(`yA?_yPL^x`pro!FN^w9zRmf=xa6`4eINLPp;oHeB ztz0_T$?~_yc-8s3-75M4)%8M~Xo!=r$Q8!sxP>vtrrhaq3vw;>yn z$MRgLby~ybCg{kG9IBAGFk(FNc~p{vd2G0^qYDCt1Mjz}=qR-iPgLp{IIH)J3(39& za~Z}UiVIl@Q+=i9)}lMLE&-ffr%y6zTLMghVvgEnJlHkW1w73t!3uwC%qoV@>Jxz= z241p11)EUzVEN78JPg60adBg#-AO_%g3kfi=>3PAm$fOT~$i-`%Dlda= zg;&tY2q4{A<6n(*=lvNSHUDfenYkvx=g)uysZ zfTQl1#=VMpW7}BRE7s*iL3`z8m6INNzKs*ccoxU;=iA-S^CrF+)cRY(Mo9t(?MFWCHK+uni)g4v!ytVhlu-fmd*6lVzU2lT>n2A$N>05g4VT zBL-$QYPwNZHrR!0U!<$GtZnTPNQFpD{@BUDn2nrujjg&Vo-t$F`iixOs$gXuetLCu z@lEb2qN2@+=x$ZPjLwndSPR*)#k0s{Cnd#B9|u6_o!}!13_s*M0i5c}HfM>zlut8# zdPoOrZlxMx=-XLpYQ=1CGY35eIjD}%WTuv`jUJIam$i4p5-|n!Y%EiP&%V%CXzNfe zHwCXqWi^g3irL@(q#-L3JQ+LFXN;o^cA8PHVkny4?&bJ?x$5WmqCY~C)p}&jUJhK{ zJHv;y9lM&mu728Kx}`K05^kU*F#NZ{o-p$DSvC6>rORGTa3{~MWY^jk1(EnS5g8AY z68q0v_P>*~j|o+PH36R7CZ6&Zji$0C+HAMHaP+ifsw)*0fLR6i&nqr>(?nr4 zqw76SWe!UW6TbCFJ)yHu>0Pc;!9$<#@Z#`-q^Z2XybdHnW)g~%YL&tf?l#dbH=PhI z0d#2;xGnY(0aP$pGzWY}0WZ+zpDP~nud(PuK#CRnX%Hgv^&9(82>@xgEnr6KfAp3Y zmEG3Vy>kmMw;8+mTJ~9bR3<#uF8fiQS>cUB8Sr+}?K-ld9H`(lnWY3a1GsZ@#+$m9 z2+v@k_C*hVpYA4A69$|$w$yqM5I}^EkK$&gs(vQ>#(OWkukN$i@@~p_HuHIuIu9Ah zY0S6%GLZ99*}RKaT{L>uxbEFXVX`QdY zqBOWk49xhTz0-dg&)K-VQ?GruCpe;@Aobq=tU3Mq_B;Pf@4bD`AhQTb!g`B0+FiMD zXbK&-`t7hd>$hK%rHcgiEPnPrfeo_jyy5qVvtD10j<@bL`KM+5*fjfWn;3Pb^lGEQ zHAV@9pCoOlUMJnOgYZgw@C^D}Od}7bl2=1uR$6bHS+JPU##OXS{ug?Vs_F0FQwDae zFu7;Hmm9kOBg?rxY}U z47GPWFMKbQ`4gJcX7fWwp7$ZCFQ&5#Yl6xh$M|b*H*M5YUg!j&Qk>tmW zhq;0%WYzI^bh=TY*`nJ|E{?M=zK>a>q<&QUY3=U0HalDH_{={!rzx#5rgsS4@tLOT zMxV6jgZ(>y^VQI1F<1!xg>}TK~+JY zKFGWueq0kl-b~X!{PCvmA@wU4yrK7q#o3>PRJVJZ?zR|O6=`2kHWvLFGRaMUM_(vU4{mUTeGalwsH*+@WdzwS_ww5^v9oQ+jWREnI&Z-XG;-@*f$5ysZF5 zbjIRS+Q*rN+JD0u>fipe)_TO;XvhHHGQV7vq3K^bz_GHL{d5H+7`pxfN35EGmFA@A zjo$bC^SwD0W`S7E%wrr`Z#QG9lK7j;8$7_T=?Nk@rPa9na=p(T&h$ zgm=^LPEza?y>R?<@;239(k>1}{U`I^@R|FvoZ3OI4zK9S1oBk_{x>^XTtA}uV_ZAL z**25*;2}# z?9x^(;>reTJz0jbm9y!>X(u(1(g?+yk%F*?J^y=7>Hnmw>iucea@U{Yi%htmNl527 zE5l84`pR}HYtqLZd-Ck6{L(mG`Vv(Y$E*8C_BfOpE7dfI!lXtgM~t*={0jT=p#CU90Svk8{-NX@>c%jEQ@|v zbFj*4?NsnayzpJ;S`h>Fx%`LsKY15FN#(Uie5~~QvZ}C9-uvHoEp{| zhY=q)KC*nqJ83|%uILnzSzn*lH23#%!!Yf7E7e2coCP^9BW^%(G*r>XbZeR`fLFWL zsb=lhNR?7&=x0TB8)mdw_D{-Vk_bw8Drjf&(b}RuPEHuH@^Lv*;oQ`F)2Za@oMCq* z7z1e=b^vn8&SRq~W$%#AqG`>PMC!vwmdxCxZzRj2kK@^~xtrhr(9xQ>3U-z=k=yEa zHi>T(AJ<~`|3Q9n;m@!gnSuyOimA^_2|FWkAsK=4hkrZ;5XbcAc1-zW(%ZtidwX}9 zP`xVjY13t!oee!*p}k`yk?__W2|YLaOyq|{$M9gAuOY`h6E+FyvKD&RVhN_Io96{xucPP1@ZGai;=C z@Go~BSuUVj11>UT3_g+0=3}$-JdYlEC#!z`M62GSn3TQLe>QuvF8t+`5`O<;)Y(m zq%sVDsCZP#qU2}<3?46Cl3JzqihpMaqb3)!{g;$+@|5nQ);%X2fxVriZ4l%ZxCocp zcV2LDa&d~|#)K`^4T}#WGw19Z`Kb)YChfiafrBB@AWzGIkDsIh2fN3%^;h(^q2L3@ zus%y`1jBq-4o%5HwA|Tp#lI8*vd`oL;@ed=2`Kmj{t|WAnyBpXQy{)Rol%ePr-z1? zD>*>`&R5a)dDomY`a+msprKUJ#-~=!ACU*sP-uuRK=UQ6oF}!=r^=Z1Cu{NyAwqjm zmZT^FK<-HtUiy^cQSZ4k_t;N?SJaUvB8$f#+wpfHBCD=@~#PQgGue>sEYG0$PK1{9JQaxPB?52Ly~; zTUj`a11N!NY%5$I#m(U65mRV*e~PFIY%%eljfR)DW&u#)Cs%$)>vF-bINMGw5H$q_ z4!^isB~jue{WC!f)Wnig+h>S~(fBCgmNyIyf{-RN!ow!y?C{qvoD;r9LpP&N!$nFB zM73nscu0hx+b&lCj~=jJMnkJ3FPS-vEAWs;S=-K5^{Ha`L?8zUrvQhXEA^#5&aH(; zHn8kY23dP{BLY|;(n4{(TelaU;IP=o-~kLfs}{r83RsQkeu_KMBbD_&30{!5mm*)T zlhIPFVuOiR_yBlG;i=V@jc&1+aX4A599RzN`vu&XDA<1%^F?s`Uu;Wsxp2w9`nyWU z4ETJOUEV1o?zA_{^|pD)cDUA9>*V#T>u<>@h~0Dhw4Rba@_1VbouA0q*ZL2gPWExM z89%;Lq9t7&lnJ8pbS+mda8P62>-t)Szd|$3I_Ua5q_45^jjfA)SIEINijGZKDKD)JiUJd>MtG%(5Y@p8Xe|W?7r+#%z?HTyfPk9_mxfj8k=+#Y9C|6!_Q%6rDIxt7RXNmL6xp zKF(dgW<^<=DZ0iK@gFCkZ(lm1dU$V)k|ZkY29K5dVFP+!;Jo#2Y~S3D-81w$Ka5d! zPT%GVti2xOzXS-gQq$wZ4D5&9w<}{%8t8EgGY4F-P290vMj8JWx}oM2akKKS=cUgi zd|~>P(T3Z$r85S+Cl4pN#)k+UX@+xxErAn_c5{`N3BfCxubQSTKZ$H93pzVRi;lRn zS;Di0wikkEaJr=xRj-xzQupKX&mHM(jy*q(Is5A`j65Unc>i}EoiU8nJ%CAM94lm&ec;lD5tGA%uv@7NE=4lSSskwVD}+2#8Z@O zLloW{*1u^zeDEZb2v(JjHLEKhA*;LzUBO*~5?BnHmFm8-+bB(7d zWER-{?4&{*N-#5YDIkgN%Na8i5vc#hU_ecMY;_*234#h!w*qy&6|=1r=wtf^N$MFp z>0E=Fr42$s5?Io^g2x^K)d+PF%|}ec*s7-VOTtRvdZ+V&1yQm(Qodi|kMdi2MdXxz zIzRcotpvGFr5IquR;C=!rADrLQ;zvo(Kqh|j5(^aI1<;(*aqA0_95^ zx2nU-^49rN>jI%#pGAn~0h{77uy%84m|~VAMtvvi27tqC6$2DfH zUV#;xi`J{;luOZjOdn?gLAa|duL0n+5v!;jp}DZB7F^u18VS%x@ycK;%WMKEZ(rK0 zK;Y$oNdLkXRUIiM>^Lt~uLmwnRfKpJbVI@tFY1idJ%inriz=XHnnyFaNI8)auD#m^ zLSUUfth1O1A2rUcDCTBIq*;-ThG#WWrN~!Sd=IS=@McXKJ*gACYR}Msv-(Q5ld?KX z{Oq21cH>s==C)it0R~Tzw{LBMXb%hTTYSL>+4Y6(ztQGIJ(t6QM=&Z{q5_bS=+dq$ zb>GbNAfvV?iZvAeAhi_7U~G+0!sSw}_q4~dC*fFLqPd_$oz_@O3kX4Pcz^JNuE?y? ztn#eV(9Q|>;EtS{5_i$mWzmG-t3$n{fx|Y-eZX^6_JUAB{WNP_`()6pJ*IlH9M~E~ z14!xiN}D1Q61MK8Go97UGBF|^L_g(5;LlEJ_9GZhs8!|D7~gMY*8L*rLkF77F5N9K zJ|9bSKt5?${$1n@L*Uf-beO64R{o!1V@;LhZ?&~0&~vo-%v?zwm@opg?Rs^%Ciab? z+L{8Q;@XJF`K;DO`$MU~9|HR6Z3I3K+2`M^mW90Bb?j@t#t1y5g76LRW7|G(nswxM zM_H8i+*)mz1OoRPG$84YQtjQrZ6tSU!P8OhjR$xl%Y#BK@#cI)D zMJ+WlljtwiPWJ++_xv6r1v4_D*?O;r#vU&|?#>OJlB$?q2kCbPn49}5K4+#$3-o2) z2P%jyX9Jrj@(KkaRH=~HCIsZ)RYwat()PIusap=anNmXF7DtauBx1-woSex93-mHD zk&2HPB>H;cD)g{TH{9wqnU{r{l*?Oksv?u!w_1DY6{ibgtY|$RNRTp2ReXFf*DQ{fP3`AL$#d(Ow z0x1nI<-P7*+Ngo_S@NxrV={z5=+!Y2xRHxD7QSAbRBJV?=^c3exu-10w}q^a#jo=( zm~X9u%)TC|Mq~sJ@&OySKp!!l{JHO3-gFVr<__7GYE|&OzH9YLW=|F|oj0viP*&aC z()GLw(Qd5h)O_~RAXvm$x->IAu$A(C1%kv53l}Rx`KsRpt|LNj78O469tdkC>=nC$ z8}LTCRcCK0+f+2FPs4A`pEWipbHCB7GKF!YwB=mAKOf$5>%3iV$px5bp_udYpueh! zigr)#vAoszSzO1k+bZfvTae+V8(k1rHqfe47ris2_(%Cu=BO~Wu0}FrY;A6+xyv*h z)x;f=s|AFyWVN%s)#5|=m%D4>GQV_+$CukxhA#roQr950POiRFYm3>d8z6me7_I;) z5XjLX`tM-}tH7ten=W1df6*t|=&xT0?*Av9O6z$>*&EKY{}GT<4fy@L`}em4N?PE+ zl~;LTg@5FI#NQk7=-12R&V>ma<3Y<@5sYy}I7O&yJ4Lkj!>dcD+E3EtvjdbjH_tak zK6~e6`fGS+aQM!CGHmf^+ahp6Zp@myBlg6Ie=7h+ zI>w<`qEoQn6}i+~_O^8&TwU<}x_=+z^k0#%aEWpd4zHj6;8QLgo_LJ_GKqq!Yg~;> zasxh_E&h-KnJwjKyyzhElih#W!`0w_mKgt=R`f*VsL$QX!6|2z07Tka$tb1b)lWl> zKI`Ky{VB%_3G5-pY4y3ly{%zyazMX~%P}USpk-F@KacrN$ozfA{c}uRDe>HNGKULX z3ejTPy_Il=12Chre$;T83)TR+x$v{!T;k~7ueddvf6i+9uPg(sZ*MG)>kr=4Y^fRK z-K<=!w(#l)wY=R%<=|ZZ{ZEv~CRVrZw4CsV_uu*NDFNPNGe3Su9QCXXNYvX)lYA(h z@J_PXto__kzrmWoz?*-0mG}#8!!O_Lx^$mTwWid>^coO2r?yHj!sv&sU3LD&AOW^6 z_*r}AtUCN*&eX?i8*A;UDn$J=(4VoN zBmXE<2C!L!*EfeR9OPO(6*(^4S+KT%IOn!}_S$A;0}GD0!Z4S=P&~E4nrfZjz9bp@ zDf8iQbIrmAuzO{e~8rqqJq0($WC`*X1ClZ+uR;(Y@L5{)+p}?gEZ} z%;`%t#(BuL;p>v|{F;SBTr!J-{AN|>z_~2)Hy_yk_av`>A)5eGt=$E`6W7Pqf|2 z3_LA)X5<1LloIUTc|HCGpn@uz-*M1Q7>7X9LtdSGc}@@d5u6I+c`I^=;YiW0{CA)o zI0?+=UMcOEh@nW1s^` zpBVT|zq!Z6mJsUENp9Wt-f_;!9;c2@qp3BG_#U zj;}U-6Enea=~OG$#a268+SvfENHWvZCvVf_hf2{O_+GUH_fG^v2yp|zOa)+r|F)^} zHCy*0&9^YpU@wlv3xOj%!<%O}4(MP<49!oQ#rA@(@1`{?TaEhvB{=nXF4RA+@M|wI z5c5$B0ALqF=onu7ra<6}&z=U+^8z8G z5m5qLbt*-mnXOM+HbpzwuyGCWzOZ%Bqt+?4BP|yhwXS>(%81RS(?~Sa(ffOGW#{Z~ zn{Fwb)q(c}1+NjrqJ4Y{+aTJjISP?wd6RIh_og6>Xj2X={8HX==ZTTR9*99$uCK0> z5>CnG;^9DS8dauO_T@1t5516Tf}!mVkQjm=Jr%)K&r5pSnnXj;S-D`q1U*fKXSK0| zf*zLPiA&i_jbWr0L+sdy*amEw`{+H!&MDA+=)yf3w$^xyn~v_pMgoUqA%+iFQB zkA`M?-0m=FrE*O&SxN<5{(V0kiYw^BXVV2jaG5Q!hA+W*ds)`Tf=D%Tr)7-7yR5ec z)!CaGTT375LeGSE2N=ObZ;U6iX%p2f&Ja&FHh>|dv@vKL!6aL;46CBzAAT=QUt08?OA zv(P%KB|WP?W9LM8FcGnwy`+ns^PJ|p6D?_xJ_Tf^Ef862)7BqmXloRgX_%`t9+Bu# zp+1d1c9e6Ad*4zTe?^EvQ4=VN#6c5~el#u4S8Dgorbk&@5^D?3Qp+hS9 z8+)(B+o{US>SoZeP^0ptyiqDCsmRFgg$O^`jpV0%|OHX~(&B;F%V8 zA2-+`mV+(n$z6ONe<9yjH*n}=d#L(M`w6je+_t^C__L=LjY=f)c zrD2>n2+sUIh<(rc;uug;4K`qzd z%Xt2n(R-NULn)cWv(D~>w_lcg_PSq$#cW}B!Uc)hpvxM}E-%fj? zTqDzn61cV#%sOgbq%fH`2K|NEZ>f|xqzA)Rzdfd|IBA3E(5P7Y*r4X8$p9y(hW$$QW_p503e1j$zDD+~-aWrdSW-0b~RTS7BPff^8TT z4#39)pjP6xl<`bWt#uS?eoW|aSAQ|HnOykOeH@plx%Jiw>34)<)X8j*d}uT8>Z5Ok zpf@Sr{!+r6Fd8>0^)7mBzw2n0R1$|rP&MFsR*pMLhC9g3<5nZY>I77b#kk3PSbJhk zV#PQTe0r+5pJWJDYmSI-^|kmRuhivEL;1h;rAL@g_}ql~9T{Xie<9NP)2U`M2eq6- zYd#K#&8Ya8L=)*i9917Tp(iCOf-cFpUMXzCdX6Y)9&q)^k1-lLy<9(GNkR zk~%4QcutfRuA10>fjbihRg<8dGD9*$4AoXm-)lDSL8d=sM@oHK5{a+*>x6f4B5#x9 zig~o~bspNJ$#>M}z8(R%<9wz8E|=8!OQs`H>lPKKK~MQ?)R8b^@gCUGKEoQ%&nl&| zw$Eh2$a&nHW`DIr@$I^L7eqAPlOYFBG5BzwST-Y$m2;68Zf}gWVGNvUg2P*)^3t{= zw(~)_?wKYk^C9v(3K1efSMTfi=`=h(eptO^!vMB@igHmWK=&?cP6_*4eSZw`M*z;X zM8DI&yEs*K2W@rUbL6~XF?_|f#MTBk6-t_+$V^a;{PN>AoG05P6}&jcw(_O5)^{Bb z55EWT!;lu%28ibcWU_0Eei3ldkA%Q6LrEI+y}g9KH27 z(-guiF+Ul&(k$x^i*$E%r-$AQwk_(kd|DqcEYMl_=L?VddTz+Gt#}&Hh)9AmyJ$X3Ot(Uz6}9M;|)_FD~10@|bw_ zTkx08hD*-D=n$Rh$bqlj&(AAQb8)aL>a_6kktyzD9`$+~fs_c9wtm&1BVNLeg?08e ztIzVHn1r-CZDpqVcDD}nH-9h3`4abvaelRnN@{j{MmCIUL4c1Oc8fC>2thYkrvd#$ zQq~J1h_e+6S#QqfPAH10MpySpTQf@-`i@2NqfF;-EwMt|*$UiNe(oWM{Da;gxs}U1 zKhUDk;ys@~!5AUxdva3FN?18QcaPp%<0hZ`kUrNtoLJ$Dwbnyp_aVXrjC7X5CtLZf zV(xk^q`gy8Q8=FN`@p_t~JOr z7@Za{oV2ynp9qFztr^+S^dxBm4WB#Crj!nidrJJ5_*(jqAQ98w?<&=NlUSO<*u|_S z`6joNFH*AwWwcvd6SXpBDR;QrS)CpgM+Hc5q8gxs4k;=W`jTBn47a*W+r8XR>>fbT z6LJd(Vv#+~@1}oD9quRDXOld`-}WlSra0&osRa@6Nyv~-=}5|8W!eDOBPMw?JK>N&T2zZnF}Rs6I0V^6)^)ZMO8fa)Pl9c&NdJH5z0*XNs&zF^trT6>87a z(Fbba+V3P`5-18|!YM)uR!^Sc;;PZU4XMOLNgUQ&#QA2nLpS}L*;5(eZeptvG}v=K5;9%8QV{aC7k?f5W99q0 zq5>ktkMY}c|D&8DRmbES>qLTQN1TO}$r)uy4%w_1pH{Z`5;)c_%QrqUn`l>zi4zr` z-}$lfyobgIn+u@kI}mkz48MCPk+pkjZ5@r5(F}_`j1#~w z4ts)!%KkU&5SvxU-ySo{xm)TQWM9nncm>Bu;d=G8X?u1(`riU^6^6h>px>3kb! zaDcey;jfZjMp#$cv=PWReqh=?+W~0Z2|?NVsS=m-0dJR1S=XQ~NcQQ6q&LiI=QTsF zFztaG9+F&h3)H)D4S8;h)0Xk(5x4xxYbn<)M{R1LAvX4D3E{w4H$M757Sf~ju2)fI zA|Tt{KHr$N=a?et?72=zeKK`4KIE2PPtD7u)asywN=401s0+E8Y@Y$~6smz zVk@PVVrQZzRa@Whac9z@1kiN$Q_RoHlOnij|=d-e#XPmx}*95cx(+{9FY@pAyAvn;0r6&<@;O z9F;hv6LmZ9O}pHYctgkPpO}{Z*m7eBdM!nV5*=JW>qVIe|I{>GRejo}u|FmuMMC<% z|F(}th^?7WXSLN<}Sg(HwAK4wLhxAPRX#Z<*CvaHj~Y3?lD51#+g8NIv1@G zdw*NqIDa4bhNAD^NxE81x`x4KxD>$lGtw#>Hr z|1b1$U=E}+U-@rrdivuD42NS)rF&qgBtSMZhWov$V_?xV9^ zEn8>0Tb`OkCG9NC+_6V*NQ|tNZ#!?**azI`zuy?Qr?*-roZL4lzPO(s`e;YUJojZ1 zt37xxMQ7F?EN{=0idFul`nzeL)vc;Mj^jEPEYN3J!b(p>Z{~sJpfgo%ocC|8akeKY zW8ZBJ|F^L5znAUpvFfVVj}=zj z?45Zm`;%U^$2Xe~cT>OZrFuLuiKG|={F1DFgUMx4b#O*K@@=@@?bu&(Jm)kNT>Mxc z6wqRAoIm~zzy5X4|6fAkDdUdYnfoITt=9Y!;77b|qcZ52w1Hadz(sB?2L|6M_lnr} zxovGr#APmkp)Cw~+8-{>z|SDlRVZ4W(rXr~Ih(SLWPby+JUYS{Mk zUf3?yGYn3DJa{JTZR?N3GW_D`51(DdRp%Vbr(|{ud-*QqI3-e)(*Ft}4=?}5{*SP) zm!{e|^+BM)WYEs$(!|j3Cm0n*4wyz+iM(cm-#T{B1P(lGQ#SLRFiYY4i(GSZxLRDI zi7Gp_sCE8Q_PV`%ccn;YQJ1{$=I!)>^=czKl{+ymVyk3>${0jYG>rKNWq0#M4YN2K z{~#i6tfenSUsN=E@*w3+C{KOem70OMEzf?f$-vw$emjJEMnh_069TrnkII^RJ{uQS zJo6t3n9jAQUL#3NBeW>1BhI^EHY)1Stu!_K}EzX>nsNd@Et_T}_XvW<&m9pwfDWQo$EU zS2coBu&a6G*=ZeS!0g+RKqb-ZSZi z+RSdnhz3#f50v)YO*g5aS9M)!ej)pfFHNPS zzye~lORB5WM;%NQZ8nr6ed%I+Q9-TfQdeOGxur)R!Fr}UvYU*am7~%UG>#a; z@GEdR_QuUlht)z1=A}{@NYFDX);}Jj$QNqrFQ!?~`Ne1thuExS9MN>Yf5`YqfF|jQ z2p_Qc(iJ>4RdPC@`l6cBu$c501BCa##Eg422>Tth`_E~T!%dulZ9Z-=<#s9UI8MIg z$KiHu$fs<<>v1pI@#gE6HKW_FORH3jIaEs@0J(S;rq7?IE9mr`zo&p(Oq{Lxxn)5P zOMK!Gclu;_wd#tb^|&HA&Cz2PG$-Taf%aC1YXPdDQ(HAzY-mF~rwxvOHp@Skmo-=iy*f&51!A&_G94=U*-s1;T7gBfqj~Jkj>km?>Zx4r=i$yZT!K?_4tdJk z*#EVJh8FOmPIvPFbFlLer&?ILJ!zfttyotCX$o?$ovfa16+NK&hY>>1rRbPVdp7Q+ z-XpKgfA4z*+w=aTqxai5g)`(}iIy(Y$n!9XzRl~-wN}_4&tzKbUcrL@2qpHF!$Urq zH2{HgBGSRxJ@NFOgR`ghLbP@XfQ@U>zLuk2hakjC$j$6cmU3B=(E}8GC zDcTK%d3jP*+Ha0X#OK(pWTXU%mZ^*Kxmok#F*RC1D4lK{Gdx+`?E8mXcu6=bOgK~U z8%Jh`hwgiEkFCGSQ=l$Ci1lDPRrT-0J@B}$s6!wef;iT-ECQO2vpba8W{XHvLxgz{ z(9tY14SiEj1+JlV1=(W)8FB* zZNdd}l4W1HIZd9U9R8$n?BRUWDb3DHq08f3(3GbS^dkvy;nm7F6OEpy^MM77T}!m2 ziTBliX@(dG1#Ibbo6D>vJKWC1vOkOQAU8!S@xh~!6a7Ag$cDsR)7~mkvk>4VReE!QrOT z>)P$xvr%R7XGP|3d4CLKkZLA67vDPl(NC*#r;UB~@Oeko*j=1e=Te1wn_enm_(W0`@hBzr*vf&~X!jyrpU5vxAkvd% zAG*cwcihz0Hl+{(kdtS!Q)^r%5DQX(Xy{8@Cx`~0R2Wyx9juWyrkhCBS_KTy4n%F9 z-MzKv9kol$RI)>ae9T$G-Ho#fNgvpRbC$kDBsy`hSCP4Bk@(zMQ_0ku$_(%~Fy8Md z!l4#T#+Edr7qnG0t$z}S*$C!f2{p>gN4t~Z_OE^V6t=RK{OqNgl62y83vR?CcOdt5 zy=mj^n697mJ$-4}1*$44iXYdcFuV zc@RK$CNv7|FqP6H(s{S z!q9u=XH2zHl&8yC%*h-xXK()DR)T9n6duZ{w({H1m_9at0ERpmHr;Z%1XGtFtwwI4 zNdim#D2O9zarfZ#xcw`@&es1mAE8c_be>B(-5hT8k(qZ`l(3{TmsJ4>ddn$yRkXM! z+0^vyuc8OW6N4p0hrB!*3dkiQNhf8coe?gWpj&WY1CdUfi@ID#uc3|Cx;&o(R2_$a zOu1-x_?eP%Y81}UWll{}>uV7MN6~e{V#cu`el z`lVatdPudB%$}uMRJf{$1RbXn_@RtmbIek3Gyx0m{LzwVymieEM9q3;5<(Jqi|Pgl zqM>6e?dD;Fyw8F1vVHSQ?SAxNT4RvQQvfbY z!srP>v(Jcd&bs;VN*jojGm&|P^U<%=YJv;o&D2LYaM)RI-yQ`mT9piKI%8W|0Gd_m zkh-DwO)b4hd2;l@(~ZMA5y$(ygX=;Q@>|BO>X>%4TQXx?-4+2J$6$rM;aN$!GvN6n zDi0F9NM+{8#C$0b9{z8^mt3Dv7yV&#w;!7jvX#38jq%@)bjoSo9L^0cvv_%`;!Z=rZ3r-ze!g!&cURD4#LdmwWH=3@_qvWyYT?{} zSfBQ^;3~c4GEuFndDV#y$#Q^!m zcYtFSFO6Hwkj!y?Y!-idAw4O5n^W(fmI(pe>bE4h_)TKBFVe4GjWrz=@w!g^5BaW6kEjNJ(v2c zPjwp_JeIZ4Y`!WYed%ElvI5fCrLQqSx|)r7iH2`_nQk5#tYg)}BNq?FOGAeGT(GsU zUPD-~k+wJ0LNb-+UpkOGMioO{z(Z%HJzUORirL;?Fu7UP^eAwmZvG`!sT z{T_-VNx`z*2!@P#Ieu+Wstdf-oIx_vPF<3g?F8io!@D?yF;-)6ToSnEf!|E0O2(TK zDqPoaGt8fBWzliC2Dz92M5!FwrYG|m86 z#P-Ci9Wn|UPR~ke>3RE1nsH1^Aqvx*YNah&*Fqph?X9cok5ObD~-;>eV%) z0$=A^apOxY0?Eu2qmkU6zO=n^w180nqG4gOZX_)x0y9}QMk`|@78jL?EQ?> zc{Sp}tb-?VZ@b0FK!^H_wB$?F#p%9Sf(fQBlqjp26`npi?VD~b9Y1FQmTz$U`eN6^ zRA0=}QIDF+0!i>&EO&6qO~!0AhAJ?UF#UMg&e<5UF4as9QrBi|nMQ@=`eM1OQYT=A z#KW5ICUR?@T8PgNhjs~9F$@?8Toty|WVJlCM#QF&FCX|?U$fZ_2ii*h2=ontuGuF} zPP?@Way-wnaYc@2&mjF9X@1V>04nxL0CjU9>W1suP#k5l!-WK98h1q+fa+XCL z^gSG_Eod)~B5O6gt()U1m_zILq{6|SVog9fq1$jL#ZHqvE6ykjC7MFO-g0}j_2;mv z1M16~i2nA;!vbGZ8HGtAA?4$P7phudcR?sF)S?4A*Ddw^)kwz{NB7;|_ked+z_Qxg zj>wy$2iNQcygI=Md-TEY%4f{Uwd7ci(n^?PHD&2lBl3U9?V~I|HtgrKV&zgNr@`fmU=khc-QW(Y%9Ib2YC5W$qF9W_2ra8X5;WI>hCaK2j{ySN~0kz@PlO4 z2>KeG{)!#75_RFh>Y29=jURd(;eY+Ih)GA0zL*R~sUY~lUcVra&B%=~d#4@U!Fw+7 zYx~D4ppSeR_D)Xdn_MJ^4sur`6&mS{c+nrfnbvSxZ}FLRL+x#*h#}zNUdhHIzK<6s z{{eai;=Z+iZKO5Au5>=>LD|zNz)0Q&n=owwmZwoVyzH+Y3F{r-=xYSj7=`>|I2=6d zg<5*2FJAA;Ji?d#$5?K-iX2 zly>j7ai*nXCnx^4Eqs#>Ym8<7Z}7$oa4{Jv>QY3(RMEG-S`Q);0@43wfPJhv^)a@$ zdt|oh=godWyHyyeN;W}gA5O9ShaOeGd|%~o2}sx4^%%&Cu~?as)EkZq_mQ&?F7Dj1 z5z{#;<>OZ2hc2(ghRt^-wFL zYFJDPJtFo!bR>@7KCpNC$lbizzs;oY*N*y##<4hF+e^K9cWcS-oG_9{qREFi=*yh? zq<^nhN0CJbY=Kq8E<+CwDPElFeFg3niFBN7>fhZ$0>(`kQA z|Ial$Y2Lw`C-Ume3y1edtz3t;>l-QM!47+D-9I3@q5Y*p1?p ztcdf+_5Lr9tfVB}VDYeYb@;X7RL=2)#^? zF*4Mb;YqV9#MoUKIuPZnZJ)FiMgI}Es%AV)4cZg%Yz8%XQNT#ZrXqT0siZ8N*c)n$ zOknoOq4d>;Vt@U9Kop^5RUlJ{s@UcVk1ZKE~>LKy4WVgeAYb4p^e+-8ePgqd48NtYo#| z2~7&Y6XecrMI^7Nyyi&A8E_JnhHO#9j0Azb50SaI#eHUalV(BkbXJ7*T+4TJdnkVA z9g2LmaI-Nq_Qn+5=BX#aBgf4R2Ux1kTOSF$WQ?IgNspza7u6y=ye7y`xLZF4iE6ve zyN=zAvHBNG2b%U9OWKMdJq2oW>N9#G@xMct4dUz z_8Y3M$UuQ)DKwN<2H(Bgt3GW;bbI}n&~4!#mVKcdMc1BBM`=7+Uo>b8%7Mb*gvkwr=8-&49h|%sxjGiBl_&Y{^3ER4f*8^0BTlS zXCq=Q@X!Xr;a$@L&!{a2R)F=J^Sns|rCMqEC_5SM zNsHGVP~9q)EQcVBD+q2awV~IJ7=wR_Cqn0URT6m;sQJU)u-sl zbEsODd(mt>O4~)s6l9Xm5`t;iy-vJE#oKxNRI_5?J4KOvNtU@w1;n1v8y-*y2zbhc zBxz=z7A!7ea}=Dv(Q17-fbf;5^{zhKYyI|k^BVRlGtG(|{^`k!S*eQ+3BTnDiyMg% zHC+nb>ZvBfXc~x?CZdw#JeJ_3QHhZRc0QuKr%QfcUNJhZBMQKJw?Zk`^iuN}6R{3` zkD}dyls|)z2oEJT_VXp^u2Sy?+AaJ)_Cj~6M;4yG|Jh^iL~~QB0e=qbGFaCZdqlKX zl^@f)SEN=4pa7b>XW8xHg{QyWQa05t8CQ&tlvx9SPQYM^m2kKn{z;5a_`s^m!3nZG zuxvJ@Ff)d_s?pAy#HMeqH|dgv8`(Ig8dtb>$)iOe(^V z1;Y%V-+eXUF|W-zMOMu?{Z0uDQ5$#srSa^ola>5% z_kv%dVU#EEcKfUXN}pgwZzpWzIG_piM6`8(wCYjL+n$SS#`Y=d$q!3NYwrhIN5+dJ zBx5t!n}m}|KGu+&409_4FF;K(;O4HqL43VV`o+UG6=Z^0x4-2t4ldWC82L}VrQho` z7mF^{fKC-5368dS?CYvqOnVa!o_G>0z{zy$ppG&^?`3^JMog-s8Y}7ydQmc3etPsXfzF2ujs~ z&@^Ljyij(8bMC3(Xh<8&J@3^+a*G-45HHxf%aY{s6wVFPJ=4FuzRhsPKyOnOC!zA0;7W5xezp9Xc* z>TWldR3q2iRJ&J*tV@Syf&2^khdZe2xx*I$=^FD!1{zfKHTt@LS`y|>HJWNXu2i{l z07rY8UO%ee(uD`+Q9|R&xntz&(R(J(knT6s^yawV!7|dRc7s(5x3W$PiwcxgNUs5l zTD}ij>wsI=w6h$|obYm?-LjPg{1>g7L|BNcm7(6?yriBoVqpw`M(kLmHj4EPc+sW0 zGL0RKnB%OTHF6kdSa2ieaG{hNlnIPMoo{CkPoHnTEXjWZlh}QAlukGdKn-8nlN;?_ zX)=(d!R3rFHw9-0STG=MYS)fLGwQmd5iD7Gg+4I^A`wNtoC+FXIFNKshCgxCmK;jL zRFMencZbI$U|IEOxYQL(o`K%WhVv~C-3Zztp$yqwzTow>W?loq)$9ClK5=p4e_{h50>1RB{> z_U;17%&xffrnwdE3U=0e_#{fvjGlpcvtWQrzUw?^?{(F*-Ok?kV#%8wYm2W3xV0N?%kP%0w&;M8SMoRl$oX8ul;5{wUDK^`v|A>rqOudz(RVH7noi$)AT-W1vOg2Yxa9M@pM@YW3DGt+V(HykC=%Dzeh}?y zzY6IzcCkU6-vs_<_lO$ER;gMvd?HvLp)Gi(Uf$z-u3NEBL}jr|#+oLcD`g{VgY+$z zML#@^JKsL)am(}Jr~C8ZyBs{$0G94=Ry<<9QrjQsVF+Xv;~6>WowSAK;)`We_C1Wy zx6*AFQQ&yHe0MUekK2lZYpUPrjlLGp?d*Rm)kI9bglkWX67|c%9QDy(EM0$YW_b&g z8gD5U2HL+w5AMDma6AEyiXh2N=#XTe3iEZz-Ru*Fo3}RmbX8$B@GAi$Oz)W0g4Nov z@z3dHxaT+AYIZg|cEpHR1hLE7ap9xZ15#NVW$fDb5IZX)&GrU`f2Wt9?S#aj4^B3^ z3U{J!Pl(~ey$g(#0O;Yu^^5p>@+bb4njy>(_cO1=NS2D;gNLQ0u<~#Qc;?=$)Z=JX z`?YglqQ6PG@9|iiKrw9W77Yl#fA-76{w~h79`U`e2eR)c!~Cv(zM!j_c;Nn}DVzqL z$Ga%c?(YrSWNYkIp)TRK_uflU!taS5;{H$Rl$hQFv=O$(UN}7BuQEBJlu~I3$w!+o zdv_iiG>PGML7unX7bp_BZigmBe8e~Z+TkqxKiB+Nw(tIsS%h5l27V5!Y_Vm5ezZe` zrv|aQ1bqEFNM2SRI(BFI$PV-$OLW84=R2-@!AOBV%1yArKE@u+YsGKFqr4dWEh>Ek z-mYnA6zq>_2@HzfCDc&~%SYbiyM;Oa6GV~wmWyS-!V@cYK|8cj66; z_Qt)5sx61|X$Zsz?klD_fG#^1D*?Qy=7p#5^he)TE z6vi0tr7%&g086lEyvO`4nVt#$>cld15Rx&Rp9`nz8?6ZD^*o{l>Co45)OwyxKP3+y zu!+y}&<|ZKC|f(J{XlVFWM|B`_0Rt547vVVe4Wde6KrX4hWcp2vgTieWFHR!54_}) zBIcLEVmy)qkYUMHL(Jt1`i3a#N(yC%^0xs1YycoiT*lViL>V=IBsFy#oi7u$@L@|x zr10~CgJMRSqF3K@8Jo>4Nx5jvn3bU^TkROw{+TSR;lMScR92E z(|0*ap=InA-iTx{JCw-Wbx$rF8qqewgiXL(FX6GzgQKHj@Vsf97Rc^PUm|C>f4ag6 z(x?SbMfTOk!oLDzQkP4R58+~D<`%!>r^h}NhzgLxc(*R1&P-qhB`v#)buBx zKHbgD=->AQsU-%%>B0m!!QzTD9MovFTnnB9r0 zUs}WGm$~b-B^G**2E~_@3@3lhj^?apqmxm)QIzki^rOOMNYirJ$o;p4FaCz==%sgH zU4yrN|LM?1iDh!o!lqOSox{UQFEOyKdtE#pfzNB6zZWo^m$9I*{vj=do9j|eNvMe) z^ji))g^u{@5B^^>^*>s@xHh_7scoPxhhoyU#?XDbks$>iFOdykr~;voCYa^N2D6_; zhtZ5Qtdw>V$MdB4kN>feQ5GMQcwaImcvr3E2$j-NbdlOB!?17xe#%8w=xHb`{HPB{ z?!DCy`=A~Z%IM)z_5>x=o@O=`0yQ)$kZO&`?N*^UnJh8k`!IZ?O95QEo~)}OR~ETu zweT3nb_(zRPoVI>_Eo|Q!$wA`HnD5s+~A)}Y-&*f>r-z%tTLZ5*c$ zrEPvAXlE^cxxZ|}-xNH1Iz?l`ss9r&AA^3V4vSe|yfS%m%hD5x%Z!T(o93r&GRQnO z)_rTIp7*3QLX!b|$x<2ZOSj|C{5{YDQQtOQ-CW{5SkVd&ems80V?^);2Ljb^Y;oMf z@k&s-dxCB&ydzY$`5eGjt}I8HStWxxhT(DBdPYt|t)Q30C)Rd}eqae^n4-^v&RWeyhvlwN~0r6m?BxYu4w_7tg<34J|`L-B)NF zopvLp{BMW00P3?_GeTc+!UA6HmlW?$?Ph zCF2ZnReNwGcS&H$w`N=JL})C_oS<7dH|Y+K^m0(#SY~pS!S-MiVs*!K?eTD! zun%x4yM?$?k-{9goms!8j;$r$)=&IN3JcZfyh-`Tt8PvF!3VZJE0_2C+meOM%O8H- zoEu>4Ez`mHn6GeVX;C`cd4T&%3FvAQwjR}mQT8zlDTlVl_jrr*hp8JE0}7xi^ukNJ zoV5bGD_j3(Aw{e@9%sm=Gt+sM7Wr+F;k5DPwa75CQTVaxhyRFcv!@-s!R(i#yq*$j z;UN3%jC`}%C*ib_V3x}q7O=PjXv33z3%#$hwe^3dqg#eG|Fdn5n2{`@`++QIy`A6tKzp3^W?kn@L=qq+72{*NCQ^wK6E zS%)O?&B1j1At+XzeW~%MNWU21waI0D1`vY+T{;Cm`rjxq9!aoSJLh7qNm=s*%R{ny`-hz0Da7<3f3rDC;3OPD1a+G{DMoVp2`2%W)MW>>8!aanb1SJfp zstl=OG+wfEaea`F>8C*#(ycu;c}XvvG9Y(Xr0t>+EyUzX|lsz}$F}=L63u||?{b;lrOg~9U&G)Ib z6sX(6GzFdNp9|B^p@{RJ3)8T|nw!Z>>D^f^q8#11^sPftFn|d0Rifx$F1Q2uPLy

Ye4KtF0TjW~}=InB*Blv+4eFI^DaCtk0 z-?B?DYb}tn+aB5;Vqd$}x)8D6@qCDMcA5Wm@l4 z1shx<utg#m{R82-8V5yKZ6Lm8)vW#Z_6n( zh<6hQ9_>pf?W@5jM9vr58v}2rHB|;Drezd*k^*vF(6N{~w3H9Pxoa{2uK@3`=XQ2d z${W43F{@$G?nCW0jbVuyt7yuQm5h20qJ1mU#F@IN=co@IXhWxU0_ky_(^I*~`yhyd zVulUdRDoB3eNARErk`L!d48zd0#lQT^PG2Y5GGbR^??4vST3LgJe?Wh+(Zn2O*_i3 zZ5jW8|I*59i=q3UufWBYIQ3c zJiKx^(vuhVrCAoZ({%mbQ<(;pz6_n4BW3x%8)yK1yfjzyJ^+h=D%FMF!6EEM(jOzZ zmeglqmomN!`I3;TZs~rD_QfCRBS*eG0#gU57H#fSUU~v7B)v>jW?vHAK^Cx0ym#HL zf3S{2UeE@yUx#+O&~OmlI8>pLyMoqHcf&{ZecRt31u zg1xd#dIFEnim=;jHfQV2t&MhAVrt5Jk}x^M3Pv?bbg4|VLABI8z4Qk5kAkzb8QvvA$j^t9}6Pd-fv2 z9*~(Nf{AGri}EEx+mKIZhBs~EF-qC>BkjG~b6G>@`0$^zIq>>)HEn#t{kX?F;72K2 z#O#dj39osu=eY#fjn=@pnkM;Ju0JXn$SCMu;2>&pZ`_^DU zL8|{#|3}bhMTL=vl(LQ7#be$8)~xx;(eCS5?uoXPS;j{(;IuzKK9_-!2LOs1xySV4 zvwAc$#O<~1c?Cw(J~BlL6K+HX)cTchAq)H{1K8|Fl5M+gk@y`>XwFw z$NeKEm23J|lIScDu{zUe$YQmn?yB3_v<08`GG>05 zM@J@mZHKo)(vhU2=y?IjI5ua>&T2cV|K*0@*TEn6z!CmlnaPfj@54+H(&@~ULR6%$ zAO*Hv-24m9-$^*PlepUw!4e|AW-s%gFqQQx(V$mlnwv@C(0>KozhY^#lhRDDzWyh(wnGsdA}x}sd)rN-txt53hN5T7C;YaZ!H!!~(ER6`LlcdABq zde3O%=}i)!1+9vy-u++Hn{>8lTQ~fbb-M#PvyY zIB>PdY&l+6j$HW6YS(sIV3`lrwh-2noB-@o3IwYXh!CFx%@yNb#pqh4NJp@OzJtJX z3`a7R?ahjT>YJ3x@q?{%2JlHk_M6>! z3-CQjd@t23&RA8_BBa;Y9GHHl?(}Z@Wx%aq1={)0gti=;$j4HnHeOflFu^mb!>uUP zkq1F>PljydD$fvHG>#%FZ#PZbd>M97A7+QV6*_u(c;XBohJDH`GjvKxZ+p`_A?vwB zbmWv2ZdyGA`c&NLeQj+8{>{`cUS1O->w!G z3VREy+RfMAMeq9%hU096FA!tPl&4VCq?inoV7ki-4Lk34sFT0ZL^4D z)Mddg&zL=2aA)dvq1a%V`;ig7n!+~rNi6{yv(a)NH3re#Ae>+@7isZE@1d|N()9UQ zzpUFe0XsmsE;;S%?t6?q_>Ju5M>n~Z1JeBM>t`IZYnatR`2{ILtH<9LY3UJqY+_zS z<(Kb@B$dNG2kSqj*Q6UPL@3tXah~m5tY#e%hE5sPSXp`ncc%_!wYooJDSL9eO&#Og zkkdcHC-Eem_z;@|ka;uE%^M<0^q%J)g%xfcI@GdwI&aj=GVr+8MwYm?ttaU-!d0uh zoY4jbGV?HQYVeyAo-D#*Equq@z8YZCQs9KbvQ;#8!xl-O!N2(vlO_cCQ*;ne4RQQP2(?3A_}t0ipQ?bWWzd zGD^`bzMXNZTM76HNOz{mI7`DK1N$%*7fS|9sx*6u8Y++~Qkc4Duy;Bz7!EN^1YII zrnya4rBSUx&7o64u)%NXY#=zO)P_p$J+91)tQ_3GqQyQ!sS9o$5NUmu1057QW;Mbr zF+wbsvSO#`9xL?faGY=|uUEhvVK$)n^OE#qy9N0=qB0PnK$4jPmo;c_Y2BmjLX2br z+SLmqwO8EgYF6H2l?AmRSHa%*J%LMb38X;a~z#&F^+~Ica;7$4rLHa=b$0 zRyRrKV^fO@6P3{+nFYeR7Ijc*%6aS@ zIJkT;UeLdMFiPFi!z%}#B6*)db<`g2{JQV1W%>|anoi>OPWE@awUvfQds7DiE=`6? zLy%-$GsB3Ao?KxFSbyalP*;$>BoFZKpyyBs{o`f1=D}~G9=a8)5jT_}Rnv1+i;MhxYm-9~_m_DcWPj+5%~--Q)_ zZ6f`RTeQ=BpiZ~@EZ(lAYvzp%5=fNNU!ViXHBTRx#gc66(awVYTCsSSCD6l9Vfdc( zj#@L|RJgv&U%DjpX^YvzTwBvEnP#=XwF4`$tWQ9SgXt6)mVL+Q8g zb+OCL#&;Wqj6(Dj;D&4=gQH%3prCdqSWLm>Ha&0sBDyr36Q|+AUyurjp{F+N6B9bP zSy{%+NX9ohTu1xq0bm|+UaE4abUM5z%OqvRpjoc2^qQacRpOarc_DT1~pe zMX~t*Bj?&b0kb$^z%(c^R_>;+2Himj(r$O!Y3mHVI)=*ldZ zIJRdw)VrI+KJ!&~*sRg^I9C16Y!>stgCTr2v`riDXe8A-Bcr~eqD5-FP_p)I_eIlM zE3vfJtoBYNk876cplkuO@EmQ6^Yz#ZcylTD##|m z#=FTHYc>Q#)u<+r7%6&#rw&Z?C&R&t7~PkTCOZwq{J)zgR6Be3q*y zDmM>;EAY-R)C-LYRjCJ?rx>GJAMkBbF)}g5MvvU+EV^{N%cImOYyX;ggo<0!czdyE zqZTuJon*;>rv6mU+^-xTc}LcE>&h_n(`9e)_nLw?I~;ZOnd_G66-O*MBXQi_C^4kT zI0cTG#=AY3ZSWlQuX^$9N~2!Mb^H)f%=mL~-f;$@>fAzVxKMI`N5l79V-??tnhPbM zfWzTc^n@pdNutpY`*IF^4=ZGnwq z=8ze^btOjm5a&h7|JE1rPX+JvjZ@n*m8%VJwhV0dxkPZ*xne?2Kt=n#;g@Z3Tq)Tb z+r3UW)Jm^{hcH?t-)zj`If}LZ0w!Dr0CauM`>MYn-9In(Km71G@v~QuAeuc-*>)huCie z+Cn^|qc=9WMPU@V5yObU7%O}wtIo<$&T5qcRLgU0D!Jsy?2MK0zh3`uFzJ69MtII= zC3E-j3Nyl_b1*QGsKV;HM6Ko4pqK@2qqEy&;bdM_${2f%hvR;+ZV-K;0nq33_3q1P&zB%~hquUQ}4{^ji#$~_EE6h>m2uB68 zkzoWQExE^XR6aZ^0vpX;VntDq_ppM&v9kR<+%6}NIwYBzTsW+^vdxHb$L>uNispgvOQ0k?&-^ zvZw!xX{)Tj^PVezN2VU3CX+A1}O z+pJMwfG{)Qx^-5m@CoPi37*m!AP1#A)y*s$FaACq^aiX~>+Ih#u?rMMhCOQggtiB% z-^N?D@q@r|&^`IZ%uuZ>&@bnf|9_OdcRbtc-#^|SRXvqfRjtM->NrJ(I7MomR&iQt z)GiUT&LLJ%JF04nQM*=g)E1+t8C&dXji^nG5__-sUe5i!ACKSX{(gR+bMD6@e;|?M zeXaNPdS1`LfmB&~t2(H}(cJcQ8AIbK|3%C4cR3{EECif%3ge!rP5)e3Dwf%GeCFMc zwD$M%Pp|%{vkX5g=3k!i6m+1ig5lqpfd+-}Jf1?onh2z~E&N>?VRJApvO*frGcW2t zkH>Tpm*A`lheQkg$RCehD=LMfK840d;mQu8LSv=MG}X>juYeNp7PoDozn+G213H4m zwIm$zO8o%>YIQ$ufFGI<8R3Qq$&jq*S=hsY{SjOqxacav1D7&g z5zJ*zV82ZIYEY$z_;>ZCU9ZP1Y^>wETkse(2m}g*U@PR`%Shv4_ zS+09=l9A-yZ(oYhk1PXz)lR94_qs&UxAi)5O^5=9FIy1Gt+3b8$a(gISQE)DdE{s{*Un=U02$gcI>kdhMffP6<{uF@}58QYlxgp!iZ z$jNXpZn&29_kDI1?JS%e&Fx6a6(W8-r-BH;D?zGwB-P4^2Vz85<-gbD1$nNLnHeq@=hD*Vy6hq0R_rZ+tNRsq|od&XupMO73&NzLs zA|Bzr#NYKgOlrE3fKKiSJ>-q&Bt_?Mqv}6z02`|ho={J~aYQ_uZj@^ud4eB?0R=tO zO2RaPwu;gq=K-byMIP|V73;1*vXCseTr3 zt=J8!Z&rzKf_xKr3*aEP5#kgcl5Eb%^~r*6f=t$qlVw?AZw38*0%QPg?6`{ z9(+sg(oBJ_Lk9#>HyJb$ z1t40-UPgkI@eX}EUDX5o7sX;r`2cKg72{1JY^1^_;$Kk2196!6`p(azi)O%#Ea31K z-$EI7L=q_l`i>!py1dZ+-o|{5NNh>Ys=JGk>$)b{&WlJcWR~cZ7b^lnSH;twU5V=b zCfKo%pDjiVupJ9^7xTQrO-`LVay!K!G?;y>SxYV`=hr&jwzm@j?i``ObTAY$f=*t6 zN<)Mk;|TN0Ys5{Hc&cG=ytlG_J~_sS7gYKPZyI{TtYLc@d|zopDo$krAIW3L?)7DI zKcVLiWguAJ%2Rd-otb~#>L_ADb0ZWdr%VKlzAY5NQMj}8Rv-;wvAeTG2v{d(dVhTY z@YRmQGZV;Tv*EXWW*H)7RS)8mr{Xy}hAg+CAj-ud2PX^ZY9pJU0jba*753-SvoJb)9jAgQQe9CeA}^GopN`&j7=Wb_AWR`$i%Oo5Rj8qOkJ`%C0m3v~Ii{jCeG>O?crx>i}~Rz zA4{H_n;H({3p;h#bx%rt;5t<}tYsoFR^dkMTB>Uh*+5{%TEM`Ss(r*!wWgjT$^lO- z{^R>`p3!1nr|rG$RmnYmtf|TD=*zIrdi~{^ZG9t*PP6y;SgQr$yEfG80V>w~=EFexKpL_ zRG+|p-FoMz($mkMs;y3QQu!wa@iipqao*TUQJ#=0DMaLcnR&214-=jlIXcIhl(%ae z1IER?p0^<{lY}msK5=EHt}`oOBx>)G{msOs{J~A$blr-FN-nZXAY+e#OBn2wB)fcBBolC@p zDx;>Cp8oMRunGjl*nSkFP&=2s{EkIsuXEdU3n5yqgvtI=R$G!}-d>ccYrOm;wGy!; z=)Ax8$k==8ZgO?#lI|_sPnJVf5UOAtY+J37V z27s}KrHSIO(pjErf7zdvfHv)-Si9dg)O`s= zV9m#zeBqCO?X9Fo1W#C!h!PNS1osint>mj5yqPLFucXw=Q4KJ%v(~t2g-P$~OEl#o zY*(P|NcQ_W9apM(C4u!V5Fo25CIhF_Mn4gGs{nWBj^+Z?u8PPE*aHWI6I)f){EJM| z1{Hg$Bm2w&Bz-BctEVu?$+n4ag&!@TjPGVd@ZrOeu-f~;jAIZ==<|08UAC#@bLx#5 zDYq+fSN>46fVx?e>fu2_I21c4w*tbiCHqW?STD9O4CmO}Eg1-zkJ>u>Wux=ZauUNg zW7J>X4|-<4*ygc4Uvpc5H#q1K=^$m)mzF>LiDYF9L4u>E2>ig@PY*5;h$Vw-%K-?s zM|Ro(IopH*x6=Sa#0M+qM5byI0GiumwnhR~lLjjGTr-%ZPaF%ycM}ST_{}00NUYS zw34Gz9fxI6kZAs)dGw@{nCO=z)$TY1jD0N7gVi6^F2LHtFLKrdKc5=9}H- z(0)=8@rPe1Z7TLF{+5rvS;rjZ&{jhD$U1{N45n%`}oPDF$o~juA;&9PCQu?KH*q&TM=m~3^ zo6G8KtYo{C)LKFx#AA6&QPga>k+tbbxs>&FDo4# zMLN@@rQ7@uZq9aB9UOTjQ(F&yKA~Uzl14yH6i?Z2Or-3^D$ljoAC>f5p^uOCqr<|$bDG|r1EQ9UQrgLgMHAg@C^&GUs+Fa@{5d7XR$R( z)TpV%nve4@!@1GxN3WIn+jz>N*#mGo1fs2#nrsN_~{W1rCT0O2C!O8qJjq$YI3tWI)zC zs>wW9b)D2ZYZ=@A^4q5r2w!#_`K#KCPq&SEv;YO)L11e!`FaZB=Gi5+fxDT@x%hu+c=01D2@Vc*={;p zx&^U9JKpA!+|Kl?zANK3u36$+eXR$w+Z8l)SI68BJD4Qv@l-!lPtErQ*Wpo-o|>iX zlro!$?^Cs3v27w$`!1i-- z#U;<;4`GHvkb^7M zR2&h4?J6&GEh$1Scj_@uPOMfkfsfVv#Y7x17zETjApP1Bawm`zM07VZn>3OKFfCHE ztXwnvfjxbt5y`xvv?+-^+1t-DJFkI}q|j@wpW9Uo^LRM~J4S!904Cq?O`1?)PwG~v z*PL4}UdJ;jIa|mqxg%GK#njZ|B&7O=f0=N!;wt5E+O?~^-XC!X0RjFJRReomw0hft>oHT##+^^>zKYE}|Qf|vXFd!5U zOV+-yQKZfk#p|-Y=`lC}=S>IFr2+HB^N13OQpXoiq`odN zIKLgeavlvNJ7Bwg-KFYwHc{F3(7lXDlFSs2?fv$ z*#SW?stc&rz=>O(LID6v1mgNf$x0Fn9l2eLPy=KXl+-a>L_7=e9D?nb#gFoIh!vEmRsqU9}$WO5#(c$K3frx#5ms#@E z4?mMe&R5UqB`_ldJS5aC=>l)lX20r&NNj`}DY6op+QL#3nw4ZqccaYt(k@i-1|oos zdEklAZ;ri7Z=sIw3l@3Xvwz=q{kSo*(B}4i4tjboQ9YAtT{3~1%OJGAD518-Yxe}E@qN1U0iLv}vHCTM6W#pU=%vZ8;r0edH=bn_8I*(j!6?zqBT}PyH$_>i?F2sKZEX)ad16V)b=cwJGZ@Ht%&0U?EM(rw z5D{|xpu!d6G^7M9(2K9bj__^t5kX~a7Pc1gZc&8;PX<`4fZmT>x-p~NGGb#HR94$r zT5P}3lv-4PbBoU1_K@fYISnV*`MO($0k1WDLbJmi<5jBTY#OM{59pU2Kr$tSwy=Nk z2>Be%QbV~`mJ|C#P0>F36vz@wZY5dv!Mf+Pvrf&ffwTwl2ppS7VHoiM6bPK@-mz-~mkq(AMVT zu~I)H4p`alh$M7J;z0jwZgoUcf~s#P4Yt;==Vyd+$nNW1gvO^j6-Q5izsDGkFji?6 zs!;ZI`uQVBLFSg9byjvZebu)uZTz1mL^Ca*C+xd((G11*PTw=i;QNoxG^XaS(%md= zTB#s5z%A-4Ygu)7sJT91u(6#~HeI7T5V~j(K5rJ}!{6IqCgjKh^AN1_<0cbL4A{{H z?$Hf+zCx`s-@NY;WUVfFDdM1LdKhtXP}?n3g>R00BHMJnkuOPORo?_)D{TJ$kyHW zw;WR1-RRK$P5`F}Pc`G23B+LWktb>$^c-{Ax*}|Ed%pc{y#2}=8GvKlbHN`^^#wBt zd_mZ!ky{c{z*`8!lW&L^1OWG`UrJd) z*#Y0C)8ikZWnk`Po&}anZcc2ZgP4qxK9CraF4>`A7$=K*?fus@J7bzTjw9OdRwmVK zzQ_1q;4jj?rBLy9y+~C|-YJVlwyjM_F%L5+WV-02sJMIi>J}~wg6w%)w>cZ|0a~3W zF{7kDmnU{ejLJ2#di5uCB*EvquZUkLDp`rZ2r1h@bte;&jM zNR1K>V)KvLgeFIR&kr-v-{>0aDH^Cs!dCy1Cm4~1V0QPB^jzI`mC)5E(H+HYR}|wj z7^Ee1+20Xvzt{}+i7k0!3UWA2?Z6&tN^35m$>)Ba%j0l}^cm(GTQX1|WFKovO28R# zM9(G|Z``%c;%&xT%ac{eUO-NZcDvA3kiu97GFk4q{>Xs63uYOmfD}ZFRKet7%);q7 z2meCqmAFPhItD|;WrYF-fKbfqdD3~0BI9+q*lv_*o<3#fOVuZXe?(j z!#tRyCL>iyqO+MFGH2I{FOKkG4sU$zrEAC649ZDx0hOiiEUl=J;)|eeQjq&H607mZ+1fN_S_>SDK6~yngGi*+$h=AVy?Sc)cM6+OVv$$ z)>(ASA#e6#L8p!lt+<3pVVAJeR>iw5W|sMjguG<1a-iy`jV+1b?p_iFXj3L7kdvoa!^ACO z+kdM&Difo&0&k@yGERAVvgmh-_Z!`1=EpV!$+kTox1-bDwg1l%=6@=1{(4zp_^&IQ zk%7H+68q`5)(6tX>QCy#jNKf=PFxR)JoVspCxv3+wd)$629*AH_2+*ZyUPP(ce<;+ zv({;o|Hs1kU-$@I0%r%qQi`4eM>i)m!6e=)*;8&?uPO1|!>tva z4jB~_ym?OB!l;%1i+XNd<2q?ea?!_%jh(n2nyj4DaM*s+Tn+qW7O# z{r^>px;?tIeR+}ZI#Idp3jNkbC#Em+jwt5eNLGu~L)MR#sFP@c9n!)Y|OfSEzs&p$Nut>7l84 zA}{auyRLjIHw6TCWECc>J2Ev7)tGS|R-d4b(g}xzpDjW;5UkGDXKK8t(b>9oQyiQO zng6J;d)Jf=fU12)hg6^42}(y1rL%7|QF7t@kGD0Pm<nk&{{f!-<33! z#I@qnxP~=_e7L7oBDI6)5#mejq$igR!dWKwa}#o$p!`I{U4hCMwU^Zn0#<| zphxi5n*9n#NyB<{7Lsj7>@Nf0)8`Fyw2rAmP2xEAIUc`>-HVFakchf zWo0<-=-e&?HCc7(VR1>8NMGpmW6WiiyO1&jZ8I8arl(?W6D(F;998H~he`Cs?Z>g- zg^b9Tbbf31st~51ubjVuN$hXBc2w#f^mMTk{z%WEd%txoE`cYyu*)PNSlr2QsWg`M zuvpxD)*apg*u2v!Dq&GJ(`<)l-x?S$!r`P?*|t(2=SDv1$c{s>OWaBMQ$!qF-o}RT znz<+be4FR%7wPA0Gg=kgX58*kmggn6EuHw>EOr`2lL!K)3FR>TkHqqp0{*jhsBNu} zOMVSVqyzkLQi5sU*ifI40T(I0XPkQ22`J_U-lX5K7S3l)G#y{gfQ;bFE_F|kg_)fe zx{Nf5E(tMNp&#k#qR2Nkml}$Os?jA3&@$oYP8+u&;u00`7CASIivS0OW4dko165T{ zIkn^YOl7Xx+S$dGmzQI}IQ+7_Go#mDZ$@frdYG7!ayL3U+R|;Gc<)?93=)Z4!3K7J z+CL$Wj*j}{#AIcWN=jS_lP8q-B5;bmZCqV7=jw3u$8V&O-MyDJGiKDq#or}eKc7zd ziWrw@a(dQyTH1K#cdF_H+zf5@>#Y9z&nUyM}iCQU2b&td0Q#PtPNr;TMSs2{9*mzIl+! z`$s`#?*bh)xNhED!uSn8@Jqh6HqFqizA&9huDO{nah2hU-eep1y%lkJ{IJ1qH`%{;VD^@6a%Ly;F`ZI5@gtjH)p;w5xq{Nl=)mP_lLxk6{9=8%KmfcToD9RP zgFuhG8?fEbu{zJa>UXz?mfkBf(zyM+VMf@0<4vYTWD!ldtJ#PRn(X9@GDJnp6ZY<` z)cCl#YA2O@#zY=J+ii)g`eFeO=)#}Y_;whXcn4z)Y-9asOZWe5OFYhUy``*n2djw0 z6v@iV!`kV&wxP$q>2IOLNA}GEURq0t(RhB}f zgeWC}vs_No=^RN>JPRYd**AcKX>nXs`v|sWdS1O;Br()```M<(4*jlU?}I)~3=MdD zizpuOx`9g)@JKE6Bjo{cL=JE~Re!^e$hztrr6sC;@>;eIcd(ze4W)cvlOzW;r6wz1 zB@h8r5XHAvg7eV4h}Dd5fpCR*Q&>kAZR266Rf^W!Ofn5ivG&Dw6^VdvOdVG&)~^Iu zBY9ctXeI~F%P-LeY+uRJ{jA6p;Qa)smU2`!AMOC5z^5pvHS(=i3q+Qb3t2*-$)xP7 zGp!{N*qkALP`Q+7ZqR#X)DsPNjA&U%zfI16${qseP+q-`1E7nP)b1R#U=!8x0&R<0 zm=LaOh=?!`yC3N#Hrc#GMD%Yds5NQ~`Wq#)<}Pro-|F2yx2aL6GQ;5#>}`z~FfKN! ze=an@SLcVT)pKS99pi}sM%(v;#Qi2d%-^V(HUwXIxY?I&mI2ZFAxz2`u7?A5T8xuH zWf&u0(*b^O1=)oL5&cL$(Kr50QPnMSn-4)Hm>FRvBxmU%%TdAz+6TlW_=eqtxoO0c zlgc@&<0p#z@nAJ?!z|6w_<}-K z1TWLB=4p#cuF5g554))LtZKpMc8$JJ;vI`1_Jsw2s+|%WMLm+ha8WG06)2(zx~Y|T zr?qW8)WbLJn~$IDNl5auiwJ)l$5+#!l+K2>eM5+>a>tcjdio_{%o9`MG4TqcV|BcS)79)lXDfmZ5jm8MrYaR?) zmYU-d%(3zCYJfH20jta70&T+7gA?({j%_F#Tzi*%5l*J7j?}ui6NDoIZ&_@=BGv&* z%`xxyW43?KWO8AE=JJzQY8%mB4O(x1ta1oyxy2a!h9NML8?T5k@@>gDHbn}L85oF+ zmIqa^uy#^kB5Sh^3h=QhJx4wqoKVhu3F|A?gw-Tk>rdUP%L2vH(lwTuA`*QAQ@cxM zmM->FQaSXyHuvr*DJx%=$fG|I)>s>CeMVAuhS25ZZRGj5<@CJpA&8tSyCovr`O@Dg z+M!J3ocXMUtf)|C#xhI)@UrY=ST6~uJZMlEK1>TI^LhBzwDMNAu%JZ1ATSB!kQ;qZ``-E z^qPaS<4@V2L|@IU4Kn;dqg?J-H*~pzSKJsO=;yYg%|&;wSBo2!1An7-l`=49biy|_ zXk(d?s=Y0UPxmmrJgjU>S&hm1CjB`p_0MYyRGN@4lIy+`N@+`Mx-6X!_&uNk=x!fv zrjuVXrOv;<2Z=Cy;nid=SaigRrhH?uehA?vC5z8l#SJ2ASA*Hd2F=+vcp3WX73TiL zRV_Y3n7#6Tl3Wr|{pcb~=Z~_rpZz&N z2l{m@hF6mnUR{Sdn?mbwdTu{sd00kLTnL}BquQU_9y6-FTB!Xj zj^=e=x5gh7pRrq#i(4FgpVL$Qcv$Ncz&)oy z$;0qC<-insW$utR<3W8oJ89L(5*2`AN>VqyTKFgA?fDj(`ui|O563<6z|i>>2cGY$NH37*g%hb z1HSWN_1uYhRd26HsF%QpF~Nury$#m4#}wIt7c(G&?6J>8-!wO(hr$G#k7O=6x8XZy z>YcdDh`{Pj&x+?(evuK^OG?dHgMLq&IymaVT_w_R?R_PDGjw%24L_=GFg)tqKOVH3 z#xBk2b#_Bh!n3eieI+J=bHAs%ipKc)NS097Zi5hrn0I^qqY3zLm-ZIzN2sCo#bU>t z+czZy1!79@CJEnn_Wecli_5n?29=!s<LRW~)*8bcqI+KiJ-KC}XSKe>63Gf!Z zeL7HWA%fYDGYuMZAGF-3t(gjU?8ZBA>orLO#acBaz%=cPy$Ngib<8pPa}b+2KGs_zdmZo3R0&h7l6g!_}Nqf>tXZu#{KW&XrlT4O30-r;S!sYxVN#YGEJEIL+I5_|r^S zSmzJWer93qsvy=a9q^OH3#ZF~7E}_I{;hqCn*2H#zD|!UHqn}U-Rdp|fr`uU_7Y99 z;jc|?fO}k?(2}r|Dk!KBgU{j{MQ8PV)OfN>8ITLHT6bO8A>vrY!Z%?cq5iEh`);~W zm*{qip1lH)Np4&ryq0k=u@v+w~a}3Kf%GD;V)VGPBvdqZTR~~D(w%f*v3@p`O4La zT>9%YOm0Z%2e2bx7Rs;C$g4FC8JZmbI#i zWofeGemX_DRIKmg(^zlJj3lg@5{w@;FLUXc4i1kW?l6z9RV93wK+3<&#mZ~deNCl3 z`W|mA7~MBqv$||Xb@0{>3H&kP)!#p4dM=1iUY|TTQKOt3rf?a$uEw-hVxO^K4~Jc* zsdk8h$-3St=_+@MS43fbTJEH!r~yI6D?{$3XQ|zBxb$+?+A8)PA9_-CN{n%e>uBXL zp?v%>CuXJE_T{7t3T0z!KYYK)X3ypP>1_MdU{&)@&C0@rTc#1Ut+JDnWFHrtKfLO* zP`CEUN0ss8b0*LwfAjDy`^mu+crV#a{7f%T)f~qf0rQ^W@uxmSS0Qa)ue1gu?}AU7-o^?)ABdganXs)zT(wwd`C zm63hpdjk{)yfIp9tsu=~Zf>{gI&|D-t+8nl$B9^NID-6GVK8pZi+ zEthV0rNx&ouf?x;x|GyUM0Gtpwy2|Ved$$TlsD>D7m6*=UY9heog7)Iz>TM@Iv(&vM~`#ve#O`87GaNSEO+Tqi>nnrAtj`Hf7fK7 zO?|B*8~hE57iyNO^05cQ1i>!Cp`}5MV;#jF?HuvZ$`PA4;UX<7@lEzHwS97n=*a;7 zMmN1Jul(!#td!-H3c8)60??DfuGCFf=QuYtskEkkXk1hN`C6(~Iu%nl`VwoFF`+ZL zj9n!rg3j~4@M#O1DKaow$hstKYvodOSKAQ`d8)o>J{eJ}J-5-8|mbX3uvWz$n+P z<@yYL`AU_uxuE}1J_qVLI%$A{(Lr`DDwSR-V3B9j;lC{U^(X z>eOR_b^Wxvb#-p|0_y5Y>G~{p?4avpZs<;u<)0$mePI#t`7R}L_4yMK1fMToyLXdb zDVgL9`QYtj3}*ck6XCM=by=)Y`uVK8lp)$T=+IyyQ_8=HJXZF5uH3Q2Q^LpKpjsMs3$LsNtOj)*SPLFs3# z%L_}cgYxp?$oXX(ya>r13`Cil3yZh6eS;5U+3LP=SM2R}VOO|oKr!d)QhtWZ<+7p# z>o$Vnzf8@f*45P&RbKHK)4RBvo0l|EfrMRuFZrZyWmaQlC^TX*i}+O~w$TXvhy}`g zM6xFlkJfQ}eiyyDyn%;~`uZi75L~eW>r1#cwBO&mHcw^-ggX;+Ky08fNNT1pvBt4h zzver-`3rA$q}Iu61SV0c?39=bQJ@4$mlE$(`9dn9xEmivDJZL`rH5W|zt;6Y{}1XAX8#RRr)+j%Y>Mr;3q2fzWodA;a^?=2$#U^gI_ga>HW5nFZ=G6x@$VScbsFUbQkWLtv!#oJHSP#7$ ztwjRa^cafG=SwVB$as?PRXKda>!+Nek0hF}5lo00?(g|9ml1<)#9TBI&OJFG)AAFB zkv(9E)v4o3f(4ryx6J3}M7E$G@`TdCV0pol z0@9BpHr;7itZRV}{j(i<_I*XQTuKQHa~YY3BPyzQi@uS?7zAuoCO{ar-$$e$wtBg( z=PfPu<%WrX0DJWs5pH1;Q;z4}YAgsMDDLj*aha0lrJ*YSP=B&J%K2%-L+|+gtuiS> zS?RNh(`X-E79VO^wZ0D_AuXSNr%zpdxP26k*u#yr+Jf2ib;1=GUg=-Es}N)y62>&r`Q&=X;WnwM}gg;Ky7@ zQu_z4uN`;H{Bi4Av$V=H-H(N3V!$@KVj6mImHw>23mprrxoNdT?@f%->U>Z4S=w&w zQ@@zDT7wB&9nFzR&h|Mr7AC)@H${}qRJJTZ6wz!48(8cCX{5FAIdiF$Zs;1eV`*Y*|p^f^1`y#0Rq z+U8u>my)29XGfxtqZ@?C%*uZCD06^{R+FXZkAj1L{2n1Vz%RUhh4TsW*us*GRtAxKA+ue2Lq!i0D|^Pt;}(prD7g!mxj$1qZy?b_Yf zC&W9>u{q;PI6~^h1C7g`W;}rY49FKNp7lMUDiPB48Ue`KOB2M7$OU)?y=;r>itWB4 zm0BV92h6Ns-=AaQ{JH>Lu9#({-!_Vz1Di{@N<&_h><&D6{{YELr*N$03B@eV7@53$ zsVm3uzE2|_f_btLTDAX1&YG6%39{*G0GcC$c=W*Ugf9>UA;7@6b%EP_#XfZtI!!gL#r3899fo-j0sgfo(5&)_W+zJ+e`rQJI_t=P;;&1iJz z0ZrXqIaG#nuz0mp9FzE`M-y9Hvr5|@n9BjywHdC(_T>?9$wL?%tX#c(3<~_cWX9?o zakHY`$;F)(95ufs@i2pWa2&t|ICS&~MXu{Kx=9<+=vxtG(2&sQ@Jk`pY`~?xJ7z^Y})t@VnsUuJ`yBwgVL7n+dE#en(lVi)!ku((UphxV8?r&X;Glz z?cOC^o!yuvLCdp)I$>A&`y@19;QNk3<6?KFdg>pm+vVMX8Ptgnsb~{%?;En{PT%wF z0QwI|#+dnW&>_&^76T(kc6eR-HQ<2b)5RSRwE4EUwz@=QXf(umdeX6-d0yOvrCpR9 z!vsuTC7KjugCBAqMa=S7>p-U)Hm)O|B#*#q`6Smh*(uT(1#s|iI9njv!U7iiK{p;& z$i@|Se;%jL=N0Z{H{lo0gL9VQKI33k>`s}U%h}T^gUk>>5CZsqePSqtGAJbu!-VL8 zTaDMlv?aY_<%{H&@l;OFifgmtI}?0(YneCfhQ0r*gVB3k%vvEuK@=l1Rz9VF3%9P zxJtc6!?c?YL0_(lt9}z~#$EA7WNz)!Nd+)xJH8xH+%Dg$MBxMejEU8cL}muhI(Oa5Gy2VcGB2*6`XU+ zukR`*Gb2f?vcN>26>}B@NJ-P5j=v6*5|6kap8V|C?CMKJyBw?-1G2b5Nk^|ZLW-f2 z0rZtp?Zu9kwdtrr6^nHN(881U0LS3Ve!hG?wX;EK{>4ly){kf7Vd@FwW_NJ)ZDl}n z6(PqmQlHu=QQypo_wbcw$6rm_wgbqrT*>AG4JC~+C@Iw%30U%B^ z)!WZnY>G1p0>@M2L-rRWb8f8VOjyz42vKm?AJu4#W5sm$w1B|G+G3p&01dnGfYspy z4m<7yP>-jx5RLa@gVG>=>b$}r5P!z2eOVrqqVq(*$-1ARI~8SmsV4gDYk(Qeh{z(b zA!v_O!6)IxH>=tmUyEI+-#clV@wR=G#%RA{OT4mVlcxFdHTTCwn~g%nisf|zvQsnBI9#DEua$dpz&PC!avHnW$RLEiq+53U7YD#LQwVlfO-1dN>2$TDkSsS20vwU8q^WUs+|% zL5;KLTj<1S`1AbJ{CmB`a*nSh!e1*5DPQa4QxXd$jJK=NSzntn6)zt3hMEe6>Q=FE z-38GV?`nxIBQu8i5KQ|{1t0xx;dx#1Ze?X>T^iM;YRBrdx}bhfEjOfVti^4Q&1<#z zlyW_(RIg7AwCK>K_20Z$R^)Rcx~Dx?0zLTVHirqC#wVEs)CXhtLP)3{Qr2dSg8zHO zD50U1FS6#!#)Vyc-Knv`VQXLK($QL6bM=&jG3%hnu<{e5X~xR}B)sC&h6|~8e~<~D zedc}PbLftyAn?{0df@#F)ni4-2;!^(gKYl8!qC5A1V$Q`{?737W#=XyYoPye!IKiW z%sV=m+u;%DebSqHIPn)W2fqL~WuK-3_iAix##?_?Det2c2wT0JtNO|0hrfTUlP)D6 zygs4!zBH(z+EwZk>OSdv?5vVTJX|S8<3AlQF153`2}$@0B%WhET#pI2*=b7a=#TQ? z`D{F5Y$SMwM~zf_*La4sM^n;lCI1%70{tR!Df#ZNrluhcJMI-Wm9bS%ugZ$%x~7Wg>SQ3~-X!D?#*{{ZS} zxS$r@kSIdp9-#eLSmRpVKD|~{7&l#=BVn)>QumxtncA-Jc?RG#Jig?REvrsR;DQRx z&EhBZ-4`Txg1OdGEh^*g{e!C#^>yNL_&~oqh9o#{HJ8?X)V{j6Titrt70u)?ZT=S~ zGWE-)?xEI06&Lf3`l4^~<$Dt_n;nt=n*M(~C_XJ-=yh(y+DzLS9IV)PQg?OP^IQpP zINZxgB&ycLr+=$yH?e_9;Z?Sa>L9J>A${nsv{0Aja&Jm$SuF(+<;*gER{*2)Z@`(U zk=qV0=y&OAYAm;glr48$?Oj7(%om<)EMBe50-quW<5;^|nmmvT9 z=TW)i_2yE;G?v%hzt#QxD|_h9Ds}Eqin_A00ylv1YB-C!x2$z*Ai1gD-``)v zc>lMnLNrKG3%_)5yv(!LNXPAbJm40kH84Ch6qmfU|H{^NX;?3E&00-UC_VJsvzajO zf!l}RmfrckK61A95<8mXm(PlQuXuKI0Xt|hy_bl(2UtjK*K2Zm9!LQFhQIJ`hC2HI z-^mpyLS6e}Rj-#@g9eZ*vHC<#GY353Rt%p|O6JSHQH8_diqxYFiFYJsSUpHND&?mc zH0+0T5ay#GMwgB%3E`OJK-$?h9CY0A^>ba1bMO|nBer4<@_J}8?@+fM0958P>D|jk zrk^4)wcX%UyB(C!(rHua#ewHf0><>5XxPK?>%R&J$4opxHl-e$;kO2uq88uF6cSW8&dM{4*g&7$viJU8SFvX8W2r zMa%P?M4Q#6UPR1TkQ5WlEX+HEqYh`RPK^$HQuwHr$S_mb#*QTOw2N89aGj&aFhVyT z<5(q>lEon#!CbFw(NB{37B(W%&t9bc;ZzAPkZqFF7P{YbEft(v`%fr!!x4(T&DW5P zQQn-A#SF*+R*ZFW=CG6Mvs2?YMT?w^njX-KXswM(w!bPpb3Wa5jU{-vVxloa<>F3a zHJKE~aXgz4KHh#s-^9?%SaY^jt^>KRtj3uwfK~ST4qf!m1eTnN@??E_C!jKsKTxTK zy8tu8n*ECkGX;>D8$O|Dd^1Y?gwp9P*TLl1G%{1yL7Ar%Zo2*Rel)5@6)@W%QCjl& z2CjLscGsj?aV(oql`jKHc|v?Dr#M@rLjiMP^~u^RC?%?Ez_Pf#x7%Iw`0h_0Ni(dF zMO)%qD7y1jJt+`XtU-$QA>W{gpM5-m^nWOO^LVJ&|9$+lQ4vWQvP>vWcHf8%eio>Q4t zf@%PlL^?ssaq2DCd=j*OE@$^kVvH!xb$V0q+G7pb&!*h^SD=zze@ceby~^4g__axP zhyH=SSED>rRL7`UdHKD3U?hyMi_p%V5xJQ^L(a+-rR2@;{5hP^LD*al=nvLpP!f*e zH5hbV#+3k{S`F9nT+Dbj7Rh#HfQJ))yC4UdBE?Gqw940)~&YkCdya4MC)m7d`Ra;Cwy zD2q0o`3R4KH$=C@V{{YsDNRZlj8L)2Az(AWqXv5v!5LsOVb2msTN8DhnOKspiSmii z#m6jJ60-D??QBRNIww`U=_<+Ec$AJrkegH9($Y}UREX8uZJ*Y?n2brtBWK5EKydeA zbq8A2X0K-gs+K7$o-z=bfnv30Og0u?zK)2{eLx`C8WLLRF|muCOB@k|{IKdYku&vA z+_&>$YUa8*v6m*(WS7P3=vQKL1T4^41fevgz$f+iXgT-dhPW|M>Ly84-$fQ6`b49Jl%0!hZ^#vsjEiqLYJ^GuSTn41 zGhDXPIE@8`G|stKmL_j4$7U4FaEuMGjgBdRVN z5cL?&=9m0QdITqLsoBZY{4);|p=18f!CRMfD!h{`QEEPkx$Av=qO?xL8U++f9t!8V zmFiAJ5PK9_lvB^;jIevp@*BPWuF1BCMasE}mq_HA2(CY^c@p@6{dUSCVP0+5Hsqtd zxC|-Er(&pbR(-vx5@p?Vl20Il8o&lS&N1J_%@-3R#5q$d?k2tqYtMNJw3by4mTel- z;i!uGQ1P06CI{tZZRC|lO}AscIkd>)3poa`n#sn!yj!$O_Zo!J3O5Xd6&xMobQK&y z#BYFl0F0*H6Or>u5O?oO{>vF2&c3vLg{2L_anJRs;*KCZIp4)GN~v9aK*#GCxDuiG zHI6G_+JFL-26zI&%shFN2}7*DQ894*0JkHIoY>fgvVM*bh-l%bky48EQXAyO*|kvT zRr)icGWa57>rRXk;)&}A=n2R83XnJ#EfMC@y)S{?(S*hrIj=)>m_NUwj$uW8?-`*p zBEkwS$fr^_r8e*v2nbs4 zucF&d?RB(MfFO-0cM!~ZNHpHowsv-Zg83lVaV>z0vu_LM>Db7$<1N*^GKM`-uF*8L zg4w{oc#mXc$C%+Ln|!PkXO~Ws=_eNphba3+xGi3}qjFXD<>$>3P0*s6TdJ(Lw%NOa zG5>(1r%&zU5zv;9IGDay0F4q!dJ3^A639@y0kHU6f1$ZPkja92`^cn?Gk#+Y|9=I2 zhV?-WV4!xum}OkaH82?DH0zY@J!Jox=hH-eAcuDj&h(vSG}`rHoY1nKI3=USdw_ceRD9Ex7i z-uwqD^4~!+0MZZoj;8O1GNu}Xhe4Si`T6trnJX4~P&uvd7|0j@QHO&PYg8Wod_&4& z#-b5qAGI%4SZ^0_{d(vBj_&RqHo!Cnv$Dv=MMFnQ89*ennNwHL*soG zO_+vKq*;8zLQd{CdjI=>4cg*s9R-8=7LUwVi?P0CA|elcNm~(B_q{JI9i?DK%GQ;A{z_H);P+7UL^pTEviSP4HSPCT z^+zZ}+k23<|HLy6Z%f75Oo*>%2FI_w{hez}x;fbo(v!Bfy1I8=8C>*T^Pt~b@&CCc zA(5vRRTgHO{x=JNkrlW7WpL2U!ouR>%*>2#{h`ajEZh*I<|-c8HzJmWi|TB!s@_!qTsEtF7q)nwPLvFt1iW3DNI8gt?H5y0t36}v!h4SQ&agTzewVBAZn#`vilwDSW;U=k*m6KiYU7#^~Bl!3vg{B ztX+@Bp*3rpM|-{KPHNkC5=4%8;NsIG*)N;Y)^xb=`+(BRQ@^JljDi9MMC{B3Fg<*i zO%cHe73O?;2?jDuFkZ(*YI}sf<@h@C>v(8!(=NibYqRANjrKxSWTFI^Zw>pj(RSi$ zwto@XPE?hI$1eHH(1B=JdUTYKpT`TLEX8eRkFM^gj zNQ;f{IJcKrw?i6s{&b00?wV22nTK}bC1#q~li~o(mB=^a$}2@vIxYg5p2dH&NCAr< zD)fx-#jM`;X;j?Bo|Q+%0KbSDERj-+pq~zY^KK{!v9x}a&r=@}C|Ivx`&TXK*k~2C zl>7yhx?$=yCK21(w@O8G+tR8DS%ywg$l1Wox0|zl4(fIbb8p16rFIwCSN{Wz51GZbJ*UUKy@#|xk8#U zkk_+q=??ok9yz{v`oi%69`@5Ord~uIBa4tzMg?Eb8G4M#u*h$=L~0lm-*I^ib4quv zd8F4cFz8dg5i@?PI0YK~@bl-}lb^O$8S4IrD_k*Ps#)Hkcdu4NJt5FXA(J;*^?>m# zNz=>oD#9^U3p;=HX)&=ugDWU4<4YqK-OqGxdb0HxyX1i-TRG*@{F>BA_w>V#4@HNX zPR=V3h2%@5UmJt3p!tJzQ}%Y}D&PWyc%`#9z?6DH9YL2^urp-lSPBreA^JE|0kR+l z$1SmRu7bpmxj_K-EtKJ$g6I1C;d8*_!E2n7eErPdYZ`SwF;!ye>Zg#CW?zD8>xS)2OODYNbgTPD##Z?lzr~%}rdVP?vutore0nlr|pC!F} zO9Er*b^LXdJf{+H-zeXaPYclmHKscCz$F#jF-(*a$X+bb=2ioRsTLbL4Qvt}v5W3$ z!C@f8K>*(yVu+H`nm3AXkugqDIBIPXG3?F7-v*Cdf+&M3EQ-0wHd%5br zZWZoC7Y7TPiU4^&I_-eghE{nzdTYq(gIO;a&T&JStO+#PB_8w6%ctP>tuH$+x*oAk zs{tAMpk&P%a+%ggl_XNKW90ZJA$v-PM%PZ%N$KuAb4Yv@O%}A?w{SkbM#>v$S6hP7 zEBR4P^85ug2|s%LO}X&d@dcjF6SHja%%@6<_8L2H+c@w9`pX{aZS8bZzWu&z)N83o z^x{7q`3YK;uuyc@Lb9s-KEAV$i3KIGuoo3Y`ZJ87q&a(9U>=8AI#o?GbfFZ!|V=8 z?rsH27tUdr!`RL(WWd{6HFawn{C@(o89*Z^snD7Hp}f+mST%OM&GgPvvQ@$}>30c6 zfnwf$TJokot!JF6sLds8)OlrRvKH4P5`@`W9~aoFQ*3qPeE3cdCKU&%S#E0&r$bvL$C$DE9 zG}Cpw`6OQqrxwldBMf@KH^l0ts22&$6Licr^E356PPZjI&IR3S6LoS*wPe2DrM9U< zdkG!eKzdf-K@1bUlr)fBq!+{?sHC1)z5jO)wYyzhFOaotVl6QDZF4SPH+Tc3;Fiz( zbmlJ#KjuuXjZKchk|+#MXU(MsoruoR>TO>5`_3s2Sj**R@#%0IA0Yj=>JO2hN|9<- z3%btzy7LfQUoOnEO~We(xDRA-7g0n)V;BH_QXGz;gfW@JafleXkX?FhClVBXFXcbigIYGn6Xd%h?guPtgiAz~Pav z7-lznnwc-oUl3rAC-$U!Ke0;p2C``@`(TOOP|*O>i74*Nw%ib(C+@>mD5t;DOOzZO z`Y)791AKeK&<;S3N`cpy1}S($USE1>DQwf-wlzI5cGkzpm0nDqr|o7pp1KJ4EUaJFfns zo)EP8By1-rp_7K(%VA~f4uo?CzfGzy;v#Fs9qRLBPi8XS9?IlzEMaZM2gJ)M>&!Pq zaw1N*tuGqUS=63HQ5ykkNxQ#3-u04dR`5D5vHgA?7?BAr0vWQL9kG1$yGy6kQNYFl zg0)f_aqmIC&lJ#1MDt5~-Y@D3uIAK2q4K+us1^F%?9F-d;i8_`<6?Kxn1b-uNEM$? zFtxv}bu#au$UU)>smWclCJJgD3ujt2<_|aLDQXvA$hD0U!pi=tC)Q;t>}MXHI~1+o z{jE&EB4X`cqPV7QYI#Yr9W6ZvNRW*6?gJ*CPr-;SAWERF2&4?;R02t8W9SJcC^)$U zEy1MF(Z82qqaMIn-#$#prxfUsTm-~;Wx6-i{@_zM56JMr3-B}TIr;`ukjs{F+{5F1 zaHvp;e{WQfy$y{&q@H5?*GCep?!>%N%Gz!3;(Kc=Vi=o_d6Ry~zQPusnyzbI zK~*-lb#c3Y10$GV@EtqV0E|HAluMoBek4W~`J}BECflvuD_YREX4CT9uiNhZaQD2Q zX2&lRep7C1yK{C)<69G^o$?G*#a`(gYVi+en%Zm(IsZs)Is zO4n;^M;Tko2baqO!cY5t$0MOyB^&F$2|Ytc&_A0pi@#(c3}^YRoSL>?;PIt{>Sh>s zmrDJ(xEJzPH73DEP(eD9iPxltqIH*s=@ZR-4)T?>H3iB>Y6%k_nHzNv}ATSF+wkMS||&)8Zk<~&DF za`igh_nrHobOCF(`}&T`X5bR0tjcN-Bp`${SJhZ!Vc|?k{-?Kor%Mj-sUO=>zec9O z(G6{1&)i_2rZ=&%8%d+SoydWe>U{b{*t72{LxWx1nl;AEy|CAF_?<0Q*53HQj@NDI zd(S%N9+W{{n5*=8izxt__JF#w;Og;27N(AM4gI}-QEpV@w-oZ=bNp$8^F5V*F(iIa zr4wHqz2d7<5w|!MC|AEZHn7p!<*ggsExyiL8$fhU_|GwCx-fTEvss8Cj~AH0WdrLf zyfv5u0}*+~!q+kGSJ&5=F~6rEr7+YT#OEuNt>x=XWIvp>MORt6&T30!>_UHwh4`0| z0n3xEJ$tb%aAMgr{4u_wfZdGz0%Lv`8^!B|LKmUIMmKx)W2Qm2Vb-XW) z`?znP$Lb2?=a|b+j!oZ8nZcBewlS178NYXR+~fbqz5nvVWcH~wK%>MGjqQh)y;C|h z{V1Co+<5A|xcYA1I2`H_@bD8cw*C9Y`nMdcC7nKJW{tz#_sWjp6t-SlD5kI1{2r&Y zFO<**>|Z%0+tQ7$pLL3Vu9#7ihp+f-SwY*1Qy4Ce-jP}SC8+p4ib`_w2t8UvbaYGL zAg_2`lkazVkJT69Usg)%=^fdONVny=y5xZ#!Z!Q=kEcS&|2#9ePRZZH0~<52$_G7r z_Bl%ZUv4aOZJze7lBH0OQ6!i53lS`{k)>7^g~p0^2##a z)2Dt<6FkF6dAVeK4LY}0P3ilWy~@g;FEUN7)i)4&Q<3-9;xm*FQY%0=|Dnfdk)dZ6 z_QX)`w`T!2kzU`B=JiRZ>39p>gN!#^Da8HhKZq?6W;F+u-TrFWnS9FrZ~`~~WNj4lq(21?k>o~9k) zrYYUK4dNSwcTY!vm0b^L{HqzK5Nsvk@3~SdsmfjudUo4sv@HAxh<4yFAeTr6fGH_csVnaL z^6Ewn>Le5fvX2rFK-_l$FQP@-JP$}H>HMxmpy-1wn&@^Z3iGu{gVkC) zN;eS!^>9^|VPC6N1diI1Pf5tmd zsUN4Wag3To;M~4koF$dEQvu+Tt&=53j~C9HfJli9SpdSL>SdZiSu%Nz%pMD}e6FpX z;~roNZpNT!spFvHuZuOebD`#6+uPo^*k&u2dAVX;@FYJQ00n^m88A5uCGzHfdL;^4 z>}!$5zb!+7D%AUU1ZT3kOek29zD7miVSUg$s-9u;dC&4T0XnccaO-}G(4VGkjdehf z@0%SOA|i3}uxey&Nrf46_WSZ2ezyxMJj`6)U1c$gkS{CwG%N_YJnDO&lC+}?2PcrP zGUQn6g4h(u@^a?)DhuXkDBhI0-h;7buFD4-Gq-%lm-*Huw;jZ;$CDgLi$kE)taG=s zdwXR&U*y{;9|Y3X)~0il%et_zFdX62`!Nl(I$p=luCM(v0|*nH*=T^ibh%L@0%p`* zo}N^>$4;~aRI6J#_qkjyF019`Wno=_wtE{D0ekQ|N(uZ0Wj|f!P?=ODa&cs2{st0+0-+xeg?Bdn zsz8SdQq}mbUd{R6%Ee!da#UjX;m>l*Is={A9l!^x}tyo zcU6IFP#tePwp3Kb}%ivy<1Dg@@u~fgBpptk7`L>bHXzIG;jx64yDb zNq-gP?L^-qByOech5HD7GyO@79{(^B$?1PX^SladfqU^;y1i!SRfty$JSV!k@5*U) zMfUxR*r!xZ12eQ`%Ismz;r?r=e7){yK&i+B-VGNUU}iDN9?aulGhpS&>OWte2J$LS z*Vzn&$Ku@JV8oq%ZSR0bW+xB5v@QdkB&cv6GH^fz$Rh+IaPiE-C3}(bgIdW)U{uN3 z#oPjrAYu!f}}%f3IVlL*W}~eTf?*WKe~dFSGi1j6 zCp14S)sI)HA#CzEs7XGe85qo5=iEjnn5;dgxjiQB#9GVR^SMR!b@SdGm9M+a{Dwup z4_e=NzfoO-Pg7jsS_no5v#30z(a)Gj^)GiHb9ckvI z1Ms|^7uO5y_lupgY~BD8vU3i@K;ov77lU&un`?G51(*TC&>YPu14Yor#)J4CslcFP zcvwxS=z1sTEC7|6rJT+3v;C=dPz@({PWZ$8oXk2`D4i=}J0-DMzT#!@Aq?O-?@}Oi z80Qp^XGHYnP&)|~k_rt3R(OFV3%h>r4{~AZyWndG2-jrs8rzWKm+9Bz(0?9!otn8b z6D+Mo4$*4Ua10g2765seN?clYt;}rly~VT*jHGCBZAd|N9_p;jN()G`JB=(_HHH(_7a5NTX{p;s)O4|Cfe6aE-AM_&5cpe~}`;5SfSZuGy zciPm|I8^cR_+Cc-O~y{};W4md3A_%?bzVMR*+DBy$s0ur#V;p5D`On#I*hHMWrvlC z)XydWc=&Lvs|_*>5ycND3^A;q+hCI54Rp{^4%Wm+Sgpa(5qimP+0N5-gZ6!JhnSZi znn21`g_(m z&}kt^U|+pR22{qZp`Nu^w2)fApJ?+-rzp-n5;*pNB^LWk$eXk~Tv!ujOvr~3o0XUD zfd8uu7(2Dz_iI-{T*XCYHZ5?A6pYwG7py*?RXy>as@}$KC8d${cLOLF}g)R zv9TVD(Z+<9dozSOx*C)$S4Q0eO`DeK%{J^fMDp z@q;xn<9u|1cS`(C$oyoI{N~YkptI2dV}!b`z#YT!Mu2*d9W2I~b=NQXYd*cw!FDq< z+qFPU2F6ZIhP-i2Uyl|79Yq)!DnMQL@UeB6_fRYW;o86MPh`=v*eM7Mm|wWP6xubw zVI)LZzf}Tq^)x+gPHPHMJDDZ%vN;_1$;6_r-sJo)kZ`7SNN_5o_Ofk)6QRCD7T9>s z_zb@?-p-8!o|yfn1`x(|fT`NErzuk)D2dL62O9~g#|F4HP}X8$#YDI+DQQK8u(>6e zjNU%8DYOgSJWF2zW3j5a>HhsrX058LP;@sWIeU@>|XT@&cUEp%d zHb-u7q8P%3uJU4)$YGgvnsPnpRgdHjh>e=tz&QTD6E2|h|L*8w%Afh4i#RBCeF3c| z(@T;*Lm4i=VPE)Pi-RG@zW4)LFrhnM7=M7jApXWL1AYia0*AScmtId@JplIz{Mhqb zN)i$6eNn-3isg%`fJnyex@KmUhC2p-KX;eN(Dk3r1fJp4s~4`e|5$}LZywB^+I=JW z`*H^$BI$hqXbQn14w}~Y?6lQ~b@xOo)g{&*@=jhCt>72u<=xDO@~#`=U;P$@4i|l} z1Z)R((D;`4D`dTi>EZD8v50*&Me z*4ba*$bdSSvTrqABrtvM3b}IM!!Uh6P=7N20?yZu#YKNRH3bX)r+^!kSwe#zr4wVL%X~jG&2K7?9oCjZz}y2rmc0!QtK$f9?$n?-t)!C{y%&7e~qSN`6li??`_#lO@&AEnlq_NKaDDLv_D4%{W`tk?xbsykHKsQrhsGt{+6M7^za zN1@&Yjshqxd@W^#|H0yW%@=>jaPC(zv2~bO|Ge{+q1&5BZ^F#D}s@(N|IxYrvf)HF1 zi2c0`l80A2v{1wtz6elKeW)iy?x$1=8}$sQzIAYSFX{sDarc(8O%dom%M9EFKE^Vi zrz~79cOE#|K{!>+%NebGnj|$^$Wn)k4d>Fu$zLJ#t)p3{nU5ZYn2k8Qz)S~3#uyGX zFcjVyI?WvMbyNavBN>vfn}H}h$2QR6=BE(PrQ8h=3@GKK(_=e9Rm>a)l#5r|0gzs- zp8e@`f$W@?%P=?SSQ)MHfQSGMcNO%>(9WOwoj9~qKT(w*f@I;P zQ*fV?mZ{&^VdP;=W#{GRJAiXY?`+}6XV=5PR6VCE?=OP@9s zCTH$|SbmUVwKMuEaR`7BwlYV-ec5;))S-n3uQeh`>se)9PrtcsZgMt#5LlM{fM z;H_zDo1smxt~}(C_FJ3{DE+QaLB4DsrYRG(Gb?H2)&kxlYm zAbAgnJwoe1E}AeY^#^|YAY~AE?M;SfbhqEr1Ra3@H!&VKXEJsOqM!&pE@&#Smz$B_ zrH^lfR5c?v0h=T5*^QHoZ;~&UE4jFlg77(~`^MJ6nL70Tl`%1=9>!pMy-SZRkOqn^ z&;g`Js4F{zM;LV@#T5#IB>n%opT}UUCY;zX7hXIHzl6z;Ujw6=;178ghz&Zfoc_t; z-f%-vn_2`rBO<-0|9EwXpaca9+tJ~hUvisvqg z>9ib=DA-R5MJL9m@zm{0Hyj{7Pe0z3CLQJQk}{SiYI89QVo5jToHVgG47rP{?03?I z^bLD=>5h5?aI=W>x`2pCdk4S^B}M{G1$zz7XrC$g1DIvf(k114wbcs`SrZ(0+=xp}`lDK-Qte z(oEjYuz~wumX}n6DnI>uHYgvEV;d=CUnviGJW<5>fXLiX40fs?jc9KZ$)i8a3|NbH z7=NJfW7WM~eDLkq@RFm>k{D?CRx;ksrL zS2R(P4qZosWj6#Y#Z_Ri?W8yH`is-(M~CA-3Q;Vn)0vk^niM5i(WAG8KA#jg(_%at z1=C{c_MH_qcT=Bu$d(ESIu6iY|8l`T8G$9g2=6YK`GK3{x#tV!I_)Y?Lr zL03N(&t&ASmH{<+Vq@IPv3rTfG7Xy)W-MoqzzXV!(K{AtYx4AfQz8YicD%rZu}1Lf zA+}fWeQuJp>4~-@rESLVh>m(}<&3+`(^e0`+y3^Wt{VD7>o0nzg~b-Z3gS{BYSR;G zLrDt6MW_3y0yL33qM$gz$R~ZU_M^w*i3t-yV4XQacP}Z_mUyVG;e_I3m8sbW>p)(x1C*z?a^W3UO{UJjhgX_-!`eSJORGlqmlkCV-BYi4Np4-p?C zYwwTk;l_3;nzAY^GLk3O%g41;4GGHRJF5ZzZqeV)<1l`h66TQa^1!N3WAMh#LRYy5 z=+W%yV({btX!^V=sFnY%?xyOcgPGOM%uh5m&y{M}=2tzZpr5dmsN`f|@U(%7PyNC; zxwjs#7EO>Cvx)W~2&l8aFjHEqF7cb$90TOD{e})ZpGURq2z{!r4`v4a`%AX^gyff* z0+r>^vlA{DiArkb*;A_^wQcfmah&OD0dnNy#8b1(`M$#VhK10#X+3zG2%TD6*1OiP zwEqqW8p>`=68Xp>s*0F$V(?TfObs?7`_%)uj^~P2e)8<}oN}bQg4+Gj;VS-n4lb1+ z?|b-i(0CEpi!&6;_txt!xx<0Vzh&M{;!zzvLJvq;p2G$e#Sn@8y2e|b^W!!a zBO_AuO*!*ljZ@VV2bZs0adLM79&vUgUXkARdh_S5e0p1P-S0J4RgDk2)Y$fjF&5uf z!&qNDl1ubbICsIL*O@8U^`4QaNQJD7Xz?xEGrZ73OdIQ3C_bo~7|h)C+UY{(8*Zi=`Of(NR)==Y z{8;*qFl|cMcc=&bqs!@MH1cP?+pEvZ)mLaBcoDno{tHQ;;~^Cq{xAn28Th(-o2qZY zj`rDB0W)7Vn($k`dLBX7MXrk zo5cOFL)2Exn-n77s!*O4f2=o)SR*U}UYlj1F6wRLwfN7M{ExB3o=!)v#c$r0)bdWN zhY_;D*CnKfI&tqSo!qY+<8TD;&?2BtaMF#|H*5cU zp>pl2#PX=W5~|~bc?IDOczOC=M}y~x;jybj{;>T4B9?IHxzq`-3{ulcYYVzT)4^f# zy?u3*vgX~DD~9lgAk^cHai*i2!t5ob?$>sM8bQ}2q}%si-u)-P%Zy&C;WWMbqq<1h zSs;6>mOs0&6>A1Jcl5WN$QmD{UlX?LH$&G_@1jwR?QK7SQ&3|(V)X?QOu3lfTCyMZ z^a{#Edw6AoAR`A~Gep;Y>%56k%{<=@kr#bZUGwcTyeGhZ5)yoHQ^CsoBXW4;C*Lxn z(zhl$SfbcQ8i8l1bpG0=2HK~G*!tyZlMEW|kwuZZ%M_RbE$g~3yvcwP#%(o(ltX zvQLLU4@HTyPpexu^A7)W2tlzoSD|G;Jdbk#M2;IS&Z>do95P^90q1~UVzk2S84!?$ zW5-8X0N;lHM#9+9>TC6Okbud?z;e015TQo}3nOGG;2uSiMK#W8)hcl|Yor#!!@=;= z{hAY$hM@U=5p{mmGy-=O^eH$8(w~9&Di8yp1A?xAuN^O%%kOGUD?==wqJg0mW`Tmr zjUo|1!fJl9EXHcTUk0XhX-GvrK;*E+ zJIgTCJGmLFn^Pghd0qMjc2NDOH%ky^Lga%RZ0ImHx^7&mV*ktqul@_l&itpa+cH^$ z7krNRnm1W}xv1I@rWTiaVmgG&agtl>S8Th@KurrDzr7^vriSRDW}sH)EaAyan5zRQ zrCnqCBkaimuNKbWf@0ghIANS6+!IY2;o|TETILFXNsfZZ;7tGZROsmRgIv->@Tyt${?be4TFpS zpZrU*TfW{tYYTig*}ju0XXS~JcC)o$28JzEFrXh;%6Zn!1b(IC&GxG*K?Jr7GZ7%0 zAn}L$+BuaqZn{Z?r_6O5V7*8*UNo8!suK|$s5vj0EeHB(Ai@bmH`d*n3qdYMkKLY4 zjTfqj7&|AJ28528sf7%c0QJiQlx$|rlh0@6X8{4vIg>8#r3L)z=XB#5ZtoA$kPB!( z_j6B!BtEns=}5}m4zE{@$OrMB>#@N(OJWNhlCz=eAn>mtAzcjPZwdtP4}W<@9%YH| zJ89t51VPXoyMdodPHn{Wo>Q?kysyw-17^)&@1~HUqpIxDl*f^C{f7z_xlV$hzJLdJ zS`64uWKGoimnF6K&ai+~Ubg8Bd*bZJ7f?lv?UfBWrP>T=ls#hd{q zn054aCc)FesXW*N%{Xa^fnQcKE@m<-yY^PmW%~8ocUV*)Q&UJM?-^-ZQu{hx=ExdN(i3RKQ+1`W^pP%nO@NU&BXs+_#k@u-g%>G%eD(n zrGKgvuaA1eaEK0j-+2%^oI%RtHI3g56Wtq7n}L>x6a^*4F^ocGsd@26?ek-him$pG zMaS2Z9LV_lFi>@g_wpz?(&yojKBCBzy-PIlE5tJkU z1JE0SzCXtV_Ds;AL$9XRiwIY1e3K&8e_DA_uY7)3C16`ObKS$#eEaQu{)uE!&qR4r zp~jsB(oLu-i-e)QKMV0ZY3Nk(UUODJb095Ngh;8%-X$n<^3_VA|8~y`i!W%Iz!$Lu*?7i8%o?@+dLy@3pw4a66vk5gA~?}l8R(<^TU49x|IqYr8Z~tZN_VylNUP0P;!zK3)^(2R z6k)S4cKtpBCBwTntj|!_N?@Z=hoBTje|+mDKKyq5h(b%@#>HdLkNmNdBw_dS;+M^V zFOOC;S^2VP+Up8D@9z53$ZuszD_{45u0XLl*d)_Lf8cQq@eTh2F{t@WCA$(~HmXx= zG#<@b=-l(&|)DV_1qSShTSU^+z z&0Fs=7KT#IWW69`3Mcp8jOiEe3`qV?sM6)X43W>JH)ohQoSvtI{ThU!rc<}fcXMGx zo4@&<*?@s~#{;LQ!(&~M(=I*q>v7g|-N8D0>ERs99mgOKBze2E^K&MJ`_Ctt2_MaqeE;FO-C)uwh@vCXEZ_)VhmliMQL_ekX{v3AI=_@muU(+4 zl$*_f7Q1Lq?P*aYNnB(44%h}SuNgwmSS~i^JO>S2;k}Qu94y>`rSW2ZU1-rP>9xxM zpBk*kwZ69zPlS}@`*#Oj(4}3viEAxKw2Yq*?{QnI2ffU*5ufGw$U2rK}L^%~+kkwrHHAUeGdv%lU z1-ZSB8K(=yWIgJDcQRS;VToyee)`j;^;39U*m!g|JQ!yi|8@C!U6nAi^Nai_0|TKM!$f>1Q`LLy z89k%#@jIi+tf^%4k3-BgZ`j4luKyh}D}>DGPsSEKyhZFTHy+Xy;2V83^K!coi5{-V zRJ(7OA=|mq=DlLrK44xJRC7^d#cuyY2pq1OYyTOn7Um>*{}vzRv99yS^^4sl>&k&Y zQCH|Lnr;i2O^EyY$Y#=k7Nh^pw>&;pTXuMybcXi#i_s|$Ir;KY);9M_ABI)G(n7JT zDt%eFEH6i_?Bd{XmtcO$t_$dTGFFKP{o`8e!;5H*k7YIG-(L<5j)qm8(Wq>IMhhsE z`xRJ6h3{@nckYH44viN#kc)l`&I?_ejFl&~F9nUpkJ@;Q)qQ&zOj)j=%H!`}adT@{ zB1=wNoTjV`g>z86SH{Lrub&pKI1DqR6!yfy&Q|&6kLb;1&kKyNy8(eh9GkB{_hkG7 z3HgF&UHiQD16iq;$uxbf5m+1x$-r(@c$ct#2kWoOg{a_dSurcYYR$njaWl2{0RL)rCIeGWXECws>GaRdJ%2PcsmT`?XM%xGg)5SyRx-3N ztt*t=OlyD5Gx*nQ16$bA;93t})#rb7QRyX@d#>-9jR`v}5BuIn(v+1oJl~yQ_5Oyiq2ciOql4{_8Z{b!mNjgx zU6UD;FE97N|Km0h^nDQjSs3wM)0MtnyF678z&D>MJ6`s|_sqi#BrxF|gXU^?m>Q+q|$a#vUfUv>=DPAC16Pwz>_Cki+%d zX}XBgxC7y*{x;g^|g7ek|5eutw}2ub>X&7EmDTWh<=JE2>UQsXwAYHd?B zhSyY7OEoP`Q$vbP5#v5FHEdK2X$>)?6;-yesi8=y`3M~(Bvh$VTM*K1h}P6}OT-ZK zyY}^->wNCVvp%lzdDiu;weIJ){`dXAJPW87;?eAt{f?bzqXrB@q1~gDomn@l#W7vC z^7I~KKkkh_q3WGAs|*RiCVro--Ho9Bx8C!^2==2oUhKpjwzt(cYH!893k5WT>t%Eb z4~1Lz+B!QF-|KgdTzS{`uR~VMC%j%}A4m5vC9rPqks)6&I2&8*Ql6))u7Q48Pe#B_|U2Zt}_iSZIMRJ|I@D-`J+Z%RC(ghIU+{Py=% z!!NIDs%_H`9nig-$L&xBuX7dEtb7h0n42I7Aw22M;Kt@A8&hdWkewC9&on&BCU$v^ z_h@IkZenUMzJTp|ezwl|$V_C6L5*wwIQ7%BEK3jx zm|zLX6rPsS9S+@QeN9*<%)s`f&Q=x&hw^QiR_Z|+*{He#J0x%-vo0ojBg)rRkMfCXl^(qwV0ft`9`A$MB}jNmobK#)Qs zv~&sm?7RkX!pX%0pFfEs@Ggs~Bl@=;VL)gZ0f%YYQQcO|Hk#@-19z^6=2Ls*FBbXd zLyc})a|yYFhHNsI57}r+BWm>LtOBN1n?PhkemNs$M3cj zL+-DSyQU$>W#i~XXPUuaDMBgN_=GjWJJ@G)+;s&Zc;wwyjoFp)Eb=!QhKv^ETdDv3 zVOpROoy}1|BU{aUzT5<|A72>Nv_SaHkvCV1Xu@#;k@PF zfj=mOU)p{FgTo1a275_*4WWk|JY*_aOquoIUEEHxsw^TSwx-(7?*Qulv&r*VvQp5hVmQv%S*H!XS>gk9|kj z*22QVy|TMr?zdfWVU)@<6K1AU9QxiBlp$P2<)u+_fwpb2_fIUF}mflT{tC8_227p)+-U=rt;Y$o=nk{g5qKjZj`S6m&Y#} z!%R^^@Pkg>5@dR*zNlTV&E^~BLAN^-IIr&GC0Q@+X zRlgCHvVST2%{{=F!{qm%$|R+!%GAB2{f&t*G#Xv#1mdUw1SW}{VP$MLgFQ^_hf?~W zLA;F)c2@%PK)amC;NSNiN&nLheN8arPhWji?i*~2J{#ozKZ z(vD9rkJgCUK$m&(?!Mqxkzcd&AQ+iv0Q#W-#_$xEnp=`Pkey>7sfME9Z2y7gj6`R6 zh+z$n;B|Ul+k>EFPaU#{-}_uC5B&+4YdEE#h|MBU6u#JbO$6je?*lARZVxG_^c!nw zGvEH{)3eW;W6asZoCE_uN%aNprKmH2Sq^gx`jqD5O2~`Q*$<)1-x<;JW zlaRLRwYMj6%)AO{wnapWh)GrHn-RVBpSG(ZW7}_DzuwumP&BDJX-e$*E2ofc7XCRW zNO{*DjQg_1>BUFFM)Y_!aFR|H1aN^fw@FPi!WM-MVs;);17fso_nqPEnkH52;>JhR z=Z?l!EsPdKaO~>W#U=8mE)zNmtC$xy%GWjd2B*nb#R@Zr4E$7C9}o83k+sMztInIm zL6{<|ZcC6Id$Qy@>Fx?}pv>%#ri;li;yZ=8q z8tV$JDHdJvm%h69Bq70!b$B2_bwTPgSu)Mq1Po3+NZ!}(@9>2`CnaV^Fd-(kFmhxE zr0BSVmj%1f_?|0=uD&A^zvSeC^7&J5P}N<#HH8|`(4F#H2>8LAQ|Yl+HL@h>1X7y) zSGEMhHtIuF%(xV_MU{2GaX{*;$+R*~w?a>s4_-wbw?<_tm2GP7CoB#<>y}@9k@abq z_3&&f(LdKHg2aZ<_96>AdAAS8yU_$8=`PBl*}C@}l6N5ym!8Ww&Z9sJa{DXS^_igB6RN7knXA*Npr;@#JgF<89pjRHj(QwG2@}p; zKxGxfI-M@%`kVXWx5-2W!~OBK-J44{0G4yPh`ZS|yskC7P(jLJH2 zPUsQcaBL_FS&5ToM_FlHTFBF2%3g9-U(z-iPC{Y@3m=i;T~5HjEEEWDgqyE2i}dQgGbD*}2!sPzvM*Y( zcG13{=x?-Wp4FwM-VBA21)}OcLEp_o9KJgSiLs4Nk5R9t3v?DlxpL*|aWvaTG}k3< z=j;7-ek;vbU2bBlu5B+jnI!Qql{3vJZ24q+yf^XQ(Z99(SPOAT&)n3Ty_u53G&N$B zY7*GS)9W|mMTpceWpR6R_YI%?=C|wN1;fc&_|cWJxv~~Ht?&dl1ri?_yafim62rgn zPl_MIB|P={xj4-W;T{_FDJMeot&BpPA5wXN`QleRYYGpJzq$>^GadB(@P#BDcWrfy zVnZVVvHP+9Y(#BwJp*=lVeMtAyA&+tQQ>1cCP>ru`(gIHP1P&@Ca7?NQ*R3Dvl&Ic8C z8yfd#6~H#{pB7IqkI^}Qg=G^_(L^57<^+2NnYMZY2=U2NId>B9sqUfl(486U(Y8202V*bf^fGP+HK|U($q`{ zs%>tcy058e2`p`}PnuNw19c= z%b<75fV)idy`F&5Z#jG!_s=hZX{})(vNc=r{-|)C_r(9AA$gk)4GolXwJy}M%NN_N zhSaTYvux1(yMZS`lyqbjWE9%b1V+4%BBqcrJ-LD=j{mqmwRXGWmnFH#Yk#}mQ0K$j zfQ*%+OQiiYsW#%GZ>`#aq1h6oO=?trzZ47M1XrfMl{qb;N14Udm%3DPzpO@4kS_H) zpML@dn_Bi(lf2qJkj>_I%CQzbQD1E{#(7-z&}R1O@=7G~CGg+uJP zWY{tMOR|G%#l-x~hjCZ1T|zxm7QJC`6>OQWqD9D(;+IXhxL8NSy{@O7q4I0Tk?@=S zr_}XSM!xlX0LLTlpJSk4(qum2p zPv>7go-Vef$+)eNnAR70muvj$VkyYRTz|!~r_ReCiKUtZz(FOJBJGwYb^u(;bp_ZM zthiP?I1}JrPgcWOTi}jfs&)+#?^MI_Zij6cy?``3@gX6V?bWpyYYV${7C_a#mbxnF z$34eXw#HA#U?On@;BVI? zgnO4%{99d>XsRhvHdtwAT@9K`|Fc?-v)_pNa4 zAyq~-n8jOtA|4@u1we9?{0{)%|C3KWbc0`}ojVxY^#lC~iTvMv?)m*wPo?L@ z)WaVEyZ;gPsc0SZZnC*G*#!CV3u)mVC~)C7YgI`<0MP!UAnZbG0EIB-^CWkFj1K[Sub-collection](understanding-the-yaml-file/stream-slicers.md#substreams) | - | Sync mode | Full refresh
Incremental | - | Schema discovery | Static schemas | - | [Stream slicing](understanding-the-yaml-file/stream-slicers.md) | [Datetime](understanding-the-yaml-file/stream-slicers.md#Datetime), [lists](understanding-the-yaml-file/stream-slicers.md#list-stream-slicer), [parent-resource id](understanding-the-yaml-file/stream-slicers.md#Substream-slicer) | - | [Record transformation](understanding-the-yaml-file/record-selector.md) | [Field selection](understanding-the-yaml-file/record-selector.md#selecting-a-field)
[Adding fields](understanding-the-yaml-file/record-selector.md#adding-fields)
[Removing fields](understanding-the-yaml-file/record-selector.md#removing-fields)
[Filtering records](understanding-the-yaml-file/record-selector.md#filtering-records) | - | [Error detection](understanding-the-yaml-file/error-handling.md) | [From HTTP status code](understanding-the-yaml-file/error-handling.md#from-status-code)
[From error message](understanding-the-yaml-file/error-handling.md#from-error-message) | - | [Backoff strategies](understanding-the-yaml-file/error-handling.md#Backoff-Strategies) | [Exponential](understanding-the-yaml-file/error-handling.md#Exponential-backoff)
[Constant](understanding-the-yaml-file/error-handling.md#Constant-Backoff)
[Derived from headers](understanding-the-yaml-file/error-handling.md#Wait-time-defined-in-header) | + | Feature | Support | + |--------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | Resource type | Collections
[Sub-collection](understanding-the-yaml-file/stream-slicers.md#substreams) | + | Sync mode | Full refresh
Incremental | + | Schema discovery | Static schemas | + | [Stream slicing](understanding-the-yaml-file/stream-slicers.md) | [Datetime](understanding-the-yaml-file/stream-slicers.md#Datetime), [lists](understanding-the-yaml-file/stream-slicers.md#list-stream-slicer), [parent-resource id](understanding-the-yaml-file/stream-slicers.md#Substream-slicer) | + | [Record transformation](understanding-the-yaml-file/record-selector.md) | [Field selection](understanding-the-yaml-file/record-selector.md#selecting-a-field)
[Adding fields](understanding-the-yaml-file/record-selector.md#adding-fields)
[Removing fields](understanding-the-yaml-file/record-selector.md#removing-fields)
[Filtering records](understanding-the-yaml-file/record-selector.md#filtering-records) | + | [Error detection](understanding-the-yaml-file/error-handling.md) | [From HTTP status code](understanding-the-yaml-file/error-handling.md#from-status-code)
[From error message](understanding-the-yaml-file/error-handling.md#from-error-message) | + | [Backoff strategies](understanding-the-yaml-file/error-handling.md#Backoff-Strategies) | [Exponential](understanding-the-yaml-file/error-handling.md#Exponential-backoff)
[Constant](understanding-the-yaml-file/error-handling.md#Constant-Backoff)
[Derived from headers](understanding-the-yaml-file/error-handling.md#Wait-time-defined-in-header) | If the answer to all questions is yes, you can use the low-code framework to build a connector for the source. If not, use the [Python CDK](../cdk-python/README.md). @@ -101,7 +101,19 @@ For each stream, configure the following components: | Transformations | | A set of transformations to be applied on the records read from the source before emitting them to the destination | | Checkpoint interval | | Defines the interval, in number of records, at which incremental syncs should be checkpointed | -For a deep dive into each of the components, refer to Understanding the YAML file +For a deep dive into each of the components, refer to [Understanding the YAML file](./understanding-the-yaml-file/yaml-overview.md) or the [full YAML Schema definition](./source_schema.yaml) + +## Tutorial + +This section a tutorial that will guide you through the end-to-end process of implementing a low-code connector. + +0. [Getting started](tutorial/0-getting-started.md) +1. [Creating a source](tutorial/1-create-source.md) +2. [Installing dependencies](tutorial/2-install-dependencies.md) +3. [Connecting to the API](tutorial/3-connecting-to-the-API-source.md) +4. [Reading data](tutorial/4-reading-data.md) +5. [Incremental reads](tutorial/5-incremental-reads.md) +6. [Testing](tutorial/6-testing.md) ## Sample connectors diff --git a/docs/connector-development/config-based/source_schema.yaml b/docs/connector-development/config-based/source_schema.yaml new file mode 100644 index 000000000000..68d37b78e243 --- /dev/null +++ b/docs/connector-development/config-based/source_schema.yaml @@ -0,0 +1,628 @@ +--- +"$schema": "http://json-schema.org/draft-06/schema#" +title: Connector Builder #TODO +type: object +description: Connector Builder structs #TODO +properties: + version: + "$ref": "#/definitions/Version" + streams: + "$ref": "#/definitions/Streams" + check: + "$ref": "#/definitions/ConnectionChecker" +definitions: + "$options": + type: object + additionalProperties: true + ConnectionChecker: + type: object + anyOf: + - "$ref": "#/definitions/CheckStream" + CheckStream: + type: object + additionalProperties: true + required: + - stream_names + properties: + "$options": + "$ref": "#/definitions/$options" + stream_names: + type: array + items: + type: string + Version: + type: string + description: "connector definition version" + Streams: + type: array + items: + "$ref": "#/definitions/Stream" + Stream: + type: object + additionalProperties: true + required: + - name + - retriever + properties: + "$options": + "$ref": "#/definitions/$options" + name: + type: string + primary_key: + "$ref": "#/definitions/PrimaryKey" + retriever: + "$ref": "#/definitions/Retriever" + stream_cursor_field: + type: string + transformations: + "$ref": "#/definitions/RecordTransformation" + checkpoint_interval: + type: integer + PrimaryKey: + type: string + Retriever: + type: object + anyOf: + - "$ref": "#/definitions/SimpleRetriever" + SimpleRetriever: + type: object + additionalProperties: true + required: + - name + - requester + - record_selector + properties: + "$options": + "$ref": "#/definitions/$options" + name: + type: string + primary_key: + "$ref": "#/definitions/PrimaryKey" + requester: + "$ref": "#/definitions/Requester" + record_selector: + "$ref": "#/definitions/HttpSelector" + paginator: + "$ref": "#/definitions/Paginator" + stream_slicer: + "$ref": "#/definitions/StreamSlicer" + Requester: + type: object + anyOf: + - "$ref": "#/definitions/HttpRequester" + HttpRequester: + type: object + additionalProperties: true + required: + - name + - url_base + - path + properties: + "$options": + "$ref": "#/definitions/$options" + name: + type: string + url_base: + type: string + description: "base url" + path: + type: string + description: "path" + http_method: + "$ref": "#/definitions/HttpMethod" + default: "GET" + request_options_provider: + "$ref": "#/definitions/RequestOptionsProvider" + authenticator: + "$ref": "#/definitions/Authenticator" + error_handler: + "$ref": "#/definitions/ErrorHandler" + HttpMethod: + type: string + enum: + - GET + - POST + RequestOptionsProvider: + type: object + anyOf: + - "$ref": "#/definitions/InterpolatedRequestOptionsProvider" + InterpolatedRequestOptionsProvider: + type: object + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + request_parameters: + "$ref": "#/definitions/RequestInput" + request_headers: + "$ref": "#/definitions/RequestInput" + request_body_data: + "$ref": "#/definitions/RequestInput" + request_body_json: + "$ref": "#/definitions/RequestInput" + RequestInput: + type: object + additionalProperties: true + Authenticator: + type: object + description: "Authenticator type" + anyOf: + - "$ref": "#/definitions/OAuth" + - "$ref": "#/definitions/ApiKeyAuthenticator" + - "$ref": "#/definitions/BearerAuthenticator" + - "$ref": "#/definitions/BasicHttpAuthenticator" + ApiKeyAuthenticator: + type: object + additionalProperties: true + required: + - header + - api_token + properties: + "$options": + "$ref": "#/definitions/$options" + header: + type: string + api_token: + type: string + BearerAuthenticator: + type: object + additionalProperties: true + required: + - api_token + properties: + "$options": + "$ref": "#/definitions/$options" + api_token: + type: string + BasicHttpAuthenticator: + type: object + additionalProperties: true + required: + - username + properties: + "$options": + "$ref": "#/definitions/$options" + username: + type: string + password: + type: string + OAuth: + type: object + additionalProperties: true + required: + - token_refresh_endpoint + - client_id + - client_secret + - refresh_token + - access_token_name + - expires_in_name + properties: + "$options": + "$ref": "#/definitions/$options" + token_refresh_endpoint: + type: string + client_id: + type: string + client_secret: + type: string + refresh_token: + type: string + scopes: + type: array + items: + type: string + default: [] + token_expiry_date: + type: string + access_token_name: + type: string + default: "access_token" + expires_in_name: + type: string + default: "expires_in" + refresh_request_body: + type: object + Paginator: + type: object + anyOf: + - "$ref": "#/definitions/DefaultPaginator" + - "$ref": "#/definitions/NoPaginator" + DefaultPaginator: + type: object + additionalProperties: true + required: + - page_token_option + - pagination_strategy + - url_base + properties: + "$options": + "$ref": "#/definitions/$options" + page_size: + type: integer + page_size_option: + "$ref": "#/definitions/RequestOption" + page_token_option: + "$ref": "#/definitions/RequestOption" + pagination_strategy: + "$ref": "#/definitions/PaginationStrategy" + url_base: + type: string + NoPaginator: + type: object + additionalProperties: true + RequestOption: + type: object + additionalProperties: true + required: + - inject_into + properties: + inject_into: + "$ref": "#/definitions/RequestOptionType" + field_name: + type: string + RequestOptionType: + type: string + enum: + - request_parameter + - header + - path + - body_data + - body_json + PaginationStrategy: + type: object + anyOf: + - "$ref": "#/definitions/CursorPagination" + - "$ref": "#/definitions/OffsetIncrement" + - "$ref": "#/definitions/PageIncrement" + CursorPagination: + type: object + additionalProperties: true + required: + - cursor_value + properties: + "$options": + "$ref": "#/definitions/$options" + cursor_value: + type: string + stop_condition: + type: string + page_size: + type: integer + OffsetIncrement: + type: object + additionalProperties: true + required: + - page_size + properties: + "$options": + "$ref": "#/definitions/$options" + page_size: + type: integer + PageIncrement: + type: object + additionalProperties: true + required: + - page_size + properties: + "$options": + "$ref": "#/definitions/$options" + page_size: + type: integer + HttpSelector: + type: object + anyOf: + - "$ref": "#/definitions/RecordSelector" + RecordSelector: + type: object + required: + - extractor + properties: + "$options": + "$ref": "#/definitions/$options" + extractor: + "$ref": "#/definitions/RecordExtractor" + record_filter: + "$ref": "#/definitions/RecordFilter" + RecordExtractor: + type: object + anyOf: + - "$ref": "#/definitions/DpathExtractor" + DpathExtractor: + type: object + additionalProperties: true + required: + - field_pointer + properties: + "$options": + "$ref": "#/definitions/$options" + field_pointer: + type: array + items: + type: string + RecordFilter: + type: object + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + condition: + type: string + StreamSlicer: + type: object + anyOf: + - "$ref": "#/definitions/DatetimeStreamSlicer" + - "$ref": "#/definitions/ListStreamSlicer" + - "$ref": "#/definitions/CartesianProductStreamSlicer" + - "$ref": "#/definitions/SubstreamSlicer" + - "$ref": "#/definitions/SingleSlice" + SingleSlice: + type: object + additionalProperties: true + SubstreamSlicer: + type: object + required: + - parent_stream_configs + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + parent_stream_configs: + type: array + items: + "$ref": "#/definitions/ParentStreamConfig" + ParentStreamConfig: + type: object + required: + - stream + - parent_key + - stream_slice_field + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + stream: + "$ref": "#/definitions/Stream" + parent_key: + type: string + stream_slice_field: + type: string + request_option: + "$ref": "#/definitions/RequestOption" + CartesianProductStreamSlicer: + type: object + required: + - stream_slicers + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + stream_slicers: + type: array + items: + "$ref": "#/definitions/StreamSlicer" + ListStreamSlicer: + type: object + required: + - slice_values + - cursor_field + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + slice_values: + type: array + items: + type: string + cursor_field: + type: string + request_option: + "$ref": "#/definitions/RequestOption" + DatetimeStreamSlicer: + type: object + required: + - start_datetime + - end_datetime + - step + - cursor_field + - datetime_format + additional_properties: false + properties: + "$options": + "$ref": "#/definitions/$options" + start_datetime: + "$ref": "#/definitions/MinMaxDatetime" + end_datetime: + "$ref": "#/definitions/MinMaxDatetime" + step: + type: string + cursor_field: + type: string + datetime_format: + type: string + start_time_option: + "$ref": "#/definitions/RequestOption" + end_time_option: + "$ref": "#/definitions/RequestOption" + stream_state_field_start: + type: string + stream_state_field_end: + type: string + lookback_window: + type: string + MinMaxDatetime: + type: object + required: + - datetime + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + datetime: + type: string + datetime_format: + type: string + min_datetime: + type: string + max_datetime: + type: string + ErrorHandler: + type: object + description: "Error handler" + anyOf: + - "$ref": "#/definitions/DefaultErrorHandler" + - "$ref": "#/definitions/CompositeErrorHandler" + DefaultErrorHandler: + type: object + required: + - max_retries + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + response_filters: + type: array + items: + "$ref": "#/definitions/HttpResponseFilter" + max_retries: + type: integer + default: 5 + backoff_strategies: + type: array + items: + "$ref": "#/definitions/BackoffStrategy" + default: [] + CompositeErrorHandler: + type: object + required: + - error_handlers + additionalProperties: + "$options": + "$ref": "#/definitions/$options" + error_handlers: + type: array + items: + "$ref": "#/definitions/ErrorHandler" + HttpResponseFilter: + type: object + required: + - action + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + action: + "$ref": "#/definitions/ResponseAction" + http_codes: + type: array + items: + type: integer + default: [] + error_message_contains: + type: string + predicate: + type: string + ResponseAction: + type: string + enum: + - SUCCESS + - FAIL + - IGNORE + - RETRY + BackoffStrategy: + type: object + anyOf: + - "$ref": "#/definitions/ExponentialBackoff" + - "$ref": "#/definitions/ConstantBackoff" + - "$ref": "#/definitions/WaitTimeFromHeader" + - "$ref": "#/definitions/WaitUntilTimeFromHeader" + ExponentialBackoff: + type: object + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + factor: + type: integer + default: 5 + ConstantBackoff: + type: object + additionalProperties: true + required: + - backoff_time_in_seconds + properties: + "$options": + "$ref": "#/definitions/$options" + backoff_time_in_seconds: + type: number + WaitTimeFromHeader: + type: object + additionalProperties: true + required: + - header + properties: + "$options": + "$ref": "#/definitions/$options" + header: + type: string + regex: + type: string + WaitUntilTimeFromHeader: + type: object + additionalProperties: true + required: + - header + properties: + "$options": + "$ref": "#/definitions/$options" + header: + type: string + regex: + type: string + min_wait: + type: number + RecordTransformation: + type: object + anyOf: + - "$ref": "#/definitions/AddFields" + - "$ref": "#/definitions/RemoveFields" + AddFields: + type: object + required: + - fields + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + fields: + type: array + items: + "$ref": "#/definitions/AddedFieldDefinition" + AddedFieldDefinition: + type: object + required: + - path + - value + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + path: + "$ref": "#/definitions/FieldPointer" + value: + type: string + FieldPointer: + type: array + items: + type: string + RemoveFields: + type: object + required: + - field_pointers + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + field_pointers: + type: array + items: + "$ref": "#/definitions/FieldPointer" diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md b/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md index ef81ed4f20b6..187752f2f200 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md @@ -2,6 +2,19 @@ The `Authenticator` defines how to configure outgoing HTTP requests to authenticate on the API source. +Schema: + +```yaml + Authenticator: + type: object + description: "Authenticator type" + anyOf: + - "$ref": "#/definitions/OAuth" + - "$ref": "#/definitions/ApiKeyAuthenticator" + - "$ref": "#/definitions/BearerAuthenticator" + - "$ref": "#/definitions/BasicHttpAuthenticator" +``` + ## Authenticators ### ApiKeyAuthenticator @@ -9,6 +22,26 @@ The `Authenticator` defines how to configure outgoing HTTP requests to authentic The `ApiKeyAuthenticator` sets an HTTP header on outgoing requests. The following definition will set the header "Authorization" with a value "Bearer hello": +Schema: + +```yaml + ApiKeyAuthenticator: + type: object + additionalProperties: true + required: + - header + - api_token + properties: + "$options": + "$ref": "#/definitions/$options" + header: + type: string + api_token: + type: string +``` + +Example: + ```yaml authenticator: type: "ApiKeyAuthenticator" @@ -21,6 +54,23 @@ authenticator: The `BearerAuthenticator` is a specialized `ApiKeyAuthenticator` that always sets the header "Authorization" with the value "Bearer {token}". The following definition will set the header "Authorization" with a value "Bearer hello" +Schema: + +```yaml + BearerAuthenticator: + type: object + additionalProperties: true + required: + - api_token + properties: + "$options": + "$ref": "#/definitions/$options" + api_token: + type: string +``` + +Example: + ```yaml authenticator: type: "BearerAuthenticator" @@ -34,6 +84,25 @@ More information on bearer authentication can be found [here](https://swagger.io The `BasicHttpAuthenticator` set the "Authorization" header with a (USER ID/password) pair, encoded using base64 as per [RFC 7617](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme). The following definition will set the header "Authorization" with a value "Basic {encoded credentials}" +Schema: + +```yaml + BasicHttpAuthenticator: + type: object + additionalProperties: true + required: + - username + properties: + "$options": + "$ref": "#/definitions/$options" + username: + type: string + password: + type: string +``` + +Example: + ```yaml authenticator: type: "BasicHttpAuthenticator" @@ -43,6 +112,8 @@ authenticator: The password is optional. Authenticating with APIs using Basic HTTP and a single API key can be done as: +Example: + ```yaml authenticator: type: "BasicHttpAuthenticator" @@ -63,6 +134,49 @@ OAuth authentication is supported through the `OAuthAuthenticator`, which requir - expires_in_name (Optional): The field to extract expires_in from in the response. Default: "expires_in" - refresh_request_body (Optional): The request body to send in the refresh request. Default: None +Schema: + +```yaml + OAuth: + type: object + additionalProperties: true + required: + - token_refresh_endpoint + - client_id + - client_secret + - refresh_token + - access_token_name + - expires_in_name + properties: + "$options": + "$ref": "#/definitions/$options" + token_refresh_endpoint: + type: string + client_id: + type: string + client_secret: + type: string + refresh_token: + type: string + scopes: + type: array + items: + type: string + default: [ ] + token_expiry_date: + type: string + access_token_name: + type: string + default: "access_token" + expires_in_name: + type: string + default: "expires_in" + refresh_request_body: + type: object +``` + +Example: + ```yaml authenticator: type: "OAuthAuthenticator" @@ -70,4 +184,9 @@ authenticator: client_id: "{{ config['api_key'] }}" client_secret: "{{ config['client_secret'] }}" refresh_token: "" -``` \ No newline at end of file +``` + +## More readings + +- [Requester](./requester.md) +- [Request options](./request-options.md) \ No newline at end of file diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md b/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md index dd8adc275aae..2346cbd76db9 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md @@ -5,6 +5,44 @@ Other HTTP errors will result in a failed read. Other behaviors can be configured through the `Requester`'s `error_handler` field. +Schema: + +```yaml + ErrorHandler: + type: object + description: "Error handler" + anyOf: + - "$ref": "#/definitions/DefaultErrorHandler" + - "$ref": "#/definitions/CompositeErrorHandler" +``` + +## Default error handler + +Schema: + +```yaml + DefaultErrorHandler: + type: object + required: + - max_retries + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + response_filters: + type: array + items: + "$ref": "#/definitions/HttpResponseFilter" + max_retries: + type: integer + default: 5 + backoff_strategies: + type: array + items: + "$ref": "#/definitions/BackoffStrategy" + default: [ ] +``` + ## Defining errors ### From status code @@ -12,6 +50,39 @@ Other behaviors can be configured through the `Requester`'s `error_handler` fiel Response filters can be used to define how to handle requests resulting in responses with a specific HTTP status code. For instance, this example will configure the handler to also retry responses with 404 error: +Schema: + +```yaml + HttpResponseFilter: + type: object + required: + - action + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + action: + "$ref": "#/definitions/ResponseAction" + http_codes: + type: array + items: + type: integer + default: [ ] + error_message_contains: + type: string + predicate: + type: string + ResponseAction: + type: string + enum: + - SUCCESS + - FAIL + - IGNORE + - RETRY +``` + +Example: + ```yaml requester: <...> @@ -72,27 +143,87 @@ requester: response_filters: - http_codes: [ 404 ] action: IGNORE - - http_codes: [ 429 ] - action: RETRY + - http_codes: [ 429 ] + action: RETRY ``` ## Backoff Strategies The error handler supports a few backoff strategies, which are described in the following sections. +Schema: + +```yaml + BackoffStrategy: + type: object + anyOf: + - "$ref": "#/definitions/ExponentialBackoff" + - "$ref": "#/definitions/ConstantBackoff" + - "$ref": "#/definitions/WaitTimeFromHeader" + - "$ref": "#/definitions/WaitUntilTimeFromHeader" +``` + ### Exponential backoff This is the default backoff strategy. The requester will backoff with an exponential backoff interval +Schema: + +```yaml + ExponentialBackoff: + type: object + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + factor: + type: integer + default: 5 +``` + ### Constant Backoff When using the `ConstantBackoffStrategy`, the requester will backoff with a constant interval. +Schema: + +```yaml + ConstantBackoff: + type: object + additionalProperties: true + required: + - backoff_time_in_seconds + properties: + "$options": + "$ref": "#/definitions/$options" + backoff_time_in_seconds: + type: number +``` + ### Wait time defined in header When using the `WaitTimeFromHeaderBackoffStrategy`, the requester will backoff by an interval specified in the response header. In this example, the requester will backoff by the response's "wait_time" header value: +Schema: + +```yaml + WaitTimeFromHeader: + type: object + additionalProperties: true + required: + - header + properties: + "$options": + "$ref": "#/definitions/$options" + header: + type: string + regex: + type: string +``` + +Example: + ```yaml requester: <...> @@ -105,6 +236,8 @@ requester: Optionally, a regular expression can be configured to extract the wait time from the header value. +Example: + ```yaml requester: <...> @@ -121,6 +254,27 @@ requester: When using the `WaitUntilTimeFromHeaderBackoffStrategy`, the requester will backoff until the time specified in the response header. In this example, the requester will wait until the time specified in the "wait_until" header value: +Schema: + +```yaml + WaitUntilTimeFromHeader: + type: object + additionalProperties: true + required: + - header + properties: + "$options": + "$ref": "#/definitions/$options" + header: + type: string + regex: + type: string + min_wait: + type: number +``` + +Example: + ```yaml requester: <...> @@ -140,6 +294,8 @@ The strategy accepts an optional regular expression to extract the time from the The error handler can have multiple backoff strategies, allowing it to fallback if a strategy cannot be evaluated. For instance, the following defines an error handler that will read the backoff time from a header, and default to a constant backoff if the wait time could not be extracted from the response: +Example: + ```yaml requester: <...> @@ -150,13 +306,30 @@ requester: header: "wait_time" - type: "ConstantBackoffStrategy" backoff_time_in_seconds: 5 - ``` The `requester` can be configured to use a `CompositeErrorHandler`, which sequentially iterates over a list of error handlers, enabling different retry mechanisms for different types of errors. In this example, a constant backoff of 5 seconds, will be applied if the response contains a "code" field, and an exponential backoff will be applied if the error code is 403: +Schema: + +```yaml + CompositeErrorHandler: + type: object + required: + - error_handlers + additionalProperties: + "$options": + "$ref": "#/definitions/$options" + error_handlers: + type: array + items: + "$ref": "#/definitions/ErrorHandler" +``` + +Example: + ```yaml requester: <...> @@ -174,4 +347,8 @@ requester: action: RETRY backoff_strategies: - type: "ExponentialBackoffStrategy" -``` \ No newline at end of file +``` + +## More readings + +- [Requester](./requester.md) diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md b/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md index 32fcb2731d62..d7284a6ff43c 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md @@ -6,12 +6,52 @@ Iterating over pages of result is different from iterating over stream slices. Stream slices have semantic value, for instance, a Datetime stream slice defines data for a specific date range. Two stream slices will have data for different date ranges. Conversely, pages don't have semantic value. More pages simply means that more records are to be read, without specifying any meaningful difference between the records of the first and later pages. -The paginator is defined by +Schema: + +```yaml + Paginator: + type: object + anyOf: + - "$ref": "#/definitions/DefaultPaginator" + - "$ref": "#/definitions/NoPaginator" + NoPaginator: + type: object + additionalProperties: true +``` + +## Default paginator + +The default paginator is defined by - `page_size_option`: How to specify the page size in the outgoing HTTP request - `pagination_strategy`: How to compute the next page to fetch - `page_token_option`: How to specify the next page to fetch in the outgoing HTTP request +Schema: + +```yaml + DefaultPaginator: + type: object + additionalProperties: true + required: + - page_token_option + - pagination_strategy + - url_base + properties: + "$options": + "$ref": "#/definitions/$options" + page_size: + type: integer + page_size_option: + "$ref": "#/definitions/RequestOption" + page_token_option: + "$ref": "#/definitions/RequestOption" + pagination_strategy: + "$ref": "#/definitions/PaginationStrategy" + url_base: + type: string +``` + 3 pagination strategies are supported 1. Page increment @@ -20,12 +60,40 @@ The paginator is defined by ## Pagination Strategies +Schema: + +```yaml + PaginationStrategy: + type: object + anyOf: + - "$ref": "#/definitions/CursorPaginator" + - "$ref": "#/definitions/OffsetIncrement" + - "$ref": "#/definitions/PageIncrement" +``` + ### Page increment When using the `PageIncrement` strategy, the page number will be set as part of the `page_token_option`. +Schema: + +```yaml + PageIncrement: + type: object + additionalProperties: true + required: + - page_size + properties: + "$options": + "$ref": "#/definitions/$options" + page_size: + type: integer +``` + The following paginator example will fetch 5 records per page, and specify the page number as a request_parameter: +Example: + ```yaml paginator: type: "DefaultPaginator" @@ -51,8 +119,25 @@ and the second request as `https://cloud.airbyte.com/api/get_data?page_size=5&pa When using the `OffsetIncrement` strategy, the number of records read will be set as part of the `page_token_option`. +Schema: + +```yaml + OffsetIncrement: + type: object + additionalProperties: true + required: + - page_size + properties: + "$options": + "$ref": "#/definitions/$options" + page_size: + type: integer +``` + The following paginator example will fetch 5 records per page, and specify the offset as a request_parameter: +Example: + ```yaml paginator: type: "DefaultPaginator" @@ -73,7 +158,7 @@ and the second request as `https://cloud.airbyte.com/api/get_data?page_size=5&of ### Cursor -The `CursorPaginationStrategy` outputs a token by evaluating its `cursor_value` string with the following parameters: +The `CursorPagination` outputs a token by evaluating its `cursor_value` string with the following parameters: - `response`: The decoded response - `headers`: HTTP headers on the response @@ -81,6 +166,25 @@ The `CursorPaginationStrategy` outputs a token by evaluating its `cursor_value` This cursor value can be used to request the next page of record. +Schema: + +```yaml + CursorPagination: + type: object + additionalProperties: true + required: + - cursor_value + properties: + "$options": + "$ref": "#/definitions/$options" + cursor_value: + type: string + stop_condition: + type: string + page_size: + type: integer +``` + #### Cursor paginator in request parameters In this example, the next page of record is defined by setting the `from` request parameter to the id of the last record read: @@ -90,7 +194,7 @@ paginator: type: "DefaultPaginator" <...> pagination_strategy: - type: "CursorPaginationStrategy" + type: "CursorPagination" cursor_value: "{{ last_records[-1]['id'] }}" page_token: field_name: "from" @@ -99,7 +203,6 @@ paginator: Assuming the endpoint to fetch data from is `https://cloud.airbyte.com/api/get_data`, the first request will be sent as `https://cloud.airbyte.com/api/get_data`. - Assuming the id of the last record fetched is 1000, the next request will be sent as `https://cloud.airbyte.com/api/get_data?from=1000`. @@ -112,7 +215,7 @@ paginator: type: "DefaultPaginator" <...> pagination_strategy: - type: "CursorPaginationStrategy" + type: "CursorPagination" cursor_value: "{{ headers['urls']['next'] }}" page_token: inject_into: "path" @@ -120,6 +223,5 @@ paginator: Assuming the endpoint to fetch data from is `https://cloud.airbyte.com/api/get_data`, the first request will be sent as `https://cloud.airbyte.com/api/get_data` - Assuming the response's next url is `https://cloud.airbyte.com/api/get_data?page=1&page_size=100`, -the next request will be sent as `https://cloud.airbyte.com/api/get_data?page=1&page_size=100` +the next request will be sent as `https://cloud.airbyte.com/api/get_data?page=1&page_size=100` \ No newline at end of file diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/record-selector.md b/docs/connector-development/config-based/understanding-the-yaml-file/record-selector.md index 568b43fc3d9b..dd729ca78ff9 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/record-selector.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/record-selector.md @@ -1,8 +1,43 @@ # Record selector The record selector is responsible for translating an HTTP response into a list of Airbyte records by extracting records from the response and optionally filtering and shaping records based on a heuristic. +Schema: + +```yaml + HttpSelector: + type: object + anyOf: + - "$ref": "#/definitions/RecordSelector" + RecordSelector: + type: object + required: + - extractor + properties: + "$options": + "$ref": "#/definitions/$options" + extractor: + "$ref": "#/definitions/RecordExtractor" + record_filter: + "$ref": "#/definitions/RecordFilter" +``` The current record extraction implementation uses [dpath](https://pypi.org/project/dpath/) to select records from the json-decoded HTTP response. +Schema: + +```yaml + DpathExtractor: + type: object + additionalProperties: true + required: + - field_pointer + properties: + "$options": + "$ref": "#/definitions/$options" + field_pointer: + type: array + items: + type: string +``` ## Common recipes: @@ -19,7 +54,6 @@ selector: ``` If the root of the response is a json object representing a single record, the record can be extracted and wrapped in an array. - For example, given a response body of the form ```json @@ -102,9 +136,7 @@ and a selector ```yaml selector: extractor: - field_pointer: - - "data" - - "records" + field_pointer: [ "data", "records" ] ``` The selected records will be @@ -139,11 +171,57 @@ selector: Fields can be added or removed from records by adding `Transformation`s to a stream's definition. +Schema: + +```yaml + RecordTransformation: + type: object + anyOf: + - "$ref": "#/definitions/AddFields" + - "$ref": "#/definitions/RemoveFields" +``` + ### Adding fields Fields can be added with the `AddFields` transformation. This example adds a top-level field "field1" with a value "static_value" +Schema: + +```yaml + AddFields: + type: object + required: + - fields + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + fields: + type: array + items: + "$ref": "#/definitions/AddedFieldDefinition" + AddedFieldDefinition: + type: object + required: + - path + - value + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + path: + "$ref": "#/definitions/FieldPointer" + value: + type: string + FieldPointer: + type: array + items: + type: string +``` + +Example: + ```yaml stream: <...> @@ -163,7 +241,7 @@ stream: - type: AddFields fields: - path: [ "start_date" ] - value: {{ stream_slice[ 'start_date' ] }} + value: { { stream_slice[ 'start_date' ] } } ``` Fields can also be added in a nested object by writing the fields' path as a list. @@ -209,6 +287,24 @@ resulting in the following record: Fields can be removed from records with the `RemoveFields` transformation. +Schema: + +```yaml + RemoveFields: + type: object + required: + - field_pointers + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + field_pointers: + type: array + items: + "$ref": "#/definitions/FieldPointer" + +``` + Given a record of the following shape: ``` @@ -251,4 +347,4 @@ resulting in the following record: }, "path3": "data_to_keep" } -``` \ No newline at end of file +``` diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/request-options.md b/docs/connector-development/config-based/understanding-the-yaml-file/request-options.md index 32595b566112..b27b53e48250 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/request-options.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/request-options.md @@ -1,12 +1,40 @@ # Request Options -There are a few ways to set request parameters, headers, and body on ongoing HTTP requests. +The primary way to set request parameters and headers is to define them as key-value pairs using a `RequestOptionsProvider`. +Other components, such as an `Authenticator` can also set additional request params or headers as needed. + +Additionally, some stateful components use a `RequestOption` to configure the options and update the value. Example of such components are [Paginators](./pagination.md) and [Stream slicers](./stream-slicers.md). ## Request Options Provider The primary way to set request options is through the `Requester`'s `RequestOptionsProvider`. The options can be configured as key value pairs: +Schema: + +```yaml + RequestOptionsProvider: + type: object + anyOf: + - "$ref": "#/definitions/InterpolatedRequestOptionsProvider" + InterpolatedRequestOptionsProvider: + type: object + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + request_parameters: + "$ref": "#/definitions/RequestInput" + request_headers: + "$ref": "#/definitions/RequestInput" + request_body_data: + "$ref": "#/definitions/RequestInput" + request_body_json: + "$ref": "#/definitions/RequestInput" +``` + +Example: + ```yaml requester: type: HttpRequester @@ -35,6 +63,33 @@ requester: key: value ``` +### Request Options + +Some components can add request options to the requests sent to the API endpoint. + +Schema: + +```yaml + RequestOption: + type: object + additionalProperties: true + required: + - inject_into + properties: + inject_into: + "$ref": "#/definitions/RequestOptionType" + field_name: + type: string + RequestOptionType: + type: string + enum: + - request_parameter + - header + - path + - body_data + - body_json +``` + ## Authenticators It is also possible for authenticators to set request parameters or headers as needed. @@ -63,7 +118,7 @@ paginator: field_name: "page" ``` -More details on paginators can be found in the [pagination section](pagination.md). +More details on paginators can be found in the [pagination section](./pagination.md). ## Stream slicers @@ -85,4 +140,10 @@ stream_slicer: inject_into: "request_parameter" ``` -More details on the stream slicers can be found in the [stream-slicers section](stream-slicers.md). +More details on the stream slicers can be found in the [stream-slicers section](./stream-slicers.md). + +## More readings + +- [Requester](./requester.md) +- [Pagination](./pagination.md) +- [Stream slicers](./stream-slicers.md) \ No newline at end of file diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/requester.md b/docs/connector-development/config-based/understanding-the-yaml-file/requester.md index f57e411932ee..d7e544424827 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/requester.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/requester.md @@ -1,10 +1,63 @@ # Requester -The Requester defines how to prepare HTTP requests to send to the source API. The current implementation is called the HttpRequester, which is defined by: - -- A base url: The root of the API source -- A path: The specific endpoint to fetch data from for a resource -- The HTTP method: the HTTP method to use (GET or POST) -- A [request options provider](request-options.md): Defines the request parameters (query parameters), headers, and request body to set on outgoing HTTP requests -- An [authenticator](authentication.md): Defines how to authenticate to the source -- An [error handler](error-handling.md): Defines how to handle errors \ No newline at end of file +The `Requester` defines how to prepare HTTP requests to send to the source API. +There is currently only one implementation, the `HttpRequester`, which is defined by + +1. A base url: The root of the API source +2. A path: The specific endpoint to fetch data from for a resource +3. The HTTP method: the HTTP method to use (GET or POST) +4. [A request options provider](./request-options.md#request-options-provider): Defines the request parameters (query parameters), headers, and request body to set on outgoing HTTP requests +5. [An authenticator](./authentication.md): Defines how to authenticate to the source +6. [An error handler](./error-handling.md): Defines how to handle errors + +The schema of a requester object is: + +```yaml + Requester: + type: object + anyOf: + - "$ref": "#/definitions/HttpRequester" + HttpRequester: + type: object + additionalProperties: true + required: + - name + - url_base + - path + properties: + "$options": + "$ref": "#/definitions/$options" + name: + type: string + url_base: + type: string + description: "base url" + path: + type: string + description: "path" + http_method: + "$ref": "#/definitions/HttpMethod" + default: "GET" + request_options_provider: + "$ref": "#/definitions/RequestOptionsProvider" + authenticator: + "$ref": "#/definitions/Authenticator" + error_handler: + "$ref": "#/definitions/ErrorHandler" + HttpMethod: + type: string + enum: + - GET + - POST +``` + +## Configuring request parameters and headers + +The primary way to set request parameters and headers is to define them as key-value pairs using a `RequestOptionsProvider`. +Other components, such as an `Authenticator` can also set additional request params or headers as needed. + +Additionally, some stateful components use a `RequestOption` to configure the options and update the value. Example of such components are [Paginators](./pagination.md) and [Stream slicers](./stream-slicers.md). + +## More readings + +- [Request options](./request-options.md) \ No newline at end of file diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/stream-slicers.md b/docs/connector-development/config-based/understanding-the-yaml-file/stream-slicers.md index 962d3065b0ac..4dfc1f9cc29a 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/stream-slicers.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/stream-slicers.md @@ -9,13 +9,34 @@ When a stream is read incrementally, a state message will be output by the conne At the beginning of a `read` operation, the `StreamSlicer` will compute the slices to sync given the connection config and the stream's current state, As the `Retriever` reads data from the `Source`, the `StreamSlicer` keeps track of the `Stream`'s state, which will be emitted after reading each stream slice. -More information of stream slicing can be found in the [stream-slices section](../../cdk-python/stream-slices.md). +More information about stream slicing can be found in the [stream-slices section](../../cdk-python/stream-slices.md). -## Implementations +Schema: -This section gives an overview of the stream slicers currently implemented. +```yaml + StreamSlicer: + type: object + anyOf: + - "$ref": "#/definitions/DatetimeStreamSlicer" + - "$ref": "#/definitions/ListStreamSlicer" + - "$ref": "#/definitions/CartesianProductStreamSlicer" + - "$ref": "#/definitions/SubstreamSlicer" + - "$ref": "#/definitions/SingleSlice" +``` + +### Single slice -### Datetime +The single slice only produces one slice for the whole stream. + +Schema: + +```yaml + SingleSlice: + type: object + additionalProperties: true +``` + +### DatetimeStreamSlicer The `DatetimeStreamSlicer` iterates over a datetime range by partitioning it into time windows. This is done by slicing the stream on the records' cursor value, defined by the Stream's `cursor_field`. @@ -23,6 +44,61 @@ This is done by slicing the stream on the records' cursor value, defined by the Given a start time, an end time, and a step function, it will partition the interval [start, end] into small windows of the size described by the step. For instance, +Schema: + +```yaml + DatetimeStreamSlicer: + type: object + required: + - start_datetime + - end_datetime + - step + - cursor_field + - datetime_format + additional_properties: false + properties: + "$options": + "$ref": "#/definitions/$options" + start_datetime: + "$ref": "#/definitions/MinMaxDatetime" + end_datetime: + "$ref": "#/definitions/MinMaxDatetime" + step: + type: string + cursor_field: + type: string + datetime_format: + type: string + start_time_option: + "$ref": "#/definitions/RequestOption" + end_time_option: + "$ref": "#/definitions/RequestOption" + stream_state_field_start: + type: string + stream_state_field_end: + type: string + lookback_window: + type: string + MinMaxDatetime: + type: object + required: + - datetime + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + datetime: + type: string + datetime_format: + type: string + min_datetime: + type: string + max_datetime: + type: string +``` + +Example: + ```yaml stream_slicer: start_datetime: "2021-02-01T00:00:00.000000+0000", @@ -86,6 +162,28 @@ It is defined by - The cursor field on a record - request_option: optional request option to set on outgoing request parameters +Schema: + +```yaml + ListStreamSlicer: + type: object + required: + - slice_values + - cursor_field + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + slice_values: + type: array + items: + type: string + cursor_field: + type: string + request_option: + "$ref": "#/definitions/RequestOption" +``` + As an example, this stream slicer will iterate over the 2 repositories ("airbyte" and "airbyte-secret") and will set a request_parameter on outgoing HTTP requests. ```yaml @@ -104,6 +202,23 @@ stream_slicer: `CartesianProductStreamSlicer` iterates over the cartesian product of its underlying stream slicers. +Schema: + +```yaml + CartesianProductStreamSlicer: + type: object + required: + - stream_slicers + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + stream_slicers: + type: array + items: + "$ref": "#/definitions/StreamSlicer" +``` + Given 2 stream slicers with the following slices: A: `[{"start_date": "2021-01-01", "end_date": "2021-01-01"}, {"start_date": "2021-01-02", "end_date": "2021-01-02"}]` B: `[{"s": "hello"}, {"s": "world"}]` @@ -118,26 +233,58 @@ the resulting stream slices are ] ``` -[^1] This is a slight oversimplification. See [update cursor section](#cursor-update) for more details on how the cursor is updated. - -## Substreams +## SubstreamSlicer Substreams are streams that depend on the records on another stream We might for instance want to read all the commits for a given repository (parent stream). -### Substream slicer - Substreams are implemented by defining their stream slicer as a`SubstreamSlicer`. -For each stream, the slicer needs to know +`SubstreamSlicer` iterates over the parent's stream slices. +We might for instance want to read all the commits for a given repository (parent resource). - what the parent stream is - what is the key of the records in the parent stream - what is the field defining the stream slice representing the parent record - how to specify that information on an outgoing HTTP request -Assuming the commits for a given repository can be read by specifying the repository as a request_parameter, this could be defined as +Schema: + +```yaml + SubstreamSlicer: + type: object + required: + - parent_stream_configs + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + parent_stream_configs: + type: array + items: + "$ref": "#/definitions/ParentStreamConfig" + ParentStreamConfig: + type: object + required: + - stream + - parent_key + - stream_slice_field + additionalProperties: true + properties: + "$options": + "$ref": "#/definitions/$options" + stream: + "$ref": "#/definitions/Stream" + parent_key: + type: string + stream_slice_field: + type: string + request_option: + "$ref": "#/definitions/RequestOption" +``` + +Example: ```yaml stream_slicer: @@ -154,21 +301,29 @@ stream_slicer: REST APIs often nest sub-resources in the URL path. If the URL to fetch commits was "/repositories/:id/commits", then the `Requester`'s path would need to refer to the stream slice's value and no `request_option` would be set: +Example: + ```yaml retriever: <...> requester: <...> - path: "/respositories/{{ stream_slice.repository }}/commits + path: "/respositories/{{ stream_slice.repository }}/commits" stream_slicer: type: "SubstreamSlicer" - parent_streams_configs: - - stream: "*ref(repositories_stream)" - parent_key: "id" - stream_slice_field: "repository" +parent_streams_configs: + - stream: "*ref(repositories_stream)" + parent_key: "id" + stream_slice_field: "repository" ``` +## Nested streams + +Nested streams, subresources, or streams that depend on other streams can be implemented using a [`SubstreamSlicer`](#SubstreamSlicer) + ## More readings - [Incremental streams](../../cdk-python/incremental-stream.md) -- [Stream slices](../../cdk-python/stream-slices.md) \ No newline at end of file +- [Stream slices](../../cdk-python/stream-slices.md) + +[^1] This is a slight oversimplification. See [update cursor section](#cursor-update) for more details on how the cursor is updated. diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/yaml-overview.md b/docs/connector-development/config-based/understanding-the-yaml-file/yaml-overview.md index c7b159632f1c..95b5059c8574 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/yaml-overview.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/yaml-overview.md @@ -4,147 +4,99 @@ The low-code framework involves editing a boilerplate [YAML file](../low-code-cd ## Stream -Streams define the schema of the data to sync, as well as how to read it from the underlying API source. A stream generally corresponds to a resource within the API. They are analogous to tables for a relational database source. +Streams define the schema of the data to sync, as well as how to read it from the underlying API source. +A stream generally corresponds to a resource within the API. They are analogous to tables for a relational database source. -A stream is defined by: +A stream's schema will can defined as a [JSONSchema](https://json-schema.org/) file in `/schemas/.json`. +More information on how to define a stream's schema can be found [here](../source_schema.yaml) -1. A name -2. Primary key (Optional): Used to uniquely identify records, enabling deduplication. Can be a string for single primary keys, a list of strings for composite primary keys, or a list of list of strings for composite primary keys consisting of nested fields -3. [Schema](../../cdk-python/schemas.md): Describes the data to sync -4. [Data retriever](#data-retriever): Describes how to retrieve the data from the API -5. [Cursor field](../../cdk-python/incremental-stream.md) (Optional): Field to use as stream cursor. Can either be a string, or a list of strings if the cursor is a nested field. -6. [Transformations](#transformations) (Optional): A set of transformations to be applied on the records read from the source before emitting them to the destination -7. [Checkpoint interval](https://docs.airbyte.com/understanding-airbyte/airbyte-protocol/#state--checkpointing) (Optional): Defines the interval, in number of records, at which incremental syncs should be checkpointed - -### Data retriever - -The data retriever defines how to read the data for a Stream, and acts as an orchestrator for the data retrieval flow. -There is currently only one implementation, the `SimpleRetriever`, which is defined by - -1. [Requester](requester.md): Describes how to submit requests to the API source -2. [Paginator](pagination.md): Describes how to navigate through the API's pages -3. [Record selector](record-selector.md): Describes how to extract records from a HTTP response -4. [Stream Slicer](stream-slicers.md): Describes how to partition the stream, enabling incremental syncs and checkpointing - -Each of those components (and their subcomponents) are defined by an explicit interface and one or many implementations. -The developer can choose and configure the implementation they need depending on specifications of the integration they are building against. - -Since the `Retriever` is defined as part of the Stream configuration, different Streams for a given Source can use different `Retriever` definitions if needed. - -### Transformations - -Fields can be added or removed from records by adding `Transformation`s to a stream's definition. - -#### Adding fields - -Fields can be added with the `AddFields` transformation. -This example adds a top-level field "field1" with a value "static_value" +The schema of a stream object is: ```yaml -stream: - <...> - transformations: - - type: AddFields - fields: - - path: [ "field1" ] - value: "static_value" + Stream: + type: object + additionalProperties: true + required: + - name + - retriever + properties: + "$options": + "$ref": "#/definitions/$options" + name: + type: string + primary_key: + "$ref": "#/definitions/PrimaryKey" + retriever: + "$ref": "#/definitions/Retriever" + stream_cursor_field: + type: string + transformations: + "$ref": "#/definitions/RecordTransformation" + checkpoint_interval: + type: integer ``` -This example adds a top-level field "start_date", whose value is evaluated from the stream slice: +More details on streams and sources can be found in the [basic concepts section](../../cdk-python/basic-concepts.md). -```yaml -stream: - <...> - transformations: - - type: AddFields - fields: - - path: [ "start_date" ] - value: {{ stream_slice[ 'start_date' ] }} -``` +### Data retriever -Fields can also be added in a nested object by writing the fields' path as a list. +The data retriever defines how to read the data for a Stream and acts as an orchestrator for the data retrieval flow. -Given a record of the following shape: +It is described by: -``` -{ - "id": 0, - "data": - { - "field0": "some_data" - } -} -``` +1. [Requester](./requester.md): Describes how to submit requests to the API source +2. [Paginator](./pagination.md): Describes how to navigate through the API's pages +3. [Record selector](./record-selector.md): Describes how to extract records from a HTTP response +4. [Stream slicer](./stream-slicers.md): Describes how to partition the stream, enabling incremental syncs and checkpointing -this definition will add a field in the "data" nested object: +Each of those components (and their subcomponents) are defined by an explicit interface and one or many implementations. +The developer can choose and configure the implementation they need depending on specifications of the integration they are building against. -```yaml -stream: - <...> - transformations: - - type: AddFields - fields: - - path: [ "data", "field1" ] - value: "static_value" -``` +Since the `Retriever` is defined as part of the Stream configuration, different Streams for a given Source can use different `Retriever` definitions if needed. -resulting in the following record: +The schema of a retriever object is: +```yaml + Retriever: + type: object + anyOf: + - "$ref": "#/definitions/SimpleRetriever" + SimpleRetriever: + type: object + additionalProperties: true + required: + - name + - requester + - record_selector + properties: + "$options": + "$ref": "#/definitions/$options" + name: + type: string + primary_key: + "$ref": "#/definitions/PrimaryKey" + requester: + "$ref": "#/definitions/Requester" + record_selector: + "$ref": "#/definitions/HttpSelector" + paginator: + "$ref": "#/definitions/Paginator" + stream_slicer: + "$ref": "#/definitions/StreamSlicer" + PrimaryKey: + type: string ``` -{ - "id": 0, - "data": - { - "field0": "some_data", - "field1": "static_value" - } -} -``` - -#### Removing fields -Fields can be removed from records with the `RemoveFields` transformation. +## Configuring the cursor field for incremental syncs -Given a record of the following shape: - -``` -{ - "path": - { - "to": - { - "field1": "data_to_remove", - "field2": "data_to_keep" - } - }, - "path2": "data_to_remove", - "path3": "data_to_keep" -} -``` +Incremental syncs are supported by using a `DatetimeStreamSlicer` to iterate over a datetime range. -this definition will remove the 2 instances of "data_to_remove" which are found in "path2" and "path.to.field1": +Given a start time, an end time, and a step function, it will partition the interval [start, end] into small windows of the size described by the step. +Note that the `StreamSlicer`'s `cursor_field` must match the `Stream`'s `stream_cursor_field`. -```yaml -the_stream: - <...> - transformations: - - type: RemoveFields - field_pointers: - - [ "path", "to", "field1" ] - - [ "path2" ] -``` +More information on `DatetimeStreamSlicer` can be found in the [stream slicers](./stream-slicers.md#datetimestreamslicer) section. -resulting in the following record: +## More readings -``` -{ - "path": - { - "to": - { - "field2": "data_to_keep" - } - }, - "path3": "data_to_keep" -} -``` \ No newline at end of file +- [Requester](./requester.md) +- [Stream slicers](./stream-slicers.md) \ No newline at end of file From 223765439249a111a6891ad3d752927c2774d11f Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 8 Oct 2022 23:11:11 -0500 Subject: [PATCH 003/498] config db data catalog (#16427) --- .../database-data-catalog.md | 74 +++++++++++++++++++ docusaurus/sidebars.js | 1 + 2 files changed, 75 insertions(+) create mode 100644 docs/understanding-airbyte/database-data-catalog.md diff --git a/docs/understanding-airbyte/database-data-catalog.md b/docs/understanding-airbyte/database-data-catalog.md new file mode 100644 index 000000000000..e129e8ead24b --- /dev/null +++ b/docs/understanding-airbyte/database-data-catalog.md @@ -0,0 +1,74 @@ +# Config Database +* `workspace` + * Each record represents a logical workspace for an Airbyte user. In the open-source version of the product, only one workspace is allowed. +* `actor_definition` + * Each record represents a connector that Airbyte supports, e.g. Postgres. This table represents all the connectors that is supported by the current running platform. + * The `actor_type` column tells us whether the record represents a Source or a Destination. + * The `spec` column is a JSON blob. The schema of this JSON blob matches the [spec](airbyte-protocol.md#actor-specification) model in the Airbyte Protocol. Because the protocol object is JSON, this has to be a JSON blob. + * The `release_stage` describes the certification level of the connector (e.g. Alpha, Beta, Generally Available). + * The `docker_repository` field is the name of the docker image associated with the connector definition. `docker_image_tag` is the tag of the docker image and the version of the connector definition. + * The `source_type` field is only used for Sources, and represents the category of the connector definition (e.g. API, Database). + * The `resource_requirements` field sets a default resource requirement for any connector of this type. This overrides the default we set for all connector definitions, and it can be overridden by a connection-specific resource requirement. The column is a JSON blob with the schema defined in [ActorDefinitionResourceRequirements.yaml](airbyte-config/config-models/src/main/resources/types/ActorDefinitionResourceRequirements.yaml) + * The `public` boolean column, describes if a connector is available to all workspaces or not. For non, `public` connector definitions, they can be provisioned to a workspace using the `actor_definition_workspace_grant` table. `custom` means that the connector is written by a user of the platform (and not packaged into the Airbyte product). + * Each record contains additional metadata and display data about a connector (e.g. `name` and `icon`), and we should add additional metadata here over time. +* `actor_definition_workspace_grant` + * Each record represents provisioning a non `public` connector definition to a workspace. + * todo (cgardens) - should this table have a `created_at` column? +* `actor` + * Each record represents a configured connector. e.g. A Postgres connector configured to pull data from my database. + * The `actor_type` column tells us whether the record represents a Source or a Destination. + * The `actor_definition_id` column is a foreign key to the connector definition that this record is implementing. + * The `configuration` column is a JSON blob. The schema of this JSON blob matches the schema specified in the `spec` column in the `connectionSpecification` field of the JSON blob. Keep in mind this schema is specific to each connector (e.g. the schema of Postgres and Salesforce are different), which is why this column has to be a JSON blob. +* `actor_catalog` + * Each record contains a catalog for an actor. The records in this table are meant to be immutable. + * The `catalog` column is a JSON blob. The schema of this JSON blob matches the [catalog](airbyte-protocol.md#catalog) model in the Airbyte Protocol. Because the protocol object is JSON, this has to be a JSON blob. The `catalog_hash` column is a 32-bit murmur3 hash ( x86 variant) of the `catalog` field to make comparisons easier. + * todo (cgardens) - should we remove the `modified_at` column? These records should be immutable. +* `actor_catalog_fetch_event` + * Each record represents an attempt to fetch the catalog for an actor. The records in this table are meant to be immutable. + * The `actor_id` column represents the actor that the catalog is being fetched for. The `config_hash` represents a hash (32-bit murmur3 hash - x86 variant) of the `configuration` column of that actor, at the time the attempt to fetch occurred. + * The `catalog_id` is a foreign key to the `actor_catalog` table. It represents the catalog fetched by this attempt. We use the foreign key, because the catalogs are often large and often multiple fetch events result in retrieving the same catalog. Also understanding how often the same catalog is fetched is interesting from a product analytics point of view. + * The `actor_version` column represents the `actor_definition` version that was in use when the fetch event happened. This column is needed, because while we can infer the `actor_definition` from the foreign key relationship with the `actor` table, we cannot do the same for the version, as that can change over time. + * todo (cgardens) - should we remove the `modified_at` column? These records should be immutable. +* `connection` + * Each record in this table configures a connection (`source_id`, `destination_id`, and relevant configuration). + * The `resource_requirements` field sets a default resource requirement for the connection. This overrides the default we set for all connector definitions and the default set for the connector definitions. The column is a JSON blob with the schema defined in [ResourceRequirements.yaml](airbyte-config/config-models/src/main/resources/types/ResourceRequirements.yaml). + * The `source_catalog_id` column is a foreign key to the `sourc_catalog` table and represents the catalog that was used to configure the connection. This should not be confused with the `catalog` column which contains the [ConfiguredCatalog](airbyte-protocol.md#catalog) for the connection. + * The `schedule_type` column defines what type of schedule is being used. If the `type` is manual, then `schedule_data` will be null. Otherwise, `schedule_data` column is a JSON blob with the schema of [StandardSync#scheduleData](airbyte-config/config-models/src/main/resources/types/StandardSync.yaml#79) that defines the actual schedule. The columns `manual` and `schedule` are deprecated and should be ignored (they will be dropped soon). + * The `namespace_type` column configures whether the namespace for the connection should use that defined by the source, the destination, or a user-defined format (`custom`). If `custom` the `namespace_format` column defines the string that will be used as the namespace. + * The `status` column describes the activity level of the connector: `active` - current schedule is respected, `inactive` - current schedule is ignored (the connection does not run) but it could be switched back to active, and `deprecated` - the connection is permanently off (cannot be moved to active or inactive). +* `state` + * The `state` table represents the current (last) state for a connection. For a connection with `stream` state, there will be a record per stream. For a connection with `global` state, there will be a record per stream and an additional record to store the shared (global) state. For a connection with `legacy` state, there will be one record per connection. + * In the `stream` and `global` state cases, the `stream_name` and `namespace` columns contains the name of the stream whose state is represented by that record. For the shared state in global `stream_name` and `namespace` will be null. + * The `state` column contains the state JSON blob. Depending on the type of the connection, the schema of the blob will be different. + * `stream` - for this type, this column is a JSON blob that is a blackbox to the platform and known only to the connector that generated it. + * `global` - for this type, this column is a JSON blob that is a blackbox to the platform and known only to the connector that generated it. This is true for both the states for each stream and the shared state. + * `legacy` - for this type, this column is a JSON blob with a top-level key called `state`. Within that `state` is a blackbox to the platform and known only to the connector that generated it. + * The `type` column describes the type of the state of the row. type can be `STREAM`, `GLOBAL` or `LEGACY`. + * The connection_id is a foreign key to the connection for which we are tracking state. +* `stream_reset` + * Each record in this table represents a stream in a connection that is enqueued to be reset or is currently being reset. It can be thought of as a queue. Once the stream is reset, the record is removed from the table. +* `operation` + * The `operation` table transformations for a connection beyond the raw output produced by the destination. The two options are: `normalization`, which outputs Airbyte's basic normalization. The second is `dbt`, which allows a user to configure their own custom dbt transformation. A connection can have multiple operations (e.g. it can do `normalization` and `dbt`). + * If the `operation` is `dbt`, then the `operator_dbt` column will be populated with a JSON blob with the schema from [OperatorDbt](airbyte-config/config-models/src/main/resources/types/OperatorDbt.yaml). + * If the `operation` is `normalization`, then the `operator_dbt` column will be populated with a JSON blob with the scehma from [OperatorNormalization](airbyte-config/config-models/src/main/resources/types/OperatorNormalization.yaml). + * Operations are scoped by workspace, using the `workspace_id` column. +* `connection_operation` + * This table joins the `operation` table to the `connection` for which it is configured. +* `workspace_service_account` + * This table is a WIP for an unfinished feature. +* `actor_oauth_parameter` + * The name of this table is misleading. It refers to parameters to be used for any instance of an `actor_definition` (not an `actor`) within a given workspace. For OAuth, the model is that a user is provisioning access to their data to a third party tool (in this case the Airbyte Platform). Each record represents information (e.g. client id, client secret) for that third party that is getting access. + * These parameters can be scoped by workspace. If `workspace_id` is not present, then the scope of the parameters is to the whole deployment of the platform (e.g. all workspaces). + * The `actor_type` column tells us whether the record represents a Source or a Destination. + * The `configuration` column is a JSON blob. The schema of this JSON blob matches the schema specified in the `spec` column in the `advanced_auth` field of the JSON blob. Keep in mind this schema is specific to each connector (e.g. the schema of Hubspot and Salesforce are different), which is why this column has to be a JSON blob. +* `secrets` + * This table is used to store secrets in open-source versions of the platform that have not set some other secrets store. This table allows us to use the same code path for secrets handling regardless of whether an external secrets store is set or not. This table is used by default for the open-source product. +* `airbyte_configs_migrations` is metadata table used by Flyway (our database migration tool). It is not used for any application use cases. +* `airbyte_configs` + * Legacy table for config storage. Should be dropped. + +# Jobs Database +* `jobs` +* `attempts` +* `airbyte_metadata` +* `airbyte_jobs_migrations` is metadata table used by Flyway (our database migration tool). It is not used for any application use cases. diff --git a/docusaurus/sidebars.js b/docusaurus/sidebars.js index 7df2a1b83f24..af1304244d38 100644 --- a/docusaurus/sidebars.js +++ b/docusaurus/sidebars.js @@ -324,6 +324,7 @@ module.exports = { 'understanding-airbyte/namespaces', 'understanding-airbyte/supported-data-types', 'understanding-airbyte/json-avro-conversion', + 'understanding-airbyte/database-data-catalog', ] }, { From 675e153cb6f2760bb95047a4e0204b40b5cbf87d Mon Sep 17 00:00:00 2001 From: Charles Date: Sat, 8 Oct 2022 23:16:28 -0500 Subject: [PATCH 004/498] jobs db descriptions (#16543) --- .../database-data-catalog.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/understanding-airbyte/database-data-catalog.md b/docs/understanding-airbyte/database-data-catalog.md index e129e8ead24b..88bb34b8ea00 100644 --- a/docs/understanding-airbyte/database-data-catalog.md +++ b/docs/understanding-airbyte/database-data-catalog.md @@ -69,6 +69,27 @@ # Jobs Database * `jobs` + * Each record in this table represents a job. + * The `config_type` column captures the type of job. We only make jobs for `sync` and `reset` (we do not use them for `spec`, `check`, `discover`). + * A job represents an attempt to use a connector (or a pair of connectors). The goal of this model is to capture the input of that run. A job can have multiple attempts (see the `attempts` table). The guarantee across all attempts is that the input into each attempt will be the same. + * That input is captured in the `config` column. This column is a JSON Blob with the schema of a [JobConfig](airbyte-config/config-models/src/main/resources/types/JobConfig.yaml). Only `sync` and `resetConnection` are ever used in that model. + * The other top-level fields are vestigial from when `spec`, `check`, `discover` were used in this model (we will eventually remove them). + * The `scope` column contains the `connection_id` for the relevant connection of the job. + * Context: It is called `scope` and not `connection_id`, because, this table was originally used for `spec`, `check`, and `discover`, and in those cases the `scope` referred to the relevant actor or actor definition. At this point the scope is always a `connection_id`. + * The `status` column contains the job status. The lifecycle of a job is explained in detail in the [Jobs & Workers documentation](jobs.md#job-state-machine). * `attempts` + * Each record in this table represents an attempt. + * Each attempt belongs to a job--this is captured by the `job_id` column. All attempts for a job will run on the same input. + * The `id` column is a unique id across all attempts while the `attempt_number` is an ascending number of the attempts for a job. + * The output of each attempt, however, can be different. The `output` column is a JSON blob with the schema of a [JobOutput](airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml). Only `sync` is used in that model. Reset jobs will also use the `sync` field, because under the hood `reset` jobs end up just doing a `sync` with special inputs. This object contains all the output info for a sync including stats on how much data was moved. + * The other top-level fields are vestigial from when `spec`, `check`, `discover` were used in this model (we will eventually remove them). + * The `status` column contains the attempt status. The lifecycle of a job / attempt is explained in detail in the [Jobs & Workers documentation](jobs.md#job-state-machine). + * If the attempt fails, the `failure_summary` column will be populated. The column is a JSON blob with the schema of (AttemptFailureReason)[airbyte-config/config-models/src/main/resources/types/AttemptFailureSummary.yaml]. + * The `log_path` column captures where logs for the attempt will be written. + * `created_at`, `started_at`, and `ended_at` track the run time. + * The `temporal_workflow_id` column keeps track of what temporal execution is associated with the attempt. * `airbyte_metadata` + * This table is a key-value store for various metadata about the platform. It is used to track information about what version the platform is currently on as well as tracking the upgrade history. + * Logically it does not make a lot of sense that it is in the jobs db. It would make sense if it were either in its own dbs or in the config dbs. + * The only two columns are `key` and `value`. It is truly just a key-value store. * `airbyte_jobs_migrations` is metadata table used by Flyway (our database migration tool). It is not used for any application use cases. From 89a9d6497ad5ccdd98f7d062d52e769d9710b40f Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Sun, 9 Oct 2022 01:25:38 -0700 Subject: [PATCH 005/498] Add missing types to the registry (#17763) * Add missing types to the registry * bump --- airbyte-cdk/python/CHANGELOG.md | 4 ++++ .../sources/declarative/parsers/class_types_registry.py | 8 ++++++++ airbyte-cdk/python/setup.py | 5 +++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index 42336f27519c..e839b8a16578 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.98 + +- Low-code: Expose WaitUntilTimeFromHeader strategy and WaitTimeFromHeader as component type + ## 0.1.97 - Revert 0.1.96 diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/class_types_registry.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/class_types_registry.py index 3c6dbcb58814..2419443089aa 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/class_types_registry.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/parsers/class_types_registry.py @@ -17,6 +17,12 @@ from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.exponential_backoff_strategy import ( ExponentialBackoffStrategy, ) +from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.wait_time_from_header_backoff_strategy import ( + WaitTimeFromHeaderBackoffStrategy, +) +from airbyte_cdk.sources.declarative.requesters.error_handlers.backoff_strategies.wait_until_time_from_header_backoff_strategy import ( + WaitUntilTimeFromHeaderBackoffStrategy, +) from airbyte_cdk.sources.declarative.requesters.error_handlers.composite_error_handler import CompositeErrorHandler from airbyte_cdk.sources.declarative.requesters.error_handlers.default_error_handler import DefaultErrorHandler from airbyte_cdk.sources.declarative.requesters.http_requester import HttpRequester @@ -69,4 +75,6 @@ "SimpleRetriever": SimpleRetriever, "SingleSlice": SingleSlice, "SubstreamSlicer": SubstreamSlicer, + "WaitUntilTimeFromHeader": WaitUntilTimeFromHeaderBackoffStrategy, + "WaitTimeFromHeader": WaitTimeFromHeaderBackoffStrategy, } diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index 91fc927ab34d..cb0d13dd4f5a 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.97", + version="0.1.98", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", @@ -44,7 +44,8 @@ packages=find_packages(exclude=("unit_tests",)), install_requires=[ "backoff", - "dataclasses-jsonschema==2.15.1", # pinned to the last working version for us temporarily while we fix + # pinned to the last working version for us temporarily while we fix + "dataclasses-jsonschema==2.15.1", "dpath~=2.0.1", "jsonschema~=3.2.0", "jsonref~=0.2", From 719cd554bdbbb4fd58d6a9dcae4b911badffd0c1 Mon Sep 17 00:00:00 2001 From: Davin Chia Date: Sun, 9 Oct 2022 14:41:30 -0700 Subject: [PATCH 006/498] Start testing buildpulse. (#17712) Buildpulse.io is a tool for detecting flaky tests that integrates well with GHA. Flaky tests are something we want to start figuring out more of. This PR starts pushing platform tests to buildpulse so we can start trialing out this tool. The idea is to try this out for a couple of weeks (enough time to see if this tool actually can detect flaky tests) and decide if this serves our needs. --- .github/workflows/gradle.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a473c86cfad5..8f56fcdc063f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -530,6 +530,16 @@ jobs: path: '/actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml,/actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml' reporter: java-junit + - name: Upload test results to BuildPulse for flaky test detection + if: '!cancelled()' # Run this step even when the tests fail. Skip if the workflow is cancelled. + uses: Workshop64/buildpulse-action@main + with: + account: 59758427 + repository: 283046497 + path: '/actions-runner/_work/airbyte/airbyte/*' + key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} + secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} + # In case of self-hosted EC2 errors, remove this block. stop-platform-build-runner: name: "Platform: Stop Build EC2 Runner" @@ -669,6 +679,16 @@ jobs: path: '/actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml' reporter: java-junit + - name: Upload test results to BuildPulse for flaky test detection + if: '!cancelled()' # Run this step even when the tests fail. Skip if the workflow is cancelled. + uses: Workshop64/buildpulse-action@main + with: + account: 59758427 + repository: 283046497 + path: '/actions-runner/_work/airbyte/airbyte/*' + key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} + secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} + - uses: actions/upload-artifact@v2 if: failure() with: From ab71f5bc29cc9aaea4dd06385c391f135dc23d30 Mon Sep 17 00:00:00 2001 From: Volodymyr Pochtar Date: Mon, 10 Oct 2022 12:35:39 +0300 Subject: [PATCH 007/498] =?UTF-8?q?feat:=20replace=20openjdk=20with=20amaz?= =?UTF-8?q?oncorretto:17.0.4=20on=20connectors=20for=20se=D1=81urity=20com?= =?UTF-8?q?pliance=20(#17511)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bases/base-java/Dockerfile | 6 ++++-- .../base-standard-source-test-file/Dockerfile | 18 ++++-------------- airbyte-integrations/bases/base/Dockerfile | 2 +- .../bases/standard-source-test/Dockerfile | 18 ++++-------------- .../connectors/destination-s3/Dockerfile | 7 +++---- .../destination-snowflake/Dockerfile | 2 +- tools/bin/build_image.sh | 2 +- 7 files changed, 18 insertions(+), 37 deletions(-) diff --git a/airbyte-integrations/bases/base-java/Dockerfile b/airbyte-integrations/bases/base-java/Dockerfile index be4a2abd1db5..e6da294f31f0 100644 --- a/airbyte-integrations/bases/base-java/Dockerfile +++ b/airbyte-integrations/bases/base-java/Dockerfile @@ -1,7 +1,9 @@ -ARG JDK_VERSION=17.0.1 -FROM openjdk:${JDK_VERSION}-slim +ARG JDK_VERSION=17.0.4 +FROM amazoncorretto:${JDK_VERSION} COPY --from=airbyte/integration-base:dev /airbyte /airbyte +RUN yum install -y tar openssl && yum clean all + WORKDIR /airbyte COPY javabase.sh . diff --git a/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile b/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile index 6d7bb0867774..82faf3f5efad 100644 --- a/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile +++ b/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile @@ -1,22 +1,12 @@ -ARG JDK_VERSION=17.0.1 -FROM openjdk:${JDK_VERSION}-slim +ARG JDK_VERSION=17.0.4 +FROM amazoncorretto:${JDK_VERSION} ARG DOCKER_BUILD_ARCH=amd64 # Install Docker to launch worker images. Eventually should be replaced with Docker-java. # See https://gitter.im/docker-java/docker-java?at=5f3eb87ba8c1780176603f4e for more information on why we are not currently using Docker-java -RUN apt-get update && apt-get install -y \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - software-properties-common -RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - -RUN add-apt-repository \ - "deb [arch=${DOCKER_BUILD_ARCH}] https://download.docker.com/linux/debian \ - $(lsb_release -cs) \ - stable" -RUN apt-get update && apt-get install -y docker-ce-cli jq +RUN amazon-linux-extras install -y docker +RUN yum install -y openssl jq tar && yum clean all ENV APPLICATION base-standard-source-test-file diff --git a/airbyte-integrations/bases/base/Dockerfile b/airbyte-integrations/bases/base/Dockerfile index 32fe5b715134..b70c2b97a1f8 100644 --- a/airbyte-integrations/bases/base/Dockerfile +++ b/airbyte-integrations/bases/base/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:10.5-slim +FROM amazonlinux:2022.0.20220831.1 WORKDIR /airbyte diff --git a/airbyte-integrations/bases/standard-source-test/Dockerfile b/airbyte-integrations/bases/standard-source-test/Dockerfile index 708fd59d233a..eae2c7f1cf6d 100644 --- a/airbyte-integrations/bases/standard-source-test/Dockerfile +++ b/airbyte-integrations/bases/standard-source-test/Dockerfile @@ -1,22 +1,12 @@ -ARG JDK_VERSION=17.0.1 -FROM openjdk:${JDK_VERSION}-slim +ARG JDK_VERSION=17.0.4 +FROM amazoncorretto:${JDK_VERSION} ARG DOCKER_BUILD_ARCH=amd64 # Install Docker to launch worker images. Eventually should be replaced with Docker-java. # See https://gitter.im/docker-java/docker-java?at=5f3eb87ba8c1780176603f4e for more information on why we are not currently using Docker-java -RUN apt-get update && apt-get install -y \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - software-properties-common -RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - -RUN add-apt-repository \ - "deb [arch=${DOCKER_BUILD_ARCH}] https://download.docker.com/linux/debian \ - $(lsb_release -cs) \ - stable" -RUN apt-get update && apt-get install -y docker-ce-cli jq +RUN amazon-linux-extras install -y docker +RUN yum install -y openssl jq tar && yum clean all ENV APPLICATION standard-source-test diff --git a/airbyte-integrations/connectors/destination-s3/Dockerfile b/airbyte-integrations/connectors/destination-s3/Dockerfile index 008f766e9ac5..96b7b1227ee0 100644 --- a/airbyte-integrations/connectors/destination-s3/Dockerfile +++ b/airbyte-integrations/connectors/destination-s3/Dockerfile @@ -19,12 +19,11 @@ RUN /bin/bash -c 'set -e && \ ARCH=`uname -m` && \ if [ "$ARCH" == "x86_64" ] || [ "$ARCH" = "amd64" ]; then \ echo "$ARCH" && \ - apt-get update; \ - apt-get install lzop liblzo2-2 liblzo2-dev -y; \ + yum install lzop lzo lzo-dev -y; \ elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" = "arm64" ]; then \ echo "$ARCH" && \ - apt-get update; \ - apt-get install lzop liblzo2-2 liblzo2-dev wget curl unzip zip build-essential maven git -y; \ + yum group install -y "Development Tools" \ + yum install lzop lzo lzo-dev wget curl unzip zip maven git -y; \ wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz -P /tmp; \ cd /tmp && tar xvfz lzo-2.10.tar.gz; \ cd /tmp/lzo-2.10/ && ./configure --enable-shared --prefix /usr/local/lzo-2.10; \ diff --git a/airbyte-integrations/connectors/destination-snowflake/Dockerfile b/airbyte-integrations/connectors/destination-snowflake/Dockerfile index 7cca922d3994..47e84df50aa0 100644 --- a/airbyte-integrations/connectors/destination-snowflake/Dockerfile +++ b/airbyte-integrations/connectors/destination-snowflake/Dockerfile @@ -1,7 +1,7 @@ FROM airbyte/integration-base-java:dev # uncomment to run Yourkit java profiling -#RUN apt-get update && apt-get install -y curl zip +#RUN yum install -y curl zip # #RUN curl -o /tmp/YourKit-JavaProfiler-2021.3-docker.zip https://www.yourkit.com/download/docker/YourKit-JavaProfiler-2021.3-docker.zip && \ # unzip /tmp/YourKit-JavaProfiler-2021.3-docker.zip -d /usr/local && \ diff --git a/tools/bin/build_image.sh b/tools/bin/build_image.sh index 6d6ee8346abb..9684d07d6116 100755 --- a/tools/bin/build_image.sh +++ b/tools/bin/build_image.sh @@ -42,7 +42,7 @@ if [ "$FOLLOW_SYMLINKS" == "true" ]; then # to use as the build context tar cL "${exclusions[@]}" . | docker build - "${args[@]}" else - JDK_VERSION="${JDK_VERSION:-17.0.1}" + JDK_VERSION="${JDK_VERSION:-17.0.4}" if [[ -z "${DOCKER_BUILD_PLATFORM}" ]]; then docker build --build-arg JDK_VERSION="$JDK_VERSION" --build-arg DOCKER_BUILD_ARCH="$DOCKER_BUILD_ARCH" . "${args[@]}" else From 6c5a150b81ead7a85be936db3d8e0768b020c56a Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Mon, 10 Oct 2022 12:43:59 +0200 Subject: [PATCH 008/498] :window: :wrench: Ignore classnames during jest snapshot comparison (#17773) * Ignore classnames during jest snapshot comparison * Replace by simple placeholder * Update snapshots --- airbyte-webapp/package.json | 1 + .../scripts/classname-serializer.js | 32 ++ .../components/ui/Spinner/Spinner.test.tsx | 4 +- .../__snapshots__/Spinner.test.tsx.snap | 12 +- .../components/GitBlock/GitBlock.test.tsx | 8 +- .../__snapshots__/GitBlock.test.tsx.snap | 160 +++--- .../FrequentlyUsedDestinations.test.tsx | 4 +- .../FrequentlyUsedDestinations.test.tsx.snap | 506 +++++++++--------- .../StartWithDestination.test.tsx | 4 +- .../StartWithDestination.test.tsx.snap | 77 +-- 10 files changed, 426 insertions(+), 382 deletions(-) create mode 100644 airbyte-webapp/scripts/classname-serializer.js diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index c074e8448712..7354e7bc034e 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -162,6 +162,7 @@ }, "jest": { "transformIgnorePatterns": [], + "snapshotSerializers": ["./scripts/classname-serializer.js"], "coveragePathIgnorePatterns": [ ".stories.tsx" ] diff --git a/airbyte-webapp/scripts/classname-serializer.js b/airbyte-webapp/scripts/classname-serializer.js new file mode 100644 index 000000000000..40bb27a80cd6 --- /dev/null +++ b/airbyte-webapp/scripts/classname-serializer.js @@ -0,0 +1,32 @@ +import { prettyDOM } from "@testing-library/react"; + +/** + * Traverse a tree of nodes and replace all class names with + * the count of classnames instead, e.g. "<3 classnames>" + */ +const traverseAndRedactClasses = (node) => { + if (node.className && typeof node.className === "string") { + node.className = ``; + } + node.childNodes.forEach(traverseAndRedactClasses); +}; + +module.exports = { + serialize(val, config) { + // Clone the whole rendered DOM tree, since we're modifying it + const clone = val.baseElement.cloneNode(true); + // Redact all classnames + traverseAndRedactClasses(clone); + // Use prettyDOM to format the modified DOM as a string. + return prettyDOM(clone, Infinity, { + indent: config.indent.length, + highlight: false, + }); + }, + + test(val) { + // Only use this serializer when creating a snapshot of RenderResult, which is + // the return value of testing-library/react's render method. + return val && val.baseElement && val.baseElement instanceof Element; + }, +}; diff --git a/airbyte-webapp/src/components/ui/Spinner/Spinner.test.tsx b/airbyte-webapp/src/components/ui/Spinner/Spinner.test.tsx index a83ba72ade2c..bbbf0a4fd32f 100644 --- a/airbyte-webapp/src/components/ui/Spinner/Spinner.test.tsx +++ b/airbyte-webapp/src/components/ui/Spinner/Spinner.test.tsx @@ -4,8 +4,8 @@ import { Spinner } from "./Spinner"; describe("", () => { it("should render without crash", () => { - const { asFragment } = render(); + const component = render(); - expect(asFragment()).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); diff --git a/airbyte-webapp/src/components/ui/Spinner/__snapshots__/Spinner.test.tsx.snap b/airbyte-webapp/src/components/ui/Spinner/__snapshots__/Spinner.test.tsx.snap index 25bf19f80f44..d234965e6e97 100644 --- a/airbyte-webapp/src/components/ui/Spinner/__snapshots__/Spinner.test.tsx.snap +++ b/airbyte-webapp/src/components/ui/Spinner/__snapshots__/Spinner.test.tsx.snap @@ -1,9 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should render without crash 1`] = ` - -

- - - + + + - + `; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx index a184b1827a39..1985f8eba272 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx @@ -18,9 +18,9 @@ const renderStartWithDestination = (props: StartWithDestinationProps) => describe("", () => { it("should renders without crash with provided props", () => { - const { asFragment } = renderStartWithDestination({ destination: mockData, onDestinationSelect: jest.fn() }); + const component = renderStartWithDestination({ destination: mockData, onDestinationSelect: jest.fn() }); - expect(asFragment()).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); it("should call provided handler with right params", async () => { diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap index c6e68f28119c..90c99f117a07 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap @@ -1,62 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should renders without crash with provided props 1`] = ` - -
- + +
-
+ `; From f5f3e87f21b79214cfab670f5ad3bead41d8c037 Mon Sep 17 00:00:00 2001 From: Alexander Marquardt Date: Mon, 10 Oct 2022 15:54:48 +0200 Subject: [PATCH 009/498] Update cdc.md (#17787) * Update cdc.md Added a link to the article about that explains Airbyte replication modes * Update cdc.md Added a link to the CDC "exploration" tutorial --- docs/understanding-airbyte/cdc.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/understanding-airbyte/cdc.md b/docs/understanding-airbyte/cdc.md index 290d23a09e37..c55425cadd10 100644 --- a/docs/understanding-airbyte/cdc.md +++ b/docs/understanding-airbyte/cdc.md @@ -41,5 +41,7 @@ We add some metadata columns for CDC sources: ## Additional information -* Read our article: [Understanding Change Data Capture (CDC): Definition, Methods and Benefits](https://airbyte.com/blog/change-data-capture-definition-methods-and-benefits) +* [An overview of Airbyte’s replication modes](https://airbyte.com/blog/understanding-data-replication-modes). +* [Understanding Change Data Capture (CDC): Definition, Methods and Benefits](https://airbyte.com/blog/change-data-capture-definition-methods-and-benefits) +* [Explore Airbyte's Change Data Capture (CDC) synchronization](https://airbyte.com/tutorials/incremental-change-data-capture-cdc-replication) From 3ea0b5a82d9ae16109869a9a0b6a84c916f517e0 Mon Sep 17 00:00:00 2001 From: Alexander Marquardt Date: Mon, 10 Oct 2022 15:55:05 +0200 Subject: [PATCH 010/498] Update incremental-deduped-history.md (#17786) Add a link to new article on choosing a replication mode --- .../connections/incremental-deduped-history.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/understanding-airbyte/connections/incremental-deduped-history.md b/docs/understanding-airbyte/connections/incremental-deduped-history.md index fbd0f9f5dd2c..f0b954e7340d 100644 --- a/docs/understanding-airbyte/connections/incremental-deduped-history.md +++ b/docs/understanding-airbyte/connections/incremental-deduped-history.md @@ -155,6 +155,7 @@ Additionally, this sync mode is only supported for destinations where dbt/normal If you are not satisfied with how transformations are applied on top of the appended data, you can find more relevant SQL transformations you might need to do on your data in the [Connecting EL with T using SQL \(part 1/2\)](../../operator-guides/transformation-and-normalization/transformations-with-sql.md) -## Related tutorial +## Related information -For an in-depth hands-on deep-dive into incremental sync, see: [Explore Airbyte’s incremental data synchronization](https://airbyte.com/tutorials/incremental-data-synchronization). +- [An overview of Airbyte’s replication modes](https://airbyte.com/blog/understanding-data-replication-modes). +- [Explore Airbyte’s incremental data synchronization](https://airbyte.com/tutorials/incremental-data-synchronization). From 5e30aaff143f4f1ccc035dfb01fdfa3384d0db80 Mon Sep 17 00:00:00 2001 From: Alexander Marquardt Date: Mon, 10 Oct 2022 15:55:15 +0200 Subject: [PATCH 011/498] Update incremental-append.md (#17785) Added a link to the new article on choosing a replication mode --- docs/understanding-airbyte/connections/incremental-append.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/understanding-airbyte/connections/incremental-append.md b/docs/understanding-airbyte/connections/incremental-append.md index 6a5194e86a8e..d7f84d5c6bcb 100644 --- a/docs/understanding-airbyte/connections/incremental-append.md +++ b/docs/understanding-airbyte/connections/incremental-append.md @@ -128,6 +128,7 @@ The current behavior of **Incremental** is not able to handle source schema chan If you are not satisfied with how transformations are applied on top of the appended data, you can find more relevant SQL transformations you might need to do on your data in the [Connecting EL with T using SQL \(part 1/2\)](incremental-append.md) -## Related tutorial +## Related information -For an in-depth hands-on deep-dive into incremental sync, see: [Explore Airbyte’s incremental data synchronization](https://airbyte.com/tutorials/incremental-data-synchronization). +- [Explore Airbyte’s incremental data synchronization](https://airbyte.com/tutorials/incremental-data-synchronization). +- [An overview of Airbyte’s replication modes](https://airbyte.com/blog/understanding-data-replication-modes). From caeb8017d34b9c2acdfa2ce49f5a9971046b2e4f Mon Sep 17 00:00:00 2001 From: Alexander Marquardt Date: Mon, 10 Oct 2022 15:55:27 +0200 Subject: [PATCH 012/498] Update full-refresh-overwrite.md (#17783) Added a link to the new article that compares replication modes --- .../connections/full-refresh-overwrite.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/understanding-airbyte/connections/full-refresh-overwrite.md b/docs/understanding-airbyte/connections/full-refresh-overwrite.md index 8d6992283bfc..44d4ff5f6699 100644 --- a/docs/understanding-airbyte/connections/full-refresh-overwrite.md +++ b/docs/understanding-airbyte/connections/full-refresh-overwrite.md @@ -41,6 +41,7 @@ Note: This is how Singer target-bigquery does it. We will consider making other flavors of full refresh configurable as first-class citizens in Airbyte. e.g. On new data, copy old data to a new table with a timestamp, and then replace the original table with the new data. As always, we will focus on adding these options in such a way that the behavior of each connector is both well documented and predictable. -## Related tutorial +## Related information -For an in-depth hands-on deep-dive into full refresh synchronization modes, see: [Explore Airbyte's full refresh data synchronization](https://airbyte.com/tutorials/full-data-synchronization). +- [An overview of Airbyte’s replication modes](https://airbyte.com/blog/understanding-data-replication-modes). +- [Explore Airbyte's full refresh data synchronization](https://airbyte.com/tutorials/full-data-synchronization). From 9cc86d857fd479daeba4d596c1b15dcba8180c61 Mon Sep 17 00:00:00 2001 From: Alexander Marquardt Date: Mon, 10 Oct 2022 15:55:42 +0200 Subject: [PATCH 013/498] Update full-refresh-append.md (#17784) Added a link to the new article comparing sync modes --- .../understanding-airbyte/connections/full-refresh-append.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/understanding-airbyte/connections/full-refresh-append.md b/docs/understanding-airbyte/connections/full-refresh-append.md index 62331ba786f2..b7343fc1c07b 100644 --- a/docs/understanding-airbyte/connections/full-refresh-append.md +++ b/docs/understanding-airbyte/connections/full-refresh-append.md @@ -62,6 +62,7 @@ data in the destination _after_ the n+1th sync: We will consider making a better detection of deletions in the source, especially with `Incremental`, and `Change Data Capture` based sync modes for example. -## Related tutorial +## Related information -For an in-depth hands-on deep-dive into full refresh synchronization modes, see: [Explore Airbyte's full refresh data synchronization](https://airbyte.com/tutorials/full-data-synchronization). +- [An overview of Airbyte’s replication modes](https://airbyte.com/blog/understanding-data-replication-modes). +- [Explore Airbyte's full refresh data synchronization](https://airbyte.com/tutorials/full-data-synchronization) From fb523afbc2dcf9943ad644d7591d8baa99bb2ac8 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Mon, 10 Oct 2022 18:20:28 +0200 Subject: [PATCH 014/498] Fix design of log view (#17765) --- .../components/AttemptDetails.module.scss | 10 +++- .../JobItem/components/AttemptDetails.tsx | 13 +++-- .../JobItem/components/MainInfo.module.scss | 57 ++++++++----------- .../JobItem/components/MainInfo.tsx | 7 +-- 4 files changed, 42 insertions(+), 45 deletions(-) diff --git a/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss b/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss index caf05666caf2..2ef8d8b7e04b 100644 --- a/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss +++ b/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss @@ -7,7 +7,7 @@ color: colors.$grey; } -.details > *:not(:last-child)::after { +.details > *:not(:last-child, .lastAttempt)::after { content: "|"; padding: 0 variables.$spacing-sm; } @@ -18,3 +18,11 @@ overflow: hidden; text-overflow: ellipsis; } + +.lastAttempt { + margin-right: variables.$spacing-sm; + + &.failed { + color: colors.$red; + } +} \ No newline at end of file diff --git a/airbyte-webapp/src/components/JobItem/components/AttemptDetails.tsx b/airbyte-webapp/src/components/JobItem/components/AttemptDetails.tsx index 069362f3ef49..936aaba697f8 100644 --- a/airbyte-webapp/src/components/JobItem/components/AttemptDetails.tsx +++ b/airbyte-webapp/src/components/JobItem/components/AttemptDetails.tsx @@ -5,20 +5,20 @@ import { FormattedMessage, useIntl } from "react-intl"; import Status from "core/statuses"; -import { AttemptRead, JobConfigType } from "../../../core/request/AirbyteClient"; +import { AttemptRead } from "../../../core/request/AirbyteClient"; import styles from "./AttemptDetails.module.scss"; -interface IProps { +interface AttemptDetailsProps { className?: string; attempt: AttemptRead; - configType?: JobConfigType; + hasMultipleAttempts?: boolean; } const getFailureFromAttempt = (attempt: AttemptRead) => { return attempt.failureSummary && attempt.failureSummary.failures[0]; }; -const AttemptDetails: React.FC = ({ attempt, className }) => { +const AttemptDetails: React.FC = ({ attempt, className, hasMultipleAttempts }) => { const { formatMessage } = useIntl(); if (attempt.status !== Status.SUCCEEDED && attempt.status !== Status.FAILED) { @@ -67,6 +67,11 @@ const AttemptDetails: React.FC = ({ attempt, className }) => { return (
+ {hasMultipleAttempts && ( + + + + )} {formatBytes(attempt?.totalStats?.bytesEmitted)} &, - div:hover > div > &, - div:hover > div > div > & { - opacity: 1; - } - } } &.open:not(.failed) { @@ -89,3 +55,26 @@ border-bottom: variables.$border-thin solid colors.$red-50; } } + +.arrow { + transform: rotate(-90deg); + transition: variables.$transition; + opacity: 0; + color: colors.$dark-blue-50; + font-size: 22px; + margin: 0 30px 0 50px; + + .open & { + transform: rotate(-0deg); + } + + .failed & { + color: colors.$red; + } + + div:hover > &, + div:hover > div > &, + div:hover > div > div > & { + opacity: 1; + } +} diff --git a/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx b/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx index 9f349ee9e0c7..c79a411bc233 100644 --- a/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx +++ b/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx @@ -89,15 +89,10 @@ const MainInfo: React.FC = ({ job, attempts = [], isOpen, onExpan {label} {attempts.length > 0 && ( <> - {attempts.length > 1 && ( -
- -
- )} {jobConfigType === "reset_connection" ? ( stream.name)} /> ) : ( - + 1} /> )} )} From 39a14b73067fda00acfef8808a9194c94b8036e4 Mon Sep 17 00:00:00 2001 From: Parker Mossman Date: Mon, 10 Oct 2022 09:55:31 -0700 Subject: [PATCH 015/498] Efficient queries for connection list (#17360) * query once for all needed models, instead of querying within connections loop * cleanup and fix failing tests * pmd fix * fix query and add test * return empty if input list is empty * undo aggressive autoformatting * don't query for connection operations in a loop, instead query once and group-by connectionID in memory * try handling operationIds in a single query instead of two * remove optional * fix operationIds query * very annoying, test was failing because operationIds can be listed in a different order. verify operationIds separately from rest of object * combined queries/functions instead of separate queries for actor and definition * remove leftover lines that aren't doing anything * format * add javadoc * format * use leftjoin so that connections that lack operations aren't left out * clean up comments and format --- .../config/persistence/ConfigRepository.java | 132 ++++++++++++++--- .../DatabaseConfigPersistence.java | 24 +-- .../config/persistence/DbConverter.java | 30 +++- .../ConfigRepositoryE2EReadWriteTest.java | 68 ++++++++- .../airbyte/config/persistence/MockData.java | 2 +- .../job/DefaultJobPersistence.java | 64 +++++++- .../persistence/job/JobPersistence.java | 4 + .../job/DefaultJobPersistenceTest.java | 140 ++++++++++++++++++ .../server/handlers/JobHistoryHandler.java | 12 ++ .../WebBackendConnectionsHandler.java | 74 +++++++-- .../WebBackendConnectionsHandlerTest.java | 25 +++- .../server/helpers/ConnectionHelpers.java | 27 +++- 12 files changed, 524 insertions(+), 78 deletions(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 02fc597ec916..c44d04de025a 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -15,9 +15,12 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.OPERATION; import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; import static org.jooq.impl.DSL.asterisk; +import static org.jooq.impl.DSL.groupConcat; import static org.jooq.impl.DSL.noCondition; +import static org.jooq.impl.DSL.select; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.collect.Sets; import com.google.common.hash.HashFunction; @@ -54,6 +57,8 @@ import java.io.IOException; import java.time.OffsetDateTime; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -82,6 +87,8 @@ public class ConfigRepository { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigRepository.class); + private static final String OPERATION_IDS_AGG_FIELD = "operation_ids_agg"; + private static final String OPERATION_IDS_AGG_DELIMITER = ","; private final ConfigPersistence persistence; private final ExceptionWrappingDatabase database; @@ -613,43 +620,73 @@ public void writeStandardSync(final StandardSync standardSync) throws JsonValida persistence.writeConfig(ConfigSchema.STANDARD_SYNC, standardSync.getConnectionId().toString(), standardSync); } - public List listStandardSyncs() throws ConfigNotFoundException, IOException, JsonValidationException { + public List listStandardSyncs() throws IOException, JsonValidationException { return persistence.listConfigs(ConfigSchema.STANDARD_SYNC, StandardSync.class); } public List listStandardSyncsUsingOperation(final UUID operationId) throws IOException { - final Result result = database.query(ctx -> ctx.select(CONNECTION.asterisk()) + + final Result connectionAndOperationIdsResult = database.query(ctx -> ctx + // SELECT connection.* plus the connection's associated operationIds as a concatenated list + .select( + CONNECTION.asterisk(), + groupConcat(CONNECTION_OPERATION.OPERATION_ID).separator(OPERATION_IDS_AGG_DELIMITER).as(OPERATION_IDS_AGG_FIELD)) .from(CONNECTION) - .join(CONNECTION_OPERATION) - .on(CONNECTION_OPERATION.CONNECTION_ID.eq(CONNECTION.ID)) - .where(CONNECTION_OPERATION.OPERATION_ID.eq(operationId))).fetch(); - return getStandardSyncsFromResult(result); + + // inner join with all connection_operation rows that match the connection's id + .join(CONNECTION_OPERATION).on(CONNECTION_OPERATION.CONNECTION_ID.eq(CONNECTION.ID)) + + // only keep rows for connections that have an operationId that matches the input. + // needs to be a sub query because we want to keep all operationIds for matching connections + // in the main query + .where(CONNECTION.ID.in( + select(CONNECTION.ID).from(CONNECTION).join(CONNECTION_OPERATION).on(CONNECTION_OPERATION.CONNECTION_ID.eq(CONNECTION.ID)) + .where(CONNECTION_OPERATION.OPERATION_ID.eq(operationId)))) + + // group by connection.id so that the groupConcat above works + .groupBy(CONNECTION.ID)).fetch(); + + return getStandardSyncsFromResult(connectionAndOperationIdsResult); } public List listWorkspaceStandardSyncs(final UUID workspaceId, final boolean includeDeleted) throws IOException { - final Result result = database.query(ctx -> ctx.select(CONNECTION.asterisk()) + final Result connectionAndOperationIdsResult = database.query(ctx -> ctx + // SELECT connection.* plus the connection's associated operationIds as a concatenated list + .select( + CONNECTION.asterisk(), + groupConcat(CONNECTION_OPERATION.OPERATION_ID).separator(OPERATION_IDS_AGG_DELIMITER).as(OPERATION_IDS_AGG_FIELD)) .from(CONNECTION) + + // left join with all connection_operation rows that match the connection's id. + // left join includes connections that don't have any connection_operations + .leftJoin(CONNECTION_OPERATION).on(CONNECTION_OPERATION.CONNECTION_ID.eq(CONNECTION.ID)) + + // join with source actors so that we can filter by workspaceId .join(ACTOR).on(CONNECTION.SOURCE_ID.eq(ACTOR.ID)) .where(ACTOR.WORKSPACE_ID.eq(workspaceId) - .and(includeDeleted ? noCondition() : CONNECTION.STATUS.notEqual(StatusType.deprecated)))) - .fetch(); - return getStandardSyncsFromResult(result); + .and(includeDeleted ? noCondition() : CONNECTION.STATUS.notEqual(StatusType.deprecated))) + + // group by connection.id so that the groupConcat above works + .groupBy(CONNECTION.ID)).fetch(); + + return getStandardSyncsFromResult(connectionAndOperationIdsResult); } - private List getStandardSyncsFromResult(final Result result) throws IOException { + private List getStandardSyncsFromResult(final Result connectionAndOperationIdsResult) { final List standardSyncs = new ArrayList<>(); - for (final Record record : result) { - final UUID connectionId = record.get(CONNECTION.ID); - final Result connectionOperationRecords = database.query(ctx -> ctx.select(asterisk()) - .from(CONNECTION_OPERATION) - .where(CONNECTION_OPERATION.CONNECTION_ID.eq(connectionId)) - .fetch()); - final List connectionOperationIds = - connectionOperationRecords.stream().map(r -> r.get(CONNECTION_OPERATION.OPERATION_ID)).collect(Collectors.toList()); - standardSyncs.add(DbConverter.buildStandardSync(record, connectionOperationIds)); + for (final Record record : connectionAndOperationIdsResult) { + final String operationIdsFromRecord = record.get(OPERATION_IDS_AGG_FIELD, String.class); + + // can be null when connection has no connectionOperations + final List operationIds = operationIdsFromRecord == null + ? Collections.emptyList() + : Arrays.stream(operationIdsFromRecord.split(OPERATION_IDS_AGG_DELIMITER)).map(UUID::fromString).toList(); + + standardSyncs.add(DbConverter.buildStandardSync(record, operationIds)); } + return standardSyncs; } @@ -798,6 +835,61 @@ private Map findCatalogByHash(final String catalogHash, fi return result; } + // Data-carrier records to hold combined result of query for a Source or Destination and its + // corresponding Definition. This enables the API layer to + // process combined information about a Source/Destination/Definition pair without requiring two + // separate queries and in-memory join operation, + // because the config models are grouped immediately in the repository layer. + @VisibleForTesting + public record SourceAndDefinition(SourceConnection source, StandardSourceDefinition definition) { + + } + + @VisibleForTesting + public record DestinationAndDefinition(DestinationConnection destination, StandardDestinationDefinition definition) { + + } + + public List getSourceAndDefinitionsFromSourceIds(final List sourceIds) throws IOException { + final Result records = database.query(ctx -> ctx + .select(ACTOR.asterisk(), ACTOR_DEFINITION.asterisk()) + .from(ACTOR) + .join(ACTOR_DEFINITION) + .on(ACTOR.ACTOR_DEFINITION_ID.eq(ACTOR_DEFINITION.ID)) + .where(ACTOR.ACTOR_TYPE.eq(ActorType.source), ACTOR.ID.in(sourceIds)) + .fetch()); + + final List sourceAndDefinitions = new ArrayList<>(); + + for (final Record record : records) { + final SourceConnection source = DbConverter.buildSourceConnection(record); + final StandardSourceDefinition definition = DbConverter.buildStandardSourceDefinition(record); + sourceAndDefinitions.add(new SourceAndDefinition(source, definition)); + } + + return sourceAndDefinitions; + } + + public List getDestinationAndDefinitionsFromDestinationIds(final List destinationIds) throws IOException { + final Result records = database.query(ctx -> ctx + .select(ACTOR.asterisk(), ACTOR_DEFINITION.asterisk()) + .from(ACTOR) + .join(ACTOR_DEFINITION) + .on(ACTOR.ACTOR_DEFINITION_ID.eq(ACTOR_DEFINITION.ID)) + .where(ACTOR.ACTOR_TYPE.eq(ActorType.destination), ACTOR.ID.in(destinationIds)) + .fetch()); + + final List destinationAndDefinitions = new ArrayList<>(); + + for (final Record record : records) { + final DestinationConnection destination = DbConverter.buildDestinationConnection(record); + final StandardDestinationDefinition definition = DbConverter.buildStandardDestinationDefinition(record); + destinationAndDefinitions.add(new DestinationAndDefinition(destination, definition)); + } + + return destinationAndDefinitions; + } + public ActorCatalog getActorCatalogById(final UUID actorCatalogId) throws IOException, ConfigNotFoundException { final Result result = database.query(ctx -> ctx.select(ACTOR_CATALOG.asterisk()) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index d5289f4bd7f4..3332c32fe5b0 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -456,7 +456,7 @@ private List> listSourceConnectionWithMetad final List> sourceConnections = new ArrayList<>(); for (final Record record : result) { - final SourceConnection sourceConnection = buildSourceConnection(record); + final SourceConnection sourceConnection = DbConverter.buildSourceConnection(record); sourceConnections.add(new ConfigWithMetadata<>( record.get(ACTOR.ID).toString(), ConfigSchema.SOURCE_CONNECTION.name(), @@ -467,16 +467,6 @@ private List> listSourceConnectionWithMetad return sourceConnections; } - private SourceConnection buildSourceConnection(final Record record) { - return new SourceConnection() - .withSourceId(record.get(ACTOR.ID)) - .withConfiguration(Jsons.deserialize(record.get(ACTOR.CONFIGURATION).data())) - .withWorkspaceId(record.get(ACTOR.WORKSPACE_ID)) - .withSourceDefinitionId(record.get(ACTOR.ACTOR_DEFINITION_ID)) - .withTombstone(record.get(ACTOR.TOMBSTONE)) - .withName(record.get(ACTOR.NAME)); - } - private List> listDestinationConnectionWithMetadata() throws IOException { return listDestinationConnectionWithMetadata(Optional.empty()); } @@ -492,7 +482,7 @@ private List> listDestinationConnectio final List> destinationConnections = new ArrayList<>(); for (final Record record : result) { - final DestinationConnection destinationConnection = buildDestinationConnection(record); + final DestinationConnection destinationConnection = DbConverter.buildDestinationConnection(record); destinationConnections.add(new ConfigWithMetadata<>( record.get(ACTOR.ID).toString(), ConfigSchema.DESTINATION_CONNECTION.name(), @@ -503,16 +493,6 @@ private List> listDestinationConnectio return destinationConnections; } - private DestinationConnection buildDestinationConnection(final Record record) { - return new DestinationConnection() - .withDestinationId(record.get(ACTOR.ID)) - .withConfiguration(Jsons.deserialize(record.get(ACTOR.CONFIGURATION).data())) - .withWorkspaceId(record.get(ACTOR.WORKSPACE_ID)) - .withDestinationDefinitionId(record.get(ACTOR.ACTOR_DEFINITION_ID)) - .withTombstone(record.get(ACTOR.TOMBSTONE)) - .withName(record.get(ACTOR.NAME)); - } - private List> listSourceOauthParamWithMetadata() throws IOException { return listSourceOauthParamWithMetadata(Optional.empty()); } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index 358cbb215e3d..f0179e982171 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -4,6 +4,7 @@ package io.airbyte.config.persistence; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_CATALOG; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_OAUTH_PARAMETER; @@ -15,12 +16,14 @@ import io.airbyte.commons.json.Jsons; import io.airbyte.config.ActorCatalog; import io.airbyte.config.ActorDefinitionResourceRequirements; +import io.airbyte.config.DestinationConnection; import io.airbyte.config.DestinationOAuthParameter; import io.airbyte.config.JobSyncConfig.NamespaceDefinitionType; import io.airbyte.config.Notification; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.Schedule; import io.airbyte.config.ScheduleData; +import io.airbyte.config.SourceConnection; import io.airbyte.config.SourceOAuthParameter; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; @@ -32,15 +35,18 @@ import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConnectorSpecification; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.jooq.Record; +/** + * Provides static methods for converting from repository layer results (often in the form of a jooq + * {@link Record}) to config models. + */ public class DbConverter { - public static StandardSync buildStandardSync(final Record record, final List connectionOperationId) throws IOException { + public static StandardSync buildStandardSync(final Record record, final List connectionOperationId) { return new StandardSync() .withConnectionId(record.get(CONNECTION.ID)) .withNamespaceDefinition( @@ -89,6 +95,26 @@ public static StandardWorkspace buildStandardWorkspace(final Record record) { .withFeedbackDone(record.get(WORKSPACE.FEEDBACK_COMPLETE)); } + public static SourceConnection buildSourceConnection(final Record record) { + return new SourceConnection() + .withSourceId(record.get(ACTOR.ID)) + .withConfiguration(Jsons.deserialize(record.get(ACTOR.CONFIGURATION).data())) + .withWorkspaceId(record.get(ACTOR.WORKSPACE_ID)) + .withSourceDefinitionId(record.get(ACTOR.ACTOR_DEFINITION_ID)) + .withTombstone(record.get(ACTOR.TOMBSTONE)) + .withName(record.get(ACTOR.NAME)); + } + + public static DestinationConnection buildDestinationConnection(final Record record) { + return new DestinationConnection() + .withDestinationId(record.get(ACTOR.ID)) + .withConfiguration(Jsons.deserialize(record.get(ACTOR.CONFIGURATION).data())) + .withWorkspaceId(record.get(ACTOR.WORKSPACE_ID)) + .withDestinationDefinitionId(record.get(ACTOR.ACTOR_DEFINITION_ID)) + .withTombstone(record.get(ACTOR.TOMBSTONE)) + .withName(record.get(ACTOR.NAME)); + } + public static StandardSourceDefinition buildStandardSourceDefinition(final Record record) { return new StandardSourceDefinition() .withSourceDefinitionId(record.get(ACTOR_DEFINITION.ID)) diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index 7d6043c01d14..c1fe7f1aebaf 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -26,6 +26,8 @@ import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncOperation; import io.airbyte.config.StandardWorkspace; +import io.airbyte.config.persistence.ConfigRepository.DestinationAndDefinition; +import io.airbyte.config.persistence.ConfigRepository.SourceAndDefinition; import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; @@ -56,6 +58,7 @@ import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -85,7 +88,7 @@ public static void dbSetup() { } @BeforeEach - void setup() throws IOException, JsonValidationException, SQLException, DatabaseInitializationException { + void setup() throws IOException, JsonValidationException, SQLException, DatabaseInitializationException, InterruptedException { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, @@ -200,16 +203,18 @@ void testSimpleInsertActorCatalog() throws IOException, JsonValidationException, @Test void testListWorkspaceStandardSyncAll() throws IOException { + final List expectedSyncs = MockData.standardSyncs().subList(0, 4); + final List actualSyncs = configRepository.listWorkspaceStandardSyncs(MockData.standardWorkspaces().get(0).getWorkspaceId(), true); - final List syncs = configRepository.listWorkspaceStandardSyncs(MockData.standardWorkspaces().get(0).getWorkspaceId(), true); - assertThat(MockData.standardSyncs().subList(0, 4)).hasSameElementsAs(syncs); + assertSyncsMatch(expectedSyncs, actualSyncs); } @Test void testListWorkspaceStandardSyncExcludeDeleted() throws IOException { + final List expectedSyncs = MockData.standardSyncs().subList(0, 3); + final List actualSyncs = configRepository.listWorkspaceStandardSyncs(MockData.standardWorkspaces().get(0).getWorkspaceId(), false); - final List syncs = configRepository.listWorkspaceStandardSyncs(MockData.standardWorkspaces().get(0).getWorkspaceId(), false); - assertThat(MockData.standardSyncs().subList(0, 3)).hasSameElementsAs(syncs); + assertSyncsMatch(expectedSyncs, actualSyncs); } @Test @@ -412,12 +417,35 @@ void testMissingSourceOAuthByDefinitionId() throws IOException { @Test void testGetStandardSyncUsingOperation() throws IOException { final UUID operationId = MockData.standardSyncOperations().get(0).getOperationId(); - final List expectedSyncs = MockData.standardSyncs().subList(0, 4); + final List expectedSyncs = MockData.standardSyncs().subList(0, 3); + final List actualSyncs = configRepository.listStandardSyncsUsingOperation(operationId); - final List syncs = configRepository.listStandardSyncsUsingOperation(operationId); + assertSyncsMatch(expectedSyncs, actualSyncs); + } - assertThat(syncs).hasSameElementsAs(expectedSyncs); + private void assertSyncsMatch(final List expectedSyncs, final List actualSyncs) { + assertEquals(expectedSyncs.size(), actualSyncs.size()); + for (final StandardSync expected : expectedSyncs) { + + final Optional maybeActual = actualSyncs.stream().filter(s -> s.getConnectionId().equals(expected.getConnectionId())).findFirst(); + if (maybeActual.isEmpty()) { + Assertions.fail(String.format("Expected to find connectionId %s in result, but actual connectionIds are %s", + expected.getConnectionId(), + actualSyncs.stream().map(StandardSync::getConnectionId).collect(Collectors.toList()))); + } + final StandardSync actual = maybeActual.get(); + + // operationIds can be ordered differently in the query result than in the mock data, so they need + // to be verified separately + // from the rest of the sync. + assertThat(actual.getOperationIds()).hasSameElementsAs(expected.getOperationIds()); + + // now, clear operationIds so the rest of the sync can be compared + expected.setOperationIds(null); + actual.setOperationIds(null); + assertEquals(expected, actual); + } } @Test @@ -439,4 +467,28 @@ void testDeleteStandardSyncOperation() } } + @Test + void testGetSourceAndDefinitionsFromSourceIds() throws IOException { + final List sourceIds = MockData.sourceConnections().subList(0, 2).stream().map(SourceConnection::getSourceId).toList(); + + final List expected = List.of( + new SourceAndDefinition(MockData.sourceConnections().get(0), MockData.standardSourceDefinitions().get(0)), + new SourceAndDefinition(MockData.sourceConnections().get(1), MockData.standardSourceDefinitions().get(1))); + + final List actual = configRepository.getSourceAndDefinitionsFromSourceIds(sourceIds); + assertThat(actual).hasSameElementsAs(expected); + } + + @Test + void testGetDestinationAndDefinitionsFromDestinationIds() throws IOException { + final List destinationIds = MockData.destinationConnections().subList(0, 2).stream().map(DestinationConnection::getDestinationId).toList(); + + final List expected = List.of( + new DestinationAndDefinition(MockData.destinationConnections().get(0), MockData.standardDestinationDefinitions().get(0)), + new DestinationAndDefinition(MockData.destinationConnections().get(1), MockData.standardDestinationDefinitions().get(1))); + + final List actual = configRepository.getDestinationAndDefinitionsFromDestinationIds(destinationIds); + assertThat(actual).hasSameElementsAs(expected); + } + } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java index f191229674f6..83d014032f10 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java @@ -481,7 +481,7 @@ public static List standardSyncs() { .withSchedule(schedule); final StandardSync standardSync4 = new StandardSync() - .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) + .withOperationIds(Collections.emptyList()) .withConnectionId(CONNECTION_ID_4) .withSourceId(SOURCE_ID_2) .withDestinationId(DESTINATION_ID_2) diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java index dfa93dd2efd1..32eaa4d6037e 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java @@ -55,6 +55,8 @@ import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -110,6 +112,13 @@ public class DefaultJobPersistence implements JobPersistence { private static final String AIRBYTE_METADATA_TABLE = "airbyte_metadata"; public static final String ORDER_BY_JOB_TIME_ATTEMPT_TIME = "ORDER BY jobs.created_at DESC, jobs.id DESC, attempts.created_at ASC, attempts.id ASC "; + public static final String ORDER_BY_JOB_CREATED_AT_DESC = "ORDER BY jobs.created_at DESC "; + public static final String LIMIT_1 = "LIMIT 1 "; + private static final String JOB_STATUS_IS_NON_TERMINAL = String.format("status IN (%s) ", + JobStatus.NON_TERMINAL_STATUSES.stream() + .map(Sqls::toSqlName) + .map(Names::singleQuote) + .collect(Collectors.joining(","))); private final ExceptionWrappingDatabase jobDatabase; private final Supplier timeSupplier; @@ -556,7 +565,7 @@ public Optional getLastReplicationJob(final UUID connectionId) throws IOExc "CAST(jobs.config_type AS VARCHAR) in " + Sqls.toSqlInFragment(Job.REPLICATION_TYPES) + AND + SCOPE_CLAUSE + "CAST(jobs.status AS VARCHAR) <> ? " + - "ORDER BY jobs.created_at DESC LIMIT 1", + ORDER_BY_JOB_CREATED_AT_DESC + LIMIT_1, connectionId.toString(), Sqls.toSqlName(JobStatus.CANCELLED)) .stream() @@ -570,7 +579,7 @@ public Optional getLastSyncJob(final UUID connectionId) throws IOException .fetch(BASE_JOB_SELECT_AND_JOIN + WHERE + "CAST(jobs.config_type AS VARCHAR) = ? " + AND + "scope = ? " + - "ORDER BY jobs.created_at DESC LIMIT 1", + ORDER_BY_JOB_CREATED_AT_DESC + LIMIT_1, Sqls.toSqlName(ConfigType.SYNC), connectionId.toString()) .stream() @@ -578,6 +587,57 @@ public Optional getLastSyncJob(final UUID connectionId) throws IOException .flatMap(r -> getJobOptional(ctx, r.get(JOB_ID, Long.class)))); } + /** + * For each connection ID in the input, find that connection's latest sync job and return it if one + * exists. + */ + @Override + public List getLastSyncJobForConnections(final List connectionIds) throws IOException { + if (connectionIds.isEmpty()) { + return Collections.emptyList(); + } + + return jobDatabase.query(ctx -> ctx + .fetch("SELECT DISTINCT ON (scope) * FROM jobs " + + WHERE + "CAST(jobs.config_type AS VARCHAR) = ? " + + AND + scopeInList(connectionIds) + + "ORDER BY scope, created_at DESC", + Sqls.toSqlName(ConfigType.SYNC)) + .stream() + .flatMap(r -> getJobOptional(ctx, r.get("id", Long.class)).stream()) + .collect(Collectors.toList())); + } + + /** + * For each connection ID in the input, find that connection's most recent non-terminal sync job and + * return it if one exists. + */ + @Override + public List getRunningSyncJobForConnections(final List connectionIds) throws IOException { + if (connectionIds.isEmpty()) { + return Collections.emptyList(); + } + + return jobDatabase.query(ctx -> ctx + .fetch("SELECT DISTINCT ON (scope) * FROM jobs " + + WHERE + "CAST(jobs.config_type AS VARCHAR) = ? " + + AND + scopeInList(connectionIds) + + AND + JOB_STATUS_IS_NON_TERMINAL + + "ORDER BY scope, created_at DESC", + Sqls.toSqlName(ConfigType.SYNC)) + .stream() + .flatMap(r -> getJobOptional(ctx, r.get("id", Long.class)).stream()) + .collect(Collectors.toList())); + } + + private String scopeInList(final Collection connectionIds) { + return String.format("scope IN (%s) ", + connectionIds.stream() + .map(UUID::toString) + .map(Names::singleQuote) + .collect(Collectors.joining(","))); + } + @Override public Optional getFirstReplicationJob(final UUID connectionId) throws IOException { return jobDatabase.query(ctx -> ctx diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java index 06554863eff0..5744dd695496 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java @@ -211,6 +211,10 @@ List listJobStatusAndTimestampWithConnection(UUID con Optional getLastSyncJob(UUID connectionId) throws IOException; + List getLastSyncJobForConnections(final List connectionIds) throws IOException; + + List getRunningSyncJobForConnections(final List connectionIds) throws IOException; + Optional getFirstReplicationJob(UUID connectionId) throws IOException; Optional getNextJob() throws IOException; diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java index 310ecbdbbb85..8629b307ddf4 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java @@ -978,6 +978,146 @@ void testGetLastSyncJobForConnectionIdEmptyBecauseOnlyReset() throws IOException } + @Nested + @DisplayName("When getting the last sync job for multiple connections") + class GetLastSyncJobForConnections { + + private static final UUID CONNECTION_ID_1 = UUID.randomUUID(); + private static final UUID CONNECTION_ID_2 = UUID.randomUUID(); + private static final UUID CONNECTION_ID_3 = UUID.randomUUID(); + private static final String SCOPE_1 = CONNECTION_ID_1.toString(); + private static final String SCOPE_2 = CONNECTION_ID_2.toString(); + private static final String SCOPE_3 = CONNECTION_ID_3.toString(); + private static final List CONNECTION_IDS = List.of(CONNECTION_ID_1, CONNECTION_ID_2, CONNECTION_ID_3); + + @Test + @DisplayName("Should return nothing if no sync job exists") + void testGetLastSyncJobsForConnectionsEmpty() throws IOException { + final List actual = jobPersistence.getLastSyncJobForConnections(CONNECTION_IDS); + + assertTrue(actual.isEmpty()); + } + + @Test + @DisplayName("Should return the last enqueued sync job for each connection") + void testGetLastSyncJobForConnections() throws IOException { + final long scope1Job1 = jobPersistence.enqueueJob(SCOPE_1, SYNC_JOB_CONFIG).orElseThrow(); + jobPersistence.succeedAttempt(scope1Job1, jobPersistence.createAttempt(scope1Job1, LOG_PATH)); + + final long scope2Job1 = jobPersistence.enqueueJob(SCOPE_2, SYNC_JOB_CONFIG).orElseThrow(); + jobPersistence.succeedAttempt(scope2Job1, jobPersistence.createAttempt(scope2Job1, LOG_PATH)); + + final long scope3Job1 = jobPersistence.enqueueJob(SCOPE_3, SYNC_JOB_CONFIG).orElseThrow(); + + final Instant afterNow = NOW.plusSeconds(1000); + when(timeSupplier.get()).thenReturn(afterNow); + + final long scope1Job2 = jobPersistence.enqueueJob(SCOPE_1, SYNC_JOB_CONFIG).orElseThrow(); + final int scope1Job2AttemptNumber = jobPersistence.createAttempt(scope1Job2, LOG_PATH); + + // should return the latest sync job even if failed + jobPersistence.failAttempt(scope1Job2, scope1Job2AttemptNumber); + final Attempt scope1Job2attempt = jobPersistence.getJob(scope1Job2).getAttempts().stream().findFirst().orElseThrow(); + jobPersistence.failJob(scope1Job2); + + // will leave this job running + final long scope2Job2 = jobPersistence.enqueueJob(SCOPE_2, SYNC_JOB_CONFIG).orElseThrow(); + jobPersistence.createAttempt(scope2Job2, LOG_PATH); + final Attempt scope2Job2attempt = jobPersistence.getJob(scope2Job2).getAttempts().stream().findFirst().orElseThrow(); + + final List actual = jobPersistence.getLastSyncJobForConnections(CONNECTION_IDS); + final List expected = new ArrayList<>(); + expected.add(createJob(scope1Job2, SYNC_JOB_CONFIG, JobStatus.FAILED, List.of(scope1Job2attempt), afterNow.getEpochSecond(), SCOPE_1)); + expected.add(createJob(scope2Job2, SYNC_JOB_CONFIG, JobStatus.RUNNING, List.of(scope2Job2attempt), afterNow.getEpochSecond(), SCOPE_2)); + expected.add(createJob(scope3Job1, SYNC_JOB_CONFIG, JobStatus.PENDING, Collections.emptyList(), NOW.getEpochSecond(), SCOPE_3)); + + assertTrue(expected.size() == actual.size() && expected.containsAll(actual) && actual.containsAll(expected)); + } + + @Test + @DisplayName("Should return nothing if only reset job exists") + void testGetLastSyncJobsForConnectionsEmptyBecauseOnlyReset() throws IOException { + final long jobId = jobPersistence.enqueueJob(SCOPE_1, RESET_JOB_CONFIG).orElseThrow(); + jobPersistence.succeedAttempt(jobId, jobPersistence.createAttempt(jobId, LOG_PATH)); + + final Instant afterNow = NOW.plusSeconds(1000); + when(timeSupplier.get()).thenReturn(afterNow); + + final List actual = jobPersistence.getLastSyncJobForConnections(CONNECTION_IDS); + + assertTrue(actual.isEmpty()); + } + + } + + @Nested + @DisplayName("When getting the last running sync job for multiple connections") + class GetRunningSyncJobForConnections { + + private static final UUID CONNECTION_ID_1 = UUID.randomUUID(); + private static final UUID CONNECTION_ID_2 = UUID.randomUUID(); + private static final UUID CONNECTION_ID_3 = UUID.randomUUID(); + private static final String SCOPE_1 = CONNECTION_ID_1.toString(); + private static final String SCOPE_2 = CONNECTION_ID_2.toString(); + private static final String SCOPE_3 = CONNECTION_ID_3.toString(); + private static final List CONNECTION_IDS = List.of(CONNECTION_ID_1, CONNECTION_ID_2, CONNECTION_ID_3); + + @Test + @DisplayName("Should return nothing if no sync job exists") + void testGetRunningSyncJobsForConnectionsEmpty() throws IOException { + final List actual = jobPersistence.getRunningSyncJobForConnections(CONNECTION_IDS); + + assertTrue(actual.isEmpty()); + } + + @Test + @DisplayName("Should return the last running sync job for each connection") + void testGetRunningSyncJobsForConnections() throws IOException { + // succeeded jobs should not be present in the result + final long scope1Job1 = jobPersistence.enqueueJob(SCOPE_1, SYNC_JOB_CONFIG).orElseThrow(); + jobPersistence.succeedAttempt(scope1Job1, jobPersistence.createAttempt(scope1Job1, LOG_PATH)); + + // fail scope2's first job, but later start a running job that should show up in the result + final long scope2Job1 = jobPersistence.enqueueJob(SCOPE_2, SYNC_JOB_CONFIG).orElseThrow(); + final int scope2Job1AttemptNumber = jobPersistence.createAttempt(scope2Job1, LOG_PATH); + jobPersistence.failAttempt(scope2Job1, scope2Job1AttemptNumber); + jobPersistence.failJob(scope2Job1); + + // pending jobs should be present in the result + final long scope3Job1 = jobPersistence.enqueueJob(SCOPE_3, SYNC_JOB_CONFIG).orElseThrow(); + + final Instant afterNow = NOW.plusSeconds(1000); + when(timeSupplier.get()).thenReturn(afterNow); + + // create a running job/attempt for scope2 + final long scope2Job2 = jobPersistence.enqueueJob(SCOPE_2, SYNC_JOB_CONFIG).orElseThrow(); + jobPersistence.createAttempt(scope2Job2, LOG_PATH); + final Attempt scope2Job2attempt = jobPersistence.getJob(scope2Job2).getAttempts().stream().findFirst().orElseThrow(); + + final List expected = new ArrayList<>(); + expected.add(createJob(scope2Job2, SYNC_JOB_CONFIG, JobStatus.RUNNING, List.of(scope2Job2attempt), afterNow.getEpochSecond(), SCOPE_2)); + expected.add(createJob(scope3Job1, SYNC_JOB_CONFIG, JobStatus.PENDING, Collections.emptyList(), NOW.getEpochSecond(), SCOPE_3)); + + final List actual = jobPersistence.getRunningSyncJobForConnections(CONNECTION_IDS); + assertTrue(expected.size() == actual.size() && expected.containsAll(actual) && actual.containsAll(expected)); + } + + @Test + @DisplayName("Should return nothing if only a running reset job exists") + void testGetRunningSyncJobsForConnectionsEmptyBecauseOnlyReset() throws IOException { + final long jobId = jobPersistence.enqueueJob(SCOPE_1, RESET_JOB_CONFIG).orElseThrow(); + jobPersistence.createAttempt(jobId, LOG_PATH); + + final Instant afterNow = NOW.plusSeconds(1000); + when(timeSupplier.get()).thenReturn(afterNow); + + final List actual = jobPersistence.getRunningSyncJobForConnections(CONNECTION_IDS); + + assertTrue(actual.isEmpty()); + } + + } + @Nested @DisplayName("When getting first replication job") class GetFirstReplicationJob { diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java index 70c56d6709e4..bb4f7bbb551f 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java @@ -140,6 +140,18 @@ public Optional getLatestSyncJob(final UUID connectionId) throws IOExce return jobPersistence.getLastSyncJob(connectionId).map(JobConverter::getJobRead); } + public List getLatestSyncJobsForConnections(final List connectionIds) throws IOException { + return jobPersistence.getLastSyncJobForConnections(connectionIds).stream() + .map(JobConverter::getJobRead) + .collect(Collectors.toList()); + } + + public List getRunningSyncJobForConnections(final List connectionIds) throws IOException { + return jobPersistence.getRunningSyncJobForConnections(connectionIds).stream() + .map(JobConverter::getJobRead) + .collect(Collectors.toList()); + } + private SourceRead getSourceRead(final ConnectionRead connectionRead) throws JsonValidationException, IOException, ConfigNotFoundException { final SourceIdRequestBody sourceIdRequestBody = new SourceIdRequestBody().sourceId(connectionRead.getSourceId()); return sourceHandler.getSource(sourceIdRequestBody); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index 88f98c05389e..5a34ab913f1a 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -61,6 +61,8 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -95,19 +97,64 @@ public ConnectionStateType getStateType(final ConnectionIdRequestBody connection return Enums.convertTo(stateHandler.getState(connectionIdRequestBody).getStateType(), ConnectionStateType.class); } - public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) - throws ConfigNotFoundException, IOException, JsonValidationException { + public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) throws IOException { + + // passing 'false' so that deleted connections are not included + final List standardSyncs = + configRepository.listWorkspaceStandardSyncs(workspaceIdRequestBody.getWorkspaceId(), false); + final Map sourceReadById = + getSourceReadById(standardSyncs.stream().map(StandardSync::getSourceId).toList()); + final Map destinationReadById = + getDestinationReadById(standardSyncs.stream().map(StandardSync::getDestinationId).toList()); + final Map latestJobByConnectionId = + getLatestJobByConnectionId(standardSyncs.stream().map(StandardSync::getConnectionId).toList()); + final Map runningJobByConnectionId = + getRunningJobByConnectionId(standardSyncs.stream().map(StandardSync::getConnectionId).toList()); final List connectionItems = Lists.newArrayList(); - // passing 'false' so that deleted connections are not included - for (final StandardSync standardSync : configRepository.listWorkspaceStandardSyncs(workspaceIdRequestBody.getWorkspaceId(), false)) { - connectionItems.add(buildWebBackendConnectionListItem(standardSync)); + for (final StandardSync standardSync : standardSyncs) { + connectionItems.add( + buildWebBackendConnectionListItem( + standardSync, + sourceReadById, + destinationReadById, + latestJobByConnectionId, + runningJobByConnectionId)); } return new WebBackendConnectionReadList().connections(connectionItems); } + private Map getLatestJobByConnectionId(final List connectionIds) throws IOException { + return jobHistoryHandler.getLatestSyncJobsForConnections(connectionIds).stream() + .collect(Collectors.toMap(j -> UUID.fromString(j.getConfigId()), Function.identity())); + } + + private Map getRunningJobByConnectionId(final List connectionIds) throws IOException { + return jobHistoryHandler.getRunningSyncJobForConnections(connectionIds).stream() + .collect(Collectors.toMap(j -> UUID.fromString(j.getConfigId()), Function.identity())); + } + + private Map getSourceReadById(final List sourceIds) throws IOException { + final List sourceReads = configRepository.getSourceAndDefinitionsFromSourceIds(sourceIds) + .stream() + .map(sourceAndDefinition -> SourceHandler.toSourceRead(sourceAndDefinition.source(), sourceAndDefinition.definition())) + .toList(); + + return sourceReads.stream().collect(Collectors.toMap(SourceRead::getSourceId, Function.identity())); + } + + private Map getDestinationReadById(final List destinationIds) throws IOException { + final List destinationReads = configRepository.getDestinationAndDefinitionsFromDestinationIds(destinationIds) + .stream() + .map(destinationAndDefinition -> DestinationHandler.toDestinationRead(destinationAndDefinition.destination(), + destinationAndDefinition.definition())) + .toList(); + + return destinationReads.stream().collect(Collectors.toMap(DestinationRead::getDestinationId, Function.identity())); + } + private WebBackendConnectionRead buildWebBackendConnectionRead(final ConnectionRead connectionRead) throws ConfigNotFoundException, IOException, JsonValidationException { final SourceRead source = getSourceRead(connectionRead.getSourceId()); @@ -129,12 +176,17 @@ private WebBackendConnectionRead buildWebBackendConnectionRead(final ConnectionR return webBackendConnectionRead; } - private WebBackendConnectionListItem buildWebBackendConnectionListItem(final StandardSync standardSync) - throws JsonValidationException, ConfigNotFoundException, IOException { - final SourceRead source = getSourceRead(standardSync.getSourceId()); - final DestinationRead destination = getDestinationRead(standardSync.getDestinationId()); - final Optional latestSyncJob = jobHistoryHandler.getLatestSyncJob(standardSync.getConnectionId()); - final Optional latestRunningSyncJob = jobHistoryHandler.getLatestRunningSyncJob(standardSync.getConnectionId()); + private WebBackendConnectionListItem buildWebBackendConnectionListItem( + final StandardSync standardSync, + final Map sourceReadById, + final Map destinationReadById, + final Map latestJobByConnectionId, + final Map runningJobByConnectionId) { + + final SourceRead source = sourceReadById.get(standardSync.getSourceId()); + final DestinationRead destination = destinationReadById.get(standardSync.getDestinationId()); + final Optional latestSyncJob = Optional.ofNullable(latestJobByConnectionId.get(standardSync.getConnectionId())); + final Optional latestRunningSyncJob = Optional.ofNullable(runningJobByConnectionId.get(standardSync.getConnectionId())); final WebBackendConnectionListItem listItem = new WebBackendConnectionListItem() .connectionId(standardSync.getConnectionId()) diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index f4c3d48652a6..49b4f2d09c47 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -72,6 +72,8 @@ import io.airbyte.config.StandardSync; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.config.persistence.ConfigRepository.DestinationAndDefinition; +import io.airbyte.config.persistence.ConfigRepository.SourceAndDefinition; import io.airbyte.protocol.models.CatalogHelpers; import io.airbyte.protocol.models.Field; import io.airbyte.protocol.models.JsonSchemaType; @@ -148,19 +150,24 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio eventRunner, configRepository); - final StandardSourceDefinition standardSourceDefinition = SourceDefinitionHelpers.generateSourceDefinition(); - standardSourceDefinition.setIcon(SOURCE_ICON); - final SourceConnection source = SourceHelpers.generateSource(UUID.randomUUID()); - sourceRead = SourceHelpers.getSourceRead(source, standardSourceDefinition); + final StandardSourceDefinition sourceDefinition = SourceDefinitionHelpers.generateSourceDefinition(); + sourceDefinition.setIcon(SOURCE_ICON); + final SourceConnection source = SourceHelpers.generateSource(sourceDefinition.getSourceDefinitionId()); + sourceRead = SourceHelpers.getSourceRead(source, sourceDefinition); final StandardDestinationDefinition destinationDefinition = DestinationDefinitionHelpers.generateDestination(); destinationDefinition.setIcon(DESTINATION_ICON); - final DestinationConnection destination = DestinationHelpers.generateDestination(UUID.randomUUID()); + final DestinationConnection destination = DestinationHelpers.generateDestination(destinationDefinition.getDestinationDefinitionId()); final DestinationRead destinationRead = DestinationHelpers.getDestinationRead(destination, destinationDefinition); - final StandardSync standardSync = ConnectionHelpers.generateSyncWithSourceId(source.getSourceId()); + final StandardSync standardSync = ConnectionHelpers.generateSyncWithSourceAndDestinationId(source.getSourceId(), destination.getDestinationId()); when(configRepository.listWorkspaceStandardSyncs(sourceRead.getWorkspaceId(), false)) .thenReturn(Collections.singletonList(standardSync)); + when(configRepository.getSourceAndDefinitionsFromSourceIds(Collections.singletonList(source.getSourceId()))) + .thenReturn(Collections.singletonList(new SourceAndDefinition(source, sourceDefinition))); + when(configRepository.getDestinationAndDefinitionsFromDestinationIds(Collections.singletonList(destination.getDestinationId()))) + .thenReturn(Collections.singletonList(new DestinationAndDefinition(destination, destinationDefinition))); + connectionRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync); operationReadList = new OperationReadList() .operations(List.of(new OperationRead() @@ -195,6 +202,9 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio when(jobHistoryHandler.getLatestSyncJob(connectionRead.getConnectionId())).thenReturn(Optional.of(jobRead.getJob())); + when(jobHistoryHandler.getLatestSyncJobsForConnections(Collections.singletonList(connectionRead.getConnectionId()))) + .thenReturn(Collections.singletonList(jobRead.getJob())); + expectedListItem = ConnectionHelpers.generateExpectedWebBackendConnectionListItem( standardSync, sourceRead, @@ -300,8 +310,7 @@ void testGetWorkspaceStateEmpty() throws IOException { } @Test - void testWebBackendListConnectionsForWorkspace() throws ConfigNotFoundException, IOException, - JsonValidationException { + void testWebBackendListConnectionsForWorkspace() throws IOException { final WorkspaceIdRequestBody workspaceIdRequestBody = new WorkspaceIdRequestBody(); workspaceIdRequestBody.setWorkspaceId(sourceRead.getWorkspaceId()); diff --git a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java index 042b537dc1ed..c6bac34a1e33 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java +++ b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java @@ -54,6 +54,8 @@ public class ConnectionHelpers { private static final String BASIC_SCHEDULE_DATA_TIME_UNITS = "days"; private static final long BASIC_SCHEDULE_DATA_UNITS = 1L; private static final String ONE_HUNDRED_G = "100g"; + private static final String STANDARD_SYNC_NAME = "presto to hudi"; + private static final String STANDARD_SYNC_PREFIX = "presto_to_hudi"; public static final StreamDescriptor STREAM_DESCRIPTOR = new StreamDescriptor().withName(STREAM_NAME); @@ -70,10 +72,10 @@ public static StandardSync generateSyncWithSourceId(final UUID sourceId) { return new StandardSync() .withConnectionId(connectionId) - .withName("presto to hudi") + .withName(STANDARD_SYNC_NAME) .withNamespaceDefinition(NamespaceDefinitionType.SOURCE) .withNamespaceFormat(null) - .withPrefix("presto_to_hudi") + .withPrefix(STANDARD_SYNC_PREFIX) .withStatus(StandardSync.Status.ACTIVE) .withCatalog(generateBasicConfiguredAirbyteCatalog()) .withSourceId(sourceId) @@ -89,10 +91,10 @@ public static StandardSync generateSyncWithDestinationId(final UUID destinationI return new StandardSync() .withConnectionId(connectionId) - .withName("presto to hudi") + .withName(STANDARD_SYNC_NAME) .withNamespaceDefinition(NamespaceDefinitionType.SOURCE) .withNamespaceFormat(null) - .withPrefix("presto_to_hudi") + .withPrefix(STANDARD_SYNC_PREFIX) .withStatus(StandardSync.Status.ACTIVE) .withCatalog(generateBasicConfiguredAirbyteCatalog()) .withSourceId(UUID.randomUUID()) @@ -101,6 +103,23 @@ public static StandardSync generateSyncWithDestinationId(final UUID destinationI .withManual(true); } + public static StandardSync generateSyncWithSourceAndDestinationId(final UUID sourceId, final UUID destinationId) { + final UUID connectionId = UUID.randomUUID(); + + return new StandardSync() + .withConnectionId(connectionId) + .withName(STANDARD_SYNC_NAME) + .withNamespaceDefinition(NamespaceDefinitionType.SOURCE) + .withNamespaceFormat(null) + .withPrefix(STANDARD_SYNC_PREFIX) + .withStatus(StandardSync.Status.ACTIVE) + .withCatalog(generateBasicConfiguredAirbyteCatalog()) + .withSourceId(sourceId) + .withDestinationId(destinationId) + .withOperationIds(List.of(UUID.randomUUID())) + .withManual(true); + } + public static ConnectionSchedule generateBasicConnectionSchedule() { return new ConnectionSchedule() .timeUnit(ConnectionSchedule.TimeUnitEnum.fromValue(BASIC_SCHEDULE_TIME_UNIT)) From f6aed2d278f6667503297c24841e295b90ff6377 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Mon, 10 Oct 2022 21:28:02 +0200 Subject: [PATCH 016/498] Cleanup StepMenu properties (#17586) --- .../components/JobItem/components/JobLogs.tsx | 20 ++++++++++++++++-- .../components/JobItem/components/Tabs.tsx | 5 +---- .../src/components/ui/StepsMenu/Step.tsx | 21 +++++-------------- .../src/components/ui/StepsMenu/StepsMenu.tsx | 15 +++++++------ 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx b/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx index bb5c2e22e970..96fd32b4cccb 100644 --- a/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx +++ b/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx @@ -3,6 +3,9 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useLocation } from "react-router-dom"; +import StatusIcon from "components/StatusIcon"; +import { StatusIconStatus } from "components/StatusIcon/StatusIcon"; + import { JobsWithJobs } from "pages/ConnectionPage/pages/ConnectionItemPage/JobsList"; import { useGetDebugInfoJob } from "services/job/JobService"; @@ -17,6 +20,20 @@ interface JobLogsProps { job: SynchronousJobRead | JobsWithJobs; } +const mapAttemptStatusToIcon = (attempt: AttemptRead): StatusIconStatus => { + if (isPartialSuccess(attempt)) { + return "warning"; + } + switch (attempt.status) { + case AttemptStatus.running: + return "loading"; + case AttemptStatus.succeeded: + return "success"; + case AttemptStatus.failed: + return "error"; + } +}; + const isPartialSuccess = (attempt: AttemptRead) => { return !!attempt.failureSummary?.partialSuccess; }; @@ -58,8 +75,7 @@ const JobLogs: React.FC = ({ jobIsFailed, job }) => { const attemptsTabs: TabsData[] = job.attempts?.map((item, index) => ({ id: index.toString(), - isPartialSuccess: isPartialSuccess(item), - status: item.status === AttemptStatus.failed || item.status === AttemptStatus.succeeded ? item.status : undefined, + icon: , name: , })) ?? []; diff --git a/airbyte-webapp/src/components/JobItem/components/Tabs.tsx b/airbyte-webapp/src/components/JobItem/components/Tabs.tsx index 370f64d76d0b..2d1ca030fcc3 100644 --- a/airbyte-webapp/src/components/JobItem/components/Tabs.tsx +++ b/airbyte-webapp/src/components/JobItem/components/Tabs.tsx @@ -3,13 +3,10 @@ import styled from "styled-components"; import { StepsMenu } from "components/ui/StepsMenu"; -import { AttemptStatus } from "core/request/AirbyteClient"; - export interface TabsData { id: string; name: string | React.ReactNode; - status?: AttemptStatus; - isPartialSuccess?: boolean; + icon?: React.ReactNode; onSelect?: () => void; } diff --git a/airbyte-webapp/src/components/ui/StepsMenu/Step.tsx b/airbyte-webapp/src/components/ui/StepsMenu/Step.tsx index ebba842db54d..0b335dd8e353 100644 --- a/airbyte-webapp/src/components/ui/StepsMenu/Step.tsx +++ b/airbyte-webapp/src/components/ui/StepsMenu/Step.tsx @@ -1,20 +1,14 @@ -import React, { useMemo } from "react"; +import React from "react"; import styled from "styled-components"; -import StatusIcon from "components/StatusIcon"; -import { StatusIconStatus } from "components/StatusIcon/StatusIcon"; - -import Status from "core/statuses"; - -interface IProps { +interface StepProps { id: string; lightMode?: boolean; name: string | React.ReactNode; onClick?: (id: string) => void; isActive?: boolean; - isPartialSuccess?: boolean; + icon?: React.ReactNode; num: number; - status?: string; } const StepView = styled.div<{ @@ -57,18 +51,13 @@ const Num = styled.div<{ isActive?: boolean }>` box-shadow: 0 1px 2px 0 ${({ theme }) => theme.shadowColor}; `; -export const Step: React.FC = ({ name, id, isActive, onClick, num, lightMode, status, isPartialSuccess }) => { +export const Step: React.FC = ({ name, id, isActive, onClick, num, lightMode, icon }) => { const onItemClickItem = () => { if (onClick) { onClick(id); } }; - const statusIconStatus: StatusIconStatus | undefined = useMemo( - () => (status !== Status.FAILED && !isPartialSuccess ? "success" : isPartialSuccess ? "warning" : undefined), - [status, isPartialSuccess] - ); - return ( = ({ name, id, isActive, onClick, num, light lightMode={lightMode} > {lightMode ? null : {num}} - {status ? : null} + {icon} {name} ); diff --git a/airbyte-webapp/src/components/ui/StepsMenu/StepsMenu.tsx b/airbyte-webapp/src/components/ui/StepsMenu/StepsMenu.tsx index 5cba7135e7b5..2ff6f282f8cf 100644 --- a/airbyte-webapp/src/components/ui/StepsMenu/StepsMenu.tsx +++ b/airbyte-webapp/src/components/ui/StepsMenu/StepsMenu.tsx @@ -3,15 +3,14 @@ import styled from "styled-components"; import { Step } from "./Step"; -export interface StepMenuItem { +interface StepMenuItem { id: string; name: string | React.ReactNode; - status?: string; - isPartialSuccess?: boolean; + icon?: React.ReactNode; onSelect?: () => void; } -interface IProps { +interface StepMenuProps { lightMode?: boolean; data: StepMenuItem[]; activeStep?: string; @@ -25,17 +24,17 @@ const Content = styled.div` font-family: ${({ theme }) => theme.regularFont}; `; -export const StepsMenu: React.FC = ({ data, onSelect, activeStep, lightMode }) => { +export const StepsMenu: React.FC = ({ data, onSelect, activeStep, lightMode }) => { return ( {data.map((item, key) => ( From fb9efb378dc52f207dab51a8be0e3f4192f809d5 Mon Sep 17 00:00:00 2001 From: Parker Mossman Date: Mon, 10 Oct 2022 13:34:19 -0700 Subject: [PATCH 017/498] Add Workspace and Connection Geography Support to API (#17650) * progress on adding geography throughout api * fix workspace handler test * more progress * implement workspace defaulting and add/update more tests * fix bootloader tests * set defaultGeography in missing places * add Geography column when reading Connection record from DB * fix pmd * add more comments/description * format * description --- .../TrackingClientSingletonTest.java | 7 +- airbyte-api/src/main/openapi/config.yaml | 52 +++++++ .../io/airbyte/bootloader/BootloaderApp.java | 4 +- .../airbyte/bootloader/BootloaderAppTest.java | 4 +- .../src/main/resources/types/Geography.yaml | 10 ++ .../main/resources/types/StandardSync.yaml | 3 + .../resources/types/StandardWorkspace.yaml | 3 + .../DatabaseConfigPersistence.java | 16 +- .../config/persistence/DbConverter.java | 17 +- .../BaseDatabaseConfigPersistenceTest.java | 7 +- ...DatabaseConfigPersistenceLoadDataTest.java | 4 +- .../airbyte/config/persistence/MockData.java | 28 ++-- .../airbyte/server/apis/ConfigurationApi.java | 9 ++ .../server/converters/ApiPojoConverters.java | 12 +- .../server/handlers/ConnectionsHandler.java | 24 ++- .../WebBackendConnectionsHandler.java | 5 +- .../WebBackendGeographiesHandler.java | 33 ++++ .../server/handlers/WorkspacesHandler.java | 17 +- .../handlers/helpers/ConnectionMatcher.java | 1 + .../handlers/ConnectionsHandlerTest.java | 146 ++++++++++++------ .../WebBackendConnectionsHandlerTest.java | 31 ++-- .../WebBackendGeographiesHandlerTest.java | 43 ++++++ .../handlers/WorkspacesHandlerTest.java | 48 ++++-- .../server/helpers/ConnectionHelpers.java | 14 +- .../api/generated-api-html/index.html | 68 ++++++++ 25 files changed, 498 insertions(+), 108 deletions(-) create mode 100644 airbyte-config/config-models/src/main/resources/types/Geography.yaml create mode 100644 airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendGeographiesHandler.java create mode 100644 airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendGeographiesHandlerTest.java diff --git a/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java b/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java index fb6908c051e2..782936f09a5a 100644 --- a/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java +++ b/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java @@ -12,6 +12,7 @@ import io.airbyte.commons.version.AirbyteVersion; import io.airbyte.config.Configs; import io.airbyte.config.Configs.WorkerEnvironment; +import io.airbyte.config.Geography; import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; @@ -111,7 +112,8 @@ void testGetTrackingIdentityNonAnonymous() throws JsonValidationException, IOExc .withEmail(EMAIL) .withAnonymousDataCollection(false) .withNews(true) - .withSecurityUpdates(true); + .withSecurityUpdates(true) + .withDefaultGeography(Geography.AUTO); when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(workspace); @@ -129,7 +131,8 @@ void testGetTrackingIdentityAnonymous() throws JsonValidationException, IOExcept .withEmail("a@airbyte.io") .withAnonymousDataCollection(true) .withNews(true) - .withSecurityUpdates(true); + .withSecurityUpdates(true) + .withDefaultGeography(Geography.AUTO); when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(workspace); diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index 6d13ebe89e34..7e90df6f3e70 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -2036,6 +2036,24 @@ paths: $ref: "#/components/responses/NotFoundResponse" "422": $ref: "#/components/responses/InvalidInputResponse" + /v1/web_backend/geographies/list: + post: + tags: + - web_backend + description: Returns all available geographies in which a data sync can run. + summary: | + Returns available geographies can be selected to run data syncs in a particular geography. + The 'auto' entry indicates that the sync will be automatically assigned to a geography according + to the platform default behavior. Entries other than 'auto' are two-letter country codes that + follow the ISO 3166-1 alpha-2 standard. + operationId: webBackendListGeographies + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/WebBackendGeographiesListResult" /v1/jobs/list: post: tags: @@ -2260,6 +2278,8 @@ components: $ref: "#/components/schemas/Notification" displaySetupWizard: type: boolean + defaultGeography: + $ref: "#/components/schemas/Geography" Notification: type: object required: @@ -2362,6 +2382,8 @@ components: type: boolean feedbackDone: type: boolean + defaultGeography: + $ref: "#/components/schemas/Geography" WorkspaceUpdateName: type: object required: @@ -2397,6 +2419,8 @@ components: type: array items: $ref: "#/components/schemas/Notification" + defaultGeography: + $ref: "#/components/schemas/Geography" WorkspaceGiveFeedback: type: object required: @@ -2424,6 +2448,15 @@ components: type: boolean hasDestinations: type: boolean + WebBackendGeographiesListResult: + type: object + required: + - geographies + properties: + geographies: + type: array + items: + $ref: "#/components/schemas/Geography" # SLUG SlugRequestBody: type: object @@ -2432,6 +2465,13 @@ components: properties: slug: type: string + # Geography + Geography: + type: string + enum: + - auto + - us + - eu # SourceDefinition SourceDefinitionId: type: string @@ -3159,6 +3199,8 @@ components: sourceCatalogId: type: string format: uuid + geography: + $ref: "#/components/schemas/Geography" WebBackendConnectionCreate: type: object required: @@ -3206,6 +3248,8 @@ components: sourceCatalogId: type: string format: uuid + geography: + $ref: "#/components/schemas/Geography" ConnectionStateCreateOrUpdate: type: object required: @@ -3256,6 +3300,8 @@ components: sourceCatalogId: type: string format: uuid + geography: + $ref: "#/components/schemas/Geography" WebBackendConnectionUpdate: type: object description: Used to apply a patch-style update to a connection, which means that null properties remain unchanged @@ -3298,6 +3344,8 @@ components: sourceCatalogId: type: string format: uuid + geography: + $ref: "#/components/schemas/Geography" ConnectionRead: type: object required: @@ -3345,6 +3393,8 @@ components: sourceCatalogId: type: string format: uuid + geography: + $ref: "#/components/schemas/Geography" ConnectionSearch: type: object properties: @@ -4619,6 +4669,8 @@ components: format: uuid catalogDiff: $ref: "#/components/schemas/CatalogDiff" + geography: + $ref: "#/components/schemas/Geography" WebBackendConnectionReadList: type: object required: diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java index 5de66219c0b1..59402fea95b8 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java @@ -12,6 +12,7 @@ import io.airbyte.commons.version.Version; import io.airbyte.config.Configs; import io.airbyte.config.EnvConfigs; +import io.airbyte.config.Geography; import io.airbyte.config.StandardWorkspace; import io.airbyte.config.init.ApplyDefinitionsHelper; import io.airbyte.config.init.DefinitionsProvider; @@ -286,7 +287,8 @@ private static void createWorkspaceIfNoneExists(final ConfigRepository configRep .withSlug(workspaceId.toString()) .withInitialSetupComplete(false) .withDisplaySetupWizard(true) - .withTombstone(false); + .withTombstone(false) + .withDefaultGeography(Geography.AUTO); configRepository.writeStandardWorkspace(workspace); } diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index d284a6f02b86..37602c7c27a5 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -21,6 +21,7 @@ import io.airbyte.commons.version.AirbyteVersion; import io.airbyte.commons.version.Version; import io.airbyte.config.Configs; +import io.airbyte.config.Geography; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardWorkspace; import io.airbyte.config.init.YamlSeedConfigPersistence; @@ -222,7 +223,8 @@ void testBootloaderAppRunSecretMigration() throws Exception { .withSlug("wSlug") .withEmail("email@mail.com") .withTombstone(false) - .withInitialSetupComplete(false)); + .withInitialSetupComplete(false) + .withDefaultGeography(Geography.AUTO)); final UUID sourceId = UUID.randomUUID(); configRepository.writeSourceConnectionNoSecrets(new SourceConnection() .withSourceDefinitionId(UUID.fromString("e7778cfc-e97c-4458-9ecb-b4f2bba8946c")) // Facebook Marketing diff --git a/airbyte-config/config-models/src/main/resources/types/Geography.yaml b/airbyte-config/config-models/src/main/resources/types/Geography.yaml new file mode 100644 index 000000000000..f545f53c1d24 --- /dev/null +++ b/airbyte-config/config-models/src/main/resources/types/Geography.yaml @@ -0,0 +1,10 @@ +--- +"$schema": http://json-schema.org/draft-07/schema# +"$id": https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/resources/types/Geography.yaml +title: Geography +description: Geography Setting +type: string +enum: + - auto + - us + - eu diff --git a/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml b/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml index 62755c70aff8..276428f8e112 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml @@ -11,6 +11,7 @@ required: - catalog - manual - namespaceDefinition + - geography additionalProperties: false properties: namespaceDefinition: @@ -114,3 +115,5 @@ properties: format: uuid resourceRequirements: "$ref": ResourceRequirements.yaml + geography: + "$ref": Geography.yaml diff --git a/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml b/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml index 44a44070ae6b..dd65857f7dbd 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml @@ -9,6 +9,7 @@ required: - name - slug - initialSetupComplete + - defaultGeography additionalProperties: false properties: workspaceId: @@ -47,3 +48,5 @@ properties: type: boolean feedbackDone: type: boolean + defaultGeography: + "$ref": Geography.yaml diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index 3332c32fe5b0..e512ae14f72d 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -1087,12 +1087,16 @@ private void writeStandardSync(final List configs, final DSLContex .set(CONNECTION.MANUAL, standardSync.getManual()) .set(CONNECTION.SCHEDULE_TYPE, standardSync.getScheduleType() == null ? null - : Enums.toEnum(standardSync.getScheduleType().value(), io.airbyte.db.instance.configs.jooq.generated.enums.ScheduleType.class) + : Enums.toEnum(standardSync.getScheduleType().value(), + io.airbyte.db.instance.configs.jooq.generated.enums.ScheduleType.class) .orElseThrow()) .set(CONNECTION.SCHEDULE_DATA, JSONB.valueOf(Jsons.serialize(standardSync.getScheduleData()))) - .set(CONNECTION.RESOURCE_REQUIREMENTS, JSONB.valueOf(Jsons.serialize(standardSync.getResourceRequirements()))) + .set(CONNECTION.RESOURCE_REQUIREMENTS, + JSONB.valueOf(Jsons.serialize(standardSync.getResourceRequirements()))) .set(CONNECTION.UPDATED_AT, timestamp) .set(CONNECTION.SOURCE_CATALOG_ID, standardSync.getSourceCatalogId()) + .set(CONNECTION.GEOGRAPHY, Enums.toEnum(standardSync.getGeography().value(), + io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType.class).orElseThrow()) .where(CONNECTION.ID.eq(standardSync.getConnectionId())) .execute(); @@ -1126,11 +1130,15 @@ private void writeStandardSync(final List configs, final DSLContex .set(CONNECTION.MANUAL, standardSync.getManual()) .set(CONNECTION.SCHEDULE_TYPE, standardSync.getScheduleType() == null ? null - : Enums.toEnum(standardSync.getScheduleType().value(), io.airbyte.db.instance.configs.jooq.generated.enums.ScheduleType.class) + : Enums.toEnum(standardSync.getScheduleType().value(), + io.airbyte.db.instance.configs.jooq.generated.enums.ScheduleType.class) .orElseThrow()) .set(CONNECTION.SCHEDULE_DATA, JSONB.valueOf(Jsons.serialize(standardSync.getScheduleData()))) - .set(CONNECTION.RESOURCE_REQUIREMENTS, JSONB.valueOf(Jsons.serialize(standardSync.getResourceRequirements()))) + .set(CONNECTION.RESOURCE_REQUIREMENTS, + JSONB.valueOf(Jsons.serialize(standardSync.getResourceRequirements()))) .set(CONNECTION.SOURCE_CATALOG_ID, standardSync.getSourceCatalogId()) + .set(CONNECTION.GEOGRAPHY, Enums.toEnum(standardSync.getGeography().value(), + io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType.class).orElseThrow()) .set(CONNECTION.CREATED_AT, timestamp) .set(CONNECTION.UPDATED_AT, timestamp) .execute(); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index f0179e982171..44805357c6d1 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -18,6 +18,7 @@ import io.airbyte.config.ActorDefinitionResourceRequirements; import io.airbyte.config.DestinationConnection; import io.airbyte.config.DestinationOAuthParameter; +import io.airbyte.config.Geography; import io.airbyte.config.JobSyncConfig.NamespaceDefinitionType; import io.airbyte.config.Notification; import io.airbyte.config.ResourceRequirements; @@ -60,16 +61,20 @@ public static StandardSync buildStandardSync(final Record record, final List standardWorkspaces() { .withTombstone(false) .withNotifications(Collections.singletonList(notification)) .withFirstCompletedSync(true) - .withFeedbackDone(true); + .withFeedbackDone(true) + .withDefaultGeography(Geography.AUTO); final StandardWorkspace workspace2 = new StandardWorkspace() .withWorkspaceId(WORKSPACE_ID_2) .withName("Another Workspace") .withSlug("another-workspace") .withInitialSetupComplete(true) - .withTombstone(false); + .withTombstone(false) + .withDefaultGeography(Geography.AUTO); final StandardWorkspace workspace3 = new StandardWorkspace() .withWorkspaceId(WORKSPACE_ID_3) .withName("Tombstoned") .withSlug("tombstoned") .withInitialSetupComplete(true) - .withTombstone(true); + .withTombstone(true) + .withDefaultGeography(Geography.AUTO); return Arrays.asList(workspace1, workspace2, workspace3); } @@ -448,7 +452,8 @@ public static List standardSyncs() { .withPrefix("") .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) - .withSchedule(schedule); + .withSchedule(schedule) + .withGeography(Geography.AUTO); final StandardSync standardSync2 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) @@ -463,7 +468,8 @@ public static List standardSyncs() { .withPrefix("") .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) - .withSchedule(schedule); + .withSchedule(schedule) + .withGeography(Geography.AUTO); final StandardSync standardSync3 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) @@ -478,7 +484,8 @@ public static List standardSyncs() { .withPrefix("") .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) - .withSchedule(schedule); + .withSchedule(schedule) + .withGeography(Geography.AUTO); final StandardSync standardSync4 = new StandardSync() .withOperationIds(Collections.emptyList()) @@ -493,7 +500,8 @@ public static List standardSyncs() { .withPrefix("") .withResourceRequirements(resourceRequirements) .withStatus(Status.DEPRECATED) - .withSchedule(schedule); + .withSchedule(schedule) + .withGeography(Geography.AUTO); final StandardSync standardSync5 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_3)) @@ -508,7 +516,8 @@ public static List standardSyncs() { .withPrefix("") .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) - .withSchedule(schedule); + .withSchedule(schedule) + .withGeography(Geography.AUTO); final StandardSync standardSync6 = new StandardSync() .withOperationIds(Arrays.asList()) @@ -523,7 +532,8 @@ public static List standardSyncs() { .withPrefix("") .withResourceRequirements(resourceRequirements) .withStatus(Status.DEPRECATED) - .withSchedule(schedule); + .withSchedule(schedule) + .withGeography(Geography.AUTO); return Arrays.asList(standardSync1, standardSync2, standardSync3, standardSync4, standardSync5, standardSync6); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index ba39d020aaaa..92236e6bc751 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -90,6 +90,7 @@ import io.airbyte.api.model.generated.WebBackendConnectionReadList; import io.airbyte.api.model.generated.WebBackendConnectionRequestBody; import io.airbyte.api.model.generated.WebBackendConnectionUpdate; +import io.airbyte.api.model.generated.WebBackendGeographiesListResult; import io.airbyte.api.model.generated.WebBackendWorkspaceState; import io.airbyte.api.model.generated.WebBackendWorkspaceStateResult; import io.airbyte.api.model.generated.WorkspaceCreate; @@ -128,6 +129,7 @@ import io.airbyte.server.handlers.SourceHandler; import io.airbyte.server.handlers.StateHandler; import io.airbyte.server.handlers.WebBackendConnectionsHandler; +import io.airbyte.server.handlers.WebBackendGeographiesHandler; import io.airbyte.server.handlers.WorkspacesHandler; import io.airbyte.server.scheduler.EventRunner; import io.airbyte.server.scheduler.SynchronousSchedulerClient; @@ -156,6 +158,7 @@ public class ConfigurationApi implements io.airbyte.api.generated.V1Api { private final StateHandler stateHandler; private final JobHistoryHandler jobHistoryHandler; private final WebBackendConnectionsHandler webBackendConnectionsHandler; + private final WebBackendGeographiesHandler webBackendGeographiesHandler; private final HealthCheckHandler healthCheckHandler; private final LogsHandler logsHandler; private final OpenApiConfigHandler openApiConfigHandler; @@ -236,6 +239,7 @@ public ConfigurationApi(final ConfigRepository configRepository, operationsHandler, eventRunner, configRepository); + webBackendGeographiesHandler = new WebBackendGeographiesHandler(); healthCheckHandler = new HealthCheckHandler(configRepository); logsHandler = new LogsHandler(); openApiConfigHandler = new OpenApiConfigHandler(); @@ -796,6 +800,11 @@ public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final return execute(() -> webBackendConnectionsHandler.webBackendListConnectionsForWorkspace(workspaceIdRequestBody)); } + @Override + public WebBackendGeographiesListResult webBackendListGeographies() { + return execute(webBackendGeographiesHandler::listGeographiesOSS); + } + @Override public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnectionRequestBody webBackendConnectionRequestBody) { return execute(() -> webBackendConnectionsHandler.webBackendGetConnection(webBackendConnectionRequestBody)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java b/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java index 73d9e0f61220..e3f6b3d81282 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java @@ -11,6 +11,7 @@ import io.airbyte.api.model.generated.ConnectionScheduleDataBasicSchedule; import io.airbyte.api.model.generated.ConnectionScheduleDataCron; import io.airbyte.api.model.generated.ConnectionStatus; +import io.airbyte.api.model.generated.Geography; import io.airbyte.api.model.generated.JobType; import io.airbyte.api.model.generated.JobTypeResourceLimit; import io.airbyte.api.model.generated.ResourceRequirements; @@ -91,7 +92,8 @@ public static ConnectionRead internalToConnectionRead(final StandardSync standar .namespaceFormat(standardSync.getNamespaceFormat()) .prefix(standardSync.getPrefix()) .syncCatalog(CatalogConverter.toApi(standardSync.getCatalog())) - .sourceCatalogId(standardSync.getSourceCatalogId()); + .sourceCatalogId(standardSync.getSourceCatalogId()) + .geography(Enums.convertTo(standardSync.getGeography(), Geography.class)); if (standardSync.getResourceRequirements() != null) { connectionRead.resourceRequirements(resourceRequirementsToApi(standardSync.getResourceRequirements())); @@ -127,6 +129,14 @@ public static StandardSync.Status toPersistenceStatus(final ConnectionStatus api return Enums.convertTo(apiStatus, StandardSync.Status.class); } + public static Geography toApiGeography(final io.airbyte.config.Geography geography) { + return Enums.convertTo(geography, Geography.class); + } + + public static io.airbyte.config.Geography toPersistenceGeography(final Geography apiGeography) { + return Enums.convertTo(apiGeography, io.airbyte.config.Geography.class); + } + public static Schedule.TimeUnit toPersistenceTimeUnit(final ConnectionSchedule.TimeUnitEnum apiTimeUnit) { return Enums.convertTo(apiTimeUnit, Schedule.TimeUnit.class); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java index c14b8874c69e..b53c40949870 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java @@ -29,6 +29,7 @@ import io.airbyte.config.ActorCatalog; import io.airbyte.config.BasicSchedule; import io.airbyte.config.DestinationConnection; +import io.airbyte.config.Geography; import io.airbyte.config.JobSyncConfig.NamespaceDefinitionType; import io.airbyte.config.Schedule; import io.airbyte.config.ScheduleData; @@ -37,6 +38,7 @@ import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSync.ScheduleType; +import io.airbyte.config.StandardWorkspace; import io.airbyte.config.helpers.ScheduleHelpers; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; @@ -138,7 +140,8 @@ public ConnectionRead createConnection(final ConnectionCreate connectionCreate) .withDestinationId(connectionCreate.getDestinationId()) .withOperationIds(operationIds) .withStatus(ApiPojoConverters.toPersistenceStatus(connectionCreate.getStatus())) - .withSourceCatalogId(connectionCreate.getSourceCatalogId()); + .withSourceCatalogId(connectionCreate.getSourceCatalogId()) + .withGeography(getGeographyFromConnectionCreateOrWorkspace(connectionCreate)); if (connectionCreate.getResourceRequirements() != null) { standardSync.withResourceRequirements(ApiPojoConverters.resourceRequirementsToInternal(connectionCreate.getResourceRequirements())); } @@ -177,6 +180,25 @@ public ConnectionRead createConnection(final ConnectionCreate connectionCreate) return buildConnectionRead(connectionId); } + private Geography getGeographyFromConnectionCreateOrWorkspace(final ConnectionCreate connectionCreate) + throws JsonValidationException, ConfigNotFoundException, IOException { + + if (connectionCreate.getGeography() != null) { + return ApiPojoConverters.toPersistenceGeography(connectionCreate.getGeography()); + } + + // connectionCreate didn't specify a geography, so use the workspace default geography if one exists + final UUID workspaceId = workspaceHelper.getWorkspaceForSourceId(connectionCreate.getSourceId()); + final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + + if (workspace.getDefaultGeography() != null) { + return workspace.getDefaultGeography(); + } + + // if the workspace doesn't have a default geography, default to 'auto' + return Geography.AUTO; + } + private void populateSyncFromLegacySchedule(final StandardSync standardSync, final ConnectionCreate connectionCreate) { if (connectionCreate.getSchedule() != null) { final Schedule schedule = new Schedule() diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index 5a34ab913f1a..6a89d1eaa145 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -247,7 +247,8 @@ private static WebBackendConnectionRead getWebBackendConnectionRead(final Connec .source(source) .destination(destination) .operations(operations.getOperations()) - .resourceRequirements(connectionRead.getResourceRequirements()); + .resourceRequirements(connectionRead.getResourceRequirements()) + .geography(connectionRead.getGeography()); } // todo (cgardens) - This logic is a headache to follow it stems from the internal data model not @@ -568,6 +569,7 @@ protected static ConnectionCreate toConnectionCreate(final WebBackendConnectionC connectionCreate.status(webBackendConnectionCreate.getStatus()); connectionCreate.resourceRequirements(webBackendConnectionCreate.getResourceRequirements()); connectionCreate.sourceCatalogId(webBackendConnectionCreate.getSourceCatalogId()); + connectionCreate.geography(webBackendConnectionCreate.getGeography()); return connectionCreate; } @@ -597,6 +599,7 @@ protected static ConnectionUpdate toConnectionPatch(final WebBackendConnectionUp connectionPatch.status(webBackendConnectionPatch.getStatus()); connectionPatch.resourceRequirements(webBackendConnectionPatch.getResourceRequirements()); connectionPatch.sourceCatalogId(webBackendConnectionPatch.getSourceCatalogId()); + connectionPatch.geography(webBackendConnectionPatch.getGeography()); connectionPatch.operationIds(finalOperationIds); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendGeographiesHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendGeographiesHandler.java new file mode 100644 index 000000000000..d8493a744d35 --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendGeographiesHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.handlers; + +import io.airbyte.api.model.generated.Geography; +import io.airbyte.api.model.generated.WebBackendGeographiesListResult; +import java.util.Arrays; +import java.util.Collections; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@AllArgsConstructor +@Slf4j +public class WebBackendGeographiesHandler { + + public WebBackendGeographiesListResult listGeographiesOSS() { + // for now, OSS only supports AUTO. This can evolve to account for complex OSS use cases, but for + // now we expect OSS deployments to use a single default Task Queue for scheduling syncs in a vast + // majority of cases. + return new WebBackendGeographiesListResult().geographies( + Collections.singletonList(Geography.AUTO)); + } + + /** + * Only called by the wrapped Cloud API to enable multi-cloud + */ + public WebBackendGeographiesListResult listGeographiesCloud() { + return new WebBackendGeographiesListResult().geographies(Arrays.asList(Geography.values())); + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java index 6a9a5fe09493..47605c5200e5 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java @@ -10,6 +10,7 @@ import io.airbyte.analytics.TrackingClientSingleton; import io.airbyte.api.model.generated.ConnectionRead; import io.airbyte.api.model.generated.DestinationRead; +import io.airbyte.api.model.generated.Geography; import io.airbyte.api.model.generated.Notification; import io.airbyte.api.model.generated.NotificationRead; import io.airbyte.api.model.generated.NotificationRead.StatusEnum; @@ -22,6 +23,7 @@ import io.airbyte.api.model.generated.WorkspaceReadList; import io.airbyte.api.model.generated.WorkspaceUpdate; import io.airbyte.api.model.generated.WorkspaceUpdateName; +import io.airbyte.commons.enums.Enums; import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; @@ -79,6 +81,11 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate) final Boolean securityUpdates = workspaceCreate.getSecurityUpdates(); final Boolean displaySetupWizard = workspaceCreate.getDisplaySetupWizard(); + // if not set on the workspaceCreate, set the defaultGeography to AUTO + final io.airbyte.config.Geography defaultGeography = workspaceCreate.getDefaultGeography() != null + ? Enums.convertTo(workspaceCreate.getDefaultGeography(), io.airbyte.config.Geography.class) + : io.airbyte.config.Geography.AUTO; + final StandardWorkspace workspace = new StandardWorkspace() .withWorkspaceId(uuidSupplier.get()) .withCustomerId(uuidSupplier.get()) @@ -90,7 +97,8 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate) .withSecurityUpdates(securityUpdates != null ? securityUpdates : false) .withDisplaySetupWizard(displaySetupWizard != null ? displaySetupWizard : false) .withTombstone(false) - .withNotifications(NotificationConverter.toConfigList(workspaceCreate.getNotifications())); + .withNotifications(NotificationConverter.toConfigList(workspaceCreate.getNotifications())) + .withDefaultGeography(defaultGeography); if (!Strings.isNullOrEmpty(email)) { workspace.withEmail(email); @@ -249,7 +257,8 @@ private static WorkspaceRead buildWorkspaceRead(final StandardWorkspace workspac .anonymousDataCollection(workspace.getAnonymousDataCollection()) .news(workspace.getNews()) .securityUpdates(workspace.getSecurityUpdates()) - .notifications(NotificationConverter.toApiList(workspace.getNotifications())); + .notifications(NotificationConverter.toApiList(workspace.getNotifications())) + .defaultGeography(Enums.convertTo(workspace.getDefaultGeography(), Geography.class)); } private void validateWorkspacePatch(final StandardWorkspace persistedWorkspace, final WorkspaceUpdate workspacePatch) { @@ -278,6 +287,10 @@ private void applyPatchToStandardWorkspace(final StandardWorkspace workspace, fi if (workspacePatch.getNotifications() != null) { workspace.setNotifications(NotificationConverter.toConfigList(workspacePatch.getNotifications())); } + if (workspacePatch.getDefaultGeography() != null) { + workspace.setDefaultGeography( + Enums.convertTo(workspacePatch.getDefaultGeography(), io.airbyte.config.Geography.class)); + } } } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java index 163132502cda..c98f628c12ef 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java @@ -43,6 +43,7 @@ public ConnectionRead match(final ConnectionRead query) { fromSearch.syncCatalog(query.getSyncCatalog()); fromSearch.operationIds(query.getOperationIds()); fromSearch.sourceCatalogId(query.getSourceCatalogId()); + fromSearch.geography(query.getGeography()); return fromSearch; } diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java index 02d6fabbe53d..5bd3d2648caf 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java @@ -45,6 +45,7 @@ import io.airbyte.config.Cron; import io.airbyte.config.DataType; import io.airbyte.config.DestinationConnection; +import io.airbyte.config.Geography; import io.airbyte.config.JobSyncConfig; import io.airbyte.config.Schedule; import io.airbyte.config.Schedule.TimeUnit; @@ -55,6 +56,7 @@ import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSync.ScheduleType; import io.airbyte.config.StandardSync.Status; +import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.persistence.job.WorkspaceHelper; @@ -147,7 +149,8 @@ void setUp() throws IOException, JsonValidationException, ConfigNotFoundExceptio .withScheduleType(ScheduleType.BASIC_SCHEDULE) .withScheduleData(ConnectionHelpers.generateBasicScheduleData()) .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS) - .withSourceCatalogId(UUID.randomUUID()); + .withSourceCatalogId(UUID.randomUUID()) + .withGeography(Geography.AUTO); standardSyncDeleted = new StandardSync() .withConnectionId(connectionId) .withName("presto to hudi2") @@ -161,7 +164,8 @@ void setUp() throws IOException, JsonValidationException, ConfigNotFoundExceptio .withOperationIds(List.of(operationId)) .withManual(false) .withSchedule(ConnectionHelpers.generateBasicSchedule()) - .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS); + .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS) + .withGeography(Geography.US); configRepository = mock(ConfigRepository.class); uuidGenerator = mock(Supplier.class); @@ -179,13 +183,30 @@ void setUp() throws IOException, JsonValidationException, ConfigNotFoundExceptio class UnMockedConnectionHelper { @BeforeEach - void setUp() { + void setUp() throws JsonValidationException, ConfigNotFoundException, IOException { connectionsHandler = new ConnectionsHandler( configRepository, uuidGenerator, workspaceHelper, trackingClient, eventRunner); + + when(uuidGenerator.get()).thenReturn(standardSync.getConnectionId()); + final StandardSourceDefinition sourceDefinition = new StandardSourceDefinition() + .withName(SOURCE_TEST) + .withSourceDefinitionId(UUID.randomUUID()); + final StandardDestinationDefinition destinationDefinition = new StandardDestinationDefinition() + .withName(DESTINATION_TEST) + .withDestinationDefinitionId(UUID.randomUUID()); + when(configRepository.getStandardSync(standardSync.getConnectionId())).thenReturn(standardSync); + when(configRepository.getSourceDefinitionFromConnection(standardSync.getConnectionId())).thenReturn( + sourceDefinition); + when(configRepository.getDestinationDefinitionFromConnection(standardSync.getConnectionId())).thenReturn( + destinationDefinition); + when(configRepository.getSourceConnection(source.getSourceId())) + .thenReturn(source); + when(configRepository.getDestinationConnection(destination.getDestinationId())) + .thenReturn(destination); } @Nested @@ -193,23 +214,17 @@ class CreateConnection { @Test void testCreateConnection() throws JsonValidationException, ConfigNotFoundException, IOException { - when(uuidGenerator.get()).thenReturn(standardSync.getConnectionId()); - final StandardSourceDefinition sourceDefinition = new StandardSourceDefinition() - .withName(SOURCE_TEST) - .withSourceDefinitionId(UUID.randomUUID()); - final StandardDestinationDefinition destinationDefinition = new StandardDestinationDefinition() - .withName(DESTINATION_TEST) - .withDestinationDefinitionId(UUID.randomUUID()); - when(configRepository.getStandardSync(standardSync.getConnectionId())).thenReturn(standardSync); - when(configRepository.getSourceDefinitionFromConnection(standardSync.getConnectionId())).thenReturn(sourceDefinition); - when(configRepository.getDestinationDefinitionFromConnection(standardSync.getConnectionId())).thenReturn(destinationDefinition); - when(configRepository.getSourceConnection(source.getSourceId())) - .thenReturn(source); - when(configRepository.getDestinationConnection(destination.getDestinationId())) - .thenReturn(destination); final AirbyteCatalog catalog = ConnectionHelpers.generateBasicApiCatalog(); + // set a defaultGeography on the workspace as EU, but expect connection to be + // created AUTO because the ConnectionCreate geography takes precedence over the workspace + // defaultGeography. + final StandardWorkspace workspace = new StandardWorkspace() + .withWorkspaceId(workspaceId) + .withDefaultGeography(Geography.EU); + when(configRepository.getStandardWorkspace(workspaceId, true)).thenReturn(workspace); + final ConnectionCreate connectionCreate = new ConnectionCreate() .sourceId(standardSync.getSourceId()) .destinationId(standardSync.getDestinationId()) @@ -226,7 +241,8 @@ void testCreateConnection() throws JsonValidationException, ConfigNotFoundExcept .cpuLimit(standardSync.getResourceRequirements().getCpuLimit()) .memoryRequest(standardSync.getResourceRequirements().getMemoryRequest()) .memoryLimit(standardSync.getResourceRequirements().getMemoryLimit())) - .sourceCatalogId(standardSync.getSourceCatalogId()); + .sourceCatalogId(standardSync.getSourceCatalogId()) + .geography(ApiPojoConverters.toApiGeography(standardSync.getGeography())); final ConnectionRead actualConnectionRead = connectionsHandler.createConnection(connectionCreate); @@ -245,13 +261,52 @@ void testCreateConnection() throws JsonValidationException, ConfigNotFoundExcept } @Test - void testValidateConnectionCreateSourceAndDestinationInDifferenceWorkspace() - throws JsonValidationException, ConfigNotFoundException, IOException { + void testCreateConnectionUsesDefaultGeographyFromWorkspace() throws JsonValidationException, ConfigNotFoundException, IOException { + + when(workspaceHelper.getWorkspaceForSourceId(sourceId)).thenReturn(workspaceId); + + final AirbyteCatalog catalog = ConnectionHelpers.generateBasicApiCatalog(); + + // don't set a geography on the ConnectionCreate to force inheritance from workspace default + final ConnectionCreate connectionCreate = new ConnectionCreate() + .sourceId(standardSync.getSourceId()) + .destinationId(standardSync.getDestinationId()) + .operationIds(standardSync.getOperationIds()) + .name(PRESTO_TO_HUDI) + .namespaceDefinition(NamespaceDefinitionType.SOURCE) + .namespaceFormat(null) + .prefix(PRESTO_TO_HUDI_PREFIX) + .status(ConnectionStatus.ACTIVE) + .schedule(ConnectionHelpers.generateBasicConnectionSchedule()) + .syncCatalog(catalog) + .resourceRequirements(new io.airbyte.api.model.generated.ResourceRequirements() + .cpuRequest(standardSync.getResourceRequirements().getCpuRequest()) + .cpuLimit(standardSync.getResourceRequirements().getCpuLimit()) + .memoryRequest(standardSync.getResourceRequirements().getMemoryRequest()) + .memoryLimit(standardSync.getResourceRequirements().getMemoryLimit())) + .sourceCatalogId(standardSync.getSourceCatalogId()); + + // set the workspace default to EU + final StandardWorkspace workspace = new StandardWorkspace() + .withWorkspaceId(workspaceId) + .withDefaultGeography(Geography.EU); + when(configRepository.getStandardWorkspace(workspaceId, true)).thenReturn(workspace); + + // the expected read and verified write is generated from the standardSync, so set this to EU as + // well + standardSync.setGeography(Geography.EU); + + final ConnectionRead expectedConnectionRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync); + final ConnectionRead actualConnectionRead = connectionsHandler.createConnection(connectionCreate); + + assertEquals(expectedConnectionRead, actualConnectionRead); + verify(configRepository).writeStandardSync(standardSync); + } + + @Test + void testValidateConnectionCreateSourceAndDestinationInDifferenceWorkspace() { + when(workspaceHelper.getWorkspaceForDestinationIdIgnoreExceptions(destinationId)).thenReturn(UUID.randomUUID()); - when(configRepository.getSourceConnection(source.getSourceId())) - .thenReturn(source); - when(configRepository.getDestinationConnection(destination.getDestinationId())) - .thenReturn(destination); final ConnectionCreate connectionCreate = new ConnectionCreate() .sourceId(standardSync.getSourceId()) @@ -261,12 +316,9 @@ void testValidateConnectionCreateSourceAndDestinationInDifferenceWorkspace() } @Test - void testValidateConnectionCreateOperationInDifferentWorkspace() throws JsonValidationException, ConfigNotFoundException, IOException { + void testValidateConnectionCreateOperationInDifferentWorkspace() { + when(workspaceHelper.getWorkspaceForOperationIdIgnoreExceptions(operationId)).thenReturn(UUID.randomUUID()); - when(configRepository.getSourceConnection(source.getSourceId())) - .thenReturn(source); - when(configRepository.getDestinationConnection(destination.getDestinationId())) - .thenReturn(destination); final ConnectionCreate connectionCreate = new ConnectionCreate() .sourceId(standardSync.getSourceId()) @@ -278,20 +330,10 @@ void testValidateConnectionCreateOperationInDifferentWorkspace() throws JsonVali @Test void testCreateConnectionWithBadDefinitionIds() throws JsonValidationException, ConfigNotFoundException, IOException { - when(uuidGenerator.get()).thenReturn(standardSync.getConnectionId()); + final UUID sourceIdBad = UUID.randomUUID(); final UUID destinationIdBad = UUID.randomUUID(); - final StandardSourceDefinition sourceDefinition = new StandardSourceDefinition() - .withName(SOURCE_TEST) - .withSourceDefinitionId(UUID.randomUUID()); - final StandardDestinationDefinition destinationDefinition = new StandardDestinationDefinition() - .withName(DESTINATION_TEST) - .withDestinationDefinitionId(UUID.randomUUID()); - when(configRepository.getStandardSync(standardSync.getConnectionId())).thenReturn(standardSync); - when(configRepository.getSourceDefinitionFromConnection(standardSync.getConnectionId())).thenReturn(sourceDefinition); - when(configRepository.getDestinationDefinitionFromConnection(standardSync.getConnectionId())).thenReturn(destinationDefinition); - when(configRepository.getSourceConnection(sourceIdBad)) .thenThrow(new ConfigNotFoundException(ConfigSchema.SOURCE_CONNECTION, sourceIdBad)); when(configRepository.getDestinationConnection(destinationIdBad)) @@ -345,7 +387,8 @@ void testUpdateConnectionPatchSingleField() throws Exception { standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()) + standardSync.getSourceCatalogId(), + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .name("newName"); final StandardSync expectedPersistedSync = Jsons.clone(standardSync).withName("newName"); @@ -369,7 +412,8 @@ void testUpdateConnectionPatchScheduleToManual() throws Exception { standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()) + standardSync.getSourceCatalogId(), + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .schedule(null) .scheduleType(ConnectionScheduleType.MANUAL) .scheduleData(null); @@ -405,7 +449,8 @@ void testUpdateConnectionPatchScheduleToCron() throws Exception { standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()) + standardSync.getSourceCatalogId(), + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .schedule(null) .scheduleType(ConnectionScheduleType.CRON) .scheduleData(cronScheduleData); @@ -441,7 +486,8 @@ void testUpdateConnectionPatchBasicSchedule() throws Exception { standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()) + standardSync.getSourceCatalogId(), + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .schedule(new ConnectionSchedule().timeUnit(ConnectionSchedule.TimeUnitEnum.DAYS).units(10L)) // still dual-writing to legacy field .scheduleType(ConnectionScheduleType.BASIC) .scheduleData(newScheduleData); @@ -490,7 +536,8 @@ void testUpdateConnectionPatchAddingNewStream() throws Exception { standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()) + standardSync.getSourceCatalogId(), + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .syncCatalog(catalogForUpdate); final StandardSync expectedPersistedSync = Jsons.clone(standardSync) @@ -532,7 +579,8 @@ void testUpdateConnectionPatchEditExistingStreamWhileAddingNewStream() throws Ex standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()) + standardSync.getSourceCatalogId(), + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .syncCatalog(catalogForUpdate); final StandardSync expectedPersistedSync = Jsons.clone(standardSync) @@ -602,7 +650,8 @@ void testUpdateConnectionPatchingSeveralFieldsAndReplaceAStream() throws JsonVal standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - newSourceCatalogId) + newSourceCatalogId, + ApiPojoConverters.toApiGeography(standardSync.getGeography())) .status(ConnectionStatus.INACTIVE) .scheduleType(ConnectionScheduleType.MANUAL) .scheduleData(null) @@ -695,7 +744,8 @@ void testSearchConnections() throws JsonValidationException, ConfigNotFoundExcep .withDestinationId(destinationId) .withOperationIds(List.of(operationId)) .withManual(true) - .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS); + .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS) + .withGeography(Geography.US); final ConnectionRead connectionRead2 = ConnectionHelpers.connectionReadFromStandardSync(standardSync2); final StandardSourceDefinition sourceDefinition = new StandardSourceDefinition() .withName(SOURCE_TEST) diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index 49b4f2d09c47..2c02bb38d679 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -36,6 +36,7 @@ import io.airbyte.api.model.generated.DestinationIdRequestBody; import io.airbyte.api.model.generated.DestinationRead; import io.airbyte.api.model.generated.DestinationSyncMode; +import io.airbyte.api.model.generated.Geography; import io.airbyte.api.model.generated.JobConfigType; import io.airbyte.api.model.generated.JobInfoRead; import io.airbyte.api.model.generated.JobRead; @@ -403,7 +404,8 @@ void testToConnectionCreate() throws IOException { .status(ConnectionStatus.INACTIVE) .schedule(schedule) .syncCatalog(catalog) - .sourceCatalogId(sourceCatalogId); + .sourceCatalogId(sourceCatalogId) + .geography(Geography.US); final List operationIds = List.of(newOperationId); @@ -418,7 +420,8 @@ void testToConnectionCreate() throws IOException { .status(ConnectionStatus.INACTIVE) .schedule(schedule) .syncCatalog(catalog) - .sourceCatalogId(sourceCatalogId); + .sourceCatalogId(sourceCatalogId) + .geography(Geography.US); final ConnectionCreate actual = WebBackendConnectionsHandler.toConnectionCreate(input, operationIds); @@ -426,7 +429,7 @@ void testToConnectionCreate() throws IOException { } @Test - void testToConnectionUpdate() throws IOException { + void testToConnectionPatch() throws IOException { final SourceConnection source = SourceHelpers.generateSource(UUID.randomUUID()); final StandardSync standardSync = ConnectionHelpers.generateSyncWithSourceId(source.getSourceId()); @@ -445,7 +448,8 @@ void testToConnectionUpdate() throws IOException { .status(ConnectionStatus.INACTIVE) .schedule(schedule) .name(standardSync.getName()) - .syncCatalog(catalog); + .syncCatalog(catalog) + .geography(Geography.US); final List operationIds = List.of(newOperationId); @@ -458,7 +462,8 @@ void testToConnectionUpdate() throws IOException { .status(ConnectionStatus.INACTIVE) .schedule(schedule) .name(standardSync.getName()) - .syncCatalog(catalog); + .syncCatalog(catalog) + .geography(Geography.US); final ConnectionUpdate actual = WebBackendConnectionsHandler.toConnectionPatch(input, operationIds); @@ -468,8 +473,9 @@ void testToConnectionUpdate() throws IOException { @Test void testForConnectionCreateCompleteness() { final Set handledMethods = - Set.of("name", "namespaceDefinition", "namespaceFormat", "prefix", "sourceId", "destinationId", "operationIds", "addOperationIdsItem", - "removeOperationIdsItem", "syncCatalog", "schedule", "scheduleType", "scheduleData", "status", "resourceRequirements", "sourceCatalogId"); + Set.of("name", "namespaceDefinition", "namespaceFormat", "prefix", "sourceId", "destinationId", "operationIds", + "addOperationIdsItem", "removeOperationIdsItem", "syncCatalog", "schedule", "scheduleType", "scheduleData", + "status", "resourceRequirements", "sourceCatalogId", "geography"); final Set methods = Arrays.stream(ConnectionCreate.class.getMethods()) .filter(method -> method.getReturnType() == ConnectionCreate.class) @@ -487,10 +493,11 @@ void testForConnectionCreateCompleteness() { } @Test - void testForConnectionUpdateCompleteness() { + void testForConnectionPatchCompleteness() { final Set handledMethods = - Set.of("schedule", "connectionId", "syncCatalog", "namespaceDefinition", "namespaceFormat", "prefix", "status", "operationIds", - "addOperationIdsItem", "removeOperationIdsItem", "resourceRequirements", "name", "sourceCatalogId", "scheduleType", "scheduleData"); + Set.of("schedule", "connectionId", "syncCatalog", "namespaceDefinition", "namespaceFormat", "prefix", "status", + "operationIds", "addOperationIdsItem", "removeOperationIdsItem", "resourceRequirements", "name", + "sourceCatalogId", "scheduleType", "scheduleData", "geography"); final Set methods = Arrays.stream(ConnectionUpdate.class.getMethods()) .filter(method -> method.getReturnType() == ConnectionUpdate.class) @@ -501,8 +508,8 @@ void testForConnectionUpdateCompleteness() { """ If this test is failing, it means you added a field to ConnectionUpdate! Congratulations, but you're not done yet.. - \tYou should update WebBackendConnectionsHandler::toConnectionUpdate - \tand ensure that the field is tested in WebBackendConnectionsHandlerTest::testToConnectionUpdate + \tYou should update WebBackendConnectionsHandler::toConnectionPatch + \tand ensure that the field is tested in WebBackendConnectionsHandlerTest::testToConnectionPatch Then you can add the field name here to make this test pass. Cheers!"""; assertEquals(handledMethods, methods, message); } diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendGeographiesHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendGeographiesHandlerTest.java new file mode 100644 index 000000000000..e4f014aa3c1d --- /dev/null +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendGeographiesHandlerTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.handlers; + +import io.airbyte.api.model.generated.Geography; +import io.airbyte.api.model.generated.WebBackendGeographiesListResult; +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class WebBackendGeographiesHandlerTest { + + private WebBackendGeographiesHandler webBackendGeographiesHandler; + + @BeforeEach + void setUp() { + webBackendGeographiesHandler = new WebBackendGeographiesHandler(); + } + + @Test + void testListGeographiesOSS() { + final WebBackendGeographiesListResult expected = new WebBackendGeographiesListResult().geographies( + List.of(Geography.AUTO)); + + final WebBackendGeographiesListResult actual = webBackendGeographiesHandler.listGeographiesOSS(); + + Assertions.assertEquals(expected, actual); + } + + @Test + void testListGeographiesCloud() { + final WebBackendGeographiesListResult expected = new WebBackendGeographiesListResult().geographies( + List.of(Geography.AUTO, Geography.US, Geography.EU)); + + final WebBackendGeographiesListResult actual = webBackendGeographiesHandler.listGeographiesCloud(); + + Assertions.assertEquals(expected, actual); + } + +} diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java index 6f6c11d0a7dc..31879030da0d 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java @@ -30,6 +30,7 @@ import io.airbyte.api.model.generated.WorkspaceUpdate; import io.airbyte.api.model.generated.WorkspaceUpdateName; import io.airbyte.commons.json.Jsons; +import io.airbyte.config.Geography; import io.airbyte.config.Notification; import io.airbyte.config.Notification.NotificationType; import io.airbyte.config.SlackNotificationConfiguration; @@ -64,6 +65,11 @@ class WorkspacesHandlerTest { private static final String TEST_WORKSPACE_NAME = "test workspace"; private static final String TEST_WORKSPACE_SLUG = "test-workspace"; + private static final io.airbyte.api.model.generated.Geography GEOGRAPHY_AUTO = + io.airbyte.api.model.generated.Geography.AUTO; + private static final io.airbyte.api.model.generated.Geography GEOGRAPHY_US = + io.airbyte.api.model.generated.Geography.US; + @SuppressWarnings("unchecked") @BeforeEach void setUp() { @@ -89,7 +95,8 @@ private StandardWorkspace generateWorkspace() { .withAnonymousDataCollection(false) .withSecurityUpdates(false) .withTombstone(false) - .withNotifications(List.of(generateNotification())); + .withNotifications(List.of(generateNotification())) + .withDefaultGeography(Geography.AUTO); } private Notification generateNotification() { @@ -121,7 +128,8 @@ void testCreateWorkspace() throws JsonValidationException, IOException { .news(false) .anonymousDataCollection(false) .securityUpdates(false) - .notifications(List.of(generateApiNotification())); + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_US); final WorkspaceRead actualRead = workspacesHandler.createWorkspace(workspaceCreate); final WorkspaceRead expectedRead = new WorkspaceRead() @@ -135,7 +143,8 @@ void testCreateWorkspace() throws JsonValidationException, IOException { .news(false) .anonymousDataCollection(false) .securityUpdates(false) - .notifications(List.of(generateApiNotification())); + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_US); assertEquals(expectedRead, actualRead); } @@ -172,7 +181,8 @@ void testCreateWorkspaceDuplicateSlug() throws JsonValidationException, IOExcept .news(false) .anonymousDataCollection(false) .securityUpdates(false) - .notifications(Collections.emptyList()); + .notifications(Collections.emptyList()) + .defaultGeography(GEOGRAPHY_AUTO); assertTrue(actualRead.getSlug().startsWith(workspace.getSlug())); assertNotEquals(workspace.getSlug(), actualRead.getSlug()); @@ -231,7 +241,8 @@ void testListWorkspaces() throws JsonValidationException, IOException { .news(workspace.getNews()) .anonymousDataCollection(workspace.getAnonymousDataCollection()) .securityUpdates(workspace.getSecurityUpdates()) - .notifications(List.of(generateApiNotification())); + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_AUTO); final WorkspaceRead expectedWorkspaceRead2 = new WorkspaceRead() .workspaceId(workspace2.getWorkspaceId()) @@ -244,7 +255,8 @@ void testListWorkspaces() throws JsonValidationException, IOException { .news(workspace2.getNews()) .anonymousDataCollection(workspace2.getAnonymousDataCollection()) .securityUpdates(workspace2.getSecurityUpdates()) - .notifications(List.of(generateApiNotification())); + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_AUTO); final WorkspaceReadList actualWorkspaceReadList = workspacesHandler.listWorkspaces(); @@ -269,7 +281,8 @@ void testGetWorkspace() throws JsonValidationException, ConfigNotFoundException, .news(false) .anonymousDataCollection(false) .securityUpdates(false) - .notifications(List.of(generateApiNotification())); + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_AUTO); assertEquals(workspaceRead, workspacesHandler.getWorkspace(workspaceIdRequestBody)); } @@ -290,7 +303,8 @@ void testGetWorkspaceBySlug() throws JsonValidationException, ConfigNotFoundExce .news(workspace.getNews()) .anonymousDataCollection(workspace.getAnonymousDataCollection()) .securityUpdates(workspace.getSecurityUpdates()) - .notifications(NotificationConverter.toApiList(workspace.getNotifications())); + .notifications(NotificationConverter.toApiList(workspace.getNotifications())) + .defaultGeography(GEOGRAPHY_AUTO); assertEquals(workspaceRead, workspacesHandler.getWorkspaceBySlug(slugRequestBody)); } @@ -306,7 +320,8 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .news(false) .initialSetupComplete(true) .displaySetupWizard(false) - .notifications(List.of(apiNotification)); + .notifications(List.of(apiNotification)) + .defaultGeography(GEOGRAPHY_US); final Notification expectedNotification = generateNotification(); expectedNotification.getSlackConfiguration().withWebhook("updated"); @@ -322,7 +337,8 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .withInitialSetupComplete(true) .withDisplaySetupWizard(false) .withTombstone(false) - .withNotifications(List.of(expectedNotification)); + .withNotifications(List.of(expectedNotification)) + .withDefaultGeography(Geography.US); when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)) .thenReturn(workspace) @@ -343,7 +359,8 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .news(false) .anonymousDataCollection(true) .securityUpdates(false) - .notifications(List.of(expectedNotificationRead)); + .notifications(List.of(expectedNotificationRead)) + .defaultGeography(GEOGRAPHY_US); verify(configRepository).writeStandardWorkspace(expectedWorkspace); @@ -369,7 +386,8 @@ void testUpdateWorkspaceNoNameUpdate() throws JsonValidationException, ConfigNot .withInitialSetupComplete(workspace.getInitialSetupComplete()) .withDisplaySetupWizard(workspace.getDisplaySetupWizard()) .withTombstone(false) - .withNotifications(workspace.getNotifications()); + .withNotifications(workspace.getNotifications()) + .withDefaultGeography(Geography.AUTO); when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)) .thenReturn(workspace) @@ -388,7 +406,8 @@ void testUpdateWorkspaceNoNameUpdate() throws JsonValidationException, ConfigNot .news(workspace.getNews()) .anonymousDataCollection(workspace.getAnonymousDataCollection()) .securityUpdates(workspace.getSecurityUpdates()) - .notifications(List.of(generateApiNotification())); + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_AUTO); verify(configRepository).writeStandardWorkspace(expectedWorkspace); @@ -420,7 +439,8 @@ void testWorkspacePatchUpdate() throws JsonValidationException, ConfigNotFoundEx .news(workspace.getNews()) .anonymousDataCollection(true) .securityUpdates(workspace.getSecurityUpdates()) - .notifications(NotificationConverter.toApiList(workspace.getNotifications())); + .notifications(NotificationConverter.toApiList(workspace.getNotifications())) + .defaultGeography(GEOGRAPHY_AUTO); final WorkspaceRead actualWorkspaceRead = workspacesHandler.updateWorkspace(workspaceUpdate); verify(configRepository).writeStandardWorkspace(expectedWorkspace); diff --git a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java index c6bac34a1e33..43e0b155e00a 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java +++ b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java @@ -18,11 +18,13 @@ import io.airbyte.api.model.generated.ConnectionScheduleType; import io.airbyte.api.model.generated.ConnectionStatus; import io.airbyte.api.model.generated.DestinationRead; +import io.airbyte.api.model.generated.Geography; import io.airbyte.api.model.generated.JobStatus; import io.airbyte.api.model.generated.ResourceRequirements; import io.airbyte.api.model.generated.SourceRead; import io.airbyte.api.model.generated.SyncMode; import io.airbyte.api.model.generated.WebBackendConnectionListItem; +import io.airbyte.commons.enums.Enums; import io.airbyte.commons.text.Names; import io.airbyte.config.BasicSchedule; import io.airbyte.config.JobSyncConfig.NamespaceDefinitionType; @@ -147,7 +149,8 @@ public static ConnectionRead generateExpectedConnectionRead(final UUID connectio final UUID sourceId, final UUID destinationId, final List operationIds, - final UUID sourceCatalogId) { + final UUID sourceCatalogId, + final Geography geography) { return new ConnectionRead() .connectionId(connectionId) @@ -168,7 +171,8 @@ public static ConnectionRead generateExpectedConnectionRead(final UUID connectio .cpuLimit(TESTING_RESOURCE_REQUIREMENTS.getCpuLimit()) .memoryRequest(TESTING_RESOURCE_REQUIREMENTS.getMemoryRequest()) .memoryLimit(TESTING_RESOURCE_REQUIREMENTS.getMemoryLimit())) - .sourceCatalogId(sourceCatalogId); + .sourceCatalogId(sourceCatalogId) + .geography(geography); } public static ConnectionRead generateExpectedConnectionRead(final StandardSync standardSync) { @@ -177,7 +181,8 @@ public static ConnectionRead generateExpectedConnectionRead(final StandardSync s standardSync.getSourceId(), standardSync.getDestinationId(), standardSync.getOperationIds(), - standardSync.getSourceCatalogId()); + standardSync.getSourceCatalogId(), + Enums.convertTo(standardSync.getGeography(), Geography.class)); if (standardSync.getSchedule() == null) { connectionRead.schedule(null); @@ -200,7 +205,8 @@ public static ConnectionRead connectionReadFromStandardSync(final StandardSync s .name(standardSync.getName()) .namespaceFormat(standardSync.getNamespaceFormat()) .prefix(standardSync.getPrefix()) - .sourceCatalogId(standardSync.getSourceCatalogId()); + .sourceCatalogId(standardSync.getSourceCatalogId()) + .geography(ApiPojoConverters.toApiGeography(standardSync.getGeography())); if (standardSync.getNamespaceDefinition() != null) { connectionRead diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 3dd1afae62a9..6149700e1fe0 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -369,6 +369,7 @@

WebBackend

  • post /v1/web_backend/connections/get
  • post /v1/web_backend/workspace/state
  • post /v1/web_backend/connections/list
  • +
  • post /v1/web_backend/geographies/list
  • post /v1/web_backend/connections/update
  • Workspace

    @@ -8831,6 +8832,49 @@

    422

    InvalidInputExceptionInfo

    +
    +
    + Up +
    post /v1/web_backend/geographies/list
    +
    Returns available geographies can be selected to run data syncs in a particular geography. +The 'auto' entry indicates that the sync will be automatically assigned to a geography according +to the platform default behavior. Entries other than 'auto' are two-letter country codes that +follow the ISO 3166-1 alpha-2 standard. (webBackendListGeographies)
    +
    Returns all available geographies in which a data sync can run.
    + + + + + + + +

    Return type

    + + + + +

    Example data

    +
    Content-Type: application/json
    +
    {
    +  "geographies" : [ null, null ]
    +}
    + +

    Produces

    + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
      +
    • application/json
    • +
    + +

    Responses

    +

    200

    + Successful operation + WebBackendGeographiesListResult +
    +
    @@ -10073,6 +10120,7 @@

    ConnectionRead - status

    resourceRequirements (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    +
    geography (optional)
    @@ -10191,6 +10239,7 @@

    ConnectionUpdate - status (optional)

    resourceRequirements (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    +
    geography (optional)
    +
    +

    Geography - Up

    +
    +
    +
    +

    GlobalState - Up

    @@ -11185,6 +11240,7 @@

    WebBackendConnectionCreate
    resourceRequirements (optional)
    operations (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    +
    geography (optional)

    @@ -11231,6 +11287,7 @@

    WebBackendConnectionRead - <
    resourceRequirements (optional)
    catalogId (optional)
    UUID format: uuid
    catalogDiff (optional)
    +
    geography (optional)

    @@ -11266,6 +11323,14 @@

    WebBackendConnectionUpdate
    skipReset (optional)
    operations (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    +
    geography (optional)
    +

    + +
    notifications (optional)
    displaySetupWizard (optional)
    +
    defaultGeography (optional)
    firstCompletedSync (optional)
    feedbackDone (optional)
    +
    defaultGeography (optional)
    @@ -11359,6 +11426,7 @@

    WorkspaceUpdate - news (optional)

    securityUpdates (optional)
    notifications (optional)
    +
    defaultGeography (optional)
    From 069eb96f3977ee35517d1454942b899d732c58c5 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Mon, 10 Oct 2022 22:52:37 +0200 Subject: [PATCH 018/498] decouple the secrets helper from connectors, to support secrets in other types of configs (#17771) --- .../persistence/SecretsRepositoryWriter.java | 6 +- .../split_secrets/SecretsHelpers.java | 65 +++++++++---------- .../split_secrets/SecretsHelpersTest.java | 10 +-- 3 files changed, 38 insertions(+), 43 deletions(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java index afe718dbba1b..d0d0fa4f5ee9 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java @@ -162,13 +162,13 @@ private JsonNode statefulUpdateSecrets(final UUID workspaceId, workspaceId, oldConfig.get(), fullConfig, - spec, + spec.getConnectionSpecification(), longLivedSecretPersistence.get()); } else { splitSecretConfig = SecretsHelpers.splitConfig( workspaceId, fullConfig, - spec); + spec.getConnectionSpecification()); } splitSecretConfig.getCoordinateToPayload().forEach(longLivedSecretPersistence.get()::write); return splitSecretConfig.getPartialConfig(); @@ -188,7 +188,7 @@ private JsonNode splitSecretConfig(final UUID workspaceId, final ConnectorSpecification spec, final Optional secretPersistence) { if (secretPersistence.isPresent()) { - final SplitSecretConfig splitSecretConfig = SecretsHelpers.splitConfig(workspaceId, fullConfig, spec); + final SplitSecretConfig splitSecretConfig = SecretsHelpers.splitConfig(workspaceId, fullConfig, spec.getConnectionSpecification()); splitSecretConfig.getCoordinateToPayload().forEach(secretPersistence.get()::write); return splitSecretConfig.getPartialConfig(); } else { diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/split_secrets/SecretsHelpers.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/split_secrets/SecretsHelpers.java index 1300bc8d5c8a..c8d975c27df1 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/split_secrets/SecretsHelpers.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/split_secrets/SecretsHelpers.java @@ -14,7 +14,6 @@ import io.airbyte.commons.json.JsonSchemas; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.util.MoreIterators; -import io.airbyte.protocol.models.ConnectorSpecification; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,18 +26,17 @@ * Contains most of the core logic surrounding secret coordinate extraction and insertion. * * These are the three main helpers provided by this class: - * {@link SecretsHelpers#splitConfig(UUID, JsonNode, ConnectorSpecification)} - * {@link SecretsHelpers#splitAndUpdateConfig(UUID, JsonNode, JsonNode, ConnectorSpecification, ReadOnlySecretPersistence)} + * {@link SecretsHelpers#splitConfig(UUID, JsonNode, JsonNode)} + * {@link SecretsHelpers#splitAndUpdateConfig(UUID, JsonNode, JsonNode, JsonNode, ReadOnlySecretPersistence)} * {@link SecretsHelpers#combineConfig(JsonNode, ReadOnlySecretPersistence)} * * Here's an overview on some terminology used in this class: * - * A "full config" represents an entire connector config as specified by an end user. This should - * conform to a connector specification. + * A "full config" represents an entire config as specified by an end user. * - * A "partial config" represents a connector config where any string that was specified as an - * airbyte_secret in the connector specification is replaced by a JSON object {"_secret": "secret - * coordinate"} that can later be used to reconstruct the "full config". + * A "partial config" represents a config where any string that was specified as an airbyte_secret + * in the specification is replaced by a JSON object {"_secret": "secret coordinate"} that can later + * be used to reconstruct the "full config". * * A {@link SecretPersistence} provides the ability to read and write secrets to a backing store * such as Google Secrets Manager. @@ -52,41 +50,39 @@ public class SecretsHelpers { public static final String COORDINATE_FIELD = "_secret"; /** - * Used to separate secrets out of a connector configuration. This will output a partial config that + * Used to separate secrets out of some configuration. This will output a partial config that * includes pointers to secrets instead of actual secret values and a map that can be used to update * a {@link SecretPersistence} at coordinates with values from the full config. * - * @param workspaceId workspace used for this connector config + * @param workspaceId workspace used for this config * @param fullConfig config including secrets - * @param spec connector specification + * @param spec specification for the config * @return a partial config + a map of coordinates to secret payloads */ public static SplitSecretConfig splitConfig(final UUID workspaceId, final JsonNode fullConfig, - final ConnectorSpecification spec) { + final JsonNode spec) { return internalSplitAndUpdateConfig( UUID::randomUUID, workspaceId, (coordinate) -> Optional.empty(), Jsons.emptyObject(), fullConfig, - spec.getConnectionSpecification()); + spec); } /** - * Used to separate secrets out of a connector configuration and output a partial config that - * includes pointers to secrets instead of actual secret values and a map that can be used to update - * a {@link SecretPersistence} at coordinates with values from the full config. If a previous config - * for this connector's configuration is provided, this method attempts to use the same base - * coordinates to refer to the same secret and increment the version of the coordinate used to - * reference a secret. + * Used to separate secrets out of a configuration and output a partial config that includes + * pointers to secrets instead of actual secret values and a map that can be used to update a + * {@link SecretPersistence} at coordinates with values from the full config. If a previous config + * for this configuration is provided, this method attempts to use the same base coordinates to + * refer to the same secret and increment the version of the coordinate used to reference a secret. * - * @param workspaceId workspace used for this connector config - * @param oldPartialConfig previous partial config for this specific connector configuration + * @param workspaceId workspace used for this config + * @param oldPartialConfig previous partial config for this specific configuration * @param newFullConfig new config containing secrets that will be used to update the partial config - * for this connector - * @param spec connector specification that should match both the previous partial config after - * filling in coordinates and the new full config. + * @param spec specification that should match both the previous partial config after filling in + * coordinates and the new full config. * @param secretReader provides a way to determine if a secret is the same or updated at a specific * location in a config * @return a partial config + a map of coordinates to secret payloads @@ -94,7 +90,7 @@ public static SplitSecretConfig splitConfig(final UUID workspaceId, public static SplitSecretConfig splitAndUpdateConfig(final UUID workspaceId, final JsonNode oldPartialConfig, final JsonNode newFullConfig, - final ConnectorSpecification spec, + final JsonNode spec, final ReadOnlySecretPersistence secretReader) { return internalSplitAndUpdateConfig( UUID::randomUUID, @@ -102,7 +98,7 @@ public static SplitSecretConfig splitAndUpdateConfig(final UUID workspaceId, secretReader, oldPartialConfig, newFullConfig, - spec.getConnectionSpecification()); + spec); } /** @@ -148,9 +144,9 @@ public static JsonNode combineConfig(final JsonNode partialConfig, final ReadOnl public static SplitSecretConfig splitConfig(final Supplier uuidSupplier, final UUID workspaceId, final JsonNode fullConfig, - final ConnectorSpecification spec) { + final JsonNode spec) { return internalSplitAndUpdateConfig(uuidSupplier, workspaceId, (coordinate) -> Optional.empty(), Jsons.emptyObject(), fullConfig, - spec.getConnectionSpecification()); + spec); } /** @@ -162,9 +158,9 @@ public static SplitSecretConfig splitAndUpdateConfig(final Supplier uuidSu final UUID workspaceId, final JsonNode oldPartialConfig, final JsonNode newFullConfig, - final ConnectorSpecification spec, + final JsonNode spec, final ReadOnlySecretPersistence secretReader) { - return internalSplitAndUpdateConfig(uuidSupplier, workspaceId, secretReader, oldPartialConfig, newFullConfig, spec.getConnectionSpecification()); + return internalSplitAndUpdateConfig(uuidSupplier, workspaceId, secretReader, oldPartialConfig, newFullConfig, spec); } /** @@ -207,7 +203,7 @@ private static SecretCoordinate getOrCreateCoordinate(final ReadOnlySecretPersis * * For splits that don't have a prior partial config (such as when a connector is created for a * source or destination for the first time), the secret reader and old partial config can be set to - * empty (see {@link SecretsHelpers#splitConfig(UUID, JsonNode, ConnectorSpecification)}). + * empty (see {@link SecretsHelpers#splitConfig(UUID, JsonNode, JsonNode)}). * * IMPORTANT: This is used recursively. In the process, the partial config, full config, and spec * inputs will represent internal json structures, not the entire configs/specs. @@ -218,10 +214,9 @@ private static SecretCoordinate getOrCreateCoordinate(final ReadOnlySecretPersis * stored for * @param secretReader provides a way to determine if a secret is the same or updated at a specific * location in a config - * @param persistedPartialConfig previous partial config for this specific connector configuration + * @param persistedPartialConfig previous partial config for this specific configuration * @param newFullConfig new config containing secrets that will be used to update the partial config - * for this connector - * @param spec connector specification + * @param spec config specification * @return a partial config + a map of coordinates to secret payloads */ @VisibleForTesting @@ -292,7 +287,7 @@ private static SecretCoordinate getCoordinateFromTextNode(final JsonNode node) { * * @param newSecret new value of a secret provides a way to determine if a secret is the same or * updated at a specific location in a config - * @param workspaceId workspace used for this connector config + * @param workspaceId workspace used for this config * @param uuidSupplier provided to allow a test case to produce known UUIDs in order for easy * fixture creation. * @param oldSecretFullCoordinate a nullable full coordinate (base+version) retrieved from the diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/split_secrets/SecretsHelpersTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/split_secrets/SecretsHelpersTest.java index 4658730bd365..ec2784532233 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/split_secrets/SecretsHelpersTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/split_secrets/SecretsHelpersTest.java @@ -88,7 +88,7 @@ void testSplit(final SecretsTestCase testCase) { uuidIterator::next, WORKSPACE_ID, inputConfig, - testCase.getSpec()); + testCase.getSpec().getConnectionSpecification()); assertEquals(testCase.getPartialConfig(), splitConfig.getPartialConfig()); assertEquals(testCase.getFirstSecretMap(), splitConfig.getCoordinateToPayload()); @@ -131,7 +131,7 @@ void testSplitUpdate(final SecretsTestCase testCase) { WORKSPACE_ID, inputPartialConfig, inputUpdateConfig, - testCase.getSpec(), + testCase.getSpec().getConnectionSpecification(), secretPersistence::read); assertEquals(testCase.getUpdatedPartialConfig(), updatedSplit.getPartialConfig()); @@ -179,7 +179,7 @@ void testUpdatingSecretsOneAtATime() { uuidIterator::next, WORKSPACE_ID, testCase.getFullConfig(), - testCase.getSpec()); + testCase.getSpec().getConnectionSpecification()); assertEquals(testCase.getPartialConfig(), splitConfig.getPartialConfig()); assertEquals(testCase.getFirstSecretMap(), splitConfig.getCoordinateToPayload()); @@ -193,7 +193,7 @@ void testUpdatingSecretsOneAtATime() { WORKSPACE_ID, testCase.getPartialConfig(), testCase.getFullConfigUpdate1(), - testCase.getSpec(), + testCase.getSpec().getConnectionSpecification(), secretPersistence::read); assertEquals(testCase.getUpdatedPartialConfigAfterUpdate1(), updatedSplit1.getPartialConfig()); @@ -208,7 +208,7 @@ void testUpdatingSecretsOneAtATime() { WORKSPACE_ID, updatedSplit1.getPartialConfig(), testCase.getFullConfigUpdate2(), - testCase.getSpec(), + testCase.getSpec().getConnectionSpecification(), secretPersistence::read); assertEquals(testCase.getUpdatedPartialConfigAfterUpdate2(), updatedSplit2.getPartialConfig()); From 62500af93b8a0747011fffc27c636bbe817d6349 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Mon, 10 Oct 2022 13:54:09 -0700 Subject: [PATCH 019/498] get module name from sys.modules (#17779) * get module name from sys.modules * bump * fix comment * throw exception * fix unittests * Add missing files * remove debug prints * indent --- airbyte-cdk/python/CHANGELOG.md | 4 ++++ .../sources/declarative/schema/json_schema.py | 16 ++++++++++++---- airbyte-cdk/python/setup.py | 2 +- .../sources/declarative/schema/__init__.py | 6 ++++++ .../declarative/schema/source_test/SourceTest.py | 8 ++++++++ .../declarative/schema/source_test/__init__.py | 3 +++ .../declarative/test_declarative_stream.py | 2 ++ .../declarative/test_yaml_declarative_source.py | 3 ++- 8 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 airbyte-cdk/python/unit_tests/sources/declarative/schema/__init__.py create mode 100644 airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/SourceTest.py create mode 100644 airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/__init__.py diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index e839b8a16578..c9832fb8fc90 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.99 + +- Low-code: Fix default stream schema loader + ## 0.1.98 - Low-code: Expose WaitUntilTimeFromHeader strategy and WaitTimeFromHeader as component type diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/schema/json_schema.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/schema/json_schema.py index 29c3d79da620..a76371757965 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/schema/json_schema.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/schema/json_schema.py @@ -4,10 +4,10 @@ import json import pkgutil +import sys from dataclasses import InitVar, dataclass, field from typing import Any, Mapping, Union -import __main__ from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString from airbyte_cdk.sources.declarative.schema.schema_loader import SchemaLoader from airbyte_cdk.sources.declarative.types import Config @@ -15,8 +15,14 @@ def _default_file_path() -> str: - main_file = __main__.__file__ - module = main_file.split("/")[-2].replace("-", "_") + # schema files are always in "source_/schemas/.json + # the connector's module name can be inferred by looking at the modules loaded and look for the one starting with source_ + source_modules = [ + k for k, v in sys.modules.items() if "source_" in k # example: ['source_exchange_rates', 'source_exchange_rates.source'] + ] + if not source_modules: + raise RuntimeError("Expected at least one module starting with 'source_'") + module = source_modules[0].split(".")[0] return f"./{module}/schemas/{{{{options['name']}}}}.json" @@ -34,9 +40,11 @@ class JsonSchema(SchemaLoader, JsonSchemaMixin): config: Config options: InitVar[Mapping[str, Any]] - file_path: Union[InterpolatedString, str] = field(default=_default_file_path()) + file_path: Union[InterpolatedString, str] = field(default=None) def __post_init__(self, options: Mapping[str, Any]): + if not self.file_path: + self.file_path = _default_file_path() self.file_path = InterpolatedString.create(self.file_path, options=options) def get_json_schema(self) -> Mapping[str, Any]: diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index cb0d13dd4f5a..c13ab4ce12ff 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.98", + version="0.1.99", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/schema/__init__.py b/airbyte-cdk/python/unit_tests/sources/declarative/schema/__init__.py new file mode 100644 index 000000000000..d1b04babc11f --- /dev/null +++ b/airbyte-cdk/python/unit_tests/sources/declarative/schema/__init__.py @@ -0,0 +1,6 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# +from .source_test.SourceTest import SourceTest + +__all__ = ["SourceTest"] diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/SourceTest.py b/airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/SourceTest.py new file mode 100644 index 000000000000..b44a56275753 --- /dev/null +++ b/airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/SourceTest.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +class SourceTest: + def __init__(self): + pass diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/__init__.py b/airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-cdk/python/unit_tests/sources/declarative/schema/source_test/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/test_declarative_stream.py b/airbyte-cdk/python/unit_tests/sources/declarative/test_declarative_stream.py index 1b6b84906271..98f5a4563504 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/test_declarative_stream.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/test_declarative_stream.py @@ -9,6 +9,8 @@ from airbyte_cdk.sources.declarative.declarative_stream import DeclarativeStream from airbyte_cdk.sources.declarative.transformations import RecordTransformation +from .schema.source_test import SourceTest # noqa #pylint: disable=unused-import + def test_declarative_stream(): name = "stream" diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/test_yaml_declarative_source.py b/airbyte-cdk/python/unit_tests/sources/declarative/test_yaml_declarative_source.py index 90aec83472cb..e1195244740d 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/test_yaml_declarative_source.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/test_yaml_declarative_source.py @@ -4,9 +4,10 @@ import json +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource + # import pytest # from airbyte_cdk.sources.declarative.exceptions import InvalidConnectorDefinitionException -from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource # import os # import tempfile From ca2605db95059131c8f327fba53126e4b862966f Mon Sep 17 00:00:00 2001 From: Anne <102554163+alovew@users.noreply.github.com> Date: Mon, 10 Oct 2022 18:04:34 -0700 Subject: [PATCH 020/498] Add breakingChange to catalogDiff (#17588) * Add breaking field to FieldTransform on catalogDiff --- airbyte-api/src/main/openapi/config.yaml | 3 + .../protocol/models/CatalogHelpers.java | 41 ++++++++- .../transform_models/FieldTransform.java | 19 ++-- .../protocol/models/CatalogHelpersTest.java | 88 ++++++++++++++++--- .../resources/breaking_change_schema.json | 60 +++++++++++++ .../src/test/resources/valid_schema.json | 1 + .../src/test/resources/valid_schema2.json | 1 + .../converters/CatalogDiffConverters.java | 1 + .../server/handlers/ConnectionsHandler.java | 4 +- .../WebBackendConnectionsHandler.java | 6 +- .../WebBackendConnectionsHandlerTest.java | 23 +++-- .../CatalogDiffModal.test.tsx | 7 +- .../CatalogDiffModal/index.stories.tsx | 7 +- .../api/generated-api-html/index.html | 37 +++++--- 14 files changed, 246 insertions(+), 52 deletions(-) create mode 100644 airbyte-protocol/protocol-models/src/test/resources/breaking_change_schema.json diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index 7e90df6f3e70..dd4fd4d4253a 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -4204,6 +4204,7 @@ components: required: - transformType - fieldName + - breaking properties: transformType: type: string @@ -4213,6 +4214,8 @@ components: - update_field_schema fieldName: $ref: "#/components/schemas/FieldName" + breaking: + type: boolean addField: $ref: "#/components/schemas/FieldAdd" removeField: diff --git a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java index 8c689db32012..9a816236ee6d 100644 --- a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java +++ b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java @@ -23,6 +23,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -308,7 +309,9 @@ private static Map streamDescriptorToMap(final * @param newCatalog - new catalog * @return difference between old and new catalogs */ - public static Set getCatalogDiff(final AirbyteCatalog oldCatalog, final AirbyteCatalog newCatalog) { + public static Set getCatalogDiff(final AirbyteCatalog oldCatalog, + final AirbyteCatalog newCatalog, + final ConfiguredAirbyteCatalog configuredCatalog) { final Set streamTransforms = new HashSet<>(); final Map descriptorToStreamOld = streamDescriptorToMap(oldCatalog); @@ -322,8 +325,14 @@ public static Set getCatalogDiff(final AirbyteCatalog oldCatalo .forEach(descriptor -> { final AirbyteStream streamOld = descriptorToStreamOld.get(descriptor); final AirbyteStream streamNew = descriptorToStreamNew.get(descriptor); + + final Optional stream = configuredCatalog.getStreams().stream() + .filter(s -> Objects.equals(s.getStream().getNamespace(), descriptor.getNamespace()) + && s.getStream().getName().equals(descriptor.getName())) + .findFirst(); + if (!streamOld.equals(streamNew)) { - streamTransforms.add(StreamTransform.createUpdateStreamTransform(descriptor, getStreamDiff(streamOld, streamNew))); + streamTransforms.add(StreamTransform.createUpdateStreamTransform(descriptor, getStreamDiff(streamOld, streamNew, stream))); } }); @@ -331,7 +340,8 @@ public static Set getCatalogDiff(final AirbyteCatalog oldCatalo } private static UpdateStreamTransform getStreamDiff(final AirbyteStream streamOld, - final AirbyteStream streamNew) { + final AirbyteStream streamNew, + final Optional configuredStream) { final Set fieldTransforms = new HashSet<>(); final Map, JsonNode> fieldNameToTypeOld = getFullyQualifiedFieldNamesWithTypes(streamOld.getJsonSchema()) .stream() @@ -347,7 +357,10 @@ private static UpdateStreamTransform getStreamDiff(final AirbyteStream streamOld CatalogHelpers::combineAccumulator); Sets.difference(fieldNameToTypeOld.keySet(), fieldNameToTypeNew.keySet()) - .forEach(fieldName -> fieldTransforms.add(FieldTransform.createRemoveFieldTransform(fieldName, fieldNameToTypeOld.get(fieldName)))); + .forEach(fieldName -> { + fieldTransforms.add(FieldTransform.createRemoveFieldTransform(fieldName, fieldNameToTypeOld.get(fieldName), + transformBreaksConnection(configuredStream, fieldName))); + }); Sets.difference(fieldNameToTypeNew.keySet(), fieldNameToTypeOld.keySet()) .forEach(fieldName -> fieldTransforms.add(FieldTransform.createAddFieldTransform(fieldName, fieldNameToTypeNew.get(fieldName)))); Sets.intersection(fieldNameToTypeOld.keySet(), fieldNameToTypeNew.keySet()).forEach(fieldName -> { @@ -358,6 +371,7 @@ private static UpdateStreamTransform getStreamDiff(final AirbyteStream streamOld fieldTransforms.add(FieldTransform.createUpdateFieldTransform(fieldName, new UpdateFieldSchemaTransform(oldType, newType))); } }); + return new UpdateStreamTransform(fieldTransforms); } @@ -384,4 +398,23 @@ static void combineAccumulator(final Map, JsonNode> accumulatorLeft }); } + static boolean transformBreaksConnection(final Optional configuredStream, final List fieldName) { + if (configuredStream.isEmpty()) { + return false; + } + + final ConfiguredAirbyteStream streamConfig = configuredStream.get(); + + final SyncMode syncMode = streamConfig.getSyncMode(); + if (SyncMode.INCREMENTAL == syncMode && streamConfig.getCursorField().equals(fieldName)) { + return true; + } + + final DestinationSyncMode destinationSyncMode = streamConfig.getDestinationSyncMode(); + if (DestinationSyncMode.APPEND_DEDUP == destinationSyncMode && streamConfig.getPrimaryKey().contains(fieldName)) { + return true; + } + return false; + } + } diff --git a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/transform_models/FieldTransform.java b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/transform_models/FieldTransform.java index c9481b93ae26..e3bc7d30aede 100644 --- a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/transform_models/FieldTransform.java +++ b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/transform_models/FieldTransform.java @@ -23,25 +23,28 @@ public final class FieldTransform { private final AddFieldTransform addFieldTransform; private final RemoveFieldTransform removeFieldTransform; private final UpdateFieldSchemaTransform updateFieldTransform; + private final boolean breaking; public static FieldTransform createAddFieldTransform(final List fieldName, final JsonNode schema) { return createAddFieldTransform(fieldName, new AddFieldTransform(schema)); } public static FieldTransform createAddFieldTransform(final List fieldName, final AddFieldTransform addFieldTransform) { - return new FieldTransform(FieldTransformType.ADD_FIELD, fieldName, addFieldTransform, null, null); + return new FieldTransform(FieldTransformType.ADD_FIELD, fieldName, addFieldTransform, null, null, false); } - public static FieldTransform createRemoveFieldTransform(final List fieldName, final JsonNode schema) { - return createRemoveFieldTransform(fieldName, new RemoveFieldTransform(fieldName, schema)); + public static FieldTransform createRemoveFieldTransform(final List fieldName, final JsonNode schema, final Boolean breaking) { + return createRemoveFieldTransform(fieldName, new RemoveFieldTransform(fieldName, schema), breaking); } - public static FieldTransform createRemoveFieldTransform(final List fieldName, final RemoveFieldTransform removeFieldTransform) { - return new FieldTransform(FieldTransformType.REMOVE_FIELD, fieldName, null, removeFieldTransform, null); + public static FieldTransform createRemoveFieldTransform(final List fieldName, + final RemoveFieldTransform removeFieldTransform, + final Boolean breaking) { + return new FieldTransform(FieldTransformType.REMOVE_FIELD, fieldName, null, removeFieldTransform, null, breaking); } public static FieldTransform createUpdateFieldTransform(final List fieldName, final UpdateFieldSchemaTransform updateFieldTransform) { - return new FieldTransform(FieldTransformType.UPDATE_FIELD_SCHEMA, fieldName, null, null, updateFieldTransform); + return new FieldTransform(FieldTransformType.UPDATE_FIELD_SCHEMA, fieldName, null, null, updateFieldTransform, false); } public FieldTransformType getTransformType() { @@ -64,4 +67,8 @@ public UpdateFieldSchemaTransform getUpdateFieldTransform() { return updateFieldTransform; } + public boolean breaking() { + return breaking; + } + } diff --git a/airbyte-protocol/protocol-models/src/test/java/io/airbyte/protocol/models/CatalogHelpersTest.java b/airbyte-protocol/protocol-models/src/test/java/io/airbyte/protocol/models/CatalogHelpersTest.java index 7be314d1128a..65b0c03163a2 100644 --- a/airbyte-protocol/protocol-models/src/test/java/io/airbyte/protocol/models/CatalogHelpersTest.java +++ b/airbyte-protocol/protocol-models/src/test/java/io/airbyte/protocol/models/CatalogHelpersTest.java @@ -38,8 +38,11 @@ class CatalogHelpersTest { private static final String SOME_ARRAY = "someArray"; private static final String PROPERTIES = "properties"; private static final String USERS = "users"; + private static final String DATE = "date"; + private static final String SALES = "sales"; private static final String COMPANIES_VALID = "companies_schema.json"; private static final String COMPANIES_INVALID = "companies_schema_invalid.json"; + private static final String VALID_SCHEMA_JSON = "valid_schema.json"; @Test void testFieldToJsonSchema() { @@ -93,10 +96,10 @@ void testGetTopLevelFieldNames() { @Test void testGetFieldNames() throws IOException { - final JsonNode node = Jsons.deserialize(MoreResources.readResource("valid_schema.json")); + final JsonNode node = Jsons.deserialize(MoreResources.readResource(VALID_SCHEMA_JSON)); final Set actualFieldNames = CatalogHelpers.getAllFieldNames(node); final List expectedFieldNames = - List.of(CAD, "DKK", "HKD", "HUF", "ISK", "PHP", "date", "nestedkey", "somekey", "something", "something2", "文", SOME_ARRAY, ITEMS, + List.of("id", CAD, "DKK", "HKD", "HUF", "ISK", "PHP", DATE, "nestedkey", "somekey", "something", "something2", "文", SOME_ARRAY, ITEMS, "oldName"); // sort so that the diff is easier to read. @@ -105,23 +108,28 @@ void testGetFieldNames() throws IOException { @Test void testGetCatalogDiff() throws IOException { - final JsonNode schema1 = Jsons.deserialize(MoreResources.readResource("valid_schema.json")); + final JsonNode schema1 = Jsons.deserialize(MoreResources.readResource(VALID_SCHEMA_JSON)); final JsonNode schema2 = Jsons.deserialize(MoreResources.readResource("valid_schema2.json")); final AirbyteCatalog catalog1 = new AirbyteCatalog().withStreams(List.of( new AirbyteStream().withName(USERS).withJsonSchema(schema1), new AirbyteStream().withName("accounts").withJsonSchema(Jsons.emptyObject()))); final AirbyteCatalog catalog2 = new AirbyteCatalog().withStreams(List.of( new AirbyteStream().withName(USERS).withJsonSchema(schema2), - new AirbyteStream().withName("sales").withJsonSchema(Jsons.emptyObject()))); + new AirbyteStream().withName(SALES).withJsonSchema(Jsons.emptyObject()))); - final Set actualDiff = CatalogHelpers.getCatalogDiff(catalog1, catalog2); + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = new ConfiguredAirbyteCatalog().withStreams(List.of( + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(USERS).withJsonSchema(schema2)).withSyncMode(SyncMode.FULL_REFRESH), + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(SALES).withJsonSchema(Jsons.emptyObject())) + .withSyncMode(SyncMode.FULL_REFRESH))); + + final Set actualDiff = CatalogHelpers.getCatalogDiff(catalog1, catalog2, configuredAirbyteCatalog); final List expectedDiff = Stream.of( - StreamTransform.createAddStreamTransform(new StreamDescriptor().withName("sales")), + StreamTransform.createAddStreamTransform(new StreamDescriptor().withName(SALES)), StreamTransform.createRemoveStreamTransform(new StreamDescriptor().withName("accounts")), StreamTransform.createUpdateStreamTransform(new StreamDescriptor().withName(USERS), new UpdateStreamTransform(Set.of( FieldTransform.createAddFieldTransform(List.of("COD"), schema2.get(PROPERTIES).get("COD")), - FieldTransform.createRemoveFieldTransform(List.of("something2"), schema1.get(PROPERTIES).get("something2")), - FieldTransform.createRemoveFieldTransform(List.of("HKD"), schema1.get(PROPERTIES).get("HKD")), + FieldTransform.createRemoveFieldTransform(List.of("something2"), schema1.get(PROPERTIES).get("something2"), false), + FieldTransform.createRemoveFieldTransform(List.of("HKD"), schema1.get(PROPERTIES).get("HKD"), false), FieldTransform.createUpdateFieldTransform(List.of(CAD), new UpdateFieldSchemaTransform( schema1.get(PROPERTIES).get(CAD), schema2.get(PROPERTIES).get(CAD))), @@ -132,7 +140,7 @@ void testGetCatalogDiff() throws IOException { schema1.get(PROPERTIES).get(SOME_ARRAY).get(ITEMS), schema2.get(PROPERTIES).get(SOME_ARRAY).get(ITEMS))), FieldTransform.createRemoveFieldTransform(List.of(SOME_ARRAY, ITEMS, "oldName"), - schema1.get(PROPERTIES).get(SOME_ARRAY).get(ITEMS).get(PROPERTIES).get("oldName")), + schema1.get(PROPERTIES).get(SOME_ARRAY).get(ITEMS).get(PROPERTIES).get("oldName"), false), FieldTransform.createAddFieldTransform(List.of(SOME_ARRAY, ITEMS, "newName"), schema2.get(PROPERTIES).get(SOME_ARRAY).get(ITEMS).get(PROPERTIES).get("newName")))))) .sorted(STREAM_TRANSFORM_COMPARATOR) @@ -195,7 +203,12 @@ void testGetCatalogDiffWithInvalidSchema() throws IOException { final AirbyteCatalog catalog2 = new AirbyteCatalog().withStreams(List.of( new AirbyteStream().withName(USERS).withJsonSchema(schema2))); - final Set actualDiff = CatalogHelpers.getCatalogDiff(catalog1, catalog2); + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = new ConfiguredAirbyteCatalog().withStreams(List.of( + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(USERS).withJsonSchema(schema2)).withSyncMode(SyncMode.FULL_REFRESH), + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(SALES).withJsonSchema(Jsons.emptyObject())) + .withSyncMode(SyncMode.FULL_REFRESH))); + + final Set actualDiff = CatalogHelpers.getCatalogDiff(catalog1, catalog2, configuredAirbyteCatalog); Assertions.assertThat(actualDiff).hasSize(1); Assertions.assertThat(actualDiff).first() @@ -212,9 +225,62 @@ void testGetCatalogDiffWithBothInvalidSchema() throws IOException { final AirbyteCatalog catalog2 = new AirbyteCatalog().withStreams(List.of( new AirbyteStream().withName(USERS).withJsonSchema(schema2))); - final Set actualDiff = CatalogHelpers.getCatalogDiff(catalog1, catalog2); + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = new ConfiguredAirbyteCatalog().withStreams(List.of( + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(USERS).withJsonSchema(schema2)).withSyncMode(SyncMode.FULL_REFRESH), + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(SALES).withJsonSchema(Jsons.emptyObject())) + .withSyncMode(SyncMode.FULL_REFRESH))); + + final Set actualDiff = CatalogHelpers.getCatalogDiff(catalog1, catalog2, configuredAirbyteCatalog); Assertions.assertThat(actualDiff).hasSize(0); } + @Test + void testCatalogDiffWithBreakingChanges() throws IOException { + final JsonNode schema1 = Jsons.deserialize(MoreResources.readResource(VALID_SCHEMA_JSON)); + final JsonNode breakingSchema = Jsons.deserialize(MoreResources.readResource("breaking_change_schema.json")); + final AirbyteCatalog catalog1 = new AirbyteCatalog().withStreams(List.of( + new AirbyteStream().withName(USERS).withJsonSchema(schema1))); + final AirbyteCatalog catalog2 = new AirbyteCatalog().withStreams(List.of( + new AirbyteStream().withName(USERS).withJsonSchema(breakingSchema))); + + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = new ConfiguredAirbyteCatalog().withStreams(List.of( + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(USERS).withJsonSchema(schema1)).withSyncMode(SyncMode.INCREMENTAL) + .withCursorField(List.of(DATE)).withDestinationSyncMode(DestinationSyncMode.APPEND_DEDUP).withPrimaryKey(List.of(List.of("id"))))); + + final Set diff = CatalogHelpers.getCatalogDiff(catalog1, catalog2, configuredAirbyteCatalog); + + final List expectedDiff = Stream.of( + StreamTransform.createUpdateStreamTransform(new StreamDescriptor().withName(USERS), new UpdateStreamTransform(Set.of( + FieldTransform.createRemoveFieldTransform(List.of(DATE), schema1.get(PROPERTIES).get(DATE), true), + FieldTransform.createRemoveFieldTransform(List.of("id"), schema1.get(PROPERTIES).get("id"), true))))) + .toList(); + + Assertions.assertThat(diff).containsAll(expectedDiff); + } + + @Test + void testCatalogDiffWithoutStreamConfig() throws IOException { + final JsonNode schema1 = Jsons.deserialize(MoreResources.readResource(VALID_SCHEMA_JSON)); + final JsonNode breakingSchema = Jsons.deserialize(MoreResources.readResource("breaking_change_schema.json")); + final AirbyteCatalog catalog1 = new AirbyteCatalog().withStreams(List.of( + new AirbyteStream().withName(USERS).withJsonSchema(schema1))); + final AirbyteCatalog catalog2 = new AirbyteCatalog().withStreams(List.of( + new AirbyteStream().withName(USERS).withJsonSchema(breakingSchema))); + + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = new ConfiguredAirbyteCatalog().withStreams(List.of( + new ConfiguredAirbyteStream().withStream(new AirbyteStream().withName(SALES).withJsonSchema(schema1)).withSyncMode(SyncMode.INCREMENTAL) + .withCursorField(List.of(DATE)).withDestinationSyncMode(DestinationSyncMode.APPEND_DEDUP).withPrimaryKey(List.of(List.of("id"))))); + + final Set diff = CatalogHelpers.getCatalogDiff(catalog1, catalog2, configuredAirbyteCatalog); + + final List expectedDiff = Stream.of( + StreamTransform.createUpdateStreamTransform(new StreamDescriptor().withName(USERS), new UpdateStreamTransform(Set.of( + FieldTransform.createRemoveFieldTransform(List.of(DATE), schema1.get(PROPERTIES).get(DATE), false), + FieldTransform.createRemoveFieldTransform(List.of("id"), schema1.get(PROPERTIES).get("id"), false))))) + .toList(); + + Assertions.assertThat(diff).containsAll(expectedDiff); + } + } diff --git a/airbyte-protocol/protocol-models/src/test/resources/breaking_change_schema.json b/airbyte-protocol/protocol-models/src/test/resources/breaking_change_schema.json new file mode 100644 index 000000000000..58a5e3a3bcd4 --- /dev/null +++ b/airbyte-protocol/protocol-models/src/test/resources/breaking_change_schema.json @@ -0,0 +1,60 @@ +{ + "type": "object", + "properties": { + "CAD": { "type": ["null", "number"] }, + "HKD": { "type": ["null", "number"] }, + "ISK": { "type": ["null", "number"] }, + "PHP": { "type": ["null", "number"] }, + "DKK": { "type": ["null", "number"] }, + "HUF": { "type": ["null", "number"] }, + "文": { "type": ["null", "number"] }, + "something": { + "type": ["null", "object"], + "properties": { + "somekey": { + "type": ["null", "object"], + "properties": { + "nestedkey": { + "type": ["null", "number"] + } + } + } + }, + "patternProperties": { + ".+": {} + } + }, + "something2": { + "oneOf": [ + { + "type": "object", + "properties": { + "oneOfOne": { + "type": "string" + } + } + }, + { + "type": "object", + "properties": { + "oneOfTwo": { + "type": "string" + } + } + } + ] + }, + "someArray": { + "type": ["array", "null"], + "items": { + "type": ["object", "null"], + "properties": { + "oldName": { + "type": ["string", "null"], + "maxLength": 100 + } + } + } + } + } +} diff --git a/airbyte-protocol/protocol-models/src/test/resources/valid_schema.json b/airbyte-protocol/protocol-models/src/test/resources/valid_schema.json index ba18ec3b40db..a940a07d328d 100644 --- a/airbyte-protocol/protocol-models/src/test/resources/valid_schema.json +++ b/airbyte-protocol/protocol-models/src/test/resources/valid_schema.json @@ -1,6 +1,7 @@ { "type": "object", "properties": { + "id": { "type": "number" }, "date": { "type": "string", "format": "date-time" }, "CAD": { "type": ["null", "number"] }, "HKD": { "type": ["null", "number"] }, diff --git a/airbyte-protocol/protocol-models/src/test/resources/valid_schema2.json b/airbyte-protocol/protocol-models/src/test/resources/valid_schema2.json index e6e63fcb4dcf..7ec2cdc54f9e 100644 --- a/airbyte-protocol/protocol-models/src/test/resources/valid_schema2.json +++ b/airbyte-protocol/protocol-models/src/test/resources/valid_schema2.json @@ -1,6 +1,7 @@ { "type": "object", "properties": { + "id": { "type": "number" }, "date": { "type": "string", "format": "date-time" }, "CAD": { "type": ["null", "string"] }, "COD": { "type": ["null", "string"] }, diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/CatalogDiffConverters.java b/airbyte-server/src/main/java/io/airbyte/server/converters/CatalogDiffConverters.java index c20fcd5575a4..181be5454880 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/CatalogDiffConverters.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/CatalogDiffConverters.java @@ -44,6 +44,7 @@ public static FieldTransform fieldTransformToApi(final io.airbyte.protocol.model return new FieldTransform() .transformType(Enums.convertTo(transform.getTransformType(), FieldTransform.TransformTypeEnum.class)) .fieldName(transform.getFieldName()) + .breaking(transform.breaking()) .addField(addFieldToApi(transform).orElse(null)) .removeField(removeFieldToApi(transform).orElse(null)) .updateFieldSchema(updateFieldToApi(transform).orElse(null)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java index b53c40949870..6b7e21c724ec 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java @@ -416,10 +416,10 @@ public ConnectionRead getConnection(final UUID connectionId) return buildConnectionRead(connectionId); } - public CatalogDiff getDiff(final AirbyteCatalog oldCatalog, final AirbyteCatalog newCatalog) { + public CatalogDiff getDiff(final AirbyteCatalog oldCatalog, final AirbyteCatalog newCatalog, final ConfiguredAirbyteCatalog configuredCatalog) { return new CatalogDiff().transforms(CatalogHelpers.getCatalogDiff( CatalogHelpers.configuredCatalogToCatalog(CatalogConverter.toProtocolKeepAllStreams(oldCatalog)), - CatalogHelpers.configuredCatalogToCatalog(CatalogConverter.toProtocolKeepAllStreams(newCatalog))) + CatalogHelpers.configuredCatalogToCatalog(CatalogConverter.toProtocolKeepAllStreams(newCatalog)), configuredCatalog) .stream() .map(CatalogDiffConverters::streamTransformToApi) .toList()); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index 6a89d1eaa145..c2b95dcd7957 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -298,7 +298,8 @@ public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnecti * but was present at time of configuration will appear in the diff as an added stream which is * confusing. We need to figure out why source_catalog_id is not always populated in the db. */ - diff = connectionsHandler.getDiff(catalogUsedToMakeConfiguredCatalog.orElse(configuredCatalog), refreshedCatalog.get().getCatalog()); + diff = connectionsHandler.getDiff(catalogUsedToMakeConfiguredCatalog.orElse(configuredCatalog), refreshedCatalog.get().getCatalog(), + CatalogConverter.toProtocol(configuredCatalog)); } else if (catalogUsedToMakeConfiguredCatalog.isPresent()) { // reconstructs a full picture of the full schema at the time the catalog was configured. syncCatalog = updateSchemaWithDiscovery(configuredCatalog, catalogUsedToMakeConfiguredCatalog.get()); @@ -457,7 +458,8 @@ private ConnectionRead resetStreamsIfNeeded(final WebBackendConnectionUpdate web if (!skipReset) { final AirbyteCatalog apiExistingCatalog = CatalogConverter.toApi(oldConfiguredCatalog); final AirbyteCatalog upToDateAirbyteCatalog = updatedConnectionRead.getSyncCatalog(); - final CatalogDiff catalogDiff = connectionsHandler.getDiff(apiExistingCatalog, upToDateAirbyteCatalog); + final CatalogDiff catalogDiff = + connectionsHandler.getDiff(apiExistingCatalog, upToDateAirbyteCatalog, CatalogConverter.toProtocol(upToDateAirbyteCatalog)); final List apiStreamsToReset = getStreamsToReset(catalogDiff); final Set changedConfigStreamDescriptors = connectionsHandler.getConfigurationDiff(apiExistingCatalog, upToDateAirbyteCatalog); diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index 2c02bb38d679..bfd50d3863d9 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -76,8 +76,10 @@ import io.airbyte.config.persistence.ConfigRepository.DestinationAndDefinition; import io.airbyte.config.persistence.ConfigRepository.SourceAndDefinition; import io.airbyte.protocol.models.CatalogHelpers; +import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.Field; import io.airbyte.protocol.models.JsonSchemaType; +import io.airbyte.server.handlers.helpers.CatalogConverter; import io.airbyte.server.helpers.ConnectionHelpers; import io.airbyte.server.helpers.DestinationDefinitionHelpers; import io.airbyte.server.helpers.DestinationHelpers; @@ -365,7 +367,7 @@ WebBackendConnectionRead testWebBackendGetConnection(final boolean withCatalogRe @Test void testWebBackendGetConnectionWithDiscovery() throws ConfigNotFoundException, IOException, JsonValidationException { - when(connectionsHandler.getDiff(any(), any())).thenReturn(expectedWithNewSchema.getCatalogDiff()); + when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(expectedWithNewSchema.getCatalogDiff()); final WebBackendConnectionRead result = testWebBackendGetConnection(true); verify(schedulerHandler).discoverSchemaForSourceFromSourceId(any()); assertEquals(expectedWithNewSchema, result); @@ -530,7 +532,7 @@ void testUpdateConnection() throws JsonValidationException, ConfigNotFoundExcept .thenReturn(ConnectionHelpers.generateBasicConfiguredAirbyteCatalog()); final CatalogDiff catalogDiff = new CatalogDiff().transforms(List.of()); - when(connectionsHandler.getDiff(any(), any())).thenReturn(catalogDiff); + when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(catalogDiff); final ConnectionIdRequestBody connectionIdRequestBody = new ConnectionIdRequestBody().connectionId(expected.getConnectionId()); when(stateHandler.getState(connectionIdRequestBody)).thenReturn(new ConnectionState().stateType(ConnectionStateType.LEGACY)); @@ -584,7 +586,7 @@ void testUpdateConnectionWithOperations() throws JsonValidationException, Config .thenReturn(ConnectionHelpers.generateBasicConfiguredAirbyteCatalog()); final CatalogDiff catalogDiff = new CatalogDiff().transforms(List.of()); - when(connectionsHandler.getDiff(any(), any())).thenReturn(catalogDiff); + when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(catalogDiff); final ConnectionIdRequestBody connectionIdRequestBody = new ConnectionIdRequestBody().connectionId(expected.getConnectionId()); when(stateHandler.getState(connectionIdRequestBody)).thenReturn(new ConnectionState().stateType(ConnectionStateType.LEGACY)); @@ -636,7 +638,7 @@ void testUpdateConnectionWithUpdatedSchemaLegacy() throws JsonValidationExceptio new StreamTransform().streamDescriptor(streamDescriptorAdd).transformType(TransformTypeEnum.ADD_STREAM); final CatalogDiff catalogDiff = new CatalogDiff().transforms(List.of(streamTransformAdd)); - when(connectionsHandler.getDiff(any(), any())).thenReturn(catalogDiff); + when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(catalogDiff); when(operationsHandler.listOperationsForConnection(any())).thenReturn(operationReadList); when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn( @@ -703,7 +705,7 @@ void testUpdateConnectionWithUpdatedSchemaPerStream() throws JsonValidationExcep new StreamTransform().streamDescriptor(streamDescriptorUpdate).transformType(TransformTypeEnum.UPDATE_STREAM); final CatalogDiff catalogDiff = new CatalogDiff().transforms(List.of(streamTransformAdd, streamTransformRemove, streamTransformUpdate)); - when(connectionsHandler.getDiff(any(), any())).thenReturn(catalogDiff); + when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(catalogDiff); when(connectionsHandler.getConfigurationDiff(any(), any())).thenReturn(Set.of(new StreamDescriptor().name("configUpdateStream"))); when(operationsHandler.listOperationsForConnection(any())).thenReturn(operationReadList); @@ -757,12 +759,13 @@ void testUpdateConnectionNoStreamsToReset() throws JsonValidationException, Conf // state is per-stream final ConnectionIdRequestBody connectionIdRequestBody = new ConnectionIdRequestBody().connectionId(expected.getConnectionId()); + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = ConnectionHelpers.generateBasicConfiguredAirbyteCatalog(); when(stateHandler.getState(connectionIdRequestBody)).thenReturn(new ConnectionState().stateType(ConnectionStateType.STREAM)); when(configRepository.getConfiguredCatalogForConnection(expected.getConnectionId())) - .thenReturn(ConnectionHelpers.generateBasicConfiguredAirbyteCatalog()); + .thenReturn(configuredAirbyteCatalog); final CatalogDiff catalogDiff = new CatalogDiff().transforms(List.of()); - when(connectionsHandler.getDiff(any(), any())).thenReturn(catalogDiff); + when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(catalogDiff); when(operationsHandler.listOperationsForConnection(any())).thenReturn(operationReadList); when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn( @@ -786,7 +789,9 @@ void testUpdateConnectionNoStreamsToReset() throws JsonValidationException, Conf assertEquals(expectedWithNewSchema.getSyncCatalog(), result.getSyncCatalog()); final ConnectionIdRequestBody connectionId = new ConnectionIdRequestBody().connectionId(result.getConnectionId()); - verify(connectionsHandler).getDiff(expected.getSyncCatalog(), expectedWithNewSchema.getSyncCatalog()); + + verify(connectionsHandler).getDiff(expected.getSyncCatalog(), expectedWithNewSchema.getSyncCatalog(), + CatalogConverter.toProtocol(result.getSyncCatalog())); verify(connectionsHandler).getConfigurationDiff(expected.getSyncCatalog(), expectedWithNewSchema.getSyncCatalog()); verify(schedulerHandler, times(0)).resetConnection(connectionId); verify(schedulerHandler, times(0)).syncConnection(connectionId); @@ -833,7 +838,7 @@ void testUpdateConnectionWithSkipReset() throws JsonValidationException, ConfigN final ConnectionIdRequestBody connectionId = new ConnectionIdRequestBody().connectionId(result.getConnectionId()); verify(schedulerHandler, times(0)).resetConnection(connectionId); verify(schedulerHandler, times(0)).syncConnection(connectionId); - verify(connectionsHandler, times(0)).getDiff(any(), any()); + verify(connectionsHandler, times(0)).getDiff(any(), any(), any()); verify(connectionsHandler, times(1)).updateConnection(any()); verify(eventRunner, times(0)).resetConnection(any(), any(), eq(true)); } diff --git a/airbyte-webapp/src/views/Connection/CatalogDiffModal/CatalogDiffModal.test.tsx b/airbyte-webapp/src/views/Connection/CatalogDiffModal/CatalogDiffModal.test.tsx index 3a99a63faab1..4aa2e0678f4f 100644 --- a/airbyte-webapp/src/views/Connection/CatalogDiffModal/CatalogDiffModal.test.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogDiffModal/CatalogDiffModal.test.tsx @@ -53,13 +53,14 @@ const updatedItems: StreamTransform[] = [ transformType: "update_stream", streamDescriptor: { namespace: "apple", name: "harissa_paste" }, updateStream: [ - { transformType: "add_field", fieldName: ["users", "phone"] }, - { transformType: "add_field", fieldName: ["users", "email"] }, - { transformType: "remove_field", fieldName: ["users", "lastName"] }, + { transformType: "add_field", fieldName: ["users", "phone"], breaking: false }, + { transformType: "add_field", fieldName: ["users", "email"], breaking: false }, + { transformType: "remove_field", fieldName: ["users", "lastName"], breaking: false }, { transformType: "update_field_schema", fieldName: ["users", "address"], + breaking: false, updateFieldSchema: { oldSchema: { type: "number" }, newSchema: { type: "string" } }, }, ], diff --git a/airbyte-webapp/src/views/Connection/CatalogDiffModal/index.stories.tsx b/airbyte-webapp/src/views/Connection/CatalogDiffModal/index.stories.tsx index b1a496ec1873..081a6c194b29 100644 --- a/airbyte-webapp/src/views/Connection/CatalogDiffModal/index.stories.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogDiffModal/index.stories.tsx @@ -33,12 +33,13 @@ Primary.args = { transformType: "update_stream", streamDescriptor: { namespace: "apple", name: "harissa_paste" }, updateStream: [ - { transformType: "add_field", fieldName: ["users", "phone"] }, - { transformType: "add_field", fieldName: ["users", "email"] }, - { transformType: "remove_field", fieldName: ["users", "lastName"] }, + { transformType: "add_field", fieldName: ["users", "phone"], breaking: false }, + { transformType: "add_field", fieldName: ["users", "email"], breaking: false }, + { transformType: "remove_field", fieldName: ["users", "lastName"], breaking: false }, { transformType: "update_field_schema", + breaking: false, fieldName: ["users", "address"], updateFieldSchema: { oldSchema: { type: "number" }, newSchema: { type: "string" } }, }, diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 6149700e1fe0..770274d10481 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -8286,13 +8286,15 @@

    Example data

    "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true }, { "updateFieldSchema" : { }, "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true } ] }, { "streamDescriptor" : { @@ -8305,13 +8307,15 @@

    Example data

    "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true }, { "updateFieldSchema" : { }, "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true } ] } ] }, @@ -8497,13 +8501,15 @@

    Example data

    "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true }, { "updateFieldSchema" : { }, "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true } ] }, { "streamDescriptor" : { @@ -8516,13 +8522,15 @@

    Example data

    "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true }, { "updateFieldSchema" : { }, "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true } ] } ] }, @@ -8956,13 +8964,15 @@

    Example data

    "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true }, { "updateFieldSchema" : { }, "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true } ] }, { "streamDescriptor" : { @@ -8975,13 +8985,15 @@

    Example data

    "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true }, { "updateFieldSchema" : { }, "fieldName" : [ "fieldName", "fieldName" ], "addField" : { }, "transformType" : "add_field", - "removeField" : { } + "removeField" : { }, + "breaking" : true } ] } ] }, @@ -10524,6 +10536,7 @@

    FieldTransform - Enum:

    add_field
    remove_field
    update_field_schema
    fieldName
    array[String] A field name is a list of strings that form the path to the field.
    +
    breaking
    addField (optional)
    removeField (optional)
    updateFieldSchema (optional)
    From ca8c66da2d0cccc98c055176bc602efa73c84116 Mon Sep 17 00:00:00 2001 From: Liren Tu Date: Mon, 10 Oct 2022 20:59:10 -0700 Subject: [PATCH 021/498] Fix postgres strict-encrypt expected spec (#17817) --- .../src/test/resources/expected_spec.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/airbyte-integrations/connectors/source-postgres/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-postgres/src/test/resources/expected_spec.json index 5ac0264eaea6..9e0f63090e8e 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-postgres/src/test/resources/expected_spec.json @@ -133,7 +133,7 @@ }, "client_certificate": { "type": "string", - "title": "Client Certificate (Optional)", + "title": "Client Certificate", "description": "Client certificate", "airbyte_secret": true, "multiline": true, @@ -141,7 +141,7 @@ }, "client_key": { "type": "string", - "title": "Client Key (Optional)", + "title": "Client Key", "description": "Client key", "airbyte_secret": true, "multiline": true, @@ -149,7 +149,7 @@ }, "client_key_password": { "type": "string", - "title": "Client key password (Optional)", + "title": "Client key password", "description": "Password for keystorage. If you do not add it - the password will be generated automatically.", "airbyte_secret": true, "order": 4 @@ -179,7 +179,7 @@ }, "client_certificate": { "type": "string", - "title": "Client Certificate (Optional)", + "title": "Client Certificate", "description": "Client certificate", "airbyte_secret": true, "multiline": true, @@ -187,7 +187,7 @@ }, "client_key": { "type": "string", - "title": "Client Key (Optional)", + "title": "Client Key", "description": "Client key", "airbyte_secret": true, "multiline": true, @@ -195,7 +195,7 @@ }, "client_key_password": { "type": "string", - "title": "Client key password (Optional)", + "title": "Client key password", "description": "Password for keystorage. If you do not add it - the password will be generated automatically.", "airbyte_secret": true, "order": 4 From 96f70ec6f26ec0f218c53c92ce2a71e403503d2b Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:39:30 +0200 Subject: [PATCH 022/498] Source Iterable to GA (docs update) (#17810) --- docs/integrations/sources/iterable.md | 72 +++++++++++++-------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/integrations/sources/iterable.md b/docs/integrations/sources/iterable.md index fa2d65c1c99b..89cdce688ab7 100644 --- a/docs/integrations/sources/iterable.md +++ b/docs/integrations/sources/iterable.md @@ -46,47 +46,47 @@ The Iterable source connector supports the following [sync modes](https://docs.a * [Campaigns](https://api.iterable.com/api/docs#campaigns_campaigns) * [Campaign Metrics](https://api.iterable.com/api/docs#campaigns_metrics) * [Channels](https://api.iterable.com/api/docs#channels_channels) -* [Email Bounce](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Click](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Complaint](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Open](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Send](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Send Skip](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Subscribe](https://api.iterable.com/api/docs#export_exportDataJson) -* [Email Unsubscribe](https://api.iterable.com/api/docs#export_exportDataJson) +* [Email Bounce](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Click](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Complaint](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Open](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Send](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Send Skip](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Subscribe](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Email Unsubscribe](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) * [Events](https://api.iterable.com/api/docs#events_User_events) * [Lists](https://api.iterable.com/api/docs#lists_getLists) * [List Users](https://api.iterable.com/api/docs#lists_getLists_0) * [Message Types](https://api.iterable.com/api/docs#messageTypes_messageTypes) * [Metadata](https://api.iterable.com/api/docs#metadata_list_tables) -* [Templates](https://api.iterable.com/api/docs#templates_getTemplates) -* [Users](https://api.iterable.com/api/docs#export_exportDataJson) -* [PushSend](https://api.iterable.com/api/docs#export_exportDataJson) -* [PushSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) -* [PushOpen](https://api.iterable.com/api/docs#export_exportDataJson) -* [PushUninstall](https://api.iterable.com/api/docs#export_exportDataJson) -* [PushBounce](https://api.iterable.com/api/docs#export_exportDataJson) -* [WebPushSend](https://api.iterable.com/api/docs#export_exportDataJson) -* [WebPushClick](https://api.iterable.com/api/docs#export_exportDataJson) -* [WebPushSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppSend](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppOpen](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppClick](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppClose](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppDelete](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppDelivery](https://api.iterable.com/api/docs#export_exportDataJson) -* [InAppSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) -* [InboxSession](https://api.iterable.com/api/docs#export_exportDataJson) -* [InboxMessageImpression](https://api.iterable.com/api/docs#export_exportDataJson) -* [SmsSend](https://api.iterable.com/api/docs#export_exportDataJson) -* [SmsBounce](https://api.iterable.com/api/docs#export_exportDataJson) -* [SmsClick](https://api.iterable.com/api/docs#export_exportDataJson) -* [SmsReceived](https://api.iterable.com/api/docs#export_exportDataJson) -* [SmsSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) -* [SmsUsageInfo](https://api.iterable.com/api/docs#export_exportDataJson) -* [Purchase](https://api.iterable.com/api/docs#export_exportDataJson) -* [CustomEvent](https://api.iterable.com/api/docs#export_exportDataJson) -* [HostedUnsubscribeClick](https://api.iterable.com/api/docs#export_exportDataJson) +* [Templates](https://api.iterable.com/api/docs#templates_getTemplates) \(Incremental\) +* [Users](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [PushSend](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [PushSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [PushOpen](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [PushUninstall](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [PushBounce](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [WebPushSend](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [WebPushClick](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [WebPushSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppSend](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppOpen](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppClick](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppClose](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppDelete](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppDelivery](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InAppSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InboxSession](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [InboxMessageImpression](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [SmsSend](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [SmsBounce](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [SmsClick](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [SmsReceived](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [SmsSendSkip](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [SmsUsageInfo](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [Purchase](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [CustomEvent](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) +* [HostedUnsubscribeClick](https://api.iterable.com/api/docs#export_exportDataJson) \(Incremental\) ## Changelog From e47c94ecffb01b037d7d96de2cbc01270fab2e4c Mon Sep 17 00:00:00 2001 From: Volodymyr Pochtar Date: Tue, 11 Oct 2022 11:58:01 +0300 Subject: [PATCH 023/498] =?UTF-8?q?Revert=20"feat:=20replace=20openjdk=20w?= =?UTF-8?q?ith=20amazoncorretto:17.0.4=20on=20connectors=20for=20se=D1=81u?= =?UTF-8?q?rity=20compliance=20(#17511)"=20(#17820)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ab71f5bc29cc9aaea4dd06385c391f135dc23d30. --- .../bases/base-java/Dockerfile | 6 ++---- .../base-standard-source-test-file/Dockerfile | 18 ++++++++++++++---- airbyte-integrations/bases/base/Dockerfile | 2 +- .../bases/standard-source-test/Dockerfile | 18 ++++++++++++++---- .../connectors/destination-s3/Dockerfile | 7 ++++--- .../destination-snowflake/Dockerfile | 2 +- tools/bin/build_image.sh | 2 +- 7 files changed, 37 insertions(+), 18 deletions(-) diff --git a/airbyte-integrations/bases/base-java/Dockerfile b/airbyte-integrations/bases/base-java/Dockerfile index e6da294f31f0..be4a2abd1db5 100644 --- a/airbyte-integrations/bases/base-java/Dockerfile +++ b/airbyte-integrations/bases/base-java/Dockerfile @@ -1,9 +1,7 @@ -ARG JDK_VERSION=17.0.4 -FROM amazoncorretto:${JDK_VERSION} +ARG JDK_VERSION=17.0.1 +FROM openjdk:${JDK_VERSION}-slim COPY --from=airbyte/integration-base:dev /airbyte /airbyte -RUN yum install -y tar openssl && yum clean all - WORKDIR /airbyte COPY javabase.sh . diff --git a/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile b/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile index 82faf3f5efad..6d7bb0867774 100644 --- a/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile +++ b/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile @@ -1,12 +1,22 @@ -ARG JDK_VERSION=17.0.4 -FROM amazoncorretto:${JDK_VERSION} +ARG JDK_VERSION=17.0.1 +FROM openjdk:${JDK_VERSION}-slim ARG DOCKER_BUILD_ARCH=amd64 # Install Docker to launch worker images. Eventually should be replaced with Docker-java. # See https://gitter.im/docker-java/docker-java?at=5f3eb87ba8c1780176603f4e for more information on why we are not currently using Docker-java -RUN amazon-linux-extras install -y docker -RUN yum install -y openssl jq tar && yum clean all +RUN apt-get update && apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg-agent \ + software-properties-common +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - +RUN add-apt-repository \ + "deb [arch=${DOCKER_BUILD_ARCH}] https://download.docker.com/linux/debian \ + $(lsb_release -cs) \ + stable" +RUN apt-get update && apt-get install -y docker-ce-cli jq ENV APPLICATION base-standard-source-test-file diff --git a/airbyte-integrations/bases/base/Dockerfile b/airbyte-integrations/bases/base/Dockerfile index b70c2b97a1f8..32fe5b715134 100644 --- a/airbyte-integrations/bases/base/Dockerfile +++ b/airbyte-integrations/bases/base/Dockerfile @@ -1,4 +1,4 @@ -FROM amazonlinux:2022.0.20220831.1 +FROM debian:10.5-slim WORKDIR /airbyte diff --git a/airbyte-integrations/bases/standard-source-test/Dockerfile b/airbyte-integrations/bases/standard-source-test/Dockerfile index eae2c7f1cf6d..708fd59d233a 100644 --- a/airbyte-integrations/bases/standard-source-test/Dockerfile +++ b/airbyte-integrations/bases/standard-source-test/Dockerfile @@ -1,12 +1,22 @@ -ARG JDK_VERSION=17.0.4 -FROM amazoncorretto:${JDK_VERSION} +ARG JDK_VERSION=17.0.1 +FROM openjdk:${JDK_VERSION}-slim ARG DOCKER_BUILD_ARCH=amd64 # Install Docker to launch worker images. Eventually should be replaced with Docker-java. # See https://gitter.im/docker-java/docker-java?at=5f3eb87ba8c1780176603f4e for more information on why we are not currently using Docker-java -RUN amazon-linux-extras install -y docker -RUN yum install -y openssl jq tar && yum clean all +RUN apt-get update && apt-get install -y \ + apt-transport-https \ + ca-certificates \ + curl \ + gnupg-agent \ + software-properties-common +RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - +RUN add-apt-repository \ + "deb [arch=${DOCKER_BUILD_ARCH}] https://download.docker.com/linux/debian \ + $(lsb_release -cs) \ + stable" +RUN apt-get update && apt-get install -y docker-ce-cli jq ENV APPLICATION standard-source-test diff --git a/airbyte-integrations/connectors/destination-s3/Dockerfile b/airbyte-integrations/connectors/destination-s3/Dockerfile index 96b7b1227ee0..008f766e9ac5 100644 --- a/airbyte-integrations/connectors/destination-s3/Dockerfile +++ b/airbyte-integrations/connectors/destination-s3/Dockerfile @@ -19,11 +19,12 @@ RUN /bin/bash -c 'set -e && \ ARCH=`uname -m` && \ if [ "$ARCH" == "x86_64" ] || [ "$ARCH" = "amd64" ]; then \ echo "$ARCH" && \ - yum install lzop lzo lzo-dev -y; \ + apt-get update; \ + apt-get install lzop liblzo2-2 liblzo2-dev -y; \ elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" = "arm64" ]; then \ echo "$ARCH" && \ - yum group install -y "Development Tools" \ - yum install lzop lzo lzo-dev wget curl unzip zip maven git -y; \ + apt-get update; \ + apt-get install lzop liblzo2-2 liblzo2-dev wget curl unzip zip build-essential maven git -y; \ wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz -P /tmp; \ cd /tmp && tar xvfz lzo-2.10.tar.gz; \ cd /tmp/lzo-2.10/ && ./configure --enable-shared --prefix /usr/local/lzo-2.10; \ diff --git a/airbyte-integrations/connectors/destination-snowflake/Dockerfile b/airbyte-integrations/connectors/destination-snowflake/Dockerfile index 47e84df50aa0..7cca922d3994 100644 --- a/airbyte-integrations/connectors/destination-snowflake/Dockerfile +++ b/airbyte-integrations/connectors/destination-snowflake/Dockerfile @@ -1,7 +1,7 @@ FROM airbyte/integration-base-java:dev # uncomment to run Yourkit java profiling -#RUN yum install -y curl zip +#RUN apt-get update && apt-get install -y curl zip # #RUN curl -o /tmp/YourKit-JavaProfiler-2021.3-docker.zip https://www.yourkit.com/download/docker/YourKit-JavaProfiler-2021.3-docker.zip && \ # unzip /tmp/YourKit-JavaProfiler-2021.3-docker.zip -d /usr/local && \ diff --git a/tools/bin/build_image.sh b/tools/bin/build_image.sh index 9684d07d6116..6d6ee8346abb 100755 --- a/tools/bin/build_image.sh +++ b/tools/bin/build_image.sh @@ -42,7 +42,7 @@ if [ "$FOLLOW_SYMLINKS" == "true" ]; then # to use as the build context tar cL "${exclusions[@]}" . | docker build - "${args[@]}" else - JDK_VERSION="${JDK_VERSION:-17.0.4}" + JDK_VERSION="${JDK_VERSION:-17.0.1}" if [[ -z "${DOCKER_BUILD_PLATFORM}" ]]; then docker build --build-arg JDK_VERSION="$JDK_VERSION" --build-arg DOCKER_BUILD_ARCH="$DOCKER_BUILD_ARCH" . "${args[@]}" else From 397f6fe6e9e5fbdddc306d7a1f971ee124ceac6b Mon Sep 17 00:00:00 2001 From: Sri Ram Date: Tue, 11 Oct 2022 16:41:08 +0530 Subject: [PATCH 024/498] Docs: Fixed gramatical errors in README.md (#17727) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1564b51d34f7..b25170efa749 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![GitHub stars](https://img.shields.io/github/stars/airbytehq/airbyte?style=social&label=Star&maxAge=2592000)](https://GitHub.com/airbytehq/airbyte/stargazers/) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/airbytehq/airbyte/Airbyte%20CI)](https://github.com/airbytehq/airbyte/actions/workflows/gradle.yml) [![License](https://img.shields.io/static/v1?label=license&message=MIT&color=brightgreen)](https://github.com/airbytehq/airbyte/tree/a9b1c6c0420550ad5069aca66c295223e0d05e27/LICENSE/README.md) [![License](https://img.shields.io/static/v1?label=license&message=ELv2&color=brightgreen)](https://github.com/airbytehq/airbyte/tree/a9b1c6c0420550ad5069aca66c295223e0d05e27/LICENSE/README.md) -**Data integration made simple, secure and extensible.** +**Data integration is made simple, secure, and extensible.** The new open-source standard to sync data from applications, APIs & databases to warehouses, lakes & other destinations. Airbyte is on a mission to make data integration pipelines a commodity. @@ -48,14 +48,14 @@ See our [Contributing guide](docs/contributing-to-airbyte/README.md) on how to g **Note that you are able to create connectors using the language you want, as Airbyte connections run as Docker containers.** -**Also, we will never ask you to maintain your connector. The goal is that the Airbyte team and the community helps maintain it, let's call it crowdsourced maintenance!** +**Also, we will never ask you to maintain your connector. The goal is that the Airbyte team and the community help maintain it, let's call it crowdsourced maintenance!** ## Community support For general help using Airbyte, please refer to the official Airbyte documentation. For additional help, you can use one of these channels to ask a question: * [Slack](https://slack.airbyte.io) \(For live discussion with the Community and Airbyte team\) -* [Forum](https://discuss.airbyte.io/) \(For deeper converstaions about features, connectors, or problems\) +* [Forum](https://discuss.airbyte.io/) \(For deeper conversations about features, connectors, or problems\) * [GitHub](https://github.com/airbytehq/airbyte) \(Bug reports, Contributions\) * [Twitter](https://twitter.com/airbytehq) \(Get the news fast\) * [Weekly office hours](https://airbyte.io/weekly-office-hours/) \(Live informal 30-minute video call sessions with the Airbyte team\) @@ -63,13 +63,13 @@ For general help using Airbyte, please refer to the official Airbyte documentati ## Reporting Vulnerabilities ⚠️ Please do not file GitHub issues or post on our public forum for security vulnerabilities as they are public! ⚠️ -Airbyte takes security issues very seriously. If you have any concern around Airbyte or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@airbyte.io. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible. +Airbyte takes security issues very seriously. If you have any concerns about Airbyte or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@airbyte.io. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible. Note that this security address should be used only for undisclosed vulnerabilities. Dealing with fixed issues or general questions on how to use the security features should be handled regularly via the user and the dev lists. Please report any security problems to us before disclosing it publicly. ## Roadmap -Check out our [roadmap](https://app.harvestr.io/roadmap/view/pQU6gdCyc/launch-week-roadmap) to get informed on what we are currently working on, and what we have in mind for the next weeks, months and years. +Check out our [roadmap](https://app.harvestr.io/roadmap/view/pQU6gdCyc/launch-week-roadmap) to get informed on what we are currently working on, and what we have in mind for the next weeks, months, and years. ## License From 0da8d7bcab89e5bb2b11239793f2020397e4165e Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Tue, 11 Oct 2022 14:20:44 +0300 Subject: [PATCH 025/498] Source Google Search Console: improve config validation - site_urls (#17751) Signed-off-by: Sergey Chvalyuk --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 8 +- .../source-google-search-console/Dockerfile | 2 +- .../source_google_search_console/source.py | 96 +++++++++++-------- .../source_google_search_console/spec.json | 4 +- .../unit_tests/conftest.py | 24 +++-- .../unit_tests/unit_test.py | 67 +++++++++---- .../unit_tests/utils.py | 19 ++++ .../sources/google-search-console.md | 1 + 9 files changed, 149 insertions(+), 74 deletions(-) create mode 100644 airbyte-integrations/connectors/source-google-search-console/unit_tests/utils.py diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 6443c29c51c8..ed3f9374c21b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -410,7 +410,7 @@ - name: Google Search Console sourceDefinitionId: eb4c9e00-db83-4d63-a386-39cfa91012a8 dockerRepository: airbyte/source-google-search-console - dockerImageTag: 0.1.16 + dockerImageTag: 0.1.17 documentationUrl: https://docs.airbyte.io/integrations/sources/google-search-console icon: googlesearchconsole.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 3150c8db90f3..bbbc350c84e9 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -4217,7 +4217,7 @@ - - "client_secret" oauthFlowOutputParameters: - - "refresh_token" -- dockerImage: "airbyte/source-google-search-console:0.1.16" +- dockerImage: "airbyte/source-google-search-console:0.1.17" spec: documentationUrl: "https://docs.airbyte.io/integrations/sources/google-search-console" connectionSpecification: @@ -4238,8 +4238,8 @@ \ Read more here." examples: - - "https://example1.com" - - "https://example2.com" + - "https://example1.com/" + - "https://example2.com/" order: 0 start_date: type: "string" @@ -4257,7 +4257,7 @@ \ will not be replicated. Must be greater or equal to the start date field." examples: - "2021-12-12" - pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" + pattern: "^$|^[0-9]{4}-[0-9]{2}-[0-9]{2}$" order: 2 authorization: type: "object" diff --git a/airbyte-integrations/connectors/source-google-search-console/Dockerfile b/airbyte-integrations/connectors/source-google-search-console/Dockerfile index f8d9e0e92c15..1276245292c8 100755 --- a/airbyte-integrations/connectors/source-google-search-console/Dockerfile +++ b/airbyte-integrations/connectors/source-google-search-console/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.16 +LABEL io.airbyte.version=0.1.17 LABEL io.airbyte.name=airbyte/source-google-search-console diff --git a/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/source.py b/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/source.py index 82f608c557a9..06729e9db31d 100755 --- a/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/source.py +++ b/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/source.py @@ -4,7 +4,9 @@ import json from typing import Any, List, Mapping, Optional, Tuple +from urllib.parse import urlparse +import jsonschema import pendulum import requests from airbyte_cdk.logger import AirbyteLogger @@ -12,7 +14,6 @@ from airbyte_cdk.sources import AbstractSource from airbyte_cdk.sources.streams import Stream from airbyte_cdk.sources.streams.http.auth import Oauth2Authenticator -from jsonschema import validate from source_google_search_console.exceptions import InvalidSiteURLValidationError from source_google_search_console.service_account_authenticator import ServiceAccountAuthenticator from source_google_search_console.streams import ( @@ -41,11 +42,42 @@ class SourceGoogleSearchConsole(AbstractSource): + @staticmethod + def normalize_url(url): + parse_result = urlparse(url) + if parse_result.path == "": + parse_result = parse_result._replace(path="/") + return parse_result.geturl() + + def _validate_and_transform(self, config: Mapping[str, Any]): + authorization = config["authorization"] + if authorization["auth_type"] == "Service": + try: + authorization["service_account_info"] = json.loads(authorization["service_account_info"]) + except ValueError: + raise Exception("authorization.service_account_info is not valid JSON") + + if "custom_reports" in config: + try: + config["custom_reports"] = json.loads(config["custom_reports"]) + except ValueError: + raise Exception("custom_reports is not valid JSON") + jsonschema.validate(config["custom_reports"], custom_reports_schema) + + pendulum.parse(config["start_date"]) + end_date = config.get("end_date") + if end_date: + pendulum.parse(end_date) + config["end_date"] = end_date or pendulum.now().to_date_string() + + config["site_urls"] = [self.normalize_url(url) for url in config["site_urls"]] + return config + def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, Any]: try: + config = self._validate_and_transform(config) stream_kwargs = self.get_stream_kwargs(config) - self.validate_site_urls(config, stream_kwargs) - + self.validate_site_urls(config["site_urls"], stream_kwargs["authenticator"]) sites = Sites(**stream_kwargs) stream_slice = sites.stream_slices(SyncMode.full_refresh) @@ -56,7 +88,7 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> next(sites_gen) return True, None - except InvalidSiteURLValidationError as e: + except (InvalidSiteURLValidationError, jsonschema.ValidationError) as e: return False, repr(e) except Exception as error: return ( @@ -65,9 +97,7 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> ) @staticmethod - def validate_site_urls(config, stream_kwargs): - auth = stream_kwargs["authenticator"] - + def validate_site_urls(site_urls, auth): if isinstance(auth, ServiceAccountAuthenticator): request = auth(requests.Request(method="GET", url="https://www.googleapis.com/webmasters/v3/sites")) with requests.Session() as s: @@ -76,10 +106,8 @@ def validate_site_urls(config, stream_kwargs): response = requests.get("https://www.googleapis.com/webmasters/v3/sites", headers=auth.get_auth_header()) response_data = response.json() - site_urls = set([s["siteUrl"] for s in response_data["siteEntry"]]) - provided_by_client = set(config["site_urls"]) - - invalid_site_url = provided_by_client - site_urls + remote_site_urls = {s["siteUrl"] for s in response_data["siteEntry"]} + invalid_site_url = set(site_urls) - remote_site_urls if invalid_site_url: raise InvalidSiteURLValidationError(f'The following URLs are not permitted: {", ".join(invalid_site_url)}') @@ -87,7 +115,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: """ :param config: A Mapping of the user input configuration as defined in the connector spec. """ - + config = self._validate_and_transform(config) stream_config = self.get_stream_kwargs(config) streams = [ @@ -106,40 +134,32 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: return streams def get_custom_reports(self, config: Mapping[str, Any], stream_config: Mapping[str, Any]) -> List[Optional[Stream]]: - if "custom_reports" not in config: - return [] - - reports = json.loads(config["custom_reports"]) - validate(reports, custom_reports_schema) - return [ type(report["name"], (SearchAnalyticsByCustomDimensions,), {})(dimensions=report["dimensions"], **stream_config) - for report in reports + for report in config.get("custom_reports", []) ] - @staticmethod - def get_stream_kwargs(config: Mapping[str, Any]) -> Mapping[str, Any]: - authorization = config.get("authorization", {}) - - stream_kwargs = { - "site_urls": config.get("site_urls"), - "start_date": config.get("start_date"), - "end_date": config.get("end_date") or pendulum.now().to_date_string(), + def get_stream_kwargs(self, config: Mapping[str, Any]) -> Mapping[str, Any]: + return { + "site_urls": config["site_urls"], + "start_date": config["start_date"], + "end_date": config["end_date"], + "authenticator": self.get_authenticator(config), } - auth_type = authorization.get("auth_type") + def get_authenticator(self, config): + authorization = config["authorization"] + auth_type = authorization["auth_type"] + if auth_type == "Client": - stream_kwargs["authenticator"] = Oauth2Authenticator( + return Oauth2Authenticator( token_refresh_endpoint="https://oauth2.googleapis.com/token", - client_secret=authorization.get("client_secret"), - client_id=authorization.get("client_id"), - refresh_token=authorization.get("refresh_token"), + client_secret=authorization["client_secret"], + client_id=authorization["client_id"], + refresh_token=authorization["refresh_token"], ) elif auth_type == "Service": - stream_kwargs["authenticator"] = ServiceAccountAuthenticator( - service_account_info=json.loads(authorization.get("service_account_info")), email=authorization.get("email") + return ServiceAccountAuthenticator( + service_account_info=authorization["service_account_info"], + email=authorization["email"], ) - else: - raise Exception(f"Invalid auth type: {auth_type}") - - return stream_kwargs diff --git a/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json b/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json index 21f8f82020e7..383e755b1864 100755 --- a/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json +++ b/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json @@ -13,7 +13,7 @@ }, "title": "Website URL Property", "description": "The URLs of the website property attached to your GSC account. Read more here.", - "examples": ["https://example1.com", "https://example2.com"], + "examples": ["https://example1.com/", "https://example2.com/"], "order": 0 }, "start_date": { @@ -29,7 +29,7 @@ "title": "End Date", "description": "UTC date in the format 2017-01-25. Any data after this date will not be replicated. Must be greater or equal to the start date field.", "examples": ["2021-12-12"], - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", + "pattern": "^$|^[0-9]{4}-[0-9]{2}-[0-9]{2}$", "order": 2 }, "authorization": { diff --git a/airbyte-integrations/connectors/source-google-search-console/unit_tests/conftest.py b/airbyte-integrations/connectors/source-google-search-console/unit_tests/conftest.py index 77ccfd4c7b05..9c3b1bff14af 100644 --- a/airbyte-integrations/connectors/source-google-search-console/unit_tests/conftest.py +++ b/airbyte-integrations/connectors/source-google-search-console/unit_tests/conftest.py @@ -2,23 +2,33 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from copy import deepcopy + from pytest import fixture @fixture(name="config") def config_fixture(requests_mock): - url = "https://oauth2.googleapis.com/token" - requests_mock.post(url, json={"access_token": "token", "expires_in": 10}) - config = { - "site_urls": ["https://example.com"], - "start_date": "start_date", - "end_date": "end_date", + return { + "site_urls": ["https://example.com/"], + "start_date": "2022-01-01", + "end_date": "2022-02-01", "authorization": { "auth_type": "Client", "client_id": "client_id", "client_secret": "client_secret", "refresh_token": "refresh_token", }, + "custom_reports": '[{"name": "custom_dimensions", "dimensions": ["date", "country", "device"]}]', } - return config + +@fixture +def config_gen(config): + def inner(**kwargs): + new_config = deepcopy(config) + # WARNING, no support deep dictionaries + new_config.update(kwargs) + return {k: v for k, v in new_config.items() if v is not ...} + + return inner diff --git a/airbyte-integrations/connectors/source-google-search-console/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-google-search-console/unit_tests/unit_test.py index b11edf33aba6..f16c4834bc5e 100755 --- a/airbyte-integrations/connectors/source-google-search-console/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-google-search-console/unit_tests/unit_test.py @@ -7,9 +7,10 @@ from urllib.parse import quote_plus import pytest -from airbyte_cdk.models import SyncMode +from airbyte_cdk.models import AirbyteConnectionStatus, Status, SyncMode from source_google_search_console.source import SourceGoogleSearchConsole from source_google_search_console.streams import ROW_LIMIT, GoogleSearchConsole, SearchAnalyticsByCustomDimensions, SearchAnalyticsByDate +from utils import command_check logger = logging.getLogger("airbyte") @@ -135,34 +136,58 @@ def test_parse_response(stream_class, expected): assert record == expected -def test_check_connection_ok(config, mocker, requests_mock): - url = "https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fexample.com" - requests_mock.get(url, json={}) - requests_mock.get("https://www.googleapis.com/webmasters/v3/sites", json={"siteEntry": [{"siteUrl": "https://example.com"}]}) - ok, error_msg = SourceGoogleSearchConsole().check_connection(logger, config=config) +def test_check_connection(config_gen, mocker, requests_mock): + requests_mock.get("https://www.googleapis.com/webmasters/v3/sites/https%3A%2F%2Fexample.com%2F", json={}) + requests_mock.get("https://www.googleapis.com/webmasters/v3/sites", json={"siteEntry": [{"siteUrl": "https://example.com/"}]}) + requests_mock.post("https://oauth2.googleapis.com/token", json={"access_token": "token", "expires_in": 10}) - assert ok - assert not error_msg + source = SourceGoogleSearchConsole() + assert command_check(source, config_gen()) == AirbyteConnectionStatus(status=Status.SUCCEEDED) -def test_check_connection_invalid_config(config): - config.pop("start_date") - ok, error_msg = SourceGoogleSearchConsole().check_connection(logger, config=config) - - assert not ok - assert error_msg - + # test site_urls + assert command_check(source, config_gen(site_urls=["https://example.com"])) == AirbyteConnectionStatus(status=Status.SUCCEEDED) + assert command_check(source, config_gen(site_urls=["https://missed.com"])) == AirbyteConnectionStatus( + status=Status.FAILED, message="\"InvalidSiteURLValidationError('The following URLs are not permitted: https://missed.com/')\"" + ) -def test_check_connection_exception(config): - ok, error_msg = SourceGoogleSearchConsole().check_connection(logger, config=config) + # test start_date + with pytest.raises(Exception): + assert command_check(source, config_gen(start_date=...)) + with pytest.raises(Exception): + assert command_check(source, config_gen(start_date="")) + with pytest.raises(Exception): + assert command_check(source, config_gen(start_date="start_date")) + assert command_check(source, config_gen(start_date="2022-99-99")) == AirbyteConnectionStatus( + status=Status.FAILED, + message="\"Unable to connect to Google Search Console API with the provided credentials - ParserError('Unable to parse string [2022-99-99]')\"", + ) - assert not ok - assert error_msg + # test end_date + assert command_check(source, config_gen(end_date=...)) == AirbyteConnectionStatus(status=Status.SUCCEEDED) + assert command_check(source, config_gen(end_date="")) == AirbyteConnectionStatus(status=Status.SUCCEEDED) + with pytest.raises(Exception): + assert command_check(source, config_gen(end_date="end_date")) + assert command_check(source, config_gen(end_date="2022-99-99")) == AirbyteConnectionStatus( + status=Status.FAILED, + message="\"Unable to connect to Google Search Console API with the provided credentials - ParserError('Unable to parse string [2022-99-99]')\"", + ) + # test custom_reports + assert command_check(source, config_gen(custom_reports="")) == AirbyteConnectionStatus( + status=Status.FAILED, + message="\"Unable to connect to Google Search Console API with the provided credentials - Exception('custom_reports is not valid JSON')\"", + ) + assert command_check(source, config_gen(custom_reports="{}")) == AirbyteConnectionStatus( + status=Status.FAILED, message="''" + ) -def test_streams(config): - streams = SourceGoogleSearchConsole().streams(config) +def test_streams(config_gen): + source = SourceGoogleSearchConsole() + streams = source.streams(config_gen()) + assert len(streams) == 9 + streams = source.streams(config_gen(custom_reports=...)) assert len(streams) == 8 diff --git a/airbyte-integrations/connectors/source-google-search-console/unit_tests/utils.py b/airbyte-integrations/connectors/source-google-search-console/unit_tests/utils.py new file mode 100644 index 000000000000..776315e717e6 --- /dev/null +++ b/airbyte-integrations/connectors/source-google-search-console/unit_tests/utils.py @@ -0,0 +1,19 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from unittest import mock + +from airbyte_cdk.models.airbyte_protocol import ConnectorSpecification +from airbyte_cdk.sources import Source +from airbyte_cdk.sources.utils.schema_helpers import check_config_against_spec_or_exit, split_config + + +def command_check(source: Source, config): + logger = mock.MagicMock() + connector_config, _ = split_config(config) + if source.check_config_against_spec: + source_spec: ConnectorSpecification = source.spec(logger) + check_config_against_spec_or_exit(connector_config, source_spec) + return source.check(logger, config) diff --git a/docs/integrations/sources/google-search-console.md b/docs/integrations/sources/google-search-console.md index 646d40c14d9c..ce01a8b19d9a 100644 --- a/docs/integrations/sources/google-search-console.md +++ b/docs/integrations/sources/google-search-console.md @@ -122,6 +122,7 @@ This connector attempts to back off gracefully when it hits Reports API's rate l | Version | Date | Pull Request | Subject | | :------- | :--------- | :------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------- | +| `0.1.17` | 2022-10-08 | [17751](https://github.com/airbytehq/airbyte/pull/17751) | Improved config validation: start_date, end_date, site_urls | | `0.1.16` | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | | `0.1.15` | 2022-09-16 | [16819](https://github.com/airbytehq/airbyte/pull/16819) | Check available site urls to avoid 403 error on sync | | `0.1.14` | 2022-09-08 | [16433](https://github.com/airbytehq/airbyte/pull/16433) | Add custom analytics stream. | From a142141c98cf32cb4acd968e919a919ac6b3362b Mon Sep 17 00:00:00 2001 From: Volodymyr Pochtar Date: Tue, 11 Oct 2022 14:22:54 +0300 Subject: [PATCH 026/498] =?UTF-8?q?Revert=20"Revert=20"feat:=20replace=20o?= =?UTF-8?q?penjdk=20with=20amazoncorretto:17.0.4=20on=20connectors=20for?= =?UTF-8?q?=20se=D1=81urity=20compliance=20(#17511)"=20(#17820)"=20(#17833?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e47c94ecffb01b037d7d96de2cbc01270fab2e4c. --- .../bases/base-java/Dockerfile | 6 ++++-- .../base-standard-source-test-file/Dockerfile | 18 ++++-------------- airbyte-integrations/bases/base/Dockerfile | 2 +- .../bases/standard-source-test/Dockerfile | 18 ++++-------------- .../connectors/destination-s3/Dockerfile | 7 +++---- .../destination-snowflake/Dockerfile | 2 +- tools/bin/build_image.sh | 2 +- 7 files changed, 18 insertions(+), 37 deletions(-) diff --git a/airbyte-integrations/bases/base-java/Dockerfile b/airbyte-integrations/bases/base-java/Dockerfile index be4a2abd1db5..e6da294f31f0 100644 --- a/airbyte-integrations/bases/base-java/Dockerfile +++ b/airbyte-integrations/bases/base-java/Dockerfile @@ -1,7 +1,9 @@ -ARG JDK_VERSION=17.0.1 -FROM openjdk:${JDK_VERSION}-slim +ARG JDK_VERSION=17.0.4 +FROM amazoncorretto:${JDK_VERSION} COPY --from=airbyte/integration-base:dev /airbyte /airbyte +RUN yum install -y tar openssl && yum clean all + WORKDIR /airbyte COPY javabase.sh . diff --git a/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile b/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile index 6d7bb0867774..82faf3f5efad 100644 --- a/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile +++ b/airbyte-integrations/bases/base-standard-source-test-file/Dockerfile @@ -1,22 +1,12 @@ -ARG JDK_VERSION=17.0.1 -FROM openjdk:${JDK_VERSION}-slim +ARG JDK_VERSION=17.0.4 +FROM amazoncorretto:${JDK_VERSION} ARG DOCKER_BUILD_ARCH=amd64 # Install Docker to launch worker images. Eventually should be replaced with Docker-java. # See https://gitter.im/docker-java/docker-java?at=5f3eb87ba8c1780176603f4e for more information on why we are not currently using Docker-java -RUN apt-get update && apt-get install -y \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - software-properties-common -RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - -RUN add-apt-repository \ - "deb [arch=${DOCKER_BUILD_ARCH}] https://download.docker.com/linux/debian \ - $(lsb_release -cs) \ - stable" -RUN apt-get update && apt-get install -y docker-ce-cli jq +RUN amazon-linux-extras install -y docker +RUN yum install -y openssl jq tar && yum clean all ENV APPLICATION base-standard-source-test-file diff --git a/airbyte-integrations/bases/base/Dockerfile b/airbyte-integrations/bases/base/Dockerfile index 32fe5b715134..b70c2b97a1f8 100644 --- a/airbyte-integrations/bases/base/Dockerfile +++ b/airbyte-integrations/bases/base/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:10.5-slim +FROM amazonlinux:2022.0.20220831.1 WORKDIR /airbyte diff --git a/airbyte-integrations/bases/standard-source-test/Dockerfile b/airbyte-integrations/bases/standard-source-test/Dockerfile index 708fd59d233a..eae2c7f1cf6d 100644 --- a/airbyte-integrations/bases/standard-source-test/Dockerfile +++ b/airbyte-integrations/bases/standard-source-test/Dockerfile @@ -1,22 +1,12 @@ -ARG JDK_VERSION=17.0.1 -FROM openjdk:${JDK_VERSION}-slim +ARG JDK_VERSION=17.0.4 +FROM amazoncorretto:${JDK_VERSION} ARG DOCKER_BUILD_ARCH=amd64 # Install Docker to launch worker images. Eventually should be replaced with Docker-java. # See https://gitter.im/docker-java/docker-java?at=5f3eb87ba8c1780176603f4e for more information on why we are not currently using Docker-java -RUN apt-get update && apt-get install -y \ - apt-transport-https \ - ca-certificates \ - curl \ - gnupg-agent \ - software-properties-common -RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - -RUN add-apt-repository \ - "deb [arch=${DOCKER_BUILD_ARCH}] https://download.docker.com/linux/debian \ - $(lsb_release -cs) \ - stable" -RUN apt-get update && apt-get install -y docker-ce-cli jq +RUN amazon-linux-extras install -y docker +RUN yum install -y openssl jq tar && yum clean all ENV APPLICATION standard-source-test diff --git a/airbyte-integrations/connectors/destination-s3/Dockerfile b/airbyte-integrations/connectors/destination-s3/Dockerfile index 008f766e9ac5..96b7b1227ee0 100644 --- a/airbyte-integrations/connectors/destination-s3/Dockerfile +++ b/airbyte-integrations/connectors/destination-s3/Dockerfile @@ -19,12 +19,11 @@ RUN /bin/bash -c 'set -e && \ ARCH=`uname -m` && \ if [ "$ARCH" == "x86_64" ] || [ "$ARCH" = "amd64" ]; then \ echo "$ARCH" && \ - apt-get update; \ - apt-get install lzop liblzo2-2 liblzo2-dev -y; \ + yum install lzop lzo lzo-dev -y; \ elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" = "arm64" ]; then \ echo "$ARCH" && \ - apt-get update; \ - apt-get install lzop liblzo2-2 liblzo2-dev wget curl unzip zip build-essential maven git -y; \ + yum group install -y "Development Tools" \ + yum install lzop lzo lzo-dev wget curl unzip zip maven git -y; \ wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz -P /tmp; \ cd /tmp && tar xvfz lzo-2.10.tar.gz; \ cd /tmp/lzo-2.10/ && ./configure --enable-shared --prefix /usr/local/lzo-2.10; \ diff --git a/airbyte-integrations/connectors/destination-snowflake/Dockerfile b/airbyte-integrations/connectors/destination-snowflake/Dockerfile index 7cca922d3994..47e84df50aa0 100644 --- a/airbyte-integrations/connectors/destination-snowflake/Dockerfile +++ b/airbyte-integrations/connectors/destination-snowflake/Dockerfile @@ -1,7 +1,7 @@ FROM airbyte/integration-base-java:dev # uncomment to run Yourkit java profiling -#RUN apt-get update && apt-get install -y curl zip +#RUN yum install -y curl zip # #RUN curl -o /tmp/YourKit-JavaProfiler-2021.3-docker.zip https://www.yourkit.com/download/docker/YourKit-JavaProfiler-2021.3-docker.zip && \ # unzip /tmp/YourKit-JavaProfiler-2021.3-docker.zip -d /usr/local && \ diff --git a/tools/bin/build_image.sh b/tools/bin/build_image.sh index 6d6ee8346abb..9684d07d6116 100755 --- a/tools/bin/build_image.sh +++ b/tools/bin/build_image.sh @@ -42,7 +42,7 @@ if [ "$FOLLOW_SYMLINKS" == "true" ]; then # to use as the build context tar cL "${exclusions[@]}" . | docker build - "${args[@]}" else - JDK_VERSION="${JDK_VERSION:-17.0.1}" + JDK_VERSION="${JDK_VERSION:-17.0.4}" if [[ -z "${DOCKER_BUILD_PLATFORM}" ]]; then docker build --build-arg JDK_VERSION="$JDK_VERSION" --build-arg DOCKER_BUILD_ARCH="$DOCKER_BUILD_ARCH" . "${args[@]}" else From bec1ecf2647038b983782349ed8d06769c603834 Mon Sep 17 00:00:00 2001 From: Anushree Agrawal Date: Tue, 11 Oct 2022 06:22:54 -0700 Subject: [PATCH 027/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Orb:=20Fix=20bu?= =?UTF-8?q?g=20to=20enrich=20multiple=20events=20with=20the=20same=20`even?= =?UTF-8?q?t=5Fid`=20(#17761)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update source Orb to handle multiple events with the same event_id * Documentation * Update documentation --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-orb/Dockerfile | 2 +- .../source-orb/source_orb/source.py | 23 +++++++++------- .../unit_tests/test_incremental_streams.py | 26 +++++++++++++++++++ docs/integrations/sources/orb.md | 1 + 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index ed3f9374c21b..7fd1f6e86376 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -733,7 +733,7 @@ - name: Orb sourceDefinitionId: 7f0455fb-4518-4ec0-b7a3-d808bf8081cc dockerRepository: airbyte/source-orb - dockerImageTag: 0.1.3 + dockerImageTag: 0.1.4 documentationUrl: https://docs.airbyte.io/integrations/sources/orb icon: orb.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index bbbc350c84e9..30bd93c2192c 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -7994,7 +7994,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-orb:0.1.3" +- dockerImage: "airbyte/source-orb:0.1.4" spec: documentationUrl: "https://docs.withorb.com/" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-orb/Dockerfile b/airbyte-integrations/connectors/source-orb/Dockerfile index 7aac1d472d9d..0e60a42a61b0 100644 --- a/airbyte-integrations/connectors/source-orb/Dockerfile +++ b/airbyte-integrations/connectors/source-orb/Dockerfile @@ -34,5 +34,5 @@ COPY source_orb ./source_orb ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.3 +LABEL io.airbyte.version=0.1.4 LABEL io.airbyte.name=airbyte/source-orb diff --git a/airbyte-integrations/connectors/source-orb/source_orb/source.py b/airbyte-integrations/connectors/source-orb/source_orb/source.py index 724625445105..8aeba8151272 100644 --- a/airbyte-integrations/connectors/source-orb/source_orb/source.py +++ b/airbyte-integrations/connectors/source-orb/source_orb/source.py @@ -301,14 +301,15 @@ def enrich_ledger_entries_with_event_data(self, ledger_entries): """ # Build up a list of the subset of ledger entries we are expected # to enrich with event metadata. - event_id_to_ledger_entry = {} + event_id_to_ledger_entries = {} for entry in ledger_entries: maybe_event_id: Optional[str] = entry.get("event_id") if maybe_event_id: - event_id_to_ledger_entry[maybe_event_id] = entry + # There can be multiple entries with the same event ID + event_id_to_ledger_entries[maybe_event_id] = event_id_to_ledger_entries.get(maybe_event_id, []) + [entry] # Nothing to enrich; short-circuit - if len(event_id_to_ledger_entry) == 0: + if len(event_id_to_ledger_entries) == 0: return ledger_entries def modify_ledger_entry_schema(ledger_entry): @@ -321,8 +322,9 @@ def modify_ledger_entry_schema(ledger_entry): ledger_entry["event"] = {} ledger_entry["event"]["id"] = event_id - for ledger_entry in event_id_to_ledger_entry.values(): - modify_ledger_entry_schema(ledger_entry=ledger_entry) + for ledger_entries_in_map in event_id_to_ledger_entries.values(): + for ledger_entry in ledger_entries_in_map: + modify_ledger_entry_schema(ledger_entry=ledger_entry) # Nothing to extract for each ledger entry merged_properties_keys = (self.string_event_properties_keys or []) + (self.numeric_event_properties_keys or []) @@ -331,7 +333,7 @@ def modify_ledger_entry_schema(ledger_entry): # The events endpoint is a `POST` endpoint which expects a list of # event_ids to filter on - request_filter_json = {"event_ids": list(event_id_to_ledger_entry)} + request_filter_json = {"event_ids": list(event_id_to_ledger_entries)} # Prepare request with self._session, which should # automatically deal with the authentication header. @@ -354,16 +356,17 @@ def modify_ledger_entry_schema(ledger_entry): # This would imply that the endpoint returned an event that wasn't part of the filter # parameters, so log an error but ignore it. - if event_id not in event_id_to_ledger_entry: + if event_id not in event_id_to_ledger_entries: self.logger.error(f"Unrecognized event received with ID {event_id} when trying to enrich ledger entries") continue # Replace ledger_entry.event_id with ledger_entry.event - event_id_to_ledger_entry[event_id]["event"]["properties"] = desired_properties_subset - num_events_enriched += 1 + for ledger_entry in event_id_to_ledger_entries[event_id]: + ledger_entry["event"]["properties"] = desired_properties_subset + num_events_enriched += 1 # Log an error if we did not enrich all the entries we asked for. - if num_events_enriched != len(event_id_to_ledger_entry): + if num_events_enriched != sum(len(le) for le in event_id_to_ledger_entries.values()): self.logger.error("Unable to enrich all eligible credit ledger entries with event metadata.") # Mutating entries within `event_id_to_ledger_entry` should have modified diff --git a/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py b/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py index 326968721a6f..f471f1cfd205 100644 --- a/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py +++ b/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py @@ -259,6 +259,32 @@ def test_credits_ledger_entries_enriches_selected_property_keys( # Does not enrich, but still passes back, irrelevant (for enrichment purposes) ledger entry assert enriched_entries[1] == original_entry_1 +@responses.activate +def test_credits_ledger_entries_enriches_with_multiple_entries_per_event(mocker): + stream = CreditsLedgerEntries(string_event_properties_keys=["ping"]) + ledger_entries = [{"event_id": "foo-event-id", "entry_type": "decrement"}, {"event_id": "foo-event-id", "entry_type": "decrement"}] + mock_response = { + "data": [ + { + "customer_id": "foo-customer-id", + "event_name": "foo-name", + "id": "foo-event-id", + "properties": {"ping": "pong"}, + "timestamp": "2022-02-21T07:00:00+00:00", + } + ], + "pagination_metadata": {"has_more": False, "next_cursor": None}, + } + responses.add(responses.POST, f"{stream.url_base}events", json=mock_response, status=200) + enriched_entries = stream.enrich_ledger_entries_with_event_data(ledger_entries) + + # We expect both events are enriched correctly + assert enriched_entries == [ + {"event": {"id": "foo-event-id", "properties": {"ping": "pong"}}, "entry_type": "decrement"}, + {"event": {"id": "foo-event-id", "properties": {"ping": "pong"}}, "entry_type": "decrement"}, + ] + + def test_supports_incremental(patch_incremental_base_class, mocker): mocker.patch.object(IncrementalOrbStream, "cursor_field", "dummy_field") diff --git a/docs/integrations/sources/orb.md b/docs/integrations/sources/orb.md index cd2fbb946655..e9422b5f40e0 100644 --- a/docs/integrations/sources/orb.md +++ b/docs/integrations/sources/orb.md @@ -52,6 +52,7 @@ an Orb Account and API Key. | Version | Date | Pull Request | Subject | | --- | --- | --- | --- | +| 0.1.4 | 2022-10-07 | [17761](https://github.com/airbytehq/airbyte/pull/17761) | Fix bug with enriching ledger entries | 0.1.3 | 2022-08-26 | [16017](https://github.com/airbytehq/airbyte/pull/16017) | Add credit block id to ledger entries | 0.1.2 | 2022-04-20 | [11528](https://github.com/airbytehq/airbyte/pull/11528) | Add cost basis to ledger entries, update expiration date, sync only committed entries | 0.1.1 | 2022-03-03 | [10839](https://github.com/airbytehq/airbyte/pull/10839) | Support ledger entries with numeric properties + schema fixes From e95a983563fc118c392f0019c1ea7c23751d601f Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 11 Oct 2022 10:14:23 -0400 Subject: [PATCH 028/498] Various Micronaut-related cleanup items (#17611) --- airbyte-cron/build.gradle | 13 +++++++++++++ airbyte-cron/src/main/resources/application.yml | 4 ++-- .../src/main/resources/micronaut-banner.txt | 9 ++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/airbyte-cron/build.gradle b/airbyte-cron/build.gradle index 5e86838403e2..c718f52bab29 100644 --- a/airbyte-cron/build.gradle +++ b/airbyte-cron/build.gradle @@ -33,6 +33,19 @@ application { applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0'] } +Properties env = new Properties() +rootProject.file('.env.dev').withInputStream { env.load(it) } + +run { + // default for running on local machine. + env.each { entry -> + environment entry.getKey(), entry.getValue() + } + + environment 'AIRBYTE_ROLE', System.getenv('AIRBYTE_ROLE') + environment 'AIRBYTE_VERSION', env.VERSION +} + tasks.named("buildDockerImage") { dependsOn copyGeneratedTar } diff --git a/airbyte-cron/src/main/resources/application.yml b/airbyte-cron/src/main/resources/application.yml index e84f075ca5e5..3d18e233014e 100644 --- a/airbyte-cron/src/main/resources/application.yml +++ b/airbyte-cron/src/main/resources/application.yml @@ -8,7 +8,7 @@ micronaut: access: - isAnonymous() server: - port: 9000 + port: 9001 datasources: config: @@ -52,7 +52,7 @@ airbyte: initialization-timeout-ms: ${CONFIGS_DATABASE_INITIALIZATION_TIMEOUT_MS:60000} minimum-migration-version: ${CONFIGS_DATABASE_MINIMUM_FLYWAY_MIGRATION_VERSION} local: - docker-mount: ${LOCAL_DOCKER_MOUNT} + docker-mount: ${LOCAL_DOCKER_MOUNT:} root: ${LOCAL_ROOT} role: ${AIRBYTE_ROLE:} temporal: diff --git a/airbyte-cron/src/main/resources/micronaut-banner.txt b/airbyte-cron/src/main/resources/micronaut-banner.txt index 713ab3df590b..d7778fa0cb04 100644 --- a/airbyte-cron/src/main/resources/micronaut-banner.txt +++ b/airbyte-cron/src/main/resources/micronaut-banner.txt @@ -1 +1,8 @@ - : airbyte-cron : + + ___ _ __ __ + / | (_)____/ /_ __ __/ /____ + / /| | / / ___/ __ \/ / / / __/ _ \ + / ___ |/ / / / /_/ / /_/ / /_/ __/ +/_/ |_/_/_/ /_.___/\__, /\__/\___/ + /____/ + : airbyte-cron : \ No newline at end of file From 7c8f8a86e9c3ea2f6bac86e3c2d40b6e2eae58f8 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 11 Oct 2022 16:40:46 +0200 Subject: [PATCH 029/498] Move links out of config (#17837) --- airbyte-webapp/scripts/validate-links.ts | 2 +- .../DocumentationPanel/DocumentationPanel.tsx | 5 +++-- airbyte-webapp/src/config/defaultConfig.ts | 2 -- airbyte-webapp/src/config/types.ts | 3 --- .../views/auth/SignupPage/components/SignupForm.tsx | 7 +++---- .../views/auth/components/GitBlock/GitBlock.tsx | 6 +++--- .../CreditsPage/components/RemainingCredits.tsx | 5 ++--- .../packages/cloud/views/layout/SideBar/SideBar.tsx | 13 ++++++------- .../pages/OnboardingPage/components/FinalStep.tsx | 5 ++--- .../pages/OnboardingPage/components/WelcomeStep.tsx | 6 ++---- .../components/CreateConnectorModal.tsx | 5 ++--- .../pages/MetricsPage/components/MetricsForm.tsx | 5 ++--- airbyte-webapp/src/{config => utils}/links.ts | 0 .../components/NormalizationField.tsx | 5 ++--- .../ConnectionForm/components/SyncCatalogField.tsx | 5 ++--- .../TransformationForm/TransformationForm.tsx | 5 ++--- .../ServiceForm/components/ShowLoadingMessage.tsx | 5 ++--- .../ServiceForm/components/WarningMessage.tsx | 5 ++--- .../Settings/PreferencesForm/PreferencesForm.tsx | 5 ++--- airbyte-webapp/src/views/layout/SideBar/SideBar.tsx | 9 +++++---- 20 files changed, 43 insertions(+), 60 deletions(-) rename airbyte-webapp/src/{config => utils}/links.ts (100%) diff --git a/airbyte-webapp/scripts/validate-links.ts b/airbyte-webapp/scripts/validate-links.ts index fa6da3de29cc..674627c27186 100644 --- a/airbyte-webapp/scripts/validate-links.ts +++ b/airbyte-webapp/scripts/validate-links.ts @@ -2,7 +2,7 @@ import fetch from "node-fetch"; -import { links } from "../src/config/links"; +import { links } from "../src/utils/links"; async function run() { // Query all domains and wait for results diff --git a/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.tsx b/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.tsx index 4e146026f910..4835bbcdc35c 100644 --- a/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.tsx +++ b/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.tsx @@ -14,6 +14,7 @@ import { PageHeader } from "components/ui/PageHeader"; import { useConfig } from "config"; import { useDocumentation } from "hooks/services/useDocumentation"; +import { links } from "utils/links"; import { useDocumentationPanelContext } from "views/Connector/ConnectorDocumentationLayout/DocumentationPanelContext"; import styles from "./DocumentationPanel.module.scss"; @@ -36,12 +37,12 @@ export const DocumentationPanel: React.FC = () => { // In links replace with a link to the external documentation instead // The external path is the markdown URL without the "../../" prefix and the .md extension const docPath = url.path.replace(/^\.\.\/\.\.\/(.*?)(\.md)?$/, "$1"); - return `${config.links.docsLink}/${docPath}`; + return `${links.docsLink}/${docPath}`; } return url.href; }; return [[urls, sanitizeLinks], [rehypeSlug]]; - }, [config.integrationUrl, config.links.docsLink]); + }, [config.integrationUrl]); const location = useLocation(); diff --git a/airbyte-webapp/src/config/defaultConfig.ts b/airbyte-webapp/src/config/defaultConfig.ts index 81611f2ff7f7..c01720a36a4b 100644 --- a/airbyte-webapp/src/config/defaultConfig.ts +++ b/airbyte-webapp/src/config/defaultConfig.ts @@ -1,8 +1,6 @@ -import { links } from "./links"; import { Config } from "./types"; const defaultConfig: Config = { - links, segment: { enabled: true, token: "" }, healthCheckInterval: 20000, version: "dev", diff --git a/airbyte-webapp/src/config/types.ts b/airbyte-webapp/src/config/types.ts index cb97cee2f51a..92265730edb8 100644 --- a/airbyte-webapp/src/config/types.ts +++ b/airbyte-webapp/src/config/types.ts @@ -1,5 +1,3 @@ -import { OutboundLinks } from "./links"; - declare global { interface Window { TRACKING_STRATEGY?: string; @@ -17,7 +15,6 @@ declare global { } export interface Config { - links: OutboundLinks; segment: { token: string; enabled: boolean }; apiUrl: string; oauthRedirectUrl: string; diff --git a/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/components/SignupForm.tsx b/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/components/SignupForm.tsx index 2be534d5fba7..2b65f471128c 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/components/SignupForm.tsx +++ b/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/components/SignupForm.tsx @@ -8,11 +8,11 @@ import * as yup from "yup"; import { LabeledInput, Link } from "components"; import { Button } from "components/ui/Button"; -import { useConfig } from "config"; import { useExperiment } from "hooks/services/Experiment"; import { FieldError } from "packages/cloud/lib/errors/FieldError"; import { useAuthService } from "packages/cloud/services/auth/AuthService"; import { isGdprCountry } from "utils/dataPrivacy"; +import { links } from "utils/links"; import CheckBoxControl from "../../components/CheckBoxControl"; import { BottomBlock, FieldItem, Form, RowFieldItem } from "../../components/FormComponents"; @@ -134,19 +134,18 @@ export const NewsField: React.FC = () => { }; export const Disclaimer: React.FC = () => { - const config = useConfig(); return (
    ( - + {terms} ), privacy: (privacy: React.ReactNode) => ( - + {privacy} ), diff --git a/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.tsx b/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.tsx index 03431820e519..a0cbc28382ff 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.tsx +++ b/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.tsx @@ -3,7 +3,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { FC } from "react"; import { FormattedMessage } from "react-intl"; -import { useConfig } from "../../../../../../config"; +import { links } from "utils/links"; + import styles from "./GitBlock.module.scss"; export interface GitBlockProps { @@ -11,10 +12,9 @@ export interface GitBlockProps { messageStyle?: React.CSSProperties; } export const GitBlock: FC = ({ titleStyle, messageStyle }) => { - const config = useConfig(); return (
    - +
    diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx index d38c42c05b29..359339ac10fb 100644 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx +++ b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/RemainingCredits.tsx @@ -8,7 +8,6 @@ import styled from "styled-components"; import { Button } from "components/ui/Button"; -import { useConfig } from "config"; import { Action, Namespace } from "core/analytics"; import { useAnalyticsService } from "hooks/services/Analytics"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; @@ -18,6 +17,7 @@ import { useGetCloudWorkspace, useInvalidateCloudWorkspace, } from "packages/cloud/services/workspaces/CloudWorkspacesService"; +import { links } from "utils/links"; interface Props { selfServiceCheckoutEnabled: boolean; @@ -60,7 +60,6 @@ function hasRecentCreditIncrease(cloudWorkspace: CloudWorkspace): boolean { const RemainingCredits: React.FC = ({ selfServiceCheckoutEnabled }) => { const retryIntervalId = useRef(); - const config = useConfig(); const currentWorkspace = useCurrentWorkspace(); const cloudWorkspace = useGetCloudWorkspace(currentWorkspace.workspaceId); const [searchParams, setSearchParams] = useSearchParams(); @@ -135,7 +134,7 @@ const RemainingCredits: React.FC = ({ selfServiceCheckoutEnabled }) => { > - diff --git a/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx b/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx index af37a47c630d..be2f40917663 100644 --- a/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx +++ b/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx @@ -10,7 +10,6 @@ import { Link } from "components"; import { CreditsIcon } from "components/icons/CreditsIcon"; import { Text } from "components/ui/Text"; -import { useConfig } from "config"; import { useExperiment } from "hooks/services/Experiment"; import { FeatureItem, IfFeatureEnabled } from "hooks/services/Feature"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; @@ -18,6 +17,7 @@ import { CloudRoutes } from "packages/cloud/cloudRoutes"; import { useIntercom } from "packages/cloud/services/thirdParty/intercom"; import { useGetCloudWorkspace } from "packages/cloud/services/workspaces/CloudWorkspacesService"; import { WorkspacePopout } from "packages/cloud/views/workspaces/WorkspacePopout"; +import { links } from "utils/links"; import ChatIcon from "views/layout/SideBar/components/ChatIcon"; import ConnectionsIcon from "views/layout/SideBar/components/ConnectionsIcon"; import DestinationIcon from "views/layout/SideBar/components/DestinationIcon"; @@ -38,7 +38,6 @@ const SideBar: React.FC = () => { const navLinkClassName = useCalculateSidebarStyles(); const workspace = useCurrentWorkspace(); const cloudWorkspace = useGetCloudWorkspace(workspace.workspaceId); - const config = useConfig(); const { show } = useIntercom(); const handleChatUs = () => show(); const hideOnboardingExperiment = useExperiment("onboarding.hideOnboarding", false); @@ -112,25 +111,25 @@ const SideBar: React.FC = () => { options={[ { type: SidebarDropdownMenuItemType.LINK, - href: config.links.docsLink, + href: links.docsLink, icon: , displayName: , }, { type: SidebarDropdownMenuItemType.LINK, - href: config.links.slackLink, + href: links.slackLink, icon: , displayName: , }, { type: SidebarDropdownMenuItemType.LINK, - href: config.links.statusLink, + href: links.statusLink, icon: , displayName: , }, { type: SidebarDropdownMenuItemType.LINK, - href: config.links.recipesLink, + href: links.recipesLink, icon: , displayName: , }, @@ -146,7 +145,7 @@ const SideBar: React.FC = () => { options={[ { type: SidebarDropdownMenuItemType.LINK, - href: config.links.supportTicketLink, + href: links.supportTicketLink, icon: , displayName: , }, diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/FinalStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/FinalStep.tsx index a03f22cfb603..f3a00d9a0183 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/FinalStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/FinalStep.tsx @@ -3,10 +3,10 @@ import { FormattedMessage } from "react-intl"; import { Text } from "components/ui/Text"; -import { useConfig } from "config"; import Status from "core/statuses"; import { useOnboardingService } from "hooks/services/Onboarding/OnboardingService"; import { useConnectionList, useGetConnection, useSyncConnection } from "hooks/services/useConnectionHook"; +import { links } from "utils/links"; import styles from "./FinalStep.module.scss"; import { FirstSuccessfulSync } from "./FirstSuccessfulSync"; @@ -16,7 +16,6 @@ import UseCaseBlock from "./UseCaseBlock"; import VideoItem from "./VideoItem"; const FinalStep: React.FC = () => { - const config = useConfig(); const { visibleUseCases, useCaseLinks, skipCase } = useOnboardingService(); const { mutateAsync: syncConnection } = useSyncConnection(); const { connections } = useConnectionList(); @@ -45,7 +44,7 @@ const FinalStep: React.FC = () => { } - link={config.links.demoLink} + link={links.demoLink} img="/videoCover.png" />
    diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx index b2b151b6d6de..ea1c86b820b7 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { Button } from "components/ui/Button"; -import { useConfig } from "config"; +import { links } from "utils/links"; import HighlightedText from "./HighlightedText"; import TitlesBlock from "./TitlesBlock"; @@ -26,8 +26,6 @@ const Videos = styled.div` `; const WelcomeStep: React.FC = ({ userName, onNextStep }) => { - const config = useConfig(); - return ( <> = ({ userName, onNextStep }) => { } img="/videoCover.png" - link={config.links.demoLink} + link={links.demoLink} />
    • - + @@ -102,19 +103,19 @@ const SideBar: React.FC = () => { options={[ { type: SidebarDropdownMenuItemType.LINK, - href: config.links.docsLink, + href: links.docsLink, icon: , displayName: , }, { type: SidebarDropdownMenuItemType.LINK, - href: config.links.slackLink, + href: links.slackLink, icon: , displayName: , }, { type: SidebarDropdownMenuItemType.LINK, - href: config.links.recipesLink, + href: links.recipesLink, icon: , displayName: , }, From 938436bcc9bd05efb02b1937dd1b5ce02f0e6052 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Tue, 11 Oct 2022 11:04:23 -0400 Subject: [PATCH 030/498] update connector specs and definitions with new .com documentation urls (#17585) * update definitions with new .com docs urls * update docs urls in specs * update generators * regenerate scaffold connectors * remove unrelated changes * update more urls * update specs * fix tests * run `:airbyte-config:specs:generateSeedConnectorSpecs` to fix formatting * revert docs changes to make pr more reviewable * revert generator readme changes to make more reviewable * fix mysql strict encrypt expected spec * fix postgres expected spec --- .../seed/destination_definitions.yaml | 84 ++--- .../resources/seed/destination_specs.yaml | 100 +++--- .../resources/seed/source_definitions.yaml | 304 ++++++++-------- .../src/main/resources/seed/source_specs.yaml | 338 +++++++++--------- .../init/LocalDefinitionsProviderTest.java | 8 +- .../init/YamlSeedConfigPersistenceTest.java | 8 +- .../destination-java/definition.yaml.hbs | 2 +- .../destination-java/spec.json.hbs | 2 +- .../destination_{{snakeCase name}}/spec.json | 2 +- .../connector-templates/generator/plopfile.js | 2 +- .../src/main/resources/spec.json.hbs | 2 +- .../spec.yaml.hbs | 2 +- .../acceptance-test-config.yml.hbs | 2 +- .../destination_amazon_sqs/spec.json | 2 +- .../destination_aws_datalake/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../destination_firebolt/spec.json | 2 +- .../destination_firestore/spec.json | 4 +- .../src/main/resources/spec.json | 2 +- .../destination_google_sheets/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../src/main/resources/spec.json | 2 +- .../resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 6 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../destination_rabbitmq/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../destination_sftp_json/spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../destination_sqlite/spec.json | 4 +- .../src/main/resources/spec.json | 2 +- .../source-adjust/acceptance-test-config.yml | 2 +- .../source-adjust/source_adjust/model.py | 2 +- .../source-adjust/source_adjust/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../source-airtable/source_airtable/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-alloydb/acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 4 +- .../source_amazon_seller_partner/source.py | 4 +- .../acceptance-test-config.yml | 2 +- .../source_amazon_sqs/spec.json | 2 +- .../source_amplitude/spec.json | 6 +- .../acceptance-test-config.yml | 2 +- .../source_apify_dataset/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_appfollow/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_appstore_singer/spec.json | 10 +- .../source-asana/acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_aws_cloudtrail/spec.json | 6 +- .../acceptance-test-config.yml | 2 +- .../source_azure_table/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_bamboo_hr/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_bigcommerce/spec.json | 2 +- .../src/main/resources/spec.json | 4 +- .../source-bing-ads/source_bing_ads/spec.json | 2 +- .../integration_tests/spec.json | 8 +- .../source_braintree/source.py | 2 +- .../source-braintree/source_braintree/spec.py | 6 +- .../source-cart/acceptance-test-config.yml | 2 +- .../source-cart/source_cart/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_chargebee/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-chargify/source_chargify/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_chartmogul/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_close_com/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../src/main/resources/expected_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_commercetools/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../source-db2/acceptance-test-config.yml | 2 +- .../source-db2/src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-dixa/acceptance-test-config.yml | 2 +- .../source-dixa/source_dixa/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_dockerhub/spec.yaml | 2 +- .../source-drift/acceptance-test-config.yml | 2 +- .../source-drift/source_drift/spec.json | 4 +- .../source-dv-360/acceptance-test-config.yml | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../src/main/resources/legacy_spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../src/main/resources/spec.json | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_exchange_rates/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 6 +- .../source_facebook_marketing/source.py | 4 +- .../source_facebook_marketing/spec.py | 2 +- .../acceptance-test-config.yml | 2 +- .../source_facebook_pages/spec.json | 2 +- .../source-faker/acceptance-test-config.yml | 2 +- .../source-fauna/acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 2 +- .../source-file/acceptance-test-config.yml | 2 +- .../sample_files/formats/yaml/demo.yaml | 2 +- .../source-file/source_file/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-firebolt/source_firebolt/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-flexport/source_flexport/spec.json | 2 +- .../source_freshcaller/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_freshdesk/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_freshsales/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_freshservice/spec.json | 2 +- .../source-gitlab/source_gitlab/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_glassfrog/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_google_analytics_v4/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_google_directory/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_google_search_console/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../google_sheets_source/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_greenhouse/spec.json | 2 +- .../source-harvest/source_harvest/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_hubplanner/spec.json | 2 +- .../source-hubspot/source_hubspot/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 6 +- .../source_instagram/source.py | 6 +- .../acceptance-test-config.yml | 2 +- .../source-intercom/source_intercom/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-iterable/source_iterable/spec.json | 4 +- .../source-jdbc/src/main/resources/spec.json | 2 +- .../source-jira/acceptance-test-config.yml | 2 +- .../source-jira/source_jira/spec.json | 6 +- .../source-kafka/src/main/resources/spec.json | 2 +- .../source-klaviyo/acceptance-test-config.yml | 2 +- .../source-klaviyo/source_klaviyo/spec.json | 6 +- .../acceptance-test-config.yml | 2 +- .../source_kustomer_singer/spec.json | 2 +- .../source-kyriba/acceptance-test-config.yml | 2 +- .../source-lemlist/source_lemlist/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 4 +- .../source_lever_hiring/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_linkedin_ads/source.py | 2 +- .../source_linkedin_ads/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_linkedin_pages/source.py | 2 +- .../acceptance-test-config.yml | 2 +- .../source_linnworks/spec.json | 2 +- .../source-looker/acceptance-test-config.yml | 2 +- .../source-looker/source_looker/spec.json | 4 +- .../source_mailchimp/spec.json | 4 +- .../source-mailgun/acceptance-test-config.yml | 2 +- .../source-mailgun/source_mailgun/spec.json | 2 +- .../source-marketo/acceptance-test-config.yml | 2 +- .../source-marketo/source_marketo/spec.json | 8 +- .../source-metabase/source_metabase/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_microsoft_teams/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-mixpanel/source_mixpanel/spec.json | 2 +- .../source-monday/acceptance-test-config.yml | 2 +- .../source-monday/source_monday/spec.json | 2 +- .../resources/expected_spec.json | 4 +- .../src/main/resources/spec.json | 6 +- .../connectors/source-mongodb/lib/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../source-mssql/acceptance-test-config.yml | 2 +- .../source-mssql/src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-my-hours/source_my_hours/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../src/test/resources/expected_spec.json | 6 +- .../source-mysql/acceptance-test-config.yml | 2 +- .../source-mysql/src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-notion/acceptance-test-config.yml | 2 +- .../source-notion/source_notion/spec.json | 2 +- .../source-okta/source_okta/spec.json | 6 +- .../acceptance-test-config.yml | 2 +- .../source_onesignal/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../src/test/resources/expected_spec.json | 2 +- .../source-oracle/acceptance-test-config.yml | 2 +- .../src/main/resources/spec.json | 2 +- .../source-orb/acceptance-test-config.yml | 2 +- .../source-orbit/acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source-outreach/source_outreach/spec.json | 2 +- .../source-pardot/acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_paypal_transaction/spec.json | 2 +- .../source-paystack/source_paystack/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_persistiq/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_pinterest/spec.json | 2 +- .../source_pipedrive/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-plaid/acceptance-test-config.yml | 2 +- .../source-pokeapi/acceptance-test-config.yml | 2 +- .../source-pokeapi/source_pokeapi/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source-python-http-tutorial/build.gradle | 2 +- .../source_python_http_tutorial/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-qualaroo/source_qualaroo/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-recurly/acceptance-test-config.yml | 2 +- .../src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source-s3/acceptance-test-config.yml | 2 +- .../source-s3/integration_tests/spec.json | 4 +- .../connectors/source-s3/source_s3/source.py | 2 +- .../source_s3/source_files_abstract/source.py | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_salesloft/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_search_metrics/spec.json | 2 +- .../source-sendgrid/source_sendgrid/spec.json | 2 +- .../source-sentry/source_sentry/spec.json | 8 +- .../source-sftp/src/main/resources/spec.json | 2 +- .../source-shopify/source_shopify/spec.json | 2 +- .../source-shortio/acceptance-test-config.yml | 2 +- .../source-slack/source_slack/spec.json | 12 +- .../source_smartsheets/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_snapchat_marketing/spec.json | 2 +- .../src/main/resources/spec.json | 2 +- .../source-square/source_square/spec.json | 2 +- .../source-strava/acceptance-test-config.yml | 2 +- .../source-strava/source_strava/spec.json | 2 +- .../source-stripe/source_stripe/spec.yaml | 2 +- .../source_surveymonkey/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source-tempo/acceptance-test-config.yml | 2 +- .../source-tempo/source_tempo/spec.json | 2 +- .../source-tidb/src/main/resources/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 4 +- .../source_tiktok_marketing/source.py | 2 +- .../source_tiktok_marketing/spec.json | 4 +- .../source-timely/acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_tplcentral/spec.json | 2 +- .../source-trello/acceptance-test-config.yml | 2 +- .../source-trello/source_trello/spec.json | 2 +- .../source-twilio/source_twilio/spec.json | 2 +- .../source-typeform/source_typeform/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_us_census/spec.json | 2 +- .../source-webflow/acceptance-test-config.yml | 2 +- .../source-webflow/source_webflow/spec.yaml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_woocommerce/spec.json | 2 +- .../source-wrike/acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../acceptance-test-config.yml | 2 +- .../source_youtube_analytics/spec.json | 2 +- .../source_zendesk_chat/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_zendesk_sunshine/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_zendesk_support/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source_zendesk_talk/spec.json | 4 +- .../acceptance-test-config.yml | 2 +- .../source-zenloop/acceptance-test-config.yml | 2 +- .../source-zenloop/source_zenloop/spec.json | 2 +- .../acceptance-test-config.yml | 2 +- .../source_zoom_singer/spec.json | 4 +- .../source-zuora/acceptance-test-config.yml | 2 +- .../source-zuora/source_zuora/spec.json | 2 +- 342 files changed, 834 insertions(+), 828 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index d7be0aaddf86..71c2d7305fd1 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -2,7 +2,7 @@ destinationDefinitionId: b4c5d105-31fd-4817-96b6-cb923bfc04cb dockerRepository: airbyte/destination-azure-blob-storage dockerImageTag: 0.1.6 - documentationUrl: https://docs.airbyte.io/integrations/destinations/azureblobstorage + documentationUrl: https://docs.airbyte.com/integrations/destinations/azureblobstorage icon: azureblobstorage.svg resourceRequirements: jobSpecific: @@ -15,20 +15,20 @@ destinationDefinitionId: 0eeee7fb-518f-4045-bacc-9619e31c43ea dockerRepository: airbyte/destination-amazon-sqs dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/amazon-sqs + documentationUrl: https://docs.airbyte.com/integrations/destinations/amazon-sqs icon: amazonsqs.svg releaseStage: alpha - name: AWS Datalake destinationDefinitionId: 99878c90-0fbd-46d3-9d98-ffde879d17fc dockerRepository: airbyte/destination-aws-datalake dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/destinations/aws-datalake + documentationUrl: https://docs.airbyte.com/integrations/destinations/aws-datalake releaseStage: alpha - name: BigQuery destinationDefinitionId: 22f6c74f-5699-40ff-833c-4a879ea40133 dockerRepository: airbyte/destination-bigquery dockerImageTag: 1.2.4 - documentationUrl: https://docs.airbyte.io/integrations/destinations/bigquery + documentationUrl: https://docs.airbyte.com/integrations/destinations/bigquery icon: bigquery.svg resourceRequirements: jobSpecific: @@ -41,7 +41,7 @@ destinationDefinitionId: 079d5540-f236-4294-ba7c-ade8fd918496 dockerRepository: airbyte/destination-bigquery-denormalized dockerImageTag: 1.2.4 - documentationUrl: https://docs.airbyte.io/integrations/destinations/bigquery + documentationUrl: https://docs.airbyte.com/integrations/destinations/bigquery icon: bigquery.svg resourceRequirements: jobSpecific: @@ -54,67 +54,67 @@ destinationDefinitionId: 707456df-6f4f-4ced-b5c6-03f73bcad1c5 dockerRepository: airbyte/destination-cassandra dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/destinations/cassandra + documentationUrl: https://docs.airbyte.com/integrations/destinations/cassandra icon: cassandra.svg releaseStage: alpha - name: Chargify (Keen) destinationDefinitionId: 81740ce8-d764-4ea7-94df-16bb41de36ae dockerRepository: airbyte/destination-keen dockerImageTag: 0.2.4 - documentationUrl: https://docs.airbyte.io/integrations/destinations/keen + documentationUrl: https://docs.airbyte.com/integrations/destinations/keen icon: chargify.svg releaseStage: alpha - name: Clickhouse destinationDefinitionId: ce0d828e-1dc4-496c-b122-2da42e637e48 dockerRepository: airbyte/destination-clickhouse dockerImageTag: 0.2.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/clickhouse + documentationUrl: https://docs.airbyte.com/integrations/destinations/clickhouse releaseStage: alpha - name: Cloudflare R2 destinationDefinitionId: 0fb07be9-7c3b-4336-850d-5efc006152ee dockerRepository: airbyte/destination-r2 dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/r2 + documentationUrl: https://docs.airbyte.com/integrations/destinations/r2 icon: cloudflare-r2.svg releaseStage: alpha - name: Databricks Lakehouse destinationDefinitionId: 072d5540-f236-4294-ba7c-ade8fd918496 dockerRepository: airbyte/destination-databricks dockerImageTag: 0.2.6 - documentationUrl: https://docs.airbyte.io/integrations/destinations/databricks + documentationUrl: https://docs.airbyte.com/integrations/destinations/databricks icon: databricks.svg releaseStage: alpha - name: DynamoDB destinationDefinitionId: 8ccd8909-4e99-4141-b48d-4984b70b2d89 dockerRepository: airbyte/destination-dynamodb dockerImageTag: 0.1.5 - documentationUrl: https://docs.airbyte.io/integrations/destinations/dynamodb + documentationUrl: https://docs.airbyte.com/integrations/destinations/dynamodb icon: dynamodb.svg releaseStage: alpha - name: E2E Testing destinationDefinitionId: 2eb65e87-983a-4fd7-b3e3-9d9dc6eb8537 dockerRepository: airbyte/destination-e2e-test dockerImageTag: 0.2.4 - documentationUrl: https://docs.airbyte.io/integrations/destinations/e2e-test + documentationUrl: https://docs.airbyte.com/integrations/destinations/e2e-test icon: airbyte.svg - destinationDefinitionId: 68f351a7-2745-4bef-ad7f-996b8e51bb8c name: ElasticSearch dockerRepository: airbyte/destination-elasticsearch dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/destinations/elasticsearch + documentationUrl: https://docs.airbyte.com/integrations/destinations/elasticsearch icon: elasticsearch.svg releaseStage: alpha - name: Firebolt destinationDefinitionId: 18081484-02a5-4662-8dba-b270b582f321 dockerRepository: airbyte/destination-firebolt dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/firebolt + documentationUrl: https://docs.airbyte.com/integrations/destinations/firebolt releaseStage: alpha - name: Google Cloud Storage (GCS) destinationDefinitionId: ca8f6566-e555-4b40-943a-545bf123117a dockerRepository: airbyte/destination-gcs dockerImageTag: 0.2.11 - documentationUrl: https://docs.airbyte.io/integrations/destinations/gcs + documentationUrl: https://docs.airbyte.com/integrations/destinations/gcs icon: googlecloudstorage.svg resourceRequirements: jobSpecific: @@ -127,119 +127,119 @@ destinationDefinitionId: 27dc7500-6d1b-40b1-8b07-e2f2aea3c9f4 dockerRepository: airbyte/destination-firestore dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/destinations/firestore + documentationUrl: https://docs.airbyte.com/integrations/destinations/firestore icon: firestore.svg releaseStage: alpha - name: Google PubSub destinationDefinitionId: 356668e2-7e34-47f3-a3b0-67a8a481b692 dockerRepository: airbyte/destination-pubsub dockerImageTag: 0.1.6 - documentationUrl: https://docs.airbyte.io/integrations/destinations/pubsub + documentationUrl: https://docs.airbyte.com/integrations/destinations/pubsub icon: googlepubsub.svg releaseStage: alpha - name: Kafka destinationDefinitionId: 9f760101-60ae-462f-9ee6-b7a9dafd454d dockerRepository: airbyte/destination-kafka dockerImageTag: 0.1.10 - documentationUrl: https://docs.airbyte.io/integrations/destinations/kafka + documentationUrl: https://docs.airbyte.com/integrations/destinations/kafka icon: kafka.svg releaseStage: alpha - name: Kinesis destinationDefinitionId: 6d1d66d4-26ab-4602-8d32-f85894b04955 dockerRepository: airbyte/destination-kinesis dockerImageTag: 0.1.5 - documentationUrl: https://docs.airbyte.io/integrations/destinations/kinesis + documentationUrl: https://docs.airbyte.com/integrations/destinations/kinesis icon: kinesis.svg releaseStage: alpha - name: Local CSV destinationDefinitionId: 8be1cf83-fde1-477f-a4ad-318d23c9f3c6 dockerRepository: airbyte/destination-csv dockerImageTag: 0.2.10 - documentationUrl: https://docs.airbyte.io/integrations/destinations/local-csv + documentationUrl: https://docs.airbyte.com/integrations/destinations/local-csv icon: file.svg releaseStage: alpha - name: Local JSON destinationDefinitionId: a625d593-bba5-4a1c-a53d-2d246268a816 dockerRepository: airbyte/destination-local-json dockerImageTag: 0.2.11 - documentationUrl: https://docs.airbyte.io/integrations/destinations/local-json + documentationUrl: https://docs.airbyte.com/integrations/destinations/local-json icon: file.svg releaseStage: alpha - name: MQTT destinationDefinitionId: f3802bc4-5406-4752-9e8d-01e504ca8194 dockerRepository: airbyte/destination-mqtt dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/destinations/mqtt + documentationUrl: https://docs.airbyte.com/integrations/destinations/mqtt icon: mqtt.svg releaseStage: alpha - name: MS SQL Server destinationDefinitionId: d4353156-9217-4cad-8dd7-c108fd4f74cf dockerRepository: airbyte/destination-mssql dockerImageTag: 0.1.20 - documentationUrl: https://docs.airbyte.io/integrations/destinations/mssql + documentationUrl: https://docs.airbyte.com/integrations/destinations/mssql icon: mssql.svg releaseStage: alpha - name: MeiliSearch destinationDefinitionId: af7c921e-5892-4ff2-b6c1-4a5ab258fb7e dockerRepository: airbyte/destination-meilisearch dockerImageTag: 0.2.13 - documentationUrl: https://docs.airbyte.io/integrations/destinations/meilisearch + documentationUrl: https://docs.airbyte.com/integrations/destinations/meilisearch icon: meilisearch.svg releaseStage: alpha - name: MongoDB destinationDefinitionId: 8b746512-8c2e-6ac1-4adc-b59faafd473c dockerRepository: airbyte/destination-mongodb dockerImageTag: 0.1.7 - documentationUrl: https://docs.airbyte.io/integrations/destinations/mongodb + documentationUrl: https://docs.airbyte.com/integrations/destinations/mongodb icon: mongodb.svg releaseStage: alpha - name: MySQL destinationDefinitionId: ca81ee7c-3163-4246-af40-094cc31e5e42 dockerRepository: airbyte/destination-mysql dockerImageTag: 0.1.20 - documentationUrl: https://docs.airbyte.io/integrations/destinations/mysql + documentationUrl: https://docs.airbyte.com/integrations/destinations/mysql icon: mysql.svg releaseStage: alpha - name: Oracle destinationDefinitionId: 3986776d-2319-4de9-8af8-db14c0996e72 dockerRepository: airbyte/destination-oracle dockerImageTag: 0.1.19 - documentationUrl: https://docs.airbyte.io/integrations/destinations/oracle + documentationUrl: https://docs.airbyte.com/integrations/destinations/oracle icon: oracle.svg releaseStage: alpha - name: Postgres destinationDefinitionId: 25c5221d-dce2-4163-ade9-739ef790f503 dockerRepository: airbyte/destination-postgres dockerImageTag: 0.3.26 - documentationUrl: https://docs.airbyte.io/integrations/destinations/postgres + documentationUrl: https://docs.airbyte.com/integrations/destinations/postgres icon: postgresql.svg releaseStage: alpha - name: Pulsar destinationDefinitionId: 2340cbba-358e-11ec-8d3d-0242ac130203 dockerRepository: airbyte/destination-pulsar dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/destinations/pulsar + documentationUrl: https://docs.airbyte.com/integrations/destinations/pulsar icon: pulsar.svg releaseStage: alpha - name: RabbitMQ destinationDefinitionId: e06ad785-ad6f-4647-b2e8-3027a5c59454 dockerRepository: airbyte/destination-rabbitmq dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/destinations/rabbitmq + documentationUrl: https://docs.airbyte.com/integrations/destinations/rabbitmq icon: pulsar.svg releaseStage: alpha - name: Redis destinationDefinitionId: d4d3fef9-e319-45c2-881a-bd02ce44cc9f dockerRepository: airbyte/destination-redis dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/destinations/redis + documentationUrl: https://docs.airbyte.com/integrations/destinations/redis icon: redis.svg releaseStage: alpha - name: Redshift destinationDefinitionId: f7a7d195-377f-cf5b-70a5-be6b819019dc dockerRepository: airbyte/destination-redshift dockerImageTag: 0.3.50 - documentationUrl: https://docs.airbyte.io/integrations/destinations/redshift + documentationUrl: https://docs.airbyte.com/integrations/destinations/redshift icon: redshift.svg resourceRequirements: jobSpecific: @@ -252,13 +252,13 @@ destinationDefinitionId: 2c9d93a7-9a17-4789-9de9-f46f0097eb70 dockerRepository: airbyte/destination-rockset dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/destinations/rockset + documentationUrl: https://docs.airbyte.com/integrations/destinations/rockset releaseStage: alpha - name: S3 destinationDefinitionId: 4816b78f-1489-44c1-9060-4b19d5fa9362 dockerRepository: airbyte/destination-s3 dockerImageTag: 0.3.16 - documentationUrl: https://docs.airbyte.io/integrations/destinations/s3 + documentationUrl: https://docs.airbyte.com/integrations/destinations/s3 icon: s3.svg resourceRequirements: jobSpecific: @@ -271,14 +271,14 @@ destinationDefinitionId: e9810f61-4bab-46d2-bb22-edfc902e0644 dockerRepository: airbyte/destination-sftp-json dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/sftp-json + documentationUrl: https://docs.airbyte.com/integrations/destinations/sftp-json icon: sftp.svg releaseStage: alpha - name: Snowflake destinationDefinitionId: 424892c4-daac-4491-b35d-c6688ba547ba dockerRepository: airbyte/destination-snowflake dockerImageTag: 0.4.38 - documentationUrl: https://docs.airbyte.io/integrations/destinations/snowflake + documentationUrl: https://docs.airbyte.com/integrations/destinations/snowflake icon: snowflake.svg resourceRequirements: jobSpecific: @@ -291,41 +291,41 @@ destinationDefinitionId: 294a4790-429b-40ae-9516-49826b9702e1 dockerRepository: airbyte/destination-mariadb-columnstore dockerImageTag: 0.1.7 - documentationUrl: https://docs.airbyte.io/integrations/destinations/mariadb-columnstore + documentationUrl: https://docs.airbyte.com/integrations/destinations/mariadb-columnstore icon: mariadb.svg releaseStage: alpha - name: Streamr destinationDefinitionId: eebd85cf-60b2-4af6-9ba0-edeca01437b0 dockerRepository: ghcr.io/devmate-cloud/streamr-airbyte-connectors dockerImageTag: 0.0.1 - documentationUrl: https://docs.airbyte.io/integrations/destinations/streamr + documentationUrl: https://docs.airbyte.com/integrations/destinations/streamr icon: streamr.svg releaseStage: alpha - name: Scylla destinationDefinitionId: 3dc6f384-cd6b-4be3-ad16-a41450899bf0 dockerRepository: airbyte/destination-scylla dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/destinations/scylla + documentationUrl: https://docs.airbyte.com/integrations/destinations/scylla icon: scylla.svg releaseStage: alpha - name: Google Sheets destinationDefinitionId: a4cbd2d1-8dbe-4818-b8bc-b90ad782d12a dockerRepository: airbyte/destination-google-sheets dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/destinations/google-sheets + documentationUrl: https://docs.airbyte.com/integrations/destinations/google-sheets icon: google-sheets.svg releaseStage: alpha - name: Local SQLite destinationDefinitionId: b76be0a6-27dc-4560-95f6-2623da0bd7b6 dockerRepository: airbyte/destination-sqlite dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/local-sqlite + documentationUrl: https://docs.airbyte.com/integrations/destinations/local-sqlite icon: sqlite.svg releaseStage: alpha - name: TiDB destinationDefinitionId: 06ec60c7-7468-45c0-91ac-174f6e1a788b dockerRepository: airbyte/destination-tidb dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/tidb + documentationUrl: https://docs.airbyte.com/integrations/destinations/tidb icon: tidb.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index a1eab9c79eb1..0c3794a58c19 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -3,7 +3,7 @@ --- - dockerImage: "airbyte/destination-azure-blob-storage:0.1.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/azureblobstorage" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/azureblobstorage" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "AzureBlobStorage Destination Spec" @@ -92,7 +92,7 @@ - "append" - dockerImage: "airbyte/destination-amazon-sqs:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/amazon-sqs" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/amazon-sqs" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination Amazon Sqs" @@ -190,7 +190,7 @@ - "append" - dockerImage: "airbyte/destination-aws-datalake:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/aws-datalake" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/aws-datalake" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "AWS Datalake Destination Spec" @@ -287,7 +287,7 @@ - "append" - dockerImage: "airbyte/destination-bigquery:1.2.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/bigquery" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/bigquery" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "BigQuery Destination Spec" @@ -497,7 +497,7 @@ - "append_dedup" - dockerImage: "airbyte/destination-bigquery-denormalized:1.2.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/bigquery" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/bigquery" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "BigQuery Denormalized Typed Struct Destination Spec" @@ -695,7 +695,7 @@ - "append" - dockerImage: "airbyte/destination-cassandra:0.1.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/cassandra" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/cassandra" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Cassandra Destination Spec" @@ -760,7 +760,7 @@ - "append" - dockerImage: "airbyte/destination-keen:0.2.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/keen" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/keen" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Keen Spec" @@ -799,7 +799,7 @@ - "append" - dockerImage: "airbyte/destination-clickhouse:0.2.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/clickhouse" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/clickhouse" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "ClickHouse Destination Spec" @@ -965,7 +965,7 @@ - "append_dedup" - dockerImage: "airbyte/destination-r2:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/r2" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/r2" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "R2 Destination Spec" @@ -1241,7 +1241,7 @@ - "append" - dockerImage: "airbyte/destination-databricks:0.2.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/databricks" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/databricks" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Databricks Lakehouse Destination Spec" @@ -1416,7 +1416,7 @@ - "append" - dockerImage: "airbyte/destination-dynamodb:0.1.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/dynamodb" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/dynamodb" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "DynamoDB Destination Spec" @@ -1497,7 +1497,7 @@ - "append" - dockerImage: "airbyte/destination-e2e-test:0.2.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/e2e-test" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/e2e-test" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "E2E Test Destination Spec" @@ -1656,7 +1656,7 @@ - "append" - dockerImage: "airbyte/destination-elasticsearch:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/elasticsearch" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/elasticsearch" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Elasticsearch Connection Configuration" @@ -1742,7 +1742,7 @@ supportsNamespaces: true - dockerImage: "airbyte/destination-firebolt:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/firebolt" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/firebolt" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Firebolt Spec" @@ -1838,7 +1838,7 @@ - "append" - dockerImage: "airbyte/destination-gcs:0.2.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/gcs" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/gcs" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "GCS Destination Spec" @@ -2217,7 +2217,7 @@ $schema: "http://json-schema.org/draft-07/schema#" - dockerImage: "airbyte/destination-firestore:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/firestore" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/firestore" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination Google Firestore" @@ -2234,7 +2234,7 @@ credentials_json: type: "string" description: "The contents of the JSON service account key. Check out the\ - \ docs if you need help generating this key. Default credentials will\ \ be used if this field is left empty." title: "Credentials JSON" @@ -2247,7 +2247,7 @@ - "overwrite" - dockerImage: "airbyte/destination-pubsub:0.1.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/pubsub" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/pubsub" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Google PubSub Destination Spec" @@ -2269,7 +2269,7 @@ credentials_json: type: "string" description: "The contents of the JSON service account key. Check out the\ - \ docs if you need help generating this key." title: "Credentials JSON" airbyte_secret: true @@ -2280,7 +2280,7 @@ - "append" - dockerImage: "airbyte/destination-kafka:0.1.10" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/kafka" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/kafka" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Kafka Destination Spec" @@ -2571,7 +2571,7 @@ - "append" - dockerImage: "airbyte/destination-kinesis:0.1.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/kinesis" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/kinesis" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Kinesis Destination Spec" @@ -2635,7 +2635,7 @@ - "append" - dockerImage: "airbyte/destination-csv:0.2.10" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/local-csv" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/local-csv" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "CSV Destination Spec" @@ -2648,7 +2648,7 @@ description: "Path to the directory where csv files will be written. The\ \ destination uses the local mount \"/local\" and any data files will\ \ be placed inside that local mount. For more information check out our\ - \ docs" type: "string" examples: @@ -2661,7 +2661,7 @@ - "append" - dockerImage: "airbyte/destination-local-json:0.2.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/local-json" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/local-json" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Local Json Destination Spec" @@ -2673,7 +2673,7 @@ destination_path: description: "Path to the directory where json files will be written. The\ \ files will be placed inside that local mount. For more information check\ - \ out our docs" title: "Destination Path" type: "string" @@ -2687,7 +2687,7 @@ - "append" - dockerImage: "airbyte/destination-mqtt:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/mqtt" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/mqtt" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MQTT Destination Spec" @@ -2794,7 +2794,7 @@ - "append" - dockerImage: "airbyte/destination-mssql:0.1.20" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/mssql" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/mssql" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MS SQL Server Destination Spec" @@ -3016,7 +3016,7 @@ - "append_dedup" - dockerImage: "airbyte/destination-meilisearch:0.2.13" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/meilisearch" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/meilisearch" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MeiliSearch Destination Spec" @@ -3033,7 +3033,7 @@ api_key: title: "API Key" airbyte_secret: true - description: "MeiliSearch API Key. See the docs for more information on how to obtain this key." type: "string" order: 1 @@ -3045,7 +3045,7 @@ - "append" - dockerImage: "airbyte/destination-mongodb:0.1.7" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/mongodb" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/mongodb" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MongoDB Destination Spec" @@ -3092,7 +3092,7 @@ type: "boolean" description: "Indicates whether TLS encryption protocol will be used\ \ to connect to MongoDB. It is recommended to use TLS connection\ - \ if possible. For more information see documentation." default: false order: 2 @@ -3183,7 +3183,7 @@ - "append" - dockerImage: "airbyte/destination-mysql:0.1.20" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/mysql" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/mysql" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MySQL Destination Spec" @@ -3348,7 +3348,7 @@ - "append" - dockerImage: "airbyte/destination-oracle:0.1.19" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/oracle" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/oracle" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Oracle Destination Spec" @@ -3580,7 +3580,7 @@ - "append" - dockerImage: "airbyte/destination-postgres:0.3.26" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/postgres" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/postgres" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Postgres Destination Spec" @@ -3901,7 +3901,7 @@ - "append_dedup" - dockerImage: "airbyte/destination-pulsar:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/pulsar" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/pulsar" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Pulsar Destination Spec" @@ -4049,7 +4049,7 @@ - "append" - dockerImage: "airbyte/destination-rabbitmq:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/rabbitmq" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/rabbitmq" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination Rabbitmq" @@ -4093,7 +4093,7 @@ - "append" - dockerImage: "airbyte/destination-redis:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/redis" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/redis" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Redis Destination Spec" @@ -4148,7 +4148,7 @@ - "append" - dockerImage: "airbyte/destination-redshift:0.3.50" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/redshift" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/redshift" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Redshift Destination Spec" @@ -4369,7 +4369,7 @@ - "append_dedup" - dockerImage: "airbyte/destination-rockset:0.1.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/rockset" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/rockset" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Rockset Destination Spec" @@ -4412,7 +4412,7 @@ - "overwrite" - dockerImage: "airbyte/destination-s3:0.3.16" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/s3" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/s3" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "S3 Destination Spec" @@ -4789,7 +4789,7 @@ - "append" - dockerImage: "airbyte/destination-sftp-json:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/sftp-json" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/sftp-json" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination SFTP JSON" @@ -4842,7 +4842,7 @@ - "append" - dockerImage: "airbyte/destination-snowflake:0.4.38" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/snowflake" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/snowflake" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Snowflake Destination Spec" @@ -4960,7 +4960,7 @@ type: "string" title: "Private Key" description: "RSA Private key to use for Snowflake connection. See\ - \ the docs for more information on how to obtain this key." multiline: true airbyte_secret: true @@ -5299,7 +5299,7 @@ - "client_secret" - dockerImage: "airbyte/destination-mariadb-columnstore:0.1.7" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/mariadb-columnstore" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/mariadb-columnstore" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MariaDB Columnstore Destination Spec" @@ -5458,7 +5458,7 @@ - "append" - dockerImage: "ghcr.io/devmate-cloud/streamr-airbyte-connectors:0.0.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/streamr" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/streamr" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination Streamr" @@ -5485,7 +5485,7 @@ - "append_dedup" - dockerImage: "airbyte/destination-scylla:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/scylla" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/scylla" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Scylla Destination Spec" @@ -5542,7 +5542,7 @@ - "append" - dockerImage: "airbyte/destination-google-sheets:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/google-sheets" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/google-sheets" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination Google Sheets" @@ -5603,7 +5603,7 @@ - - "refresh_token" - dockerImage: "airbyte/destination-sqlite:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/sqlite" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/sqlite" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Destination Sqlite" @@ -5615,7 +5615,7 @@ destination_path: type: "string" description: "Path to the sqlite.db file. The file will be placed inside\ - \ that local mount. For more information check out our docs" example: "/local/sqlite.db" supportsIncremental: true @@ -5626,7 +5626,7 @@ - "append" - dockerImage: "airbyte/destination-tidb:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/tidb" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/tidb" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "TiDB Destination Spec" diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 7fd1f6e86376..077d3e164de9 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -2,7 +2,7 @@ sourceDefinitionId: d3b7fa46-111b-419a-998a-d7f046f6d66d dockerRepository: airbyte/source-adjust dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/adjust + documentationUrl: https://docs.airbyte.com/integrations/sources/adjust icon: adjust.svg sourceType: api releaseStage: alpha @@ -10,7 +10,7 @@ sourceDefinitionId: 14c6e7ea-97ed-4f5e-a7b5-25e9a80b8212 dockerRepository: airbyte/source-airtable dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/airtable + documentationUrl: https://docs.airbyte.com/integrations/sources/airtable icon: airtable.svg sourceType: api releaseStage: alpha @@ -18,7 +18,7 @@ sourceDefinitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 dockerRepository: airbyte/source-alloydb dockerImageTag: 1.0.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/alloydb + documentationUrl: https://docs.airbyte.com/integrations/sources/alloydb icon: alloydb.svg sourceType: database releaseStage: generally_available @@ -26,7 +26,7 @@ sourceDefinitionId: 6ff047c0-f5d5-4ce5-8c81-204a830fa7e1 dockerRepository: airbyte/source-aws-cloudtrail dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/aws-cloudtrail + documentationUrl: https://docs.airbyte.com/integrations/sources/aws-cloudtrail icon: awscloudtrail.svg sourceType: api releaseStage: alpha @@ -34,7 +34,7 @@ sourceDefinitionId: c6b0a29e-1da9-4512-9002-7bfd0cba2246 dockerRepository: airbyte/source-amazon-ads dockerImageTag: 0.1.22 - documentationUrl: https://docs.airbyte.io/integrations/sources/amazon-ads + documentationUrl: https://docs.airbyte.com/integrations/sources/amazon-ads icon: amazonads.svg sourceType: api releaseStage: generally_available @@ -43,21 +43,21 @@ dockerRepository: airbyte/source-amazon-seller-partner dockerImageTag: 0.2.27 sourceType: api - documentationUrl: https://docs.airbyte.io/integrations/sources/amazon-seller-partner + documentationUrl: https://docs.airbyte.com/integrations/sources/amazon-seller-partner icon: amazonsellerpartner.svg releaseStage: alpha - name: Amazon SQS sourceDefinitionId: 983fd355-6bf3-4709-91b5-37afa391eeb6 dockerRepository: airbyte/source-amazon-sqs dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/amazon-sqs + documentationUrl: https://docs.airbyte.com/integrations/sources/amazon-sqs sourceType: api releaseStage: alpha - name: Amplitude sourceDefinitionId: fa9f58c6-2d03-4237-aaa4-07d75e0c1396 dockerRepository: airbyte/source-amplitude dockerImageTag: 0.1.15 - documentationUrl: https://docs.airbyte.io/integrations/sources/amplitude + documentationUrl: https://docs.airbyte.com/integrations/sources/amplitude icon: amplitude.svg sourceType: api releaseStage: generally_available @@ -65,7 +65,7 @@ sourceDefinitionId: 47f17145-fe20-4ef5-a548-e29b048adf84 dockerRepository: airbyte/source-apify-dataset dockerImageTag: 0.1.11 - documentationUrl: https://docs.airbyte.io/integrations/sources/apify-dataset + documentationUrl: https://docs.airbyte.com/integrations/sources/apify-dataset icon: apify.svg sourceType: api releaseStage: alpha @@ -73,7 +73,7 @@ sourceDefinitionId: b4375641-e270-41d3-9c20-4f9cecad87a8 dockerRepository: airbyte/source-appfollow dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/appfollow + documentationUrl: https://docs.airbyte.com/integrations/sources/appfollow icon: appfollow.svg sourceType: api releaseStage: alpha @@ -81,7 +81,7 @@ sourceDefinitionId: 2af123bf-0aaf-4e0d-9784-cb497f23741a dockerRepository: airbyte/source-appstore-singer dockerImageTag: 0.2.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/appstore + documentationUrl: https://docs.airbyte.com/integrations/sources/appstore icon: appstore.svg sourceType: api releaseStage: alpha @@ -89,7 +89,7 @@ sourceDefinitionId: d0243522-dccf-4978-8ba0-37ed47a0bdbf dockerRepository: airbyte/source-asana dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/asana + documentationUrl: https://docs.airbyte.com/integrations/sources/asana icon: asana.svg sourceType: api releaseStage: beta @@ -97,7 +97,7 @@ sourceDefinitionId: 798ae795-5189-42b6-b64e-3cb91db93338 dockerRepository: airbyte/source-azure-table dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/azure-table + documentationUrl: https://docs.airbyte.com/integrations/sources/azure-table icon: azureblobstorage.svg sourceType: database releaseStage: alpha @@ -105,7 +105,7 @@ sourceDefinitionId: 90916976-a132-4ce9-8bce-82a03dd58788 dockerRepository: airbyte/source-bamboo-hr dockerImageTag: 0.2.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/bamboo-hr + documentationUrl: https://docs.airbyte.com/integrations/sources/bamboo-hr icon: bamboohr.svg sourceType: api releaseStage: alpha @@ -113,7 +113,7 @@ sourceDefinitionId: 59c5501b-9f95-411e-9269-7143c939adbd dockerRepository: airbyte/source-bigcommerce dockerImageTag: 0.1.7 - documentationUrl: https://docs.airbyte.io/integrations/sources/bigcommerce + documentationUrl: https://docs.airbyte.com/integrations/sources/bigcommerce icon: bigcommerce.svg sourceType: api releaseStage: alpha @@ -121,7 +121,7 @@ sourceDefinitionId: bfd1ddf8-ae8a-4620-b1d7-55597d2ba08c dockerRepository: airbyte/source-bigquery dockerImageTag: 0.2.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/bigquery + documentationUrl: https://docs.airbyte.com/integrations/sources/bigquery icon: bigquery.svg sourceType: database releaseStage: alpha @@ -129,7 +129,7 @@ sourceDefinitionId: 47f25999-dd5e-4636-8c39-e7cea2453331 dockerRepository: airbyte/source-bing-ads dockerImageTag: 0.1.15 - documentationUrl: https://docs.airbyte.io/integrations/sources/bing-ads + documentationUrl: https://docs.airbyte.com/integrations/sources/bing-ads icon: bingads.svg sourceType: api releaseStage: generally_available @@ -137,7 +137,7 @@ sourceDefinitionId: 63cea06f-1c75-458d-88fe-ad48c7cb27fd dockerRepository: airbyte/source-braintree dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/braintree + documentationUrl: https://docs.airbyte.com/integrations/sources/braintree icon: braintree.svg sourceType: api releaseStage: alpha @@ -145,7 +145,7 @@ sourceDefinitionId: bb1a6d31-6879-4819-a2bd-3eed299ea8e2 dockerRepository: airbyte/source-cart dockerImageTag: 0.2.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/cart + documentationUrl: https://docs.airbyte.com/integrations/sources/cart icon: cart.svg sourceType: api releaseStage: alpha @@ -153,7 +153,7 @@ sourceDefinitionId: 686473f1-76d9-4994-9cc7-9b13da46147c dockerRepository: airbyte/source-chargebee dockerImageTag: 0.1.15 - documentationUrl: https://docs.airbyte.io/integrations/sources/chargebee + documentationUrl: https://docs.airbyte.com/integrations/sources/chargebee icon: chargebee.svg sourceType: api releaseStage: generally_available @@ -161,7 +161,7 @@ sourceDefinitionId: 9b2d3607-7222-4709-9fa2-c2abdebbdd88 dockerRepository: airbyte/source-chargify dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/chargify + documentationUrl: https://docs.airbyte.com/integrations/sources/chargify icon: chargify.svg sourceType: api releaseStage: alpha @@ -169,7 +169,7 @@ sourceDefinitionId: b6604cbd-1b12-4c08-8767-e140d0fb0877 dockerRepository: airbyte/source-chartmogul dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/chartmogul + documentationUrl: https://docs.airbyte.com/integrations/sources/chartmogul icon: chartmogul.svg sourceType: api releaseStage: alpha @@ -177,7 +177,7 @@ sourceDefinitionId: bad83517-5e54-4a3d-9b53-63e85fbd4d7c dockerRepository: airbyte/source-clickhouse dockerImageTag: 0.1.14 - documentationUrl: https://docs.airbyte.io/integrations/sources/clickhouse + documentationUrl: https://docs.airbyte.com/integrations/sources/clickhouse icon: cliskhouse.svg sourceType: database releaseStage: alpha @@ -185,7 +185,7 @@ sourceDefinitionId: dfffecb7-9a13-43e9-acdc-b92af7997ca9 dockerRepository: airbyte/source-close-com dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/close-com + documentationUrl: https://docs.airbyte.com/integrations/sources/close-com icon: close.svg sourceType: api releaseStage: alpha @@ -193,7 +193,7 @@ sourceDefinitionId: 9fa5862c-da7c-11eb-8d19-0242ac130003 dockerRepository: airbyte/source-cockroachdb dockerImageTag: 0.1.18 - documentationUrl: https://docs.airbyte.io/integrations/sources/cockroachdb + documentationUrl: https://docs.airbyte.com/integrations/sources/cockroachdb icon: cockroachdb.svg sourceType: database releaseStage: alpha @@ -201,7 +201,7 @@ sourceDefinitionId: 008b2e26-11a3-11ec-82a8-0242ac130003 dockerRepository: airbyte/source-commercetools dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/commercetools + documentationUrl: https://docs.airbyte.com/integrations/sources/commercetools icon: commercetools.svg sourceType: api releaseStage: alpha @@ -209,7 +209,7 @@ sourceDefinitionId: cf40a7f8-71f8-45ce-a7fa-fca053e4028c dockerRepository: airbyte/source-confluence dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/confluence + documentationUrl: https://docs.airbyte.com/integrations/sources/confluence icon: confluence.svg sourceType: api releaseStage: alpha @@ -217,7 +217,7 @@ sourceDefinitionId: c47d6804-8b98-449f-970a-5ddb5cb5d7aa dockerRepository: farosai/airbyte-customer-io-source dockerImageTag: 0.1.23 - documentationUrl: https://docs.airbyte.io/integrations/sources/customer-io + documentationUrl: https://docs.airbyte.com/integrations/sources/customer-io icon: customer-io.svg sourceType: api releaseStage: alpha @@ -225,7 +225,7 @@ sourceDefinitionId: cc88c43f-6f53-4e8a-8c4d-b284baaf9635 dockerRepository: airbyte/source-delighted dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/delighted + documentationUrl: https://docs.airbyte.com/integrations/sources/delighted icon: delighted.svg sourceType: api releaseStage: alpha @@ -233,7 +233,7 @@ sourceDefinitionId: 0b5c867e-1b12-4d02-ab74-97b2184ff6d7 dockerRepository: airbyte/source-dixa dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/dixa + documentationUrl: https://docs.airbyte.com/integrations/sources/dixa icon: dixa.svg sourceType: api releaseStage: alpha @@ -241,7 +241,7 @@ sourceDefinitionId: 72d405a3-56d8-499f-a571-667c03406e43 dockerRepository: airbyte/source-dockerhub dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/dockerhub + documentationUrl: https://docs.airbyte.com/integrations/sources/dockerhub icon: dockerhub.svg sourceType: api releaseStage: alpha @@ -249,7 +249,7 @@ sourceDefinitionId: 445831eb-78db-4b1f-8f1f-0d96ad8739e2 dockerRepository: airbyte/source-drift dockerImageTag: 0.2.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/drift + documentationUrl: https://docs.airbyte.com/integrations/sources/drift icon: drift.svg sourceType: api releaseStage: alpha @@ -257,14 +257,14 @@ sourceDefinitionId: 1356e1d9-977f-4057-ad4b-65f25329cf61 dockerRepository: airbyte/source-dv-360 dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/dv-360 + documentationUrl: https://docs.airbyte.com/integrations/sources/dv-360 sourceType: api releaseStage: alpha - name: E2E Testing sourceDefinitionId: d53f9084-fa6b-4a5a-976c-5b8392f4ad8a dockerRepository: airbyte/source-e2e-test dockerImageTag: 2.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/e2e-test + documentationUrl: https://docs.airbyte.com/integrations/sources/e2e-test icon: airbyte.svg sourceType: api releaseStage: alpha @@ -272,7 +272,7 @@ sourceDefinitionId: e2b40e36-aa0e-4bed-b41b-bcea6fa348b1 dockerRepository: airbyte/source-exchange-rates dockerImageTag: 1.2.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/exchangeratesapi + documentationUrl: https://docs.airbyte.com/integrations/sources/exchangeratesapi icon: exchangeratesapi.svg sourceType: api releaseStage: alpha @@ -280,7 +280,7 @@ sourceDefinitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c dockerRepository: airbyte/source-facebook-marketing dockerImageTag: 0.2.67 - documentationUrl: https://docs.airbyte.io/integrations/sources/facebook-marketing + documentationUrl: https://docs.airbyte.com/integrations/sources/facebook-marketing icon: facebook.svg sourceType: api releaseStage: generally_available @@ -311,7 +311,7 @@ sourceDefinitionId: 778daa7c-feaf-4db6-96f3-70fd645acc77 dockerRepository: airbyte/source-file dockerImageTag: 0.2.24 - documentationUrl: https://docs.airbyte.io/integrations/sources/file + documentationUrl: https://docs.airbyte.com/integrations/sources/file icon: file.svg sourceType: file releaseStage: beta @@ -319,19 +319,19 @@ sourceDefinitionId: 8a5d48f6-03bb-4038-a942-a8d3f175cca3 dockerRepository: airbyte/source-freshcaller dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/freshcaller + documentationUrl: https://docs.airbyte.com/integrations/sources/freshcaller - name: Flexport sourceDefinitionId: f95337f1-2ad1-4baf-922f-2ca9152de630 dockerRepository: airbyte/source-flexport dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/flexport + documentationUrl: https://docs.airbyte.com/integrations/sources/flexport sourceType: api releaseStage: alpha - name: Freshdesk sourceDefinitionId: ec4b9503-13cb-48ab-a4ab-6ade4be46567 dockerRepository: airbyte/source-freshdesk dockerImageTag: 0.3.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/freshdesk + documentationUrl: https://docs.airbyte.com/integrations/sources/freshdesk icon: freshdesk.svg sourceType: api releaseStage: generally_available @@ -339,7 +339,7 @@ sourceDefinitionId: eca08d79-7b92-4065-b7f3-79c14836ebe7 dockerRepository: airbyte/source-freshsales dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/freshsales + documentationUrl: https://docs.airbyte.com/integrations/sources/freshsales icon: freshsales.svg sourceType: api releaseStage: alpha @@ -347,7 +347,7 @@ sourceDefinitionId: 9bb85338-ea95-4c93-b267-6be89125b267 dockerRepository: airbyte/source-freshservice dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/freshservice + documentationUrl: https://docs.airbyte.com/integrations/sources/freshservice icon: freshservice.svg sourceType: api releaseStage: alpha @@ -355,7 +355,7 @@ sourceDefinitionId: ef69ef6e-aa7f-4af1-a01d-ef775033524e dockerRepository: airbyte/source-github dockerImageTag: 0.3.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/github + documentationUrl: https://docs.airbyte.com/integrations/sources/github icon: github.svg sourceType: api releaseStage: generally_available @@ -363,7 +363,7 @@ sourceDefinitionId: 5e6175e5-68e1-4c17-bff9-56103bbb0d80 dockerRepository: airbyte/source-gitlab dockerImageTag: 0.1.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/gitlab + documentationUrl: https://docs.airbyte.com/integrations/sources/gitlab icon: gitlab.svg sourceType: api releaseStage: alpha @@ -371,7 +371,7 @@ sourceDefinitionId: cf8ff320-6272-4faa-89e6-4402dc17e5d5 dockerRepository: airbyte/source-glassfrog dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/glassfrog + documentationUrl: https://docs.airbyte.com/integrations/sources/glassfrog icon: glassfrog.svg sourceType: api releaseStage: alpha @@ -379,7 +379,7 @@ sourceDefinitionId: 253487c0-2246-43ba-a21f-5116b20a2c50 dockerRepository: airbyte/source-google-ads dockerImageTag: 0.2.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/google-ads + documentationUrl: https://docs.airbyte.com/integrations/sources/google-ads icon: google-adwords.svg sourceType: api releaseStage: generally_available @@ -403,7 +403,7 @@ sourceDefinitionId: d19ae824-e289-4b14-995a-0632eb46d246 dockerRepository: airbyte/source-google-directory dockerImageTag: 0.1.9 - documentationUrl: https://docs.airbyte.io/integrations/sources/google-directory + documentationUrl: https://docs.airbyte.com/integrations/sources/google-directory icon: googledirectory.svg sourceType: api releaseStage: alpha @@ -411,7 +411,7 @@ sourceDefinitionId: eb4c9e00-db83-4d63-a386-39cfa91012a8 dockerRepository: airbyte/source-google-search-console dockerImageTag: 0.1.17 - documentationUrl: https://docs.airbyte.io/integrations/sources/google-search-console + documentationUrl: https://docs.airbyte.com/integrations/sources/google-search-console icon: googlesearchconsole.svg sourceType: api releaseStage: generally_available @@ -419,7 +419,7 @@ sourceDefinitionId: 71607ba1-c0ac-4799-8049-7f4b90dd50f7 dockerRepository: airbyte/source-google-sheets dockerImageTag: 0.2.19 - documentationUrl: https://docs.airbyte.io/integrations/sources/google-sheets + documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets icon: google-sheets.svg sourceType: file releaseStage: generally_available @@ -427,7 +427,7 @@ sourceDefinitionId: ed9dfefa-1bbc-419d-8c5e-4d78f0ef6734 dockerRepository: airbyte/source-google-workspace-admin-reports dockerImageTag: 0.1.8 - documentationUrl: https://docs.airbyte.io/integrations/sources/google-workspace-admin-reports + documentationUrl: https://docs.airbyte.com/integrations/sources/google-workspace-admin-reports icon: googleworkpace.svg sourceType: api releaseStage: alpha @@ -435,7 +435,7 @@ sourceDefinitionId: 59f1e50a-331f-4f09-b3e8-2e8d4d355f44 dockerRepository: airbyte/source-greenhouse dockerImageTag: 0.2.11 - documentationUrl: https://docs.airbyte.io/integrations/sources/greenhouse + documentationUrl: https://docs.airbyte.com/integrations/sources/greenhouse icon: greenhouse.svg sourceType: api releaseStage: generally_available @@ -443,7 +443,7 @@ sourceDefinitionId: 6fe89830-d04d-401b-aad6-6552ffa5c4af dockerRepository: farosai/airbyte-harness-source dockerImageTag: 0.1.23 - documentationUrl: https://docs.airbyte.io/integrations/sources/harness + documentationUrl: https://docs.airbyte.com/integrations/sources/harness icon: harness.svg sourceType: api releaseStage: alpha @@ -451,7 +451,7 @@ sourceDefinitionId: fe2b4084-3386-4d3b-9ad6-308f61a6f1e6 dockerRepository: airbyte/source-harvest dockerImageTag: 0.1.11 - documentationUrl: https://docs.airbyte.io/integrations/sources/harvest + documentationUrl: https://docs.airbyte.com/integrations/sources/harvest icon: harvest.svg sourceType: api releaseStage: generally_available @@ -459,21 +459,21 @@ sourceDefinitionId: 492b56d1-937c-462e-8076-21ad2031e784 dockerRepository: airbyte/source-hellobaton dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/hellobaton + documentationUrl: https://docs.airbyte.com/integrations/sources/hellobaton sourceType: api releaseStage: alpha - name: Hubplanner sourceDefinitionId: 8097ceb9-383f-42f6-9f92-d3fd4bcc7689 dockerRepository: airbyte/source-hubplanner dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/hubplanner + documentationUrl: https://docs.airbyte.com/integrations/sources/hubplanner sourceType: api releaseStage: alpha - name: HubSpot sourceDefinitionId: 36c891d9-4bd9-43ac-bad2-10e12756272c dockerRepository: airbyte/source-hubspot dockerImageTag: 0.2.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/hubspot + documentationUrl: https://docs.airbyte.com/integrations/sources/hubspot icon: hubspot.svg sourceType: api releaseStage: generally_available @@ -481,7 +481,7 @@ sourceDefinitionId: 447e0381-3780-4b46-bb62-00a4e3c8b8e2 dockerRepository: airbyte/source-db2 dockerImageTag: 0.1.16 - documentationUrl: https://docs.airbyte.io/integrations/sources/db2 + documentationUrl: https://docs.airbyte.com/integrations/sources/db2 icon: db2.svg sourceType: database releaseStage: alpha @@ -497,7 +497,7 @@ sourceDefinitionId: d8313939-3782-41b0-be29-b3ca20d8dd3a dockerRepository: airbyte/source-intercom dockerImageTag: 0.1.27 - documentationUrl: https://docs.airbyte.io/integrations/sources/intercom + documentationUrl: https://docs.airbyte.com/integrations/sources/intercom icon: intercom.svg sourceType: api releaseStage: generally_available @@ -505,7 +505,7 @@ sourceDefinitionId: 2e875208-0c0b-4ee4-9e92-1cb3156ea799 dockerRepository: airbyte/source-iterable dockerImageTag: 0.1.19 - documentationUrl: https://docs.airbyte.io/integrations/sources/iterable + documentationUrl: https://docs.airbyte.com/integrations/sources/iterable icon: iterable.svg sourceType: api releaseStage: generally_available @@ -513,7 +513,7 @@ sourceDefinitionId: d6f73702-d7a0-4e95-9758-b0fb1af0bfba dockerRepository: farosai/airbyte-jenkins-source dockerImageTag: 0.1.23 - documentationUrl: https://docs.airbyte.io/integrations/sources/jenkins + documentationUrl: https://docs.airbyte.com/integrations/sources/jenkins icon: jenkins.svg sourceType: api releaseStage: alpha @@ -521,7 +521,7 @@ sourceDefinitionId: 68e63de2-bb83-4c7e-93fa-a8a9051e3993 dockerRepository: airbyte/source-jira dockerImageTag: 0.2.21 - documentationUrl: https://docs.airbyte.io/integrations/sources/jira + documentationUrl: https://docs.airbyte.com/integrations/sources/jira icon: jira.svg sourceType: api releaseStage: alpha @@ -529,7 +529,7 @@ sourceDefinitionId: d917a47b-8537-4d0d-8c10-36a9928d4265 dockerRepository: airbyte/source-kafka dockerImageTag: 0.2.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/kafka + documentationUrl: https://docs.airbyte.com/integrations/sources/kafka icon: kafka.svg sourceType: database releaseStage: alpha @@ -537,7 +537,7 @@ sourceDefinitionId: 95e8cffd-b8c4-4039-968e-d32fb4a69bde dockerRepository: airbyte/source-klaviyo dockerImageTag: 0.1.10 - documentationUrl: https://docs.airbyte.io/integrations/sources/klaviyo + documentationUrl: https://docs.airbyte.com/integrations/sources/klaviyo icon: klaviyo.svg sourceType: api releaseStage: generally_available @@ -545,21 +545,21 @@ sourceDefinitionId: 547dc08e-ab51-421d-953b-8f3745201a8c dockerRepository: airbyte/source-kyriba dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/kyriba + documentationUrl: https://docs.airbyte.com/integrations/sources/kyriba sourceType: api releaseStage: alpha - name: Lemlist sourceDefinitionId: 789f8e7a-2d28-11ec-8d3d-0242ac130003 dockerRepository: airbyte/source-lemlist dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/lemlist + documentationUrl: https://docs.airbyte.com/integrations/sources/lemlist sourceType: api releaseStage: alpha - name: Lever Hiring sourceDefinitionId: 3981c999-bd7d-4afc-849b-e53dea90c948 dockerRepository: airbyte/source-lever-hiring dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/lever-hiring + documentationUrl: https://docs.airbyte.com/integrations/sources/lever-hiring icon: leverhiring.svg sourceType: api releaseStage: alpha @@ -567,7 +567,7 @@ sourceDefinitionId: 137ece28-5434-455c-8f34-69dc3782f451 dockerRepository: airbyte/source-linkedin-ads dockerImageTag: 0.1.11 - documentationUrl: https://docs.airbyte.io/integrations/sources/linkedin-ads + documentationUrl: https://docs.airbyte.com/integrations/sources/linkedin-ads icon: linkedin.svg sourceType: api releaseStage: generally_available @@ -575,7 +575,7 @@ sourceDefinitionId: af54297c-e8f8-4d63-a00d-a94695acc9d3 dockerRepository: airbyte/source-linkedin-pages dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/linkedin-pages + documentationUrl: https://docs.airbyte.com/integrations/sources/linkedin-pages icon: linkedin.svg sourceType: api releaseStage: alpha @@ -583,7 +583,7 @@ sourceDefinitionId: 7b86879e-26c5-4ef6-a5ce-2be5c7b46d1e dockerRepository: airbyte/source-linnworks dockerImageTag: 0.1.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/linnworks + documentationUrl: https://docs.airbyte.com/integrations/sources/linnworks icon: linnworks.svg sourceType: api releaseStage: alpha @@ -591,7 +591,7 @@ sourceDefinitionId: 00405b19-9768-4e0c-b1ae-9fc2ee2b2a8c dockerRepository: airbyte/source-looker dockerImageTag: 0.2.7 - documentationUrl: https://docs.airbyte.io/integrations/sources/looker + documentationUrl: https://docs.airbyte.com/integrations/sources/looker icon: looker.svg sourceType: api releaseStage: alpha @@ -599,7 +599,7 @@ sourceDefinitionId: b03a9f3e-22a5-11eb-adc1-0242ac120002 dockerRepository: airbyte/source-mailchimp dockerImageTag: 0.2.15 - documentationUrl: https://docs.airbyte.io/integrations/sources/mailchimp + documentationUrl: https://docs.airbyte.com/integrations/sources/mailchimp icon: mailchimp.svg sourceType: api releaseStage: generally_available @@ -607,7 +607,7 @@ sourceDefinitionId: 5b9cb09e-1003-4f9c-983d-5779d1b2cd51 dockerRepository: airbyte/source-mailgun dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/mailgun + documentationUrl: https://docs.airbyte.com/integrations/sources/mailgun icon: mailgun.svg sourceType: api releaseStage: alpha @@ -615,7 +615,7 @@ sourceDefinitionId: 9e0556f4-69df-4522-a3fb-03264d36b348 dockerRepository: airbyte/source-marketo dockerImageTag: 0.1.11 - documentationUrl: https://docs.airbyte.io/integrations/sources/marketo + documentationUrl: https://docs.airbyte.com/integrations/sources/marketo icon: marketo.svg sourceType: api releaseStage: generally_available @@ -623,7 +623,7 @@ sourceDefinitionId: c7cb421b-942e-4468-99ee-e369bcabaec5 dockerRepository: airbyte/source-metabase dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/metabase + documentationUrl: https://docs.airbyte.com/integrations/sources/metabase icon: metabase.svg sourceType: api releaseStage: alpha @@ -631,7 +631,7 @@ sourceDefinitionId: b5ea17b1-f170-46dc-bc31-cc744ca984c1 dockerRepository: airbyte/source-mssql dockerImageTag: 0.4.20 - documentationUrl: https://docs.airbyte.io/integrations/sources/mssql + documentationUrl: https://docs.airbyte.com/integrations/sources/mssql icon: mssql.svg sourceType: database releaseStage: alpha @@ -639,7 +639,7 @@ sourceDefinitionId: eaf50f04-21dd-4620-913b-2a83f5635227 dockerRepository: airbyte/source-microsoft-teams dockerImageTag: 0.2.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/microsoft-teams + documentationUrl: https://docs.airbyte.com/integrations/sources/microsoft-teams icon: microsoft-teams.svg sourceType: api releaseStage: alpha @@ -647,7 +647,7 @@ sourceDefinitionId: 12928b32-bf0a-4f1e-964f-07e12e37153a dockerRepository: airbyte/source-mixpanel dockerImageTag: 0.1.28 - documentationUrl: https://docs.airbyte.io/integrations/sources/mixpanel + documentationUrl: https://docs.airbyte.com/integrations/sources/mixpanel icon: mixpanel.svg sourceType: api releaseStage: generally_available @@ -655,7 +655,7 @@ sourceDefinitionId: 80a54ea2-9959-4040-aac1-eee42423ec9b dockerRepository: airbyte/source-monday dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/monday + documentationUrl: https://docs.airbyte.com/integrations/sources/monday icon: monday.svg sourceType: api releaseStage: alpha @@ -663,7 +663,7 @@ sourceDefinitionId: b2e713cd-cc36-4c0a-b5bd-b47cb8a0561e dockerRepository: airbyte/source-mongodb-v2 dockerImageTag: 0.1.19 - documentationUrl: https://docs.airbyte.io/integrations/sources/mongodb-v2 + documentationUrl: https://docs.airbyte.com/integrations/sources/mongodb-v2 icon: mongodb.svg sourceType: database releaseStage: alpha @@ -671,7 +671,7 @@ sourceDefinitionId: 722ba4bf-06ec-45a4-8dd5-72e4a5cf3903 dockerRepository: airbyte/source-my-hours dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/my-hours + documentationUrl: https://docs.airbyte.com/integrations/sources/my-hours icon: my-hours.svg sourceType: api releaseStage: alpha @@ -679,7 +679,7 @@ sourceDefinitionId: 435bb9a5-7887-4809-aa58-28c27df0d7ad dockerRepository: airbyte/source-mysql dockerImageTag: 1.0.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/mysql + documentationUrl: https://docs.airbyte.com/integrations/sources/mysql icon: mysql.svg sourceType: database releaseStage: beta @@ -687,7 +687,7 @@ sourceDefinitionId: 4f2f093d-ce44-4121-8118-9d13b7bfccd0 dockerRepository: airbyte/source-netsuite dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/netsuite + documentationUrl: https://docs.airbyte.com/integrations/sources/netsuite # icon: notion.svg sourceType: api releaseStage: alpha @@ -695,7 +695,7 @@ sourceDefinitionId: 6e00b415-b02e-4160-bf02-58176a0ae687 dockerRepository: airbyte/source-notion dockerImageTag: 0.1.10 - documentationUrl: https://docs.airbyte.io/integrations/sources/notion + documentationUrl: https://docs.airbyte.com/integrations/sources/notion icon: notion.svg sourceType: api releaseStage: generally_available @@ -703,7 +703,7 @@ sourceDefinitionId: 1d4fdb25-64fc-4569-92da-fcdca79a8372 dockerRepository: airbyte/source-okta dockerImageTag: 0.1.13 - documentationUrl: https://docs.airbyte.io/integrations/sources/okta + documentationUrl: https://docs.airbyte.com/integrations/sources/okta icon: okta.svg sourceType: api releaseStage: alpha @@ -711,7 +711,7 @@ sourceDefinitionId: bb6afd81-87d5-47e3-97c4-e2c2901b1cf8 dockerRepository: airbyte/source-onesignal dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/onesignal + documentationUrl: https://docs.airbyte.com/integrations/sources/onesignal icon: onesignal.svg sourceType: api releaseStage: alpha @@ -719,14 +719,14 @@ sourceDefinitionId: d8540a80-6120-485d-b7d6-272bca477d9b dockerRepository: airbyte/source-openweather dockerImageTag: 0.1.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/openweather + documentationUrl: https://docs.airbyte.com/integrations/sources/openweather sourceType: api releaseStage: alpha - name: Oracle DB sourceDefinitionId: b39a7370-74c3-45a6-ac3a-380d48520a83 dockerRepository: airbyte/source-oracle dockerImageTag: 0.3.21 - documentationUrl: https://docs.airbyte.io/integrations/sources/oracle + documentationUrl: https://docs.airbyte.com/integrations/sources/oracle icon: oracle.svg sourceType: database releaseStage: alpha @@ -734,7 +734,7 @@ sourceDefinitionId: 7f0455fb-4518-4ec0-b7a3-d808bf8081cc dockerRepository: airbyte/source-orb dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/orb + documentationUrl: https://docs.airbyte.com/integrations/sources/orb icon: orb.svg sourceType: api releaseStage: alpha @@ -742,7 +742,7 @@ sourceDefinitionId: 95bcc041-1d1a-4c2e-8802-0ca5b1bfa36a dockerRepository: airbyte/source-orbit dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/orbit + documentationUrl: https://docs.airbyte.com/integrations/sources/orbit icon: orbit.svg sourceType: api releaseStage: alpha @@ -750,7 +750,7 @@ name: Outreach dockerRepository: airbyte/source-outreach dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/outreach + documentationUrl: https://docs.airbyte.com/integrations/sources/outreach icon: outreach.svg sourceType: api releaseStage: alpha @@ -758,7 +758,7 @@ sourceDefinitionId: 2817b3f0-04e4-4c7a-9f32-7a5e8a83db95 dockerRepository: farosai/airbyte-pagerduty-source dockerImageTag: 0.1.23 - documentationUrl: https://docs.airbyte.io/integrations/sources/pagerduty + documentationUrl: https://docs.airbyte.com/integrations/sources/pagerduty icon: pagerduty.svg sourceType: api releaseStage: alpha @@ -766,7 +766,7 @@ sourceDefinitionId: d913b0f2-cc51-4e55-a44c-8ba1697b9239 dockerRepository: airbyte/source-paypal-transaction dockerImageTag: 0.1.10 - documentationUrl: https://docs.airbyte.io/integrations/sources/paypal-transaction + documentationUrl: https://docs.airbyte.com/integrations/sources/paypal-transaction icon: paypal.svg sourceType: api releaseStage: generally_available @@ -774,7 +774,7 @@ sourceDefinitionId: 193bdcb8-1dd9-48d1-aade-91cadfd74f9b dockerRepository: airbyte/source-paystack dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/paystack + documentationUrl: https://docs.airbyte.com/integrations/sources/paystack icon: paystack.svg sourceType: api releaseStage: alpha @@ -782,7 +782,7 @@ sourceDefinitionId: 3052c77e-8b91-47e2-97a0-a29a22794b4b dockerRepository: airbyte/source-persistiq dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/persistiq + documentationUrl: https://docs.airbyte.com/integrations/sources/persistiq icon: persistiq.svg sourceType: api releaseStage: alpha @@ -790,7 +790,7 @@ sourceDefinitionId: 5cb7e5fe-38c2-11ec-8d3d-0242ac130003 dockerRepository: airbyte/source-pinterest dockerImageTag: 0.1.7 - documentationUrl: https://docs.airbyte.io/integrations/sources/pinterest + documentationUrl: https://docs.airbyte.com/integrations/sources/pinterest icon: pinterest.svg sourceType: api releaseStage: generally_available @@ -798,7 +798,7 @@ sourceDefinitionId: d8286229-c680-4063-8c59-23b9b391c700 dockerRepository: airbyte/source-pipedrive dockerImageTag: 0.1.13 - documentationUrl: https://docs.airbyte.io/integrations/sources/pipedrive + documentationUrl: https://docs.airbyte.com/integrations/sources/pipedrive icon: pipedrive.svg sourceType: api releaseStage: alpha @@ -806,14 +806,14 @@ sourceDefinitionId: d60f5393-f99e-4310-8d05-b1876820f40e dockerRepository: airbyte/source-pivotal-tracker dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/pivotal-tracker + documentationUrl: https://docs.airbyte.com/integrations/sources/pivotal-tracker sourceType: api releaseStage: alpha - name: Plaid sourceDefinitionId: ed799e2b-2158-4c66-8da4-b40fe63bc72a dockerRepository: airbyte/source-plaid dockerImageTag: 0.3.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/plaid + documentationUrl: https://docs.airbyte.com/integrations/sources/plaid icon: plaid.svg sourceType: api releaseStage: alpha @@ -821,7 +821,7 @@ sourceDefinitionId: 6371b14b-bc68-4236-bfbd-468e8df8e968 dockerRepository: airbyte/source-pokeapi dockerImageTag: 0.1.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/pokeapi + documentationUrl: https://docs.airbyte.com/integrations/sources/pokeapi icon: pokeapi.svg sourceType: api releaseStage: alpha @@ -829,7 +829,7 @@ sourceDefinitionId: af6d50ee-dddf-4126-a8ee-7faee990774f dockerRepository: airbyte/source-posthog dockerImageTag: 0.1.7 - documentationUrl: https://docs.airbyte.io/integrations/sources/posthog + documentationUrl: https://docs.airbyte.com/integrations/sources/posthog icon: posthog.svg sourceType: api releaseStage: alpha @@ -837,7 +837,7 @@ sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres dockerImageTag: 1.0.14 - documentationUrl: https://docs.airbyte.io/integrations/sources/postgres + documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database releaseStage: generally_available @@ -845,7 +845,7 @@ sourceDefinitionId: d60a46d4-709f-4092-a6b7-2457f7d455f5 dockerRepository: airbyte/source-prestashop dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/presta-shop + documentationUrl: https://docs.airbyte.com/integrations/sources/presta-shop icon: prestashop.svg sourceType: api releaseStage: alpha @@ -853,7 +853,7 @@ sourceDefinitionId: f636c3c6-4077-45ac-b109-19fc62a283c1 dockerRepository: airbyte/source-primetric dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/presta-shop + documentationUrl: https://docs.airbyte.com/integrations/sources/presta-shop icon: primetric.svg sourceType: api releaseStage: alpha @@ -861,7 +861,7 @@ sourceDefinitionId: b08e4776-d1de-4e80-ab5c-1e51dad934a2 dockerRepository: airbyte/source-qualaroo dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/qualaroo + documentationUrl: https://docs.airbyte.com/integrations/sources/qualaroo icon: qualaroo.svg sourceType: api releaseStage: alpha @@ -869,7 +869,7 @@ sourceDefinitionId: 29b409d9-30a5-4cc8-ad50-886eb846fea3 dockerRepository: airbyte/source-quickbooks-singer dockerImageTag: 0.1.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/quickbooks + documentationUrl: https://docs.airbyte.com/integrations/sources/quickbooks icon: qb.svg sourceType: api releaseStage: alpha @@ -877,7 +877,7 @@ sourceDefinitionId: 45d2e135-2ede-49e1-939f-3e3ec357a65e dockerRepository: airbyte/source-recharge dockerImageTag: 0.2.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/recharge + documentationUrl: https://docs.airbyte.com/integrations/sources/recharge icon: recharge.svg sourceType: api releaseStage: generally_available @@ -885,7 +885,7 @@ sourceDefinitionId: cd42861b-01fc-4658-a8ab-5d11d0510f01 dockerRepository: airbyte/source-recurly dockerImageTag: 0.4.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/recurly + documentationUrl: https://docs.airbyte.com/integrations/sources/recurly icon: recurly.svg sourceType: api releaseStage: alpha @@ -893,7 +893,7 @@ sourceDefinitionId: e87ffa8e-a3b5-f69c-9076-6011339de1f6 dockerRepository: airbyte/source-redshift dockerImageTag: 0.3.14 - documentationUrl: https://docs.airbyte.io/integrations/sources/redshift + documentationUrl: https://docs.airbyte.com/integrations/sources/redshift icon: redshift.svg sourceType: database releaseStage: alpha @@ -901,7 +901,7 @@ sourceDefinitionId: db04ecd1-42e7-4115-9cec-95812905c626 dockerRepository: airbyte/source-retently dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/retently + documentationUrl: https://docs.airbyte.com/integrations/sources/retently icon: retently.svg sourceType: api releaseStage: alpha @@ -909,14 +909,14 @@ sourceDefinitionId: d78e5de0-aa44-4744-aa4f-74c818ccfe19 dockerRepository: airbyte/source-rki-covid dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/rki-covid + documentationUrl: https://docs.airbyte.com/integrations/sources/rki-covid sourceType: api releaseStage: alpha - name: S3 sourceDefinitionId: 69589781-7828-43c5-9f63-8925b1c1ccc2 dockerRepository: airbyte/source-s3 dockerImageTag: 0.1.22 - documentationUrl: https://docs.airbyte.io/integrations/sources/s3 + documentationUrl: https://docs.airbyte.com/integrations/sources/s3 icon: s3.svg sourceType: file releaseStage: generally_available @@ -924,7 +924,7 @@ sourceDefinitionId: 41991d12-d4b5-439e-afd0-260a31d4c53f dockerRepository: airbyte/source-salesloft dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/salesloft + documentationUrl: https://docs.airbyte.com/integrations/sources/salesloft icon: salesloft.svg sourceType: api releaseStage: alpha @@ -932,7 +932,7 @@ sourceDefinitionId: b117307c-14b6-41aa-9422-947e34922962 dockerRepository: airbyte/source-salesforce dockerImageTag: 1.0.20 - documentationUrl: https://docs.airbyte.io/integrations/sources/salesforce + documentationUrl: https://docs.airbyte.com/integrations/sources/salesforce icon: salesforce.svg sourceType: api releaseStage: generally_available @@ -940,7 +940,7 @@ sourceDefinitionId: 8d7ef552-2c0f-11ec-8d3d-0242ac130003 dockerRepository: airbyte/source-search-metrics dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/search-metrics + documentationUrl: https://docs.airbyte.com/integrations/sources/search-metrics icon: searchmetrics.svg sourceType: api releaseStage: alpha @@ -948,7 +948,7 @@ sourceDefinitionId: fbb5fbe2-16ad-4cf4-af7d-ff9d9c316c87 dockerRepository: airbyte/source-sendgrid dockerImageTag: 0.2.14 - documentationUrl: https://docs.airbyte.io/integrations/sources/sendgrid + documentationUrl: https://docs.airbyte.com/integrations/sources/sendgrid icon: sendgrid.svg sourceType: api releaseStage: beta @@ -956,7 +956,7 @@ sourceDefinitionId: 9da77001-af33-4bcd-be46-6252bf9342b9 dockerRepository: airbyte/source-shopify dockerImageTag: 0.1.37 - documentationUrl: https://docs.airbyte.io/integrations/sources/shopify + documentationUrl: https://docs.airbyte.com/integrations/sources/shopify icon: shopify.svg sourceType: api releaseStage: alpha @@ -964,7 +964,7 @@ sourceDefinitionId: 2fed2292-5586-480c-af92-9944e39fe12d dockerRepository: airbyte/source-shortio dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/shortio + documentationUrl: https://docs.airbyte.com/integrations/sources/shortio icon: short.svg sourceType: api releaseStage: alpha @@ -972,7 +972,7 @@ sourceDefinitionId: c2281cee-86f9-4a86-bb48-d23286b4c7bd dockerRepository: airbyte/source-slack dockerImageTag: 0.1.18 - documentationUrl: https://docs.airbyte.io/integrations/sources/slack + documentationUrl: https://docs.airbyte.com/integrations/sources/slack icon: slack.svg sourceType: api releaseStage: generally_available @@ -980,7 +980,7 @@ sourceDefinitionId: 374ebc65-6636-4ea0-925c-7d35999a8ffc dockerRepository: airbyte/source-smartsheets dockerImageTag: 0.1.12 - documentationUrl: https://docs.airbyte.io/integrations/sources/smartsheets + documentationUrl: https://docs.airbyte.com/integrations/sources/smartsheets icon: smartsheet.svg sourceType: api releaseStage: beta @@ -988,7 +988,7 @@ sourceDefinitionId: 200330b2-ea62-4d11-ac6d-cfe3e3f8ab2b dockerRepository: airbyte/source-snapchat-marketing dockerImageTag: 0.1.8 - documentationUrl: https://docs.airbyte.io/integrations/sources/snapchat-marketing + documentationUrl: https://docs.airbyte.com/integrations/sources/snapchat-marketing icon: snapchat.svg sourceType: api releaseStage: generally_available @@ -996,7 +996,7 @@ sourceDefinitionId: e2d65910-8c8b-40a1-ae7d-ee2416b2bfa2 dockerRepository: airbyte/source-snowflake dockerImageTag: 0.1.24 - documentationUrl: https://docs.airbyte.io/integrations/sources/snowflake + documentationUrl: https://docs.airbyte.com/integrations/sources/snowflake icon: snowflake.svg sourceType: database releaseStage: alpha @@ -1004,7 +1004,7 @@ sourceDefinitionId: 77225a51-cd15-4a13-af02-65816bd0ecf4 dockerRepository: airbyte/source-square dockerImageTag: 0.1.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/square + documentationUrl: https://docs.airbyte.com/integrations/sources/square icon: square.svg sourceType: api releaseStage: alpha @@ -1012,7 +1012,7 @@ name: Strava dockerRepository: airbyte/source-strava dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/strava + documentationUrl: https://docs.airbyte.com/integrations/sources/strava icon: strava.svg sourceType: api releaseStage: alpha @@ -1020,7 +1020,7 @@ sourceDefinitionId: e094cb9a-26de-4645-8761-65c0c425d1de dockerRepository: airbyte/source-stripe dockerImageTag: 0.1.39 - documentationUrl: https://docs.airbyte.io/integrations/sources/stripe + documentationUrl: https://docs.airbyte.com/integrations/sources/stripe icon: stripe.svg sourceType: api releaseStage: generally_available @@ -1028,7 +1028,7 @@ sourceDefinitionId: badc5925-0485-42be-8caa-b34096cb71b5 dockerRepository: airbyte/source-surveymonkey dockerImageTag: 0.1.11 - documentationUrl: https://docs.airbyte.io/integrations/sources/surveymonkey + documentationUrl: https://docs.airbyte.com/integrations/sources/surveymonkey icon: surveymonkey.svg sourceType: api releaseStage: generally_available @@ -1036,7 +1036,7 @@ sourceDefinitionId: f00d2cf4-3c28-499a-ba93-b50b6f26359e dockerRepository: airbyte/source-talkdesk-explore dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/talkdesk-explore + documentationUrl: https://docs.airbyte.com/integrations/sources/talkdesk-explore icon: talkdesk-explore.svg sourceType: api releaseStage: alpha @@ -1044,7 +1044,7 @@ sourceDefinitionId: d1aa448b-7c54-498e-ad95-263cbebcd2db dockerRepository: airbyte/source-tempo dockerImageTag: 0.2.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/tempo + documentationUrl: https://docs.airbyte.com/integrations/sources/tempo icon: tempo.svg sourceType: api releaseStage: alpha @@ -1052,7 +1052,7 @@ sourceDefinitionId: 0dad1a35-ccf8-4d03-b73e-6788c00b13ae dockerRepository: airbyte/source-tidb dockerImageTag: 0.2.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/tidb + documentationUrl: https://docs.airbyte.com/integrations/sources/tidb icon: tidb.svg sourceType: database releaseStage: alpha @@ -1060,7 +1060,7 @@ sourceDefinitionId: 4bfac00d-ce15-44ff-95b9-9e3c3e8fbd35 dockerRepository: airbyte/source-tiktok-marketing dockerImageTag: 0.1.17 - documentationUrl: https://docs.airbyte.io/integrations/sources/tiktok-marketing + documentationUrl: https://docs.airbyte.com/integrations/sources/tiktok-marketing icon: tiktok.svg sourceType: api releaseStage: generally_available @@ -1068,7 +1068,7 @@ sourceDefinitionId: bc617b5f-1b9e-4a2d-bebe-782fd454a771 dockerRepository: airbyte/source-timely dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/timely + documentationUrl: https://docs.airbyte.com/integrations/sources/timely icon: timely.svg sourceType: api releaseStage: alpha @@ -1076,7 +1076,7 @@ sourceDefinitionId: 8da67652-004c-11ec-9a03-0242ac130003 dockerRepository: airbyte/source-trello dockerImageTag: 0.1.6 - documentationUrl: https://docs.airbyte.io/integrations/sources/trello + documentationUrl: https://docs.airbyte.com/integrations/sources/trello icon: trelllo.svg sourceType: api releaseStage: alpha @@ -1084,7 +1084,7 @@ sourceDefinitionId: b9dc6155-672e-42ea-b10d-9f1f1fb95ab1 dockerRepository: airbyte/source-twilio dockerImageTag: 0.1.12 - documentationUrl: https://docs.airbyte.io/integrations/sources/twilio + documentationUrl: https://docs.airbyte.com/integrations/sources/twilio icon: twilio.svg sourceType: api releaseStage: generally_available @@ -1092,7 +1092,7 @@ sourceDefinitionId: e7eff203-90bf-43e5-a240-19ea3056c474 dockerRepository: airbyte/source-typeform dockerImageTag: 0.1.9 - documentationUrl: https://docs.airbyte.io/integrations/sources/typeform + documentationUrl: https://docs.airbyte.com/integrations/sources/typeform icon: typeform.svg sourceType: api releaseStage: alpha @@ -1100,7 +1100,7 @@ sourceDefinitionId: c4cfaeda-c757-489a-8aba-859fb08b6970 dockerRepository: airbyte/source-us-census dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/us-census + documentationUrl: https://docs.airbyte.com/integrations/sources/us-census icon: uscensus.svg sourceType: api releaseStage: alpha @@ -1108,7 +1108,7 @@ name: YouTube Analytics dockerRepository: airbyte/source-youtube-analytics dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/youtube-analytics + documentationUrl: https://docs.airbyte.com/integrations/sources/youtube-analytics icon: youtube.svg sourceType: api releaseStage: beta @@ -1116,7 +1116,7 @@ sourceDefinitionId: 7e20ce3e-d820-4327-ad7a-88f3927fd97a dockerRepository: farosai/airbyte-victorops-source dockerImageTag: 0.1.23 - documentationUrl: https://docs.airbyte.io/integrations/sources/victorops + documentationUrl: https://docs.airbyte.com/integrations/sources/victorops icon: victorops.svg sourceType: api releaseStage: alpha @@ -1124,7 +1124,7 @@ sourceDefinitionId: ef580275-d9a9-48bb-af5e-db0f5855be04 dockerRepository: airbyte/source-webflow dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/webflow + documentationUrl: https://docs.airbyte.com/integrations/sources/webflow icon: webflow.svg sourceType: api releaseStage: alpha @@ -1132,7 +1132,7 @@ sourceDefinitionId: 2a2552ca-9f78-4c1c-9eb7-4d0dc66d72df dockerRepository: airbyte/source-woocommerce dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/woocommerce + documentationUrl: https://docs.airbyte.com/integrations/sources/woocommerce icon: woocommerce.svg sourceType: api releaseStage: alpha @@ -1140,7 +1140,7 @@ sourceDefinitionId: 9c13f986-a13b-4988-b808-4705badf71c2 dockerRepository: airbyte/source-wrike dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/wrike + documentationUrl: https://docs.airbyte.com/integrations/sources/wrike icon: wrike.svg sourceType: api releaseStage: alpha @@ -1148,7 +1148,7 @@ sourceDefinitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 dockerRepository: airbyte/source-zendesk-chat dockerImageTag: 0.1.10 - documentationUrl: https://docs.airbyte.io/integrations/sources/zendesk-chat + documentationUrl: https://docs.airbyte.com/integrations/sources/zendesk-chat icon: zendesk.svg sourceType: api releaseStage: generally_available @@ -1156,7 +1156,7 @@ sourceDefinitionId: 325e0640-e7b3-4e24-b823-3361008f603f dockerRepository: airbyte/source-zendesk-sunshine dockerImageTag: 0.1.1 - documentationUrl: https://docs.airbyte.io/integrations/sources/zendesk-sunshine + documentationUrl: https://docs.airbyte.com/integrations/sources/zendesk-sunshine icon: zendesk.svg sourceType: api releaseStage: alpha @@ -1164,7 +1164,7 @@ sourceDefinitionId: 79c1aa37-dae3-42ae-b333-d1c105477715 dockerRepository: airbyte/source-zendesk-support dockerImageTag: 0.2.16 - documentationUrl: https://docs.airbyte.io/integrations/sources/zendesk-support + documentationUrl: https://docs.airbyte.com/integrations/sources/zendesk-support icon: zendesk.svg sourceType: api releaseStage: generally_available @@ -1172,7 +1172,7 @@ sourceDefinitionId: c8630570-086d-4a40-99ae-ea5b18673071 dockerRepository: airbyte/source-zendesk-talk dockerImageTag: 0.1.5 - documentationUrl: https://docs.airbyte.io/integrations/sources/zendesk-talk + documentationUrl: https://docs.airbyte.com/integrations/sources/zendesk-talk icon: zendesk.svg sourceType: api releaseStage: generally_available @@ -1180,7 +1180,7 @@ sourceDefinitionId: 8baba53d-2fe3-4e33-bc85-210d0eb62884 dockerRepository: airbyte/source-zenefits dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/zenefits + documentationUrl: https://docs.airbyte.com/integrations/sources/zenefits icon: zenefits.svg sourceType: api releaseStage: alpha @@ -1188,14 +1188,14 @@ sourceDefinitionId: f1e4c7f6-db5c-4035-981f-d35ab4998794 dockerRepository: airbyte/source-zenloop dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/zenloop + documentationUrl: https://docs.airbyte.com/integrations/sources/zenloop sourceType: api releaseStage: alpha - sourceDefinitionId: cdaf146a-9b75-49fd-9dd2-9d64a0bb4781 name: Sentry dockerRepository: airbyte/source-sentry dockerImageTag: 0.1.7 - documentationUrl: https://docs.airbyte.io/integrations/sources/sentry + documentationUrl: https://docs.airbyte.com/integrations/sources/sentry icon: sentry.svg sourceType: api releaseStage: generally_available @@ -1203,7 +1203,7 @@ sourceDefinitionId: aea2fd0d-377d-465e-86c0-4fdc4f688e51 dockerRepository: airbyte/source-zoom-singer dockerImageTag: 0.2.4 - documentationUrl: https://docs.airbyte.io/integrations/sources/zoom + documentationUrl: https://docs.airbyte.com/integrations/sources/zoom icon: zoom.svg sourceType: api releaseStage: alpha @@ -1211,7 +1211,7 @@ sourceDefinitionId: 3dc3037c-5ce8-4661-adc2-f7a9e3c5ece5 dockerRepository: airbyte/source-zuora dockerImageTag: 0.1.3 - documentationUrl: https://docs.airbyte.io/integrations/sources/zuora + documentationUrl: https://docs.airbyte.com/integrations/sources/zuora icon: zuora.svg sourceType: api releaseStage: alpha @@ -1219,7 +1219,7 @@ sourceDefinitionId: cd06e646-31bf-4dc8-af48-cbc6530fcad3 dockerRepository: airbyte/source-kustomer-singer dockerImageTag: 0.1.2 - documentationUrl: https://docs.airbyte.io/integrations/sources/kustomer + documentationUrl: https://docs.airbyte.com/integrations/sources/kustomer sourceType: api releaseStage: alpha - name: ZohoCRM @@ -1240,20 +1240,20 @@ sourceDefinitionId: 6f2ac653-8623-43c4-8950-19218c7caf3d dockerRepository: airbyte/source-firebolt dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/firebolt + documentationUrl: https://docs.airbyte.com/integrations/sources/firebolt sourceType: database releaseStage: alpha - name: Elasticsearch sourceDefinitionId: 7cf88806-25f5-4e1a-b422-b2fa9e1b0090 dockerRepository: airbyte/source-elasticsearch dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/elasticsearch + documentationUrl: https://docs.airbyte.com/integrations/sources/elasticsearch sourceType: api releaseStage: alpha - name: Yandex Metrica sourceDefinitionId: 7865dce4-2211-4f6a-88e5-9d0fe161afe7 dockerRepository: airbyte/source-yandex-metrica dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/sources/yandex-metrica + documentationUrl: https://docs.airbyte.com/integrations/sources/yandex-metrica sourceType: api releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 30bd93c2192c..b734bd35b586 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -158,7 +158,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-airtable:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/airtable" + documentationUrl: "https://docs.airbyte.com/integrations/sources/airtable" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Airtable Source Spec" @@ -605,7 +605,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-aws-cloudtrail:0.1.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/aws-cloudtrail" + documentationUrl: "https://docs.airbyte.com/integrations/sources/aws-cloudtrail" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Aws CloudTrail Spec" @@ -620,13 +620,13 @@ aws_key_id: type: "string" title: "Key ID" - description: "AWS CloudTrail Access Key ID. See the docs for more information on how to obtain this key." airbyte_secret: true aws_secret_key: type: "string" title: "Secret Key" - description: "AWS CloudTrail Access Key ID. See the docs for more information on how to obtain this key." airbyte_secret: true aws_region_name: @@ -773,8 +773,8 @@ - "client_secret" - dockerImage: "airbyte/source-amazon-seller-partner:0.2.27" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/amazon-seller-partner" - changelogUrl: "https://docs.airbyte.io/integrations/sources/amazon-seller-partner" + documentationUrl: "https://docs.airbyte.com/integrations/sources/amazon-seller-partner" + changelogUrl: "https://docs.airbyte.com/integrations/sources/amazon-seller-partner" connectionSpecification: title: "Amazon Seller Partner Spec" type: "object" @@ -998,7 +998,7 @@ - "lwa_client_secret" - dockerImage: "airbyte/source-amazon-sqs:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/amazon-sqs" + documentationUrl: "https://docs.airbyte.com/integrations/sources/amazon-sqs" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Amazon SQS Source Spec" @@ -1107,7 +1107,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-amplitude:0.1.15" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/amplitude" + documentationUrl: "https://docs.airbyte.com/integrations/sources/amplitude" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Amplitude Spec" @@ -1121,13 +1121,13 @@ api_key: type: "string" title: "API Key" - description: "Amplitude API Key. See the setup guide for more information on how to obtain this key." airbyte_secret: true secret_key: type: "string" title: "Secret Key" - description: "Amplitude Secret Key. See the setup guide for more information on how to obtain this key." airbyte_secret: true start_date: @@ -1143,7 +1143,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-apify-dataset:0.1.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/apify-dataset" + documentationUrl: "https://docs.airbyte.com/integrations/sources/apify-dataset" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Apify Dataset Spec" @@ -1167,7 +1167,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-appfollow:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/appfollow" + documentationUrl: "https://docs.airbyte.com/integrations/sources/appfollow" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Appfollow Spec" @@ -1202,7 +1202,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-appstore-singer:0.2.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/appstore" + documentationUrl: "https://docs.airbyte.com/integrations/sources/appstore" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Appstore Singer Spec" @@ -1218,24 +1218,24 @@ key_id: type: "string" title: "Key ID" - description: "Appstore Key ID. See the docs for more information on how to obtain this key." private_key: type: "string" title: "Private Key" - description: "Appstore Private Key. See the docs for more information on how to obtain this key." airbyte_secret: true multiline: true issuer_id: type: "string" title: "Issuer ID" - description: "Appstore Issuer ID. See the docs for more information on how to obtain this ID." vendor: type: "string" title: "Vendor ID" - description: "Appstore Vendor ID. See the docs for more information on how to obtain this ID." start_date: type: "string" @@ -1340,7 +1340,7 @@ storage_access_key: title: "Access Key" type: "string" - description: "Azure Table Storage Access Key. See the docs for more information on how to obtain this key." order: 1 airbyte_secret: true @@ -1348,7 +1348,7 @@ title: "Endpoint Suffix" type: "string" description: "Azure Table Storage service account URL suffix. See the docs\ + \ href=\"https://docs.airbyte.com/integrations/sources/azure-table\">docs\ \ for more information on how to obtain endpoint suffix" order: 2 default: "core.windows.net" @@ -1361,7 +1361,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-bamboo-hr:0.2.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/bamboo-hr" + documentationUrl: "https://docs.airbyte.com/integrations/sources/bamboo-hr" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Bamboo HR Spec" @@ -1392,7 +1392,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-bigcommerce:0.1.7" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/bigcommerce" + documentationUrl: "https://docs.airbyte.com/integrations/sources/bigcommerce" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "BigCommerce Source CDK Specifications" @@ -1425,7 +1425,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-bigquery:0.2.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/bigquery" + documentationUrl: "https://docs.airbyte.com/integrations/sources/bigquery" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "BigQuery Source Spec" @@ -1448,7 +1448,7 @@ credentials_json: type: "string" description: "The contents of your Service Account Key JSON file. See the\ - \ docs for more information on how to obtain this key." title: "Credentials JSON" airbyte_secret: true @@ -1462,7 +1462,7 @@ - "append_dedup" - dockerImage: "airbyte/source-bing-ads:0.1.15" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/bing-ads" + documentationUrl: "https://docs.airbyte.com/integrations/sources/bing-ads" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Bing Ads Spec" @@ -1569,7 +1569,7 @@ - "client_secret" - dockerImage: "airbyte/source-braintree:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/braintree" + documentationUrl: "https://docs.airbyte.com/integrations/sources/braintree" connectionSpecification: title: "Braintree Spec" type: "object" @@ -1577,19 +1577,19 @@ merchant_id: title: "Merchant ID" description: "The unique identifier for your entire gateway account. See\ - \ the docs for more information on how to obtain this ID." name: "Merchant ID" type: "string" public_key: title: "Public Key" - description: "Braintree Public Key. See the docs for more information on how to obtain this key." name: "Public Key" type: "string" private_key: title: "Private Key" - description: "Braintree Private Key. See the docs for more information on how to obtain this key." name: "Private Key" airbyte_secret: true @@ -1630,7 +1630,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-cart:0.2.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/cart" + documentationUrl: "https://docs.airbyte.com/integrations/sources/cart" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Cart.com Spec" @@ -1735,7 +1735,7 @@ site_api_key: type: "string" title: "API Key" - description: "Chargebee API Key. See the docs for more information on how to obtain this key." examples: - "test_3yzfanAXF66USdWC9wQcM555DQJkSYoppu" @@ -1762,7 +1762,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-chargify:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/chargify" + documentationUrl: "https://docs.airbyte.com/integrations/sources/chargify" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Chargify Spec" @@ -1785,7 +1785,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-chartmogul:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/chartmogul" + documentationUrl: "https://docs.airbyte.com/integrations/sources/chartmogul" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Chartmogul Spec" @@ -1825,7 +1825,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-clickhouse:0.1.14" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/clickhouse" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/clickhouse" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "ClickHouse Source Spec" @@ -1990,7 +1990,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-close-com:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/close-com" + documentationUrl: "https://docs.airbyte.com/integrations/sources/close-com" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Close.com Spec" @@ -2017,7 +2017,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-cockroachdb:0.1.18" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/cockroachdb" + documentationUrl: "https://docs.airbyte.com/integrations/sources/cockroachdb" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Cockroach Source Spec" @@ -2079,7 +2079,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-commercetools:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/commercetools" + documentationUrl: "https://docs.airbyte.com/integrations/sources/commercetools" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Commercetools Source CDK Specifications" @@ -2205,7 +2205,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-dixa:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/dixa" + documentationUrl: "https://docs.airbyte.com/integrations/sources/dixa" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Dixa Spec" @@ -2238,7 +2238,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-dockerhub:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/dockerhub" + documentationUrl: "https://docs.airbyte.com/integrations/sources/dockerhub" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Dockerhub Spec" @@ -2259,7 +2259,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-drift:0.2.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/drift" + documentationUrl: "https://docs.airbyte.com/integrations/sources/drift" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Drift Spec" @@ -2322,7 +2322,7 @@ access_token: type: "string" title: "Access Token" - description: "Drift Access Token. See the docs for more information on how to generate this key." airbyte_secret: true supportsNormalization: false @@ -2412,7 +2412,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-e2e-test:2.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/e2e-test" + documentationUrl: "https://docs.airbyte.com/integrations/sources/e2e-test" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "E2E Test Source Spec" @@ -2525,7 +2525,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-exchange-rates:1.2.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/exchangeratesapi" + documentationUrl: "https://docs.airbyte.com/integrations/sources/exchangeratesapi" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "exchangeratesapi.io Source Spec" @@ -2563,8 +2563,8 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-facebook-marketing:0.2.67" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/facebook-marketing" - changelogUrl: "https://docs.airbyte.io/integrations/sources/facebook-marketing" + documentationUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" + changelogUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" connectionSpecification: title: "Source Facebook Marketing" type: "object" @@ -2603,7 +2603,7 @@ access_token: title: "Access Token" description: "The value of the access token generated. See the docs\ + https://docs.airbyte.com/integrations/sources/facebook-marketing\">docs\ \ for more information" order: 3 airbyte_secret: true @@ -2920,7 +2920,7 @@ - - "access_token" - dockerImage: "airbyte/source-facebook-pages:0.1.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/facebook-pages" + documentationUrl: "https://docs.airbyte.com/integrations/sources/facebook-pages" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Facebook Pages Spec" @@ -3094,7 +3094,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-file:0.2.24" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/file" + documentationUrl: "https://docs.airbyte.com/integrations/sources/file" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "File Source Spec" @@ -3327,7 +3327,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-freshcaller:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/freshcaller" + documentationUrl: "https://docs.airbyte.com/integrations/sources/freshcaller" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Freshcaller Spec" @@ -3347,7 +3347,7 @@ api_key: type: "string" title: "API Key" - description: "Freshcaller API Key. See the docs for more information on how to obtain this key." airbyte_secret: true requests_per_minute: @@ -3374,7 +3374,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-flexport:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/flexport" + documentationUrl: "https://docs.airbyte.com/integrations/sources/flexport" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Flexport Spec" @@ -3399,7 +3399,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-freshdesk:0.3.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/freshdesk" + documentationUrl: "https://docs.airbyte.com/integrations/sources/freshdesk" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Freshdesk Spec" @@ -3419,7 +3419,7 @@ api_key: type: "string" title: "API Key" - description: "Freshdesk API Key. See the docs for more information on how to obtain this key." airbyte_secret: true requests_per_minute: @@ -3442,7 +3442,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-freshsales:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/freshsales" + documentationUrl: "https://docs.airbyte.com/integrations/sources/freshsales" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Freshsales Spec" @@ -3469,7 +3469,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-freshservice:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/freshservice" + documentationUrl: "https://docs.airbyte.com/integrations/sources/freshservice" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Freshservice Spec" @@ -3640,7 +3640,7 @@ - "client_secret" - dockerImage: "airbyte/source-gitlab:0.1.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/gitlab" + documentationUrl: "https://docs.airbyte.com/integrations/sources/gitlab" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source GitLab Singer Spec" @@ -3690,7 +3690,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-glassfrog:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/glassfrog" + documentationUrl: "https://docs.airbyte.com/integrations/sources/glassfrog" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Glassfrog Spec" @@ -3957,7 +3957,7 @@ type: "string" title: "Custom Reports" description: "A JSON array describing the custom reports you want to sync\ - \ from Google Analytics. See the docs for more information about the exact format you can use\ \ to fill out this field." window_in_days: @@ -4084,7 +4084,7 @@ type: "string" title: "Custom Reports" description: "A JSON array describing the custom reports you want to sync\ - \ from Google Analytics. See the docs for more information about the exact format you can use\ \ to fill out this field." window_in_days: @@ -4125,7 +4125,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-google-directory:0.1.9" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/google-directory" + documentationUrl: "https://docs.airbyte.com/integrations/sources/google-directory" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Google Directory Spec" @@ -4219,7 +4219,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-google-search-console:0.1.17" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/google-search-console" + documentationUrl: "https://docs.airbyte.com/integrations/sources/google-search-console" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Google Search Console Spec" @@ -4356,7 +4356,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-google-sheets:0.2.19" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/google-sheets" + documentationUrl: "https://docs.airbyte.com/integrations/sources/google-sheets" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Google Sheets Source Spec" @@ -4443,7 +4443,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-google-workspace-admin-reports:0.1.8" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/google-workspace-admin-reports" + documentationUrl: "https://docs.airbyte.com/integrations/sources/google-workspace-admin-reports" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Google Directory Spec" @@ -4489,7 +4489,7 @@ api_key: title: "API Key" type: "string" - description: "Greenhouse API Key. See the docs for more information on how to generate this key." airbyte_secret: true order: 0 @@ -4542,7 +4542,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-harvest:0.1.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/harvest" + documentationUrl: "https://docs.airbyte.com/integrations/sources/harvest" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Harvest Spec" @@ -4698,7 +4698,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-hubplanner:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/hubplanner" + documentationUrl: "https://docs.airbyte.com/integrations/sources/hubplanner" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Hubplanner Spec" @@ -4717,7 +4717,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-hubspot:0.2.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/hubspot" + documentationUrl: "https://docs.airbyte.com/integrations/sources/hubspot" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "HubSpot Source Spec" @@ -4832,7 +4832,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-db2:0.1.16" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/db2" + documentationUrl: "https://docs.airbyte.com/integrations/sources/db2" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "IBM Db2 Source Spec" @@ -4927,8 +4927,8 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-instagram:1.0.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/instagram" - changelogUrl: "https://docs.airbyte.io/integrations/sources/instagram" + documentationUrl: "https://docs.airbyte.com/integrations/sources/instagram" + changelogUrl: "https://docs.airbyte.com/integrations/sources/instagram" connectionSpecification: title: "Source Instagram" type: "object" @@ -4946,7 +4946,7 @@ access_token: title: "Access Token" description: "The value of the access token generated. See the docs for\ + https://docs.airbyte.com/integrations/sources/instagram\">docs for\ \ more information" airbyte_secret: true type: "string" @@ -4967,7 +4967,7 @@ - - "access_token" - dockerImage: "airbyte/source-intercom:0.1.27" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/intercom" + documentationUrl: "https://docs.airbyte.com/integrations/sources/intercom" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Intercom Spec" @@ -5004,7 +5004,7 @@ - - "access_token" - dockerImage: "airbyte/source-iterable:0.1.19" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/iterable" + documentationUrl: "https://docs.airbyte.com/integrations/sources/iterable" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Iterable Spec" @@ -5017,7 +5017,7 @@ api_key: type: "string" title: "API Key" - description: "Iterable API Key. See the docs for more information on how to obtain this key." airbyte_secret: true order: 0 @@ -5082,7 +5082,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-jira:0.2.21" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/jira" + documentationUrl: "https://docs.airbyte.com/integrations/sources/jira" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Jira Spec" @@ -5096,7 +5096,7 @@ api_token: type: "string" title: "API Token" - description: "Jira API Token. See the docs for more information on how to generate this key." airbyte_secret: true domain: @@ -5153,7 +5153,7 @@ type: "boolean" title: "Enable Experimental Streams" description: "Allow the use of experimental streams which rely on undocumented\ - \ Jira API endpoints. See https://docs.airbyte.io/integrations/sources/jira#experimental-tables\ + \ Jira API endpoints. See https://docs.airbyte.com/integrations/sources/jira#experimental-tables\ \ for more info." default: false supportsNormalization: false @@ -5161,7 +5161,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-kafka:0.2.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/kafka" + documentationUrl: "https://docs.airbyte.com/integrations/sources/kafka" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Kafka Source Spec" @@ -5453,8 +5453,8 @@ - "append" - dockerImage: "airbyte/source-klaviyo:0.1.10" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/klaviyo" - changelogUrl: "https://docs.airbyte.io/integrations/sources/klaviyo" + documentationUrl: "https://docs.airbyte.com/integrations/sources/klaviyo" + changelogUrl: "https://docs.airbyte.com/integrations/sources/klaviyo" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Klaviyo Spec" @@ -5462,7 +5462,7 @@ properties: api_key: title: "Api Key" - description: "Klaviyo API Key. See our docs if you need help finding this key." airbyte_secret: true type: "string" @@ -5548,8 +5548,8 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-lever-hiring:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/lever-hiring" - changelogUrl: "https://docs.airbyte.io/integrations/sources/lever-hiring#changelog" + documentationUrl: "https://docs.airbyte.com/integrations/sources/lever-hiring" + changelogUrl: "https://docs.airbyte.com/integrations/sources/lever-hiring#changelog" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Lever Hiring Source Spec" @@ -5676,7 +5676,7 @@ - "client_secret" - dockerImage: "airbyte/source-linkedin-ads:0.1.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/linkedin-ads" + documentationUrl: "https://docs.airbyte.com/integrations/sources/linkedin-ads" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Linkedin Ads Spec" @@ -5841,7 +5841,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-linnworks:0.1.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/linnworks" + documentationUrl: "https://docs.airbyte.com/integrations/sources/linnworks" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Linnworks Spec" @@ -5876,7 +5876,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-looker:0.2.7" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/looker" + documentationUrl: "https://docs.airbyte.com/integrations/sources/looker" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Looker Spec" @@ -5900,7 +5900,7 @@ title: "Client ID" type: "string" description: "The Client ID is first part of an API3 key that is specific\ - \ to each Looker user. See the docs for more information on how to generate this key." client_secret: title: "Client Secret" @@ -5918,7 +5918,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-mailchimp:0.2.15" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/mailchimp" + documentationUrl: "https://docs.airbyte.com/integrations/sources/mailchimp" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Mailchimp Spec" @@ -5969,7 +5969,7 @@ apikey: type: "string" title: "API Key" - description: "Mailchimp API Key. See the docs for information on how to generate this key." airbyte_secret: true supportsNormalization: false @@ -6015,7 +6015,7 @@ - "client_secret" - dockerImage: "airbyte/source-mailgun:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/mailgun" + documentationUrl: "https://docs.airbyte.com/integrations/sources/mailgun" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Mailgun Spec" @@ -6048,7 +6048,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-marketo:0.1.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/marketo" + documentationUrl: "https://docs.airbyte.com/integrations/sources/marketo" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Marketo Spec" @@ -6064,7 +6064,7 @@ title: "Domain URL" type: "string" order: 3 - description: "Your Marketo Base URL. See the docs for info on how to obtain this." examples: - "https://000-AAA-000.mktorest.com" @@ -6073,15 +6073,15 @@ title: "Client ID" type: "string" description: "The Client ID of your Marketo developer application. See the docs\ - \ for info on how to obtain this." + \ href=\"https://docs.airbyte.com/integrations/sources/marketo\"> the\ + \ docs for info on how to obtain this." order: 0 airbyte_secret: true client_secret: title: "Client Secret" type: "string" description: "The Client Secret of your Marketo developer application. See\ - \ the\ + \ the\ \ docs for info on how to obtain this." order: 1 airbyte_secret: true @@ -6099,7 +6099,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-metabase:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/metabase" + documentationUrl: "https://docs.airbyte.com/integrations/sources/metabase" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Metabase Source Spec" @@ -6137,7 +6137,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-mssql:0.4.20" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/mssql" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/mssql" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MSSQL Source Spec" @@ -6415,7 +6415,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-microsoft-teams:0.2.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/microsoft-teams" + documentationUrl: "https://docs.airbyte.com/integrations/sources/microsoft-teams" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Microsoft Teams Spec" @@ -6562,7 +6562,7 @@ - "client_secret" - dockerImage: "airbyte/source-mixpanel:0.1.28" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/mixpanel" + documentationUrl: "https://docs.airbyte.com/integrations/sources/mixpanel" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Mixpanel Spec" @@ -6690,7 +6690,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-monday:0.1.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/monday" + documentationUrl: "https://docs.airbyte.com/integrations/sources/monday" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Monday Spec" @@ -6803,8 +6803,8 @@ - "client_secret" - dockerImage: "airbyte/source-mongodb-v2:0.1.19" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/mongodb-v2" - changelogUrl: "https://docs.airbyte.io/integrations/sources/mongodb-v2" + documentationUrl: "https://docs.airbyte.com/integrations/sources/mongodb-v2" + changelogUrl: "https://docs.airbyte.com/integrations/sources/mongodb-v2" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MongoDb Source Spec" @@ -6851,7 +6851,7 @@ type: "boolean" description: "Indicates whether TLS encryption protocol will be used\ \ to connect to MongoDB. It is recommended to use TLS connection\ - \ if possible. For more information see documentation." default: false order: 2 @@ -6924,7 +6924,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-my-hours:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/my-hours" + documentationUrl: "https://docs.airbyte.com/integrations/sources/my-hours" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "My Hours Spec" @@ -6967,7 +6967,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-mysql:1.0.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/mysql" + documentationUrl: "https://docs.airbyte.com/integrations/sources/mysql" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "MySql Source Spec" @@ -7384,7 +7384,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-notion:0.1.10" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/notion" + documentationUrl: "https://docs.airbyte.com/integrations/sources/notion" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Notion Source Spec" @@ -7464,7 +7464,7 @@ - - "access_token" - dockerImage: "airbyte/source-okta:0.1.13" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/okta" + documentationUrl: "https://docs.airbyte.com/integrations/sources/okta" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Okta Spec" @@ -7475,7 +7475,7 @@ domain: type: "string" title: "Okta domain" - description: "The Okta domain. See the docs for instructions on how to find it." airbyte_secret: false start_date: @@ -7531,7 +7531,7 @@ api_token: type: "string" title: "Personal API Token" - description: "An Okta token. See the docs for instructions on how to generate it." airbyte_secret: true supportsNormalization: false @@ -7585,7 +7585,7 @@ - "client_secret" - dockerImage: "airbyte/source-onesignal:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/onesignal" + documentationUrl: "https://docs.airbyte.com/integrations/sources/onesignal" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "OneSignal Source Spec" @@ -7742,7 +7742,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-oracle:0.3.21" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/oracle" + documentationUrl: "https://docs.airbyte.com/integrations/sources/oracle" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Oracle Source Spec" @@ -8085,7 +8085,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-outreach:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/outreach" + documentationUrl: "https://docs.airbyte.com/integrations/sources/outreach" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Outreach Spec" @@ -8181,7 +8181,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-paypal-transaction:0.1.10" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/paypal-transactions" + documentationUrl: "https://docs.airbyte.com/integrations/sources/paypal-transactions" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Paypal Transaction Search" @@ -8225,7 +8225,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-paystack:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/paystack" + documentationUrl: "https://docs.airbyte.com/integrations/sources/paystack" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Paystack Source Spec" @@ -8264,7 +8264,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-persistiq:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/persistiq" + documentationUrl: "https://docs.airbyte.com/integrations/sources/persistiq" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Persistiq Spec" @@ -8283,7 +8283,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-pinterest:0.1.7" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/pinterest" + documentationUrl: "https://docs.airbyte.com/integrations/sources/pinterest" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Pinterest Spec" @@ -8387,7 +8387,7 @@ - "client_secret" - dockerImage: "airbyte/source-pipedrive:0.1.13" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/pipedrive" + documentationUrl: "https://docs.airbyte.com/integrations/sources/pipedrive" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Pipedrive Spec" @@ -8534,7 +8534,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-pokeapi:0.1.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/pokeapi" + documentationUrl: "https://docs.airbyte.com/integrations/sources/pokeapi" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Pokeapi Spec" @@ -9054,7 +9054,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-qualaroo:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/qualaroo" + documentationUrl: "https://docs.airbyte.com/integrations/sources/qualaroo" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Qualaroo Spec" @@ -9238,7 +9238,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-redshift:0.3.14" spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/redshift" + documentationUrl: "https://docs.airbyte.com/integrations/destinations/redshift" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Redshift Source Spec" @@ -9432,8 +9432,8 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-s3:0.1.22" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/s3" - changelogUrl: "https://docs.airbyte.io/integrations/sources/s3" + documentationUrl: "https://docs.airbyte.com/integrations/sources/s3" + changelogUrl: "https://docs.airbyte.com/integrations/sources/s3" connectionSpecification: title: "S3 Source Spec" type: "object" @@ -9737,7 +9737,7 @@ - "append_dedup" - dockerImage: "airbyte/source-salesloft:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/salesloft" + documentationUrl: "https://docs.airbyte.com/integrations/sources/salesloft" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Salesloft Spec" @@ -9906,7 +9906,7 @@ - "client_secret" - dockerImage: "airbyte/source-search-metrics:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/seacrh-metrics" + documentationUrl: "https://docs.airbyte.com/integrations/sources/seacrh-metrics" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Search Metrics Spec" @@ -9980,7 +9980,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-sendgrid:0.2.14" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/sendgrid" + documentationUrl: "https://docs.airbyte.com/integrations/sources/sendgrid" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Sendgrid Spec" @@ -10013,7 +10013,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-shopify:0.1.37" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/shopify" + documentationUrl: "https://docs.airbyte.com/integrations/sources/shopify" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Shopify Source CDK Specifications" @@ -10168,7 +10168,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-slack:0.1.18" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/slack" + documentationUrl: "https://docs.airbyte.com/integrations/sources/slack" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Slack Spec" @@ -10231,7 +10231,7 @@ const: "Default OAuth2.0 authorization" client_id: title: "Client ID" - description: "Slack client_id. See our docs if you need help finding this id." type: "string" examples: @@ -10239,7 +10239,7 @@ airbyte_secret: true client_secret: title: "Client Secret" - description: "Slack client_secret. See our docs if you need help finding this secret." type: "string" examples: @@ -10247,7 +10247,7 @@ airbyte_secret: true access_token: title: "Access token" - description: "Slack access_token. See our docs if you need help generating the token." type: "string" examples: @@ -10255,7 +10255,7 @@ airbyte_secret: true refresh_token: title: "Refresh token" - description: "Slack refresh_token. See our docs if you need help generating the token." type: "string" examples: @@ -10274,7 +10274,7 @@ api_token: type: "string" title: "API Token" - description: "A Slack bot token. See the docs for instructions on how to generate it." airbyte_secret: true order: 1 @@ -10295,7 +10295,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-smartsheets:0.1.12" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/smartsheets" + documentationUrl: "https://docs.airbyte.com/integrations/sources/smartsheets" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Smartsheets Source Spec" @@ -10311,7 +10311,7 @@ \ This access token must be generated by a user with at least read access\ \ to the data you'd like to replicate. Generate an access token in the\ \ Smartsheets main menu by clicking Account > Apps & Integrations > API\ - \ Access. See the setup guide for information on how to obtain this token." type: "string" order: 0 @@ -10364,7 +10364,7 @@ properties: {} - dockerImage: "airbyte/source-snapchat-marketing:0.1.8" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/snapchat-marketing" + documentationUrl: "https://docs.airbyte.com/integrations/sources/snapchat-marketing" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Snapchat Marketing Spec" @@ -10425,7 +10425,7 @@ - - "refresh_token" - dockerImage: "airbyte/source-snowflake:0.1.24" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/snowflake" + documentationUrl: "https://docs.airbyte.com/integrations/sources/snowflake" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Snowflake Source Spec" @@ -10605,7 +10605,7 @@ - "client_secret" - dockerImage: "airbyte/source-square:0.1.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/square" + documentationUrl: "https://docs.airbyte.com/integrations/sources/square" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Square Source CDK Specifications" @@ -10747,7 +10747,7 @@ - "client_secret" - dockerImage: "airbyte/source-strava:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/strava" + documentationUrl: "https://docs.airbyte.com/integrations/sources/strava" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Strava Spec" @@ -10841,7 +10841,7 @@ - "client_secret" - dockerImage: "airbyte/source-stripe:0.1.39" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/stripe" + documentationUrl: "https://docs.airbyte.com/integrations/sources/stripe" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Stripe Source Spec" @@ -10905,7 +10905,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-surveymonkey:0.1.11" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/surveymonkey" + documentationUrl: "https://docs.airbyte.com/integrations/sources/surveymonkey" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "SurveyMonkey Spec" @@ -10921,8 +10921,8 @@ type: "string" airbyte_secret: true description: "Access Token for making authenticated requests. See the docs\ - \ for information on how to generate this key." + \ href=\"https://docs.airbyte.com/integrations/sources/surveymonkey\"\ + >docs for information on how to generate this key." start_date: title: "Start Date" order: 1 @@ -11003,7 +11003,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-tempo:0.2.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/" + documentationUrl: "https://docs.airbyte.com/integrations/sources/" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Tempo Spec" @@ -11022,7 +11022,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-tidb:0.2.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/tidb" + documentationUrl: "https://docs.airbyte.com/integrations/sources/tidb" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "TiDB Source Spec" @@ -11183,8 +11183,8 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-tiktok-marketing:0.1.17" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/tiktok-marketing" - changelogUrl: "https://docs.airbyte.io/integrations/sources/tiktok-marketing" + documentationUrl: "https://docs.airbyte.com/integrations/sources/tiktok-marketing" + changelogUrl: "https://docs.airbyte.com/integrations/sources/tiktok-marketing" connectionSpecification: title: "TikTok Marketing Source Spec" type: "object" @@ -11365,7 +11365,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-trello:0.1.6" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/trello" + documentationUrl: "https://docs.airbyte.com/integrations/sources/trello" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Trello Spec" @@ -11417,7 +11417,7 @@ - - "key" - dockerImage: "airbyte/source-twilio:0.1.12" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/twilio" + documentationUrl: "https://docs.airbyte.com/integrations/sources/twilio" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Twilio Spec" @@ -11466,7 +11466,7 @@ - "append" - dockerImage: "airbyte/source-typeform:0.1.9" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/typeform" + documentationUrl: "https://docs.airbyte.com/integrations/sources/typeform" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Typeform Spec" @@ -11505,7 +11505,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-us-census:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/us-census" + documentationUrl: "https://docs.airbyte.com/integrations/sources/us-census" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "https://api.census.gov/ Source Spec" @@ -11541,7 +11541,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-youtube-analytics:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/youtube-analytics" + documentationUrl: "https://docs.airbyte.com/integrations/sources/youtube-analytics" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "YouTube Analytics Spec" @@ -11627,7 +11627,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-webflow:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/webflow" + documentationUrl: "https://docs.airbyte.com/integrations/sources/webflow" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Webflow Spec" @@ -11656,7 +11656,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-woocommerce:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/woocommerce" + documentationUrl: "https://docs.airbyte.com/integrations/sources/woocommerce" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Woocommerce Source CDK Specifications" @@ -11740,7 +11740,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-zendesk-chat:0.1.10" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk-chat" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zendesk-chat" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Zendesk Chat Spec" @@ -11864,7 +11864,7 @@ - "client_secret" - dockerImage: "airbyte/source-zendesk-sunshine:0.1.1" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk_sunshine" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zendesk_sunshine" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Zendesk Sunshine Spec" @@ -11937,7 +11937,7 @@ api_token: type: "string" title: "API Token" - description: "API Token. See the docs for information on how to generate this key." airbyte_secret: true email: @@ -11995,7 +11995,7 @@ - "client_secret" - dockerImage: "airbyte/source-zendesk-support:0.2.16" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk-support" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zendesk-support" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Zendesk Support Spec" @@ -12040,8 +12040,8 @@ type: "string" title: "Access Token" description: "The value of the API token generated. See the docs\ - \ for more information." + https://docs.airbyte.com/integrations/sources/zendesk-support\"\ + >docs for more information." airbyte_secret: true - title: "API Token" type: "object" @@ -12116,7 +12116,7 @@ - "client_secret" - dockerImage: "airbyte/source-zendesk-talk:0.1.5" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zendesk-talk" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zendesk-talk" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Zendesk Talk Spec" @@ -12174,7 +12174,7 @@ type: "string" title: "Access Token" description: "The value of the API token generated. See the docs\ + https://docs.airbyte.com/integrations/sources/zendesk-talk\">docs\ \ for more information." airbyte_secret: true start_date: @@ -12257,7 +12257,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-zenloop:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zenloop" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zenloop" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Zenloop Spec" @@ -12292,7 +12292,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-sentry:0.1.7" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/sentry" + documentationUrl: "https://docs.airbyte.com/integrations/sources/sentry" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Sentry Spec" @@ -12329,7 +12329,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-zoom-singer:0.2.4" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zoom" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zoom" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Zoom Singer Spec" @@ -12341,7 +12341,7 @@ jwt: title: "JWT Token" type: "string" - description: "Zoom JWT Token. See the docs for more information on how to obtain this key." airbyte_secret: true supportsNormalization: false @@ -12349,7 +12349,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-zuora:0.1.3" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/zuora" + documentationUrl: "https://docs.airbyte.com/integrations/sources/zuora" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Zuora Connector Configuration" @@ -12425,7 +12425,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-kustomer-singer:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/kustomer" + documentationUrl: "https://docs.airbyte.com/integrations/sources/kustomer" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Source Kustomer Singer Spec" @@ -12532,7 +12532,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-sftp:0.1.2" spec: - documentationUrl: "https://docs.airbyte.io/integrations/source/sftp" + documentationUrl: "https://docs.airbyte.com/integrations/source/sftp" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "SFTP Source Spec" @@ -12636,7 +12636,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-firebolt:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/firebolt" + documentationUrl: "https://docs.airbyte.com/integrations/sources/firebolt" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Firebolt Spec" @@ -12680,7 +12680,7 @@ supported_destination_sync_modes: [] - dockerImage: "airbyte/source-elasticsearch:0.1.0" spec: - documentationUrl: "https://docs.airbyte.io/integrations/source/elasticsearch" + documentationUrl: "https://docs.airbyte.com/integrations/source/elasticsearch" connectionSpecification: $schema: "http://json-schema.org/draft-07/schema#" title: "Elasticsearch Connection Configuration" diff --git a/airbyte-config/init/src/test/java/io/airbyte/config/init/LocalDefinitionsProviderTest.java b/airbyte-config/init/src/test/java/io/airbyte/config/init/LocalDefinitionsProviderTest.java index e7ae0117dd29..d17e6320b9f8 100644 --- a/airbyte-config/init/src/test/java/io/airbyte/config/init/LocalDefinitionsProviderTest.java +++ b/airbyte-config/init/src/test/java/io/airbyte/config/init/LocalDefinitionsProviderTest.java @@ -41,9 +41,9 @@ void testGetSourceDefinition() throws Exception { assertEquals(stripeSourceId, stripeSource.getSourceDefinitionId()); assertEquals("Stripe", stripeSource.getName()); assertEquals("airbyte/source-stripe", stripeSource.getDockerRepository()); - assertEquals("https://docs.airbyte.io/integrations/sources/stripe", stripeSource.getDocumentationUrl()); + assertEquals("https://docs.airbyte.com/integrations/sources/stripe", stripeSource.getDocumentationUrl()); assertEquals("stripe.svg", stripeSource.getIcon()); - assertEquals(URI.create("https://docs.airbyte.io/integrations/sources/stripe"), stripeSource.getSpec().getDocumentationUrl()); + assertEquals(URI.create("https://docs.airbyte.com/integrations/sources/stripe"), stripeSource.getSpec().getDocumentationUrl()); assertEquals(false, stripeSource.getTombstone()); assertEquals("0.2.0", stripeSource.getProtocolVersion()); } @@ -57,8 +57,8 @@ void testGetDestinationDefinition() throws Exception { assertEquals(s3DestinationId, s3Destination.getDestinationDefinitionId()); assertEquals("S3", s3Destination.getName()); assertEquals("airbyte/destination-s3", s3Destination.getDockerRepository()); - assertEquals("https://docs.airbyte.io/integrations/destinations/s3", s3Destination.getDocumentationUrl()); - assertEquals(URI.create("https://docs.airbyte.io/integrations/destinations/s3"), s3Destination.getSpec().getDocumentationUrl()); + assertEquals("https://docs.airbyte.com/integrations/destinations/s3", s3Destination.getDocumentationUrl()); + assertEquals(URI.create("https://docs.airbyte.com/integrations/destinations/s3"), s3Destination.getSpec().getDocumentationUrl()); assertEquals(false, s3Destination.getTombstone()); assertEquals("0.2.0", s3Destination.getProtocolVersion()); } diff --git a/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java b/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java index 1d01b4879898..4acc200e3ade 100644 --- a/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java +++ b/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java @@ -41,9 +41,9 @@ void testGetConfig() throws Exception { assertEquals(mySqlSourceId, mysqlSource.getSourceDefinitionId().toString()); assertEquals("MySQL", mysqlSource.getName()); assertEquals("airbyte/source-mysql", mysqlSource.getDockerRepository()); - assertEquals("https://docs.airbyte.io/integrations/sources/mysql", mysqlSource.getDocumentationUrl()); + assertEquals("https://docs.airbyte.com/integrations/sources/mysql", mysqlSource.getDocumentationUrl()); assertEquals("mysql.svg", mysqlSource.getIcon()); - assertEquals(URI.create("https://docs.airbyte.io/integrations/sources/mysql"), mysqlSource.getSpec().getDocumentationUrl()); + assertEquals(URI.create("https://docs.airbyte.com/integrations/sources/mysql"), mysqlSource.getSpec().getDocumentationUrl()); assertEquals(true, mysqlSource.getPublic()); assertEquals(false, mysqlSource.getCustom()); @@ -54,8 +54,8 @@ void testGetConfig() throws Exception { assertEquals(s3DestinationId, s3Destination.getDestinationDefinitionId().toString()); assertEquals("S3", s3Destination.getName()); assertEquals("airbyte/destination-s3", s3Destination.getDockerRepository()); - assertEquals("https://docs.airbyte.io/integrations/destinations/s3", s3Destination.getDocumentationUrl()); - assertEquals(URI.create("https://docs.airbyte.io/integrations/destinations/s3"), s3Destination.getSpec().getDocumentationUrl()); + assertEquals("https://docs.airbyte.com/integrations/destinations/s3", s3Destination.getDocumentationUrl()); + assertEquals(URI.create("https://docs.airbyte.com/integrations/destinations/s3"), s3Destination.getSpec().getDocumentationUrl()); assertEquals(true, s3Destination.getPublic()); assertEquals(false, s3Destination.getCustom()); } diff --git a/airbyte-integrations/connector-templates/destination-java/definition.yaml.hbs b/airbyte-integrations/connector-templates/destination-java/definition.yaml.hbs index 6c28efacc084..c7277c4ae49e 100644 --- a/airbyte-integrations/connector-templates/destination-java/definition.yaml.hbs +++ b/airbyte-integrations/connector-templates/destination-java/definition.yaml.hbs @@ -2,4 +2,4 @@ name: {{capitalCase name}} dockerRepository: airbyte/destination-{{dashCase name}} dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/{{dashCase name}} \ No newline at end of file + documentationUrl: https://docs.airbyte.com/integrations/destinations/{{dashCase name}} \ No newline at end of file diff --git a/airbyte-integrations/connector-templates/destination-java/spec.json.hbs b/airbyte-integrations/connector-templates/destination-java/spec.json.hbs index 12042e079886..b56ea8a843fc 100644 --- a/airbyte-integrations/connector-templates/destination-java/spec.json.hbs +++ b/airbyte-integrations/connector-templates/destination-java/spec.json.hbs @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/{{dashCase name}}", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/{{dashCase name}}", "supportsIncremental": TODO, "supported_destination_sync_modes": ["TODO"], "connectionSpecification": { diff --git a/airbyte-integrations/connector-templates/destination-python/destination_{{snakeCase name}}/spec.json b/airbyte-integrations/connector-templates/destination-python/destination_{{snakeCase name}}/spec.json index 9a8e4857a08b..67c5c72f181f 100644 --- a/airbyte-integrations/connector-templates/destination-python/destination_{{snakeCase name}}/spec.json +++ b/airbyte-integrations/connector-templates/destination-python/destination_{{snakeCase name}}/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/{{kebabCase name}}", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/{{kebabCase name}}", "supported_destination_sync_modes": [ "TODO, available options are: 'overwrite', 'append', and 'append_dedup'" ], diff --git a/airbyte-integrations/connector-templates/generator/plopfile.js b/airbyte-integrations/connector-templates/generator/plopfile.js index 993126254448..1ddbc7a42592 100644 --- a/airbyte-integrations/connector-templates/generator/plopfile.js +++ b/airbyte-integrations/connector-templates/generator/plopfile.js @@ -236,7 +236,7 @@ module.exports = function (plop) { templateFile: `${pythonSourceInputRoot}/.dockerignore.hbs`, path: `${pythonSourceOutputRoot}/.dockerignore` }, - {type: 'emitSuccess', outputPath: pythonSourceOutputRoot, message: "For a checklist of what to do next go to https://docs.airbyte.io/tutorials/building-a-python-source"}] + {type: 'emitSuccess', outputPath: pythonSourceOutputRoot, message: "For a checklist of what to do next go to https://docs.airbyte.com/connector-development/tutorials/building-a-python-source"}] }); plop.setGenerator('Java JDBC Source', { diff --git a/airbyte-integrations/connector-templates/source-java-jdbc/src/main/resources/spec.json.hbs b/airbyte-integrations/connector-templates/source-java-jdbc/src/main/resources/spec.json.hbs index 7a4ec6dd8c8a..c839b40a716b 100644 --- a/airbyte-integrations/connector-templates/source-java-jdbc/src/main/resources/spec.json.hbs +++ b/airbyte-integrations/connector-templates/source-java-jdbc/src/main/resources/spec.json.hbs @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/{{snakeCase name}}", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/{{snakeCase name}}", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "{{pascalCase name}} Source Spec", diff --git a/airbyte-integrations/connector-templates/source-singer/source_{{snakeCase name}}_singer/spec.yaml.hbs b/airbyte-integrations/connector-templates/source-singer/source_{{snakeCase name}}_singer/spec.yaml.hbs index c856ae2d33fc..9525c8f90c61 100644 --- a/airbyte-integrations/connector-templates/source-singer/source_{{snakeCase name}}_singer/spec.yaml.hbs +++ b/airbyte-integrations/connector-templates/source-singer/source_{{snakeCase name}}_singer/spec.yaml.hbs @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/{{snakeCase name}} +documentationUrl: https://docs.airbyte.com/integrations/sources/{{snakeCase name}} connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Source {{capitalCase name}} Singer Spec diff --git a/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs b/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs index 3981383e5d76..b5d79a215eb4 100644 --- a/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs +++ b/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: {{ connectorImage }} tests: diff --git a/airbyte-integrations/connectors/destination-amazon-sqs/destination_amazon_sqs/spec.json b/airbyte-integrations/connectors/destination-amazon-sqs/destination_amazon_sqs/spec.json index 415cbaf4fcd8..3c51e5cb2aec 100644 --- a/airbyte-integrations/connectors/destination-amazon-sqs/destination_amazon_sqs/spec.json +++ b/airbyte-integrations/connectors/destination-amazon-sqs/destination_amazon_sqs/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/amazon-sqs", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/amazon-sqs", "supported_destination_sync_modes": ["append"], "supportsIncremental": true, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-aws-datalake/destination_aws_datalake/spec.json b/airbyte-integrations/connectors/destination-aws-datalake/destination_aws_datalake/spec.json index b5b876c41a6e..6602b53d28cc 100644 --- a/airbyte-integrations/connectors/destination-aws-datalake/destination_aws_datalake/spec.json +++ b/airbyte-integrations/connectors/destination-aws-datalake/destination_aws_datalake/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/aws-datalake", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/aws-datalake", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/resources/spec.json index d07ea2ae7f01..36f4ecb4227f 100644 --- a/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/azureblobstorage", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/azureblobstorage", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/resources/spec.json index a45321cf1c17..fa11d9c3745d 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/bigquery", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/bigquery", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json index e79a9f8fd37f..07005df53cc9 100644 --- a/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-bigquery/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/bigquery", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/bigquery", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-cassandra/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-cassandra/src/main/resources/spec.json index 61e3c0a7ab72..fac77fe847be 100644 --- a/airbyte-integrations/connectors/destination-cassandra/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-cassandra/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/cassandra", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/cassandra", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-clickhouse-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-clickhouse-strict-encrypt/src/test/resources/expected_spec.json index 0cc91dd63bf1..a64b1e0c31a1 100644 --- a/airbyte-integrations/connectors/destination-clickhouse-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-clickhouse-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/clickhouse", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/clickhouse", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-clickhouse/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-clickhouse/src/main/resources/spec.json index 94f5acffb296..4f3c51333f8a 100644 --- a/airbyte-integrations/connectors/destination-clickhouse/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-clickhouse/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/clickhouse", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/clickhouse", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-csv/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-csv/src/main/resources/spec.json index 389af6735425..8236712ee5e3 100644 --- a/airbyte-integrations/connectors/destination-csv/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-csv/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/local-csv", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/local-csv", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, @@ -12,7 +12,7 @@ "additionalProperties": false, "properties": { "destination_path": { - "description": "Path to the directory where csv files will be written. The destination uses the local mount \"/local\" and any data files will be placed inside that local mount. For more information check out our docs", + "description": "Path to the directory where csv files will be written. The destination uses the local mount \"/local\" and any data files will be placed inside that local mount. For more information check out our docs", "type": "string", "examples": ["/local"] } diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json index 2893bc9d1376..50d52db6ae6d 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/databricks", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/databricks", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-dev-null/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-dev-null/src/test/resources/expected_spec.json index 94dc12007097..a42d27591f52 100644 --- a/airbyte-integrations/connectors/destination-dev-null/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-dev-null/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/e2e-test", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/e2e-test", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-dynamodb/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-dynamodb/src/main/resources/spec.json index 5463149917e1..c77cd537ff98 100644 --- a/airbyte-integrations/connectors/destination-dynamodb/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-dynamodb/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/dynamodb", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/dynamodb", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-e2e-test/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-e2e-test/src/main/resources/spec.json index b35585285001..91df74edc1d7 100644 --- a/airbyte-integrations/connectors/destination-e2e-test/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-e2e-test/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/e2e-test", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/e2e-test", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json index e8e769b20e28..a951230fe95a 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/elasticsearch", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/elasticsearch", "supportsIncremental": true, "supportsNormalization": false, "supportsNamespaces": true, diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json index 53e2eba24d9a..546f962d51b2 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/elasticsearch", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/elasticsearch", "supportsIncremental": true, "supportsNormalization": false, "supportsNamespaces": true, diff --git a/airbyte-integrations/connectors/destination-firebolt/destination_firebolt/spec.json b/airbyte-integrations/connectors/destination-firebolt/destination_firebolt/spec.json index 53f6d83ac6fc..a0263800bf39 100644 --- a/airbyte-integrations/connectors/destination-firebolt/destination_firebolt/spec.json +++ b/airbyte-integrations/connectors/destination-firebolt/destination_firebolt/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/firebolt", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/firebolt", "supported_destination_sync_modes": ["overwrite", "append"], "supportsIncremental": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-firestore/destination_firestore/spec.json b/airbyte-integrations/connectors/destination-firestore/destination_firestore/spec.json index a0b0fbefdb19..ec3148bdd708 100644 --- a/airbyte-integrations/connectors/destination-firestore/destination_firestore/spec.json +++ b/airbyte-integrations/connectors/destination-firestore/destination_firestore/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/firestore", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/firestore", "supported_destination_sync_modes": ["append", "overwrite"], "supportsIncremental": true, "supportsDBT": false, @@ -18,7 +18,7 @@ }, "credentials_json": { "type": "string", - "description": "The contents of the JSON service account key. Check out the docs if you need help generating this key. Default credentials will be used if this field is left empty.", + "description": "The contents of the JSON service account key. Check out the docs if you need help generating this key. Default credentials will be used if this field is left empty.", "title": "Credentials JSON", "airbyte_secret": true } diff --git a/airbyte-integrations/connectors/destination-gcs/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-gcs/src/main/resources/spec.json index b788c07f09ae..3a55b154c54e 100644 --- a/airbyte-integrations/connectors/destination-gcs/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-gcs/src/main/resources/spec.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/gcs", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/gcs", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spec.json b/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spec.json index 3ac69676990e..cdc4c97fe849 100644 --- a/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spec.json +++ b/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/google-sheets", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/google-sheets", "supported_destination_sync_modes": ["overwrite", "append", "append_dedup"], "supportsIncremental": true, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-jdbc/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-jdbc/src/main/resources/spec.json index d3b352bc5146..e3a079c28e11 100644 --- a/airbyte-integrations/connectors/destination-jdbc/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-jdbc/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/postgres", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/postgres", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-kafka/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-kafka/src/main/resources/spec.json index d64b596a0e6d..a27f85adde7c 100644 --- a/airbyte-integrations/connectors/destination-kafka/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-kafka/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/kafka", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/kafka", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-keen/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-keen/src/main/resources/spec.json index 56abae7b719b..084eb3f2a1a4 100644 --- a/airbyte-integrations/connectors/destination-keen/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-keen/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/keen", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/keen", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json index 476716e66b7f..6fb91d335c02 100644 --- a/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/kinesis", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/kinesis", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-local-json/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-local-json/src/main/resources/spec.json index d6fc2f30f6b3..c30aae2c6736 100644 --- a/airbyte-integrations/connectors/destination-local-json/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-local-json/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/local-json", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/local-json", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, @@ -12,7 +12,7 @@ "additionalProperties": false, "properties": { "destination_path": { - "description": "Path to the directory where json files will be written. The files will be placed inside that local mount. For more information check out our docs", + "description": "Path to the directory where json files will be written. The files will be placed inside that local mount. For more information check out our docs", "title": "Destination Path", "type": "string", "examples": ["/json_data"] diff --git a/airbyte-integrations/connectors/destination-mariadb-columnstore/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-mariadb-columnstore/src/main/resources/spec.json index 06b52efe8bf4..163d9f42afea 100644 --- a/airbyte-integrations/connectors/destination-mariadb-columnstore/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-mariadb-columnstore/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mariadb-columnstore", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mariadb-columnstore", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-meilisearch/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-meilisearch/src/main/resources/spec.json index e3d0095df141..d6afe8e712eb 100644 --- a/airbyte-integrations/connectors/destination-meilisearch/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-meilisearch/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/meilisearch", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/meilisearch", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, @@ -20,7 +20,7 @@ "api_key": { "title": "API Key", "airbyte_secret": true, - "description": "MeiliSearch API Key. See the docs for more information on how to obtain this key.", + "description": "MeiliSearch API Key. See the docs for more information on how to obtain this key.", "type": "string", "order": 1 } diff --git a/airbyte-integrations/connectors/destination-mongodb-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-mongodb-strict-encrypt/src/test/resources/expected_spec.json index f23873784b23..10f94c577c35 100644 --- a/airbyte-integrations/connectors/destination-mongodb-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-mongodb-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mongodb", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mongodb", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-mongodb/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-mongodb/src/main/resources/spec.json index f9b8c1692a87..d0656fae449d 100644 --- a/airbyte-integrations/connectors/destination-mongodb/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-mongodb/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mongodb", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mongodb", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, @@ -44,7 +44,7 @@ "tls": { "title": "TLS Connection", "type": "boolean", - "description": "Indicates whether TLS encryption protocol will be used to connect to MongoDB. It is recommended to use TLS connection if possible. For more information see documentation.", + "description": "Indicates whether TLS encryption protocol will be used to connect to MongoDB. It is recommended to use TLS connection if possible. For more information see documentation.", "default": false, "order": 2 } diff --git a/airbyte-integrations/connectors/destination-mqtt/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-mqtt/src/main/resources/spec.json index 759bc66ef0bb..64933cba71f4 100644 --- a/airbyte-integrations/connectors/destination-mqtt/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-mqtt/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mqtt", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mqtt", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-mssql-strict-encrypt/src/test-integration/resources/expected_spec.json b/airbyte-integrations/connectors/destination-mssql-strict-encrypt/src/test-integration/resources/expected_spec.json index 54d6ff5af828..b008ea367c8d 100644 --- a/airbyte-integrations/connectors/destination-mssql-strict-encrypt/src/test-integration/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-mssql-strict-encrypt/src/test-integration/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mssql", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mssql", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-mssql/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-mssql/src/main/resources/spec.json index 3aff969e59ab..aa9ca41be384 100644 --- a/airbyte-integrations/connectors/destination-mssql/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-mssql/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mssql", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mssql", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test/resources/expected_spec.json index dae4ce3d8032..90a8896098c8 100644 --- a/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mysql", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mysql", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-mysql/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-mysql/src/main/resources/spec.json index 0605667e3ff3..7b068ddc74e6 100644 --- a/airbyte-integrations/connectors/destination-mysql/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-mysql/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mysql", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mysql", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-oracle-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-oracle-strict-encrypt/src/test/resources/expected_spec.json index edcf94077b1d..86b2da9a042e 100644 --- a/airbyte-integrations/connectors/destination-oracle-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-oracle-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/oracle", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/oracle", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-oracle/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-oracle/src/main/resources/spec.json index d7fb730fc112..35aa4090b786 100644 --- a/airbyte-integrations/connectors/destination-oracle/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-oracle/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/oracle", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/oracle", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test/resources/expected_spec.json index 7d228b98dbcb..5410a917e982 100644 --- a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/postgres", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/postgres", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, @@ -143,7 +143,7 @@ }, "client_key_password": { "type": "string", - "title": "Client key password (Optional)", + "title": "Client key password", "description": "Password for keystorage. This field is optional. If you do not add it - the password will be generated automatically.", "airbyte_secret": true, "order": 4 @@ -194,7 +194,7 @@ }, "client_key_password": { "type": "string", - "title": "Client key password (Optional)", + "title": "Client key password", "description": "Password for keystorage. This field is optional. If you do not add it - the password will be generated automatically.", "airbyte_secret": true, "order": 4 diff --git a/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json index 0ca8be2b7c75..e310cb5a10f0 100644 --- a/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-postgres/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/postgres", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/postgres", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-pubsub/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-pubsub/src/main/resources/spec.json index 82bd13c4b868..7b4d6e680d88 100644 --- a/airbyte-integrations/connectors/destination-pubsub/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-pubsub/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/pubsub", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/pubsub", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, @@ -23,7 +23,7 @@ }, "credentials_json": { "type": "string", - "description": "The contents of the JSON service account key. Check out the docs if you need help generating this key.", + "description": "The contents of the JSON service account key. Check out the docs if you need help generating this key.", "title": "Credentials JSON", "airbyte_secret": true } diff --git a/airbyte-integrations/connectors/destination-pulsar/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-pulsar/src/main/resources/spec.json index 7dc40a064f49..e31691e78069 100644 --- a/airbyte-integrations/connectors/destination-pulsar/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-pulsar/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/pulsar", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/pulsar", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-r2/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-r2/src/main/resources/spec.json index 30e4ae6ca83b..5274dcf4e5d0 100644 --- a/airbyte-integrations/connectors/destination-r2/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-r2/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/r2", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/r2", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-rabbitmq/destination_rabbitmq/spec.json b/airbyte-integrations/connectors/destination-rabbitmq/destination_rabbitmq/spec.json index 6c24d4fccd2d..f3a4de508740 100644 --- a/airbyte-integrations/connectors/destination-rabbitmq/destination_rabbitmq/spec.json +++ b/airbyte-integrations/connectors/destination-rabbitmq/destination_rabbitmq/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/rabbitmq", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/rabbitmq", "supported_destination_sync_modes": ["append"], "supportsIncremental": true, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-redis/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-redis/src/main/resources/spec.json index ef2de6be9111..4ce15add8597 100644 --- a/airbyte-integrations/connectors/destination-redis/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-redis/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/redis", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/redis", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-redshift/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-redshift/src/main/resources/spec.json index 94fe0aa542f3..9028e01570de 100644 --- a/airbyte-integrations/connectors/destination-redshift/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-redshift/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/redshift", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/redshift", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/destination-rockset/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-rockset/src/main/resources/spec.json index 7eedb8e489c2..1bf5a3af68af 100644 --- a/airbyte-integrations/connectors/destination-rockset/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-rockset/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/rockset", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/rockset", "supportsIncremental": true, "supported_destination_sync_modes": ["append", "overwrite"], "connectionSpecification": { diff --git a/airbyte-integrations/connectors/destination-s3/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-s3/src/main/resources/spec.json index 94d64a8b9455..8dee35fc4714 100644 --- a/airbyte-integrations/connectors/destination-s3/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-s3/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/s3", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/s3", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-scaffold-destination-python/destination_scaffold_destination_python/spec.json b/airbyte-integrations/connectors/destination-scaffold-destination-python/destination_scaffold_destination_python/spec.json index aba800f419e6..f6e2d06413ff 100644 --- a/airbyte-integrations/connectors/destination-scaffold-destination-python/destination_scaffold_destination_python/spec.json +++ b/airbyte-integrations/connectors/destination-scaffold-destination-python/destination_scaffold_destination_python/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/scaffold-destination-python", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/scaffold-destination-python", "supported_destination_sync_modes": [ "TODO, available options are: 'overwrite', 'append', and 'append_dedup'" ], diff --git a/airbyte-integrations/connectors/destination-scylla/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-scylla/src/main/resources/spec.json index 6fbed67d0478..fb1ea4161761 100644 --- a/airbyte-integrations/connectors/destination-scylla/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-scylla/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/scylla", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/scylla", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-sftp-json/destination_sftp_json/spec.json b/airbyte-integrations/connectors/destination-sftp-json/destination_sftp_json/spec.json index 920997d1aef2..51227e1329ab 100644 --- a/airbyte-integrations/connectors/destination-sftp-json/destination_sftp_json/spec.json +++ b/airbyte-integrations/connectors/destination-sftp-json/destination_sftp_json/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/sftp-json", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/sftp-json", "supported_destination_sync_modes": ["overwrite", "append"], "supportsIncremental": true, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json index 332d02d27290..3f374694a97a 100644 --- a/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-snowflake/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/snowflake", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/snowflake", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, @@ -116,7 +116,7 @@ "private_key": { "type": "string", "title": "Private Key", - "description": "RSA Private key to use for Snowflake connection. See the docs for more information on how to obtain this key.", + "description": "RSA Private key to use for Snowflake connection. See the docs for more information on how to obtain this key.", "multiline": true, "airbyte_secret": true }, diff --git a/airbyte-integrations/connectors/destination-sqlite/destination_sqlite/spec.json b/airbyte-integrations/connectors/destination-sqlite/destination_sqlite/spec.json index 5d7f145ab7e7..e62fc3b3cd85 100644 --- a/airbyte-integrations/connectors/destination-sqlite/destination_sqlite/spec.json +++ b/airbyte-integrations/connectors/destination-sqlite/destination_sqlite/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/sqlite", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/sqlite", "supported_destination_sync_modes": ["overwrite", "append"], "supportsIncremental": true, "supportsDBT": false, @@ -13,7 +13,7 @@ "properties": { "destination_path": { "type": "string", - "description": "Path to the sqlite.db file. The file will be placed inside that local mount. For more information check out our docs", + "description": "Path to the sqlite.db file. The file will be placed inside that local mount. For more information check out our docs", "example": "/local/sqlite.db" } } diff --git a/airbyte-integrations/connectors/destination-tidb/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-tidb/src/main/resources/spec.json index d1fade3149e3..876d9e648fb2 100644 --- a/airbyte-integrations/connectors/destination-tidb/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-tidb/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/tidb", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/tidb", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, diff --git a/airbyte-integrations/connectors/source-adjust/acceptance-test-config.yml b/airbyte-integrations/connectors/source-adjust/acceptance-test-config.yml index a8fd3a6afbb3..58c97a2f50d4 100644 --- a/airbyte-integrations/connectors/source-adjust/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-adjust/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-adjust:dev tests: diff --git a/airbyte-integrations/connectors/source-adjust/source_adjust/model.py b/airbyte-integrations/connectors/source-adjust/source_adjust/model.py index 4bed0e4d21da..4eba9972859d 100644 --- a/airbyte-integrations/connectors/source-adjust/source_adjust/model.py +++ b/airbyte-integrations/connectors/source-adjust/source_adjust/model.py @@ -175,7 +175,7 @@ def schema_extra(schema: typing.Dict[str, typing.Any]): spec[key] = schema.pop(key) schema["connectionSpecification"] = spec - schema["documentationUrl"] = "https://docs.airbyte.io/integrations/sources/adjust" + schema["documentationUrl"] = "https://docs.airbyte.com/integrations/sources/adjust" class Report(pydantic.BaseModel): diff --git a/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml b/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml index 13e1664e2780..854e485ddf5c 100644 --- a/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml +++ b/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml @@ -147,4 +147,4 @@ connectionSpecification: - dimensions title: Adjust Spec type: object -documentationUrl: https://docs.airbyte.io/integrations/sources/adjust +documentationUrl: https://docs.airbyte.com/integrations/sources/adjust diff --git a/airbyte-integrations/connectors/source-airtable/acceptance-test-config.yml b/airbyte-integrations/connectors/source-airtable/acceptance-test-config.yml index 29d0624c98ae..d35ed96ef761 100644 --- a/airbyte-integrations/connectors/source-airtable/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-airtable/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-airtable:dev tests: diff --git a/airbyte-integrations/connectors/source-airtable/source_airtable/spec.json b/airbyte-integrations/connectors/source-airtable/source_airtable/spec.json index 0db138f20e40..b032dd767d47 100644 --- a/airbyte-integrations/connectors/source-airtable/source_airtable/spec.json +++ b/airbyte-integrations/connectors/source-airtable/source_airtable/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/airtable", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/airtable", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Airtable Source Spec", diff --git a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/acceptance-test-config.yml index a03ef7fed68d..ec3eed823ea1 100644 --- a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-postgres-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-alloydb/acceptance-test-config.yml b/airbyte-integrations/connectors/source-alloydb/acceptance-test-config.yml index 94c25ae1348d..761386060182 100644 --- a/airbyte-integrations/connectors/source-alloydb/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-alloydb/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-alloydb:dev tests: diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/spec.json b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/spec.json index 611c84e51efc..d88a5fcd7e23 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/amazon-seller-partner", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/amazon-seller-partner", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/amazon-seller-partner", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/amazon-seller-partner", "connectionSpecification": { "title": "Amazon Seller Partner Spec", "type": "object", diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/source.py b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/source.py index 7f7637a53ae1..0c13cdcb8f59 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/source.py +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/source.py @@ -179,8 +179,8 @@ def spec(self, *args, **kwargs) -> ConnectorSpecification: schema["properties"]["region"] = schema["definitions"]["AWSRegion"] return ConnectorSpecification( - documentationUrl="https://docs.airbyte.io/integrations/sources/amazon-seller-partner", - changelogUrl="https://docs.airbyte.io/integrations/sources/amazon-seller-partner", + documentationUrl="https://docs.airbyte.com/integrations/sources/amazon-seller-partner", + changelogUrl="https://docs.airbyte.com/integrations/sources/amazon-seller-partner", connectionSpecification=schema, advanced_auth=advanced_auth, ) diff --git a/airbyte-integrations/connectors/source-amazon-sqs/acceptance-test-config.yml b/airbyte-integrations/connectors/source-amazon-sqs/acceptance-test-config.yml index ed9571848de1..a68ba39ce0c3 100644 --- a/airbyte-integrations/connectors/source-amazon-sqs/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-amazon-sqs/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-amazon-sqs:dev tests: diff --git a/airbyte-integrations/connectors/source-amazon-sqs/source_amazon_sqs/spec.json b/airbyte-integrations/connectors/source-amazon-sqs/source_amazon_sqs/spec.json index 3bddbaf43236..0bb7d64eded0 100644 --- a/airbyte-integrations/connectors/source-amazon-sqs/source_amazon_sqs/spec.json +++ b/airbyte-integrations/connectors/source-amazon-sqs/source_amazon_sqs/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/amazon-sqs", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/amazon-sqs", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Amazon SQS Source Spec", diff --git a/airbyte-integrations/connectors/source-amplitude/source_amplitude/spec.json b/airbyte-integrations/connectors/source-amplitude/source_amplitude/spec.json index 4460bdc84fa2..dde8d92de7ad 100644 --- a/airbyte-integrations/connectors/source-amplitude/source_amplitude/spec.json +++ b/airbyte-integrations/connectors/source-amplitude/source_amplitude/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/amplitude", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/amplitude", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Amplitude Spec", @@ -10,13 +10,13 @@ "api_key": { "type": "string", "title": "API Key", - "description": "Amplitude API Key. See the setup guide for more information on how to obtain this key.", + "description": "Amplitude API Key. See the setup guide for more information on how to obtain this key.", "airbyte_secret": true }, "secret_key": { "type": "string", "title": "Secret Key", - "description": "Amplitude Secret Key. See the setup guide for more information on how to obtain this key.", + "description": "Amplitude Secret Key. See the setup guide for more information on how to obtain this key.", "airbyte_secret": true }, "start_date": { diff --git a/airbyte-integrations/connectors/source-apify-dataset/acceptance-test-config.yml b/airbyte-integrations/connectors/source-apify-dataset/acceptance-test-config.yml index b3bd08cf1ddb..5006c335ca41 100644 --- a/airbyte-integrations/connectors/source-apify-dataset/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-apify-dataset/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-apify-dataset:dev tests: diff --git a/airbyte-integrations/connectors/source-apify-dataset/source_apify_dataset/spec.json b/airbyte-integrations/connectors/source-apify-dataset/source_apify_dataset/spec.json index 4d8c313a967f..21ad77968932 100644 --- a/airbyte-integrations/connectors/source-apify-dataset/source_apify_dataset/spec.json +++ b/airbyte-integrations/connectors/source-apify-dataset/source_apify_dataset/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/apify-dataset", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/apify-dataset", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Apify Dataset Spec", diff --git a/airbyte-integrations/connectors/source-appfollow/acceptance-test-config.yml b/airbyte-integrations/connectors/source-appfollow/acceptance-test-config.yml index 3d14fbd40383..142bb3253c2c 100644 --- a/airbyte-integrations/connectors/source-appfollow/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-appfollow/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# # See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# # See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # # for more information about how to configure these tests connector_image: airbyte/source-appfollow:dev diff --git a/airbyte-integrations/connectors/source-appfollow/source_appfollow/spec.yaml b/airbyte-integrations/connectors/source-appfollow/source_appfollow/spec.yaml index ef66540c0877..f2fa2d7b82d1 100644 --- a/airbyte-integrations/connectors/source-appfollow/source_appfollow/spec.yaml +++ b/airbyte-integrations/connectors/source-appfollow/source_appfollow/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/appfollow +documentationUrl: https://docs.airbyte.com/integrations/sources/appfollow connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Appfollow Spec diff --git a/airbyte-integrations/connectors/source-appsflyer/acceptance-test-config.yml b/airbyte-integrations/connectors/source-appsflyer/acceptance-test-config.yml index bdab2d0ed70a..9675ce1e7743 100644 --- a/airbyte-integrations/connectors/source-appsflyer/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-appsflyer/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-appsflyer:dev tests: diff --git a/airbyte-integrations/connectors/source-appstore-singer/source_appstore_singer/spec.json b/airbyte-integrations/connectors/source-appstore-singer/source_appstore_singer/spec.json index d104b72377c8..408f65f29d44 100644 --- a/airbyte-integrations/connectors/source-appstore-singer/source_appstore_singer/spec.json +++ b/airbyte-integrations/connectors/source-appstore-singer/source_appstore_singer/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/appstore", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/appstore", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Appstore Singer Spec", @@ -10,24 +10,24 @@ "key_id": { "type": "string", "title": "Key ID", - "description": "Appstore Key ID. See the docs for more information on how to obtain this key." + "description": "Appstore Key ID. See the docs for more information on how to obtain this key." }, "private_key": { "type": "string", "title": "Private Key", - "description": "Appstore Private Key. See the docs for more information on how to obtain this key.", + "description": "Appstore Private Key. See the docs for more information on how to obtain this key.", "airbyte_secret": true, "multiline": true }, "issuer_id": { "type": "string", "title": "Issuer ID", - "description": "Appstore Issuer ID. See the docs for more information on how to obtain this ID." + "description": "Appstore Issuer ID. See the docs for more information on how to obtain this ID." }, "vendor": { "type": "string", "title": "Vendor ID", - "description": "Appstore Vendor ID. See the docs for more information on how to obtain this ID." + "description": "Appstore Vendor ID. See the docs for more information on how to obtain this ID." }, "start_date": { "type": "string", diff --git a/airbyte-integrations/connectors/source-asana/acceptance-test-config.yml b/airbyte-integrations/connectors/source-asana/acceptance-test-config.yml index 685d299ed108..b919a29e843f 100644 --- a/airbyte-integrations/connectors/source-asana/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-asana/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-asana:dev tests: diff --git a/airbyte-integrations/connectors/source-aws-cloudtrail/acceptance-test-config.yml b/airbyte-integrations/connectors/source-aws-cloudtrail/acceptance-test-config.yml index 9b0264476f0e..89b078848f2a 100644 --- a/airbyte-integrations/connectors/source-aws-cloudtrail/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-aws-cloudtrail/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-aws-cloudtrail:dev tests: diff --git a/airbyte-integrations/connectors/source-aws-cloudtrail/source_aws_cloudtrail/spec.json b/airbyte-integrations/connectors/source-aws-cloudtrail/source_aws_cloudtrail/spec.json index 8489e44f1fe5..4be11ff36949 100644 --- a/airbyte-integrations/connectors/source-aws-cloudtrail/source_aws_cloudtrail/spec.json +++ b/airbyte-integrations/connectors/source-aws-cloudtrail/source_aws_cloudtrail/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/aws-cloudtrail", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/aws-cloudtrail", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Aws CloudTrail Spec", @@ -15,13 +15,13 @@ "aws_key_id": { "type": "string", "title": "Key ID", - "description": "AWS CloudTrail Access Key ID. See the docs for more information on how to obtain this key.", + "description": "AWS CloudTrail Access Key ID. See the docs for more information on how to obtain this key.", "airbyte_secret": true }, "aws_secret_key": { "type": "string", "title": "Secret Key", - "description": "AWS CloudTrail Access Key ID. See the docs for more information on how to obtain this key.", + "description": "AWS CloudTrail Access Key ID. See the docs for more information on how to obtain this key.", "airbyte_secret": true }, "aws_region_name": { diff --git a/airbyte-integrations/connectors/source-azure-table/acceptance-test-config.yml b/airbyte-integrations/connectors/source-azure-table/acceptance-test-config.yml index 2847c928a271..9fac21867ac6 100644 --- a/airbyte-integrations/connectors/source-azure-table/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-azure-table/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-azure-table:dev tests: diff --git a/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json b/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json index 31b922d6363d..b163fa470114 100644 --- a/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json +++ b/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json @@ -17,14 +17,14 @@ "storage_access_key": { "title": "Access Key", "type": "string", - "description": "Azure Table Storage Access Key. See the docs for more information on how to obtain this key.", + "description": "Azure Table Storage Access Key. See the docs for more information on how to obtain this key.", "order": 1, "airbyte_secret": true }, "storage_endpoint_suffix": { "title": "Endpoint Suffix", "type": "string", - "description": "Azure Table Storage service account URL suffix. See the docs for more information on how to obtain endpoint suffix", + "description": "Azure Table Storage service account URL suffix. See the docs for more information on how to obtain endpoint suffix", "order": 2, "default": "core.windows.net", "examples": ["core.windows.net", "core.chinacloudapi.cn"], diff --git a/airbyte-integrations/connectors/source-bamboo-hr/acceptance-test-config.yml b/airbyte-integrations/connectors/source-bamboo-hr/acceptance-test-config.yml index 83494173fb2e..643902593238 100644 --- a/airbyte-integrations/connectors/source-bamboo-hr/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-bamboo-hr/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-bamboo-hr:dev tests: diff --git a/airbyte-integrations/connectors/source-bamboo-hr/source_bamboo_hr/spec.json b/airbyte-integrations/connectors/source-bamboo-hr/source_bamboo_hr/spec.json index 3a6e8bdfeb72..1c362f7fac33 100644 --- a/airbyte-integrations/connectors/source-bamboo-hr/source_bamboo_hr/spec.json +++ b/airbyte-integrations/connectors/source-bamboo-hr/source_bamboo_hr/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/bamboo-hr", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/bamboo-hr", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Bamboo HR Spec", diff --git a/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml b/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml index 60feb0605b83..3f9d1efe2ee2 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-bigcommerce:dev tests: diff --git a/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/spec.json b/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/spec.json index 9cc4bc6f2154..02b6ddf1f69f 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/spec.json +++ b/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/bigcommerce", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/bigcommerce", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "BigCommerce Source CDK Specifications", diff --git a/airbyte-integrations/connectors/source-bigquery/src/main/resources/spec.json b/airbyte-integrations/connectors/source-bigquery/src/main/resources/spec.json index 13185cc7e970..92bf4ebe4a1b 100644 --- a/airbyte-integrations/connectors/source-bigquery/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-bigquery/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/bigquery", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/bigquery", "supportsIncremental": true, "supportsNormalization": true, "supportsDBT": true, @@ -22,7 +22,7 @@ }, "credentials_json": { "type": "string", - "description": "The contents of your Service Account Key JSON file. See the docs for more information on how to obtain this key.", + "description": "The contents of your Service Account Key JSON file. See the docs for more information on how to obtain this key.", "title": "Credentials JSON", "airbyte_secret": true } diff --git a/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/spec.json b/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/spec.json index 7807377f8d14..17044fea9114 100644 --- a/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/spec.json +++ b/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/bing-ads", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/bing-ads", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Bing Ads Spec", diff --git a/airbyte-integrations/connectors/source-braintree/integration_tests/spec.json b/airbyte-integrations/connectors/source-braintree/integration_tests/spec.json index 4fd243e7c604..3a9b3ded0a6e 100644 --- a/airbyte-integrations/connectors/source-braintree/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-braintree/integration_tests/spec.json @@ -1,24 +1,24 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/braintree", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/braintree", "connectionSpecification": { "title": "Braintree Spec", "type": "object", "properties": { "merchant_id": { "title": "Merchant ID", - "description": "The unique identifier for your entire gateway account. See the docs for more information on how to obtain this ID.", + "description": "The unique identifier for your entire gateway account. See the docs for more information on how to obtain this ID.", "name": "Merchant ID", "type": "string" }, "public_key": { "title": "Public Key", - "description": "Braintree Public Key. See the docs for more information on how to obtain this key.", + "description": "Braintree Public Key. See the docs for more information on how to obtain this key.", "name": "Public Key", "type": "string" }, "private_key": { "title": "Private Key", - "description": "Braintree Private Key. See the docs for more information on how to obtain this key.", + "description": "Braintree Private Key. See the docs for more information on how to obtain this key.", "name": "Private Key", "airbyte_secret": true, "type": "string" diff --git a/airbyte-integrations/connectors/source-braintree/source_braintree/source.py b/airbyte-integrations/connectors/source-braintree/source_braintree/source.py index 32c5c7fc44e7..80b76ae2b667 100644 --- a/airbyte-integrations/connectors/source-braintree/source_braintree/source.py +++ b/airbyte-integrations/connectors/source-braintree/source_braintree/source.py @@ -46,5 +46,5 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: def spec(self, logger: AirbyteLogger) -> ConnectorSpecification: return ConnectorSpecification( - connectionSpecification=BraintreeConfig.schema(), documentationUrl="https://docs.airbyte.io/integrations/sources/braintree" + connectionSpecification=BraintreeConfig.schema(), documentationUrl="https://docs.airbyte.com/integrations/sources/braintree" ) diff --git a/airbyte-integrations/connectors/source-braintree/source_braintree/spec.py b/airbyte-integrations/connectors/source-braintree/source_braintree/spec.py index 523ffd0ed258..0103c27c87a7 100644 --- a/airbyte-integrations/connectors/source-braintree/source_braintree/spec.py +++ b/airbyte-integrations/connectors/source-braintree/source_braintree/spec.py @@ -23,17 +23,17 @@ class Config: merchant_id: str = Field( name="Merchant ID", title="Merchant ID", - description='The unique identifier for your entire gateway account. See the docs for more information on how to obtain this ID.', + description='The unique identifier for your entire gateway account. See the docs for more information on how to obtain this ID.', ) public_key: str = Field( name="Public Key", title="Public Key", - description='Braintree Public Key. See the docs for more information on how to obtain this key.', + description='Braintree Public Key. See the docs for more information on how to obtain this key.', ) private_key: str = Field( name="Private Key", title="Private Key", - description='Braintree Private Key. See the docs for more information on how to obtain this key.', + description='Braintree Private Key. See the docs for more information on how to obtain this key.', airbyte_secret=True, ) start_date: datetime = Field( diff --git a/airbyte-integrations/connectors/source-cart/acceptance-test-config.yml b/airbyte-integrations/connectors/source-cart/acceptance-test-config.yml index 75d436bfb019..8a8f34a782bd 100644 --- a/airbyte-integrations/connectors/source-cart/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-cart/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-cart:dev tests: diff --git a/airbyte-integrations/connectors/source-cart/source_cart/spec.json b/airbyte-integrations/connectors/source-cart/source_cart/spec.json index 4907592ad55d..05380699879e 100644 --- a/airbyte-integrations/connectors/source-cart/source_cart/spec.json +++ b/airbyte-integrations/connectors/source-cart/source_cart/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/cart", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/cart", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cart.com Spec", diff --git a/airbyte-integrations/connectors/source-chargebee/acceptance-test-config.yml b/airbyte-integrations/connectors/source-chargebee/acceptance-test-config.yml index ab71a0e60f38..22e062549773 100644 --- a/airbyte-integrations/connectors/source-chargebee/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-chargebee/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-chargebee:dev tests: diff --git a/airbyte-integrations/connectors/source-chargebee/source_chargebee/spec.json b/airbyte-integrations/connectors/source-chargebee/source_chargebee/spec.json index 33ece302848e..1395cf343764 100644 --- a/airbyte-integrations/connectors/source-chargebee/source_chargebee/spec.json +++ b/airbyte-integrations/connectors/source-chargebee/source_chargebee/spec.json @@ -16,7 +16,7 @@ "site_api_key": { "type": "string", "title": "API Key", - "description": "Chargebee API Key. See the docs for more information on how to obtain this key.", + "description": "Chargebee API Key. See the docs for more information on how to obtain this key.", "examples": ["test_3yzfanAXF66USdWC9wQcM555DQJkSYoppu"], "airbyte_secret": true }, diff --git a/airbyte-integrations/connectors/source-chargify/acceptance-test-config.yml b/airbyte-integrations/connectors/source-chargify/acceptance-test-config.yml index 4d98e83c9e31..3c96f7968faa 100644 --- a/airbyte-integrations/connectors/source-chargify/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-chargify/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-chargify:dev tests: diff --git a/airbyte-integrations/connectors/source-chargify/source_chargify/spec.json b/airbyte-integrations/connectors/source-chargify/source_chargify/spec.json index 4d77ed7f983d..5e0f26fde644 100644 --- a/airbyte-integrations/connectors/source-chargify/source_chargify/spec.json +++ b/airbyte-integrations/connectors/source-chargify/source_chargify/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/chargify", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/chargify", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Chargify Spec", diff --git a/airbyte-integrations/connectors/source-chartmogul/acceptance-test-config.yml b/airbyte-integrations/connectors/source-chartmogul/acceptance-test-config.yml index ba3029611366..67afba2dc79a 100644 --- a/airbyte-integrations/connectors/source-chartmogul/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-chartmogul/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-chartmogul:dev tests: diff --git a/airbyte-integrations/connectors/source-chartmogul/source_chartmogul/spec.json b/airbyte-integrations/connectors/source-chartmogul/source_chartmogul/spec.json index ff3d92dec022..8c625f060db8 100644 --- a/airbyte-integrations/connectors/source-chartmogul/source_chartmogul/spec.json +++ b/airbyte-integrations/connectors/source-chartmogul/source_chartmogul/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/chartmogul", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/chartmogul", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Chartmogul Spec", diff --git a/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/acceptance-test-config.yml index 6755ea3b92c0..fd4344e84e43 100644 --- a/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-clickhouse-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/src/test-integration/resources/expected_spec.json b/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/src/test-integration/resources/expected_spec.json index b6000d108e35..06530405e24a 100644 --- a/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/src/test-integration/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-clickhouse-strict-encrypt/src/test-integration/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/clickhouse", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/clickhouse", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ClickHouse Source Spec", diff --git a/airbyte-integrations/connectors/source-clickhouse/src/main/resources/spec.json b/airbyte-integrations/connectors/source-clickhouse/src/main/resources/spec.json index caf9f40395ed..c096b68fc12c 100644 --- a/airbyte-integrations/connectors/source-clickhouse/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-clickhouse/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/clickhouse", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/clickhouse", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ClickHouse Source Spec", diff --git a/airbyte-integrations/connectors/source-close-com/acceptance-test-config.yml b/airbyte-integrations/connectors/source-close-com/acceptance-test-config.yml index c166de2f2c6a..c8b56b0b5f2c 100644 --- a/airbyte-integrations/connectors/source-close-com/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-close-com/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-close-com:dev tests: diff --git a/airbyte-integrations/connectors/source-close-com/source_close_com/spec.json b/airbyte-integrations/connectors/source-close-com/source_close_com/spec.json index 2c067de018aa..ea67ed4f237e 100644 --- a/airbyte-integrations/connectors/source-close-com/source_close_com/spec.json +++ b/airbyte-integrations/connectors/source-close-com/source_close_com/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/close-com", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/close-com", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Close.com Spec", diff --git a/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/acceptance-test-config.yml index a45130214fbe..b76eb32040bc 100644 --- a/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-cockroachdb-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/src/main/resources/expected_spec.json b/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/src/main/resources/expected_spec.json index 964654e6dfeb..c24e2c7a25f0 100644 --- a/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/src/main/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-cockroachdb-strict-encrypt/src/main/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/cockroachdb", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/cockroachdb", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cockroach Source Spec", diff --git a/airbyte-integrations/connectors/source-cockroachdb/src/main/resources/spec.json b/airbyte-integrations/connectors/source-cockroachdb/src/main/resources/spec.json index 68e798ffa05a..d479d46e553c 100644 --- a/airbyte-integrations/connectors/source-cockroachdb/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-cockroachdb/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/cockroachdb", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/cockroachdb", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cockroach Source Spec", diff --git a/airbyte-integrations/connectors/source-commercetools/acceptance-test-config.yml b/airbyte-integrations/connectors/source-commercetools/acceptance-test-config.yml index 7dbb1e19d5a6..c12a6d3841bb 100644 --- a/airbyte-integrations/connectors/source-commercetools/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-commercetools/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-commercetools:dev tests: diff --git a/airbyte-integrations/connectors/source-commercetools/source_commercetools/spec.json b/airbyte-integrations/connectors/source-commercetools/source_commercetools/spec.json index f70c16dbfebb..446cc77984b8 100644 --- a/airbyte-integrations/connectors/source-commercetools/source_commercetools/spec.json +++ b/airbyte-integrations/connectors/source-commercetools/source_commercetools/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/commercetools", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/commercetools", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Commercetools Source CDK Specifications", diff --git a/airbyte-integrations/connectors/source-confluence/acceptance-test-config.yml b/airbyte-integrations/connectors/source-confluence/acceptance-test-config.yml index 48739b8cf72e..b468c4900f0a 100644 --- a/airbyte-integrations/connectors/source-confluence/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-confluence/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-confluence:dev tests: diff --git a/airbyte-integrations/connectors/source-db2-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-db2-strict-encrypt/acceptance-test-config.yml index 14f479009a00..3c75a9839b74 100644 --- a/airbyte-integrations/connectors/source-db2-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-db2-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-db2-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/resources/expected_spec.json index 0e2de7cf80c0..557025936b6f 100644 --- a/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/db2", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/db2", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "IBM Db2 Source Spec", diff --git a/airbyte-integrations/connectors/source-db2/acceptance-test-config.yml b/airbyte-integrations/connectors/source-db2/acceptance-test-config.yml index 0d964e5289a8..5106bfe080ca 100644 --- a/airbyte-integrations/connectors/source-db2/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-db2/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-db2:dev tests: diff --git a/airbyte-integrations/connectors/source-db2/src/main/resources/spec.json b/airbyte-integrations/connectors/source-db2/src/main/resources/spec.json index f96481498c01..45235a6e0cd6 100644 --- a/airbyte-integrations/connectors/source-db2/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-db2/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/db2", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/db2", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "IBM Db2 Source Spec", diff --git a/airbyte-integrations/connectors/source-delighted/acceptance-test-config.yml b/airbyte-integrations/connectors/source-delighted/acceptance-test-config.yml index 5158a03f4050..5a279ddfcfc3 100644 --- a/airbyte-integrations/connectors/source-delighted/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-delighted/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) +# See [Source Acceptance Tests](https://docs.airbyte.com/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) # for more information about how to configure these tests connector_image: airbyte/source-delighted:dev tests: diff --git a/airbyte-integrations/connectors/source-dixa/acceptance-test-config.yml b/airbyte-integrations/connectors/source-dixa/acceptance-test-config.yml index 754492b21709..ae61b2275289 100644 --- a/airbyte-integrations/connectors/source-dixa/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-dixa/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-dixa:dev tests: diff --git a/airbyte-integrations/connectors/source-dixa/source_dixa/spec.json b/airbyte-integrations/connectors/source-dixa/source_dixa/spec.json index f15e4a668137..016b237406a5 100644 --- a/airbyte-integrations/connectors/source-dixa/source_dixa/spec.json +++ b/airbyte-integrations/connectors/source-dixa/source_dixa/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/dixa", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/dixa", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Dixa Spec", diff --git a/airbyte-integrations/connectors/source-dockerhub/acceptance-test-config.yml b/airbyte-integrations/connectors/source-dockerhub/acceptance-test-config.yml index 353c0fd0ae64..ad86d5fe26ee 100644 --- a/airbyte-integrations/connectors/source-dockerhub/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-dockerhub/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-dockerhub:dev tests: diff --git a/airbyte-integrations/connectors/source-dockerhub/source_dockerhub/spec.yaml b/airbyte-integrations/connectors/source-dockerhub/source_dockerhub/spec.yaml index 2461d7f0a8d9..e2de2922d552 100644 --- a/airbyte-integrations/connectors/source-dockerhub/source_dockerhub/spec.yaml +++ b/airbyte-integrations/connectors/source-dockerhub/source_dockerhub/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/dockerhub +documentationUrl: https://docs.airbyte.com/integrations/sources/dockerhub connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Dockerhub Spec diff --git a/airbyte-integrations/connectors/source-drift/acceptance-test-config.yml b/airbyte-integrations/connectors/source-drift/acceptance-test-config.yml index 008cf40c888c..a4956b8af17c 100644 --- a/airbyte-integrations/connectors/source-drift/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-drift/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-drift:dev tests: diff --git a/airbyte-integrations/connectors/source-drift/source_drift/spec.json b/airbyte-integrations/connectors/source-drift/source_drift/spec.json index 3b1ad1f39f9c..a37ee17b7761 100644 --- a/airbyte-integrations/connectors/source-drift/source_drift/spec.json +++ b/airbyte-integrations/connectors/source-drift/source_drift/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/drift", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/drift", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Drift Spec", @@ -70,7 +70,7 @@ "access_token": { "type": "string", "title": "Access Token", - "description": "Drift Access Token. See the docs for more information on how to generate this key.", + "description": "Drift Access Token. See the docs for more information on how to generate this key.", "airbyte_secret": true } } diff --git a/airbyte-integrations/connectors/source-dv-360/acceptance-test-config.yml b/airbyte-integrations/connectors/source-dv-360/acceptance-test-config.yml index 6462d2f5a5ea..8f7bf89fa3bc 100644 --- a/airbyte-integrations/connectors/source-dv-360/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-dv-360/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-dv-360:dev tests: diff --git a/airbyte-integrations/connectors/source-e2e-test-cloud/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-e2e-test-cloud/src/test/resources/expected_spec.json index ca70dd250aae..52210061916e 100644 --- a/airbyte-integrations/connectors/source-e2e-test-cloud/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-e2e-test-cloud/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/e2e-test", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/e2e-test", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Cloud E2E Test Source Spec", diff --git a/airbyte-integrations/connectors/source-e2e-test/src/main/resources/legacy_spec.json b/airbyte-integrations/connectors/source-e2e-test/src/main/resources/legacy_spec.json index 14aa79720d8c..69d8e1a20d2f 100644 --- a/airbyte-integrations/connectors/source-e2e-test/src/main/resources/legacy_spec.json +++ b/airbyte-integrations/connectors/source-e2e-test/src/main/resources/legacy_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/e2e-test", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/e2e-test", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "E2E Test Source Spec", diff --git a/airbyte-integrations/connectors/source-e2e-test/src/main/resources/spec.json b/airbyte-integrations/connectors/source-e2e-test/src/main/resources/spec.json index bd7bb680d662..0183263d0636 100644 --- a/airbyte-integrations/connectors/source-e2e-test/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-e2e-test/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/e2e-test", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/e2e-test", "protocol_version": "0.2.1", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/airbyte-integrations/connectors/source-elasticsearch/acceptance-test-config.yml b/airbyte-integrations/connectors/source-elasticsearch/acceptance-test-config.yml index fed3fdc47c64..7f499fdefc40 100644 --- a/airbyte-integrations/connectors/source-elasticsearch/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-elasticsearch/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-elasticsearch tests: diff --git a/airbyte-integrations/connectors/source-elasticsearch/src/main/resources/spec.json b/airbyte-integrations/connectors/source-elasticsearch/src/main/resources/spec.json index a2b88dfbf374..fba748601133 100644 --- a/airbyte-integrations/connectors/source-elasticsearch/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-elasticsearch/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/source/elasticsearch", + "documentationUrl": "https://docs.airbyte.com/integrations/source/elasticsearch", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Elasticsearch Connection Configuration", diff --git a/airbyte-integrations/connectors/source-elasticsearch/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-elasticsearch/src/test/resources/expected_spec.json index a2b88dfbf374..fba748601133 100644 --- a/airbyte-integrations/connectors/source-elasticsearch/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-elasticsearch/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/source/elasticsearch", + "documentationUrl": "https://docs.airbyte.com/integrations/source/elasticsearch", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Elasticsearch Connection Configuration", diff --git a/airbyte-integrations/connectors/source-exchange-rates/acceptance-test-config.yml b/airbyte-integrations/connectors/source-exchange-rates/acceptance-test-config.yml index 457a11cf3d24..9f6c1b7ced5e 100644 --- a/airbyte-integrations/connectors/source-exchange-rates/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-exchange-rates/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-exchange-rates:dev tests: diff --git a/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/spec.yaml b/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/spec.yaml index 0ef6825fb114..9b1ce7591fd4 100644 --- a/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/spec.yaml +++ b/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/exchangeratesapi +documentationUrl: https://docs.airbyte.com/integrations/sources/exchangeratesapi connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: exchangeratesapi.io Source Spec diff --git a/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml b/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml index 0ca72508301d..71f03da883a5 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-facebook-marketing:dev tests: diff --git a/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/spec.json b/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/spec.json index 706eae4c9f02..7ad0be36a7a4 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/facebook-marketing", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/facebook-marketing", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/facebook-marketing", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/facebook-marketing", "connectionSpecification": { "title": "Source Facebook Marketing", "type": "object", @@ -32,7 +32,7 @@ }, "access_token": { "title": "Access Token", - "description": "The value of the access token generated. See the docs for more information", + "description": "The value of the access token generated. See the docs for more information", "order": 3, "airbyte_secret": true, "type": "string" diff --git a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/source.py b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/source.py index adeb1296c7e7..76ac52e64c7e 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/source.py +++ b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/source.py @@ -157,8 +157,8 @@ def spec(self, *args, **kwargs) -> ConnectorSpecification: (e.g: username and password) required to run this integration. """ return ConnectorSpecification( - documentationUrl="https://docs.airbyte.io/integrations/sources/facebook-marketing", - changelogUrl="https://docs.airbyte.io/integrations/sources/facebook-marketing", + documentationUrl="https://docs.airbyte.com/integrations/sources/facebook-marketing", + changelogUrl="https://docs.airbyte.com/integrations/sources/facebook-marketing", supportsIncremental=True, supported_destination_sync_modes=[DestinationSyncMode.append], connectionSpecification=ConnectorConfig.schema(), diff --git a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/spec.py b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/spec.py index 397787ca8ad5..2c62ddc9a0c8 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/spec.py +++ b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/spec.py @@ -128,7 +128,7 @@ class Config: order=3, description=( "The value of the access token generated. " - 'See the docs for more information' + 'See the docs for more information' ), airbyte_secret=True, ) diff --git a/airbyte-integrations/connectors/source-facebook-pages/acceptance-test-config.yml b/airbyte-integrations/connectors/source-facebook-pages/acceptance-test-config.yml index f78b8aefb46a..1d837a3089b6 100644 --- a/airbyte-integrations/connectors/source-facebook-pages/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-facebook-pages/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-facebook-pages:dev tests: diff --git a/airbyte-integrations/connectors/source-facebook-pages/source_facebook_pages/spec.json b/airbyte-integrations/connectors/source-facebook-pages/source_facebook_pages/spec.json index 0320a9f340f0..305f9756b95c 100755 --- a/airbyte-integrations/connectors/source-facebook-pages/source_facebook_pages/spec.json +++ b/airbyte-integrations/connectors/source-facebook-pages/source_facebook_pages/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/facebook-pages", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/facebook-pages", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Facebook Pages Spec", diff --git a/airbyte-integrations/connectors/source-faker/acceptance-test-config.yml b/airbyte-integrations/connectors/source-faker/acceptance-test-config.yml index 1484247c4d7c..8e505bb363ad 100644 --- a/airbyte-integrations/connectors/source-faker/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-faker/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-faker:dev tests: diff --git a/airbyte-integrations/connectors/source-fauna/acceptance-test-config.yml b/airbyte-integrations/connectors/source-fauna/acceptance-test-config.yml index d8c5c2e6887a..8ac6d0c9f90c 100644 --- a/airbyte-integrations/connectors/source-fauna/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-fauna/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-fauna:dev tests: diff --git a/airbyte-integrations/connectors/source-file-secure/acceptance-test-config.yml b/airbyte-integrations/connectors/source-file-secure/acceptance-test-config.yml index e8a25bfedf97..ad5aeb0e006e 100644 --- a/airbyte-integrations/connectors/source-file-secure/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-file-secure/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests # Here we tries to test a basic tests only. diff --git a/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json b/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json index 9dc5be7a95e3..1653e3eb5d8b 100644 --- a/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/file", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/file", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "File Source Spec", diff --git a/airbyte-integrations/connectors/source-file/acceptance-test-config.yml b/airbyte-integrations/connectors/source-file/acceptance-test-config.yml index e769a740cc3d..ade248fea791 100644 --- a/airbyte-integrations/connectors/source-file/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-file/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-file:dev tests: diff --git a/airbyte-integrations/connectors/source-file/integration_tests/sample_files/formats/yaml/demo.yaml b/airbyte-integrations/connectors/source-file/integration_tests/sample_files/formats/yaml/demo.yaml index 4d496b27ce0c..deda24d0e2a2 100644 --- a/airbyte-integrations/connectors/source-file/integration_tests/sample_files/formats/yaml/demo.yaml +++ b/airbyte-integrations/connectors/source-file/integration_tests/sample_files/formats/yaml/demo.yaml @@ -17,7 +17,7 @@ sourceDefinitionId: 778daa7c-feaf-4db6-96f3-70fd645acc77 dockerRepository: airbyte/source-file dockerImageTag: 0.2.10 - documentationUrl: https://docs.airbyte.io/integrations/sources/file + documentationUrl: https://docs.airbyte.com/integrations/sources/file icon: file.svg sourceType: file releaseStage: alpha diff --git a/airbyte-integrations/connectors/source-file/source_file/spec.json b/airbyte-integrations/connectors/source-file/source_file/spec.json index 9af744dc32f6..2a6b3ea3e432 100644 --- a/airbyte-integrations/connectors/source-file/source_file/spec.json +++ b/airbyte-integrations/connectors/source-file/source_file/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/file", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/file", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/airbyte-integrations/connectors/source-firebolt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-firebolt/acceptance-test-config.yml index 5888449a7fc4..d174d9e75b50 100644 --- a/airbyte-integrations/connectors/source-firebolt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-firebolt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-firebolt:dev tests: diff --git a/airbyte-integrations/connectors/source-firebolt/source_firebolt/spec.json b/airbyte-integrations/connectors/source-firebolt/source_firebolt/spec.json index b3423e1eb4c1..48b100dab629 100644 --- a/airbyte-integrations/connectors/source-firebolt/source_firebolt/spec.json +++ b/airbyte-integrations/connectors/source-firebolt/source_firebolt/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/firebolt", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/firebolt", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Firebolt Spec", diff --git a/airbyte-integrations/connectors/source-flexport/acceptance-test-config.yml b/airbyte-integrations/connectors/source-flexport/acceptance-test-config.yml index c4cb1c4d10eb..2bdfec546bff 100644 --- a/airbyte-integrations/connectors/source-flexport/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-flexport/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-flexport:dev tests: diff --git a/airbyte-integrations/connectors/source-flexport/source_flexport/spec.json b/airbyte-integrations/connectors/source-flexport/source_flexport/spec.json index 8589bb856cd9..d74cccc49727 100644 --- a/airbyte-integrations/connectors/source-flexport/source_flexport/spec.json +++ b/airbyte-integrations/connectors/source-flexport/source_flexport/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/flexport", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/flexport", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Flexport Spec", diff --git a/airbyte-integrations/connectors/source-freshcaller/source_freshcaller/spec.json b/airbyte-integrations/connectors/source-freshcaller/source_freshcaller/spec.json index e3202175d5ff..6c282a1da618 100644 --- a/airbyte-integrations/connectors/source-freshcaller/source_freshcaller/spec.json +++ b/airbyte-integrations/connectors/source-freshcaller/source_freshcaller/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/freshcaller", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/freshcaller", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Freshcaller Spec", @@ -16,7 +16,7 @@ "api_key": { "type": "string", "title": "API Key", - "description": "Freshcaller API Key. See the docs for more information on how to obtain this key.", + "description": "Freshcaller API Key. See the docs for more information on how to obtain this key.", "airbyte_secret": true }, "requests_per_minute": { diff --git a/airbyte-integrations/connectors/source-freshdesk/acceptance-test-config.yml b/airbyte-integrations/connectors/source-freshdesk/acceptance-test-config.yml index d8dfa0c6dbec..c37cd980e511 100644 --- a/airbyte-integrations/connectors/source-freshdesk/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-freshdesk/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-freshdesk:dev tests: diff --git a/airbyte-integrations/connectors/source-freshdesk/source_freshdesk/spec.json b/airbyte-integrations/connectors/source-freshdesk/source_freshdesk/spec.json index 7faa3559dac6..a1113b9a26f1 100644 --- a/airbyte-integrations/connectors/source-freshdesk/source_freshdesk/spec.json +++ b/airbyte-integrations/connectors/source-freshdesk/source_freshdesk/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/freshdesk", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/freshdesk", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Freshdesk Spec", @@ -17,7 +17,7 @@ "api_key": { "type": "string", "title": "API Key", - "description": "Freshdesk API Key. See the docs for more information on how to obtain this key.", + "description": "Freshdesk API Key. See the docs for more information on how to obtain this key.", "airbyte_secret": true }, "requests_per_minute": { diff --git a/airbyte-integrations/connectors/source-freshsales/acceptance-test-config.yml b/airbyte-integrations/connectors/source-freshsales/acceptance-test-config.yml index d8678f3f2444..34c6bbb77470 100644 --- a/airbyte-integrations/connectors/source-freshsales/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-freshsales/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-freshsales:dev tests: diff --git a/airbyte-integrations/connectors/source-freshsales/source_freshsales/spec.json b/airbyte-integrations/connectors/source-freshsales/source_freshsales/spec.json index fee78c2b571c..655ffd649d40 100644 --- a/airbyte-integrations/connectors/source-freshsales/source_freshsales/spec.json +++ b/airbyte-integrations/connectors/source-freshsales/source_freshsales/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/freshsales", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/freshsales", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Freshsales Spec", diff --git a/airbyte-integrations/connectors/source-freshservice/acceptance-test-config.yml b/airbyte-integrations/connectors/source-freshservice/acceptance-test-config.yml index 1fdf714e8fcf..3f3c20a088bb 100644 --- a/airbyte-integrations/connectors/source-freshservice/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-freshservice/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-freshservice:dev tests: diff --git a/airbyte-integrations/connectors/source-freshservice/source_freshservice/spec.json b/airbyte-integrations/connectors/source-freshservice/source_freshservice/spec.json index 05f99811d50b..2929c3450695 100644 --- a/airbyte-integrations/connectors/source-freshservice/source_freshservice/spec.json +++ b/airbyte-integrations/connectors/source-freshservice/source_freshservice/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/freshservice", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/freshservice", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Freshservice Spec", diff --git a/airbyte-integrations/connectors/source-gitlab/source_gitlab/spec.json b/airbyte-integrations/connectors/source-gitlab/source_gitlab/spec.json index 8ea35a964b8b..42ceafa8ecc6 100644 --- a/airbyte-integrations/connectors/source-gitlab/source_gitlab/spec.json +++ b/airbyte-integrations/connectors/source-gitlab/source_gitlab/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/gitlab", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/gitlab", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source GitLab Singer Spec", diff --git a/airbyte-integrations/connectors/source-glassfrog/acceptance-test-config.yml b/airbyte-integrations/connectors/source-glassfrog/acceptance-test-config.yml index cfd1b79ada46..b4bc499bf5a7 100644 --- a/airbyte-integrations/connectors/source-glassfrog/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-glassfrog/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-glassfrog:dev tests: diff --git a/airbyte-integrations/connectors/source-glassfrog/source_glassfrog/spec.yaml b/airbyte-integrations/connectors/source-glassfrog/source_glassfrog/spec.yaml index 46be13d613ae..e0e1c6faa12d 100644 --- a/airbyte-integrations/connectors/source-glassfrog/source_glassfrog/spec.yaml +++ b/airbyte-integrations/connectors/source-glassfrog/source_glassfrog/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/glassfrog +documentationUrl: https://docs.airbyte.com/integrations/sources/glassfrog connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Glassfrog Spec diff --git a/airbyte-integrations/connectors/source-google-ads/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-ads/acceptance-test-config.yml index cd5378a6fb8a..ffbd92b657d5 100644 --- a/airbyte-integrations/connectors/source-google-ads/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-ads/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-ads:dev tests: diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml index 04482497e8a9..3ceab93b6499 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-analytics-data-api:dev tests: diff --git a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json index 083f8b4aed84..29856c48a765 100644 --- a/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json +++ b/airbyte-integrations/connectors/source-google-analytics-data-api/source_google_analytics_data_api/spec.json @@ -92,7 +92,7 @@ "order": 3, "type": "string", "title": "Custom Reports", - "description": "A JSON array describing the custom reports you want to sync from Google Analytics. See the docs for more information about the exact format you can use to fill out this field." + "description": "A JSON array describing the custom reports you want to sync from Google Analytics. See the docs for more information about the exact format you can use to fill out this field." }, "window_in_days": { "type": "integer", diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-analytics-v4/acceptance-test-config.yml index c497e268a2c2..4c3cb0ea4112 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-analytics-v4/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-analytics-v4:dev diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/spec.json b/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/spec.json index e937fe2058ac..985b8867128a 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/spec.json +++ b/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/spec.json @@ -93,7 +93,7 @@ "order": 3, "type": "string", "title": "Custom Reports", - "description": "A JSON array describing the custom reports you want to sync from Google Analytics. See the docs for more information about the exact format you can use to fill out this field." + "description": "A JSON array describing the custom reports you want to sync from Google Analytics. See the docs for more information about the exact format you can use to fill out this field." }, "window_in_days": { "type": "integer", diff --git a/airbyte-integrations/connectors/source-google-directory/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-directory/acceptance-test-config.yml index 8d3a0596d25d..c9eae6557530 100644 --- a/airbyte-integrations/connectors/source-google-directory/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-directory/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-directory:dev tests: diff --git a/airbyte-integrations/connectors/source-google-directory/source_google_directory/spec.json b/airbyte-integrations/connectors/source-google-directory/source_google_directory/spec.json index 2eda4d3be1bf..ce8bf46c7891 100644 --- a/airbyte-integrations/connectors/source-google-directory/source_google_directory/spec.json +++ b/airbyte-integrations/connectors/source-google-directory/source_google_directory/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/google-directory", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/google-directory", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Google Directory Spec", diff --git a/airbyte-integrations/connectors/source-google-search-console/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-search-console/acceptance-test-config.yml index dfe33253fde0..c6228a70c37d 100755 --- a/airbyte-integrations/connectors/source-google-search-console/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-search-console/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-search-console:dev tests: diff --git a/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json b/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json index 383e755b1864..7d7266ab77a9 100755 --- a/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json +++ b/airbyte-integrations/connectors/source-google-search-console/source_google_search_console/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/google-search-console", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/google-search-console", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Google Search Console Spec", diff --git a/airbyte-integrations/connectors/source-google-sheets/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-sheets/acceptance-test-config.yml index b40f18218c93..3129002b2ad4 100644 --- a/airbyte-integrations/connectors/source-google-sheets/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-sheets/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-sheets:dev tests: diff --git a/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/spec.yaml b/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/spec.yaml index 3c0fd05dff9b..68983c6a3e61 100644 --- a/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/spec.yaml +++ b/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/google-sheets +documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Google Sheets Source Spec diff --git a/airbyte-integrations/connectors/source-google-workspace-admin-reports/acceptance-test-config.yml b/airbyte-integrations/connectors/source-google-workspace-admin-reports/acceptance-test-config.yml index cf27f5ba2ca1..0e4ef47ccfca 100755 --- a/airbyte-integrations/connectors/source-google-workspace-admin-reports/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-google-workspace-admin-reports/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-google-workspace-admin-reports:dev tests: diff --git a/airbyte-integrations/connectors/source-google-workspace-admin-reports/source_google_workspace_admin_reports/spec.json b/airbyte-integrations/connectors/source-google-workspace-admin-reports/source_google_workspace_admin_reports/spec.json index a80790951d62..25a5263cedac 100644 --- a/airbyte-integrations/connectors/source-google-workspace-admin-reports/source_google_workspace_admin_reports/spec.json +++ b/airbyte-integrations/connectors/source-google-workspace-admin-reports/source_google_workspace_admin_reports/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/google-workspace-admin-reports", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/google-workspace-admin-reports", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Google Directory Spec", diff --git a/airbyte-integrations/connectors/source-greenhouse/acceptance-test-config.yml b/airbyte-integrations/connectors/source-greenhouse/acceptance-test-config.yml index bb4e50f9ad08..96bbb9f0b59b 100644 --- a/airbyte-integrations/connectors/source-greenhouse/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-greenhouse/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-greenhouse:dev tests: diff --git a/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/spec.json b/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/spec.json index 9ecfdedf7a65..e791fd2694fd 100644 --- a/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/spec.json +++ b/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/spec.json @@ -10,7 +10,7 @@ "api_key": { "title": "API Key", "type": "string", - "description": "Greenhouse API Key. See the docs for more information on how to generate this key.", + "description": "Greenhouse API Key. See the docs for more information on how to generate this key.", "airbyte_secret": true, "order": 0 } diff --git a/airbyte-integrations/connectors/source-harvest/source_harvest/spec.json b/airbyte-integrations/connectors/source-harvest/source_harvest/spec.json index 75b9cdad3d7a..4e06112b33d1 100644 --- a/airbyte-integrations/connectors/source-harvest/source_harvest/spec.json +++ b/airbyte-integrations/connectors/source-harvest/source_harvest/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/harvest", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/harvest", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Harvest Spec", diff --git a/airbyte-integrations/connectors/source-hellobaton/acceptance-test-config.yml b/airbyte-integrations/connectors/source-hellobaton/acceptance-test-config.yml index 79787e5b9620..16a85d2829f9 100644 --- a/airbyte-integrations/connectors/source-hellobaton/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-hellobaton/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-hellobaton:dev tests: diff --git a/airbyte-integrations/connectors/source-hubplanner/acceptance-test-config.yml b/airbyte-integrations/connectors/source-hubplanner/acceptance-test-config.yml index 7dece07aad48..7551eb7ba2fc 100644 --- a/airbyte-integrations/connectors/source-hubplanner/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-hubplanner/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-hubplanner:dev tests: diff --git a/airbyte-integrations/connectors/source-hubplanner/source_hubplanner/spec.json b/airbyte-integrations/connectors/source-hubplanner/source_hubplanner/spec.json index aa061822e322..97897dc9370a 100644 --- a/airbyte-integrations/connectors/source-hubplanner/source_hubplanner/spec.json +++ b/airbyte-integrations/connectors/source-hubplanner/source_hubplanner/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/hubplanner", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/hubplanner", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Hubplanner Spec", diff --git a/airbyte-integrations/connectors/source-hubspot/source_hubspot/spec.yaml b/airbyte-integrations/connectors/source-hubspot/source_hubspot/spec.yaml index 61b5a8557dc1..00b93937667f 100644 --- a/airbyte-integrations/connectors/source-hubspot/source_hubspot/spec.yaml +++ b/airbyte-integrations/connectors/source-hubspot/source_hubspot/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/hubspot +documentationUrl: https://docs.airbyte.com/integrations/sources/hubspot connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: HubSpot Source Spec diff --git a/airbyte-integrations/connectors/source-instagram/acceptance-test-config.yml b/airbyte-integrations/connectors/source-instagram/acceptance-test-config.yml index 4760483b6fce..0a53d8267a4d 100644 --- a/airbyte-integrations/connectors/source-instagram/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-instagram/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-instagram:dev tests: diff --git a/airbyte-integrations/connectors/source-instagram/integration_tests/spec.json b/airbyte-integrations/connectors/source-instagram/integration_tests/spec.json index f5bf710d413a..23f465b39df3 100644 --- a/airbyte-integrations/connectors/source-instagram/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-instagram/integration_tests/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/instagram", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/instagram", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/instagram", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/instagram", "connectionSpecification": { "title": "Source Instagram", "type": "object", @@ -15,7 +15,7 @@ }, "access_token": { "title": "Access Token", - "description": "The value of the access token generated. See the docs for more information", + "description": "The value of the access token generated. See the docs for more information", "airbyte_secret": true, "type": "string" } diff --git a/airbyte-integrations/connectors/source-instagram/source_instagram/source.py b/airbyte-integrations/connectors/source-instagram/source_instagram/source.py index d29a44d21d35..4c336fb12a0f 100644 --- a/airbyte-integrations/connectors/source-instagram/source_instagram/source.py +++ b/airbyte-integrations/connectors/source-instagram/source_instagram/source.py @@ -24,7 +24,7 @@ class Config: ) access_token: str = Field( - description='The value of the access token generated. See the docs for more information', + description='The value of the access token generated. See the docs for more information', airbyte_secret=True, ) @@ -74,8 +74,8 @@ def spec(self, *args, **kwargs) -> ConnectorSpecification: required to run this integration. """ return ConnectorSpecification( - documentationUrl="https://docs.airbyte.io/integrations/sources/instagram", - changelogUrl="https://docs.airbyte.io/integrations/sources/instagram", + documentationUrl="https://docs.airbyte.com/integrations/sources/instagram", + changelogUrl="https://docs.airbyte.com/integrations/sources/instagram", supportsIncremental=True, supported_destination_sync_modes=[DestinationSyncMode.append], connectionSpecification=ConnectorConfig.schema(), diff --git a/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml b/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml index dac3524d0f41..9795eeb1ec66 100644 --- a/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-intercom/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-intercom:dev tests: diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json b/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json index f431e158f00b..3fa003d12486 100644 --- a/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/intercom", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/intercom", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Intercom Spec", diff --git a/airbyte-integrations/connectors/source-iterable/acceptance-test-config.yml b/airbyte-integrations/connectors/source-iterable/acceptance-test-config.yml index e593a18812da..e76473f1336d 100644 --- a/airbyte-integrations/connectors/source-iterable/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-iterable/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) +# See [Source Acceptance Tests](https://docs.airbyte.com/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) # for more information about how to configure these tests connector_image: airbyte/source-iterable:dev tests: diff --git a/airbyte-integrations/connectors/source-iterable/source_iterable/spec.json b/airbyte-integrations/connectors/source-iterable/source_iterable/spec.json index 7f4a96014c49..f37e615b6f6e 100644 --- a/airbyte-integrations/connectors/source-iterable/source_iterable/spec.json +++ b/airbyte-integrations/connectors/source-iterable/source_iterable/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/iterable", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/iterable", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Iterable Spec", @@ -10,7 +10,7 @@ "api_key": { "type": "string", "title": "API Key", - "description": "Iterable API Key. See the docs for more information on how to obtain this key.", + "description": "Iterable API Key. See the docs for more information on how to obtain this key.", "airbyte_secret": true, "order": 0 }, diff --git a/airbyte-integrations/connectors/source-jdbc/src/main/resources/spec.json b/airbyte-integrations/connectors/source-jdbc/src/main/resources/spec.json index 1356aae791bb..4d668c68efb7 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-jdbc/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/postgres", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/postgres", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "JDBC Source Spec", diff --git a/airbyte-integrations/connectors/source-jira/acceptance-test-config.yml b/airbyte-integrations/connectors/source-jira/acceptance-test-config.yml index 69156b5d08f0..c5de3a473160 100644 --- a/airbyte-integrations/connectors/source-jira/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-jira/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-jira:dev tests: diff --git a/airbyte-integrations/connectors/source-jira/source_jira/spec.json b/airbyte-integrations/connectors/source-jira/source_jira/spec.json index 0c581c538909..1f21bc250d87 100644 --- a/airbyte-integrations/connectors/source-jira/source_jira/spec.json +++ b/airbyte-integrations/connectors/source-jira/source_jira/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/jira", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/jira", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Jira Spec", @@ -10,7 +10,7 @@ "api_token": { "type": "string", "title": "API Token", - "description": "Jira API Token. See the docs for more information on how to generate this key.", + "description": "Jira API Token. See the docs for more information on how to generate this key.", "airbyte_secret": true }, "domain": { @@ -65,7 +65,7 @@ "enable_experimental_streams": { "type": "boolean", "title": "Enable Experimental Streams", - "description": "Allow the use of experimental streams which rely on undocumented Jira API endpoints. See https://docs.airbyte.io/integrations/sources/jira#experimental-tables for more info.", + "description": "Allow the use of experimental streams which rely on undocumented Jira API endpoints. See https://docs.airbyte.com/integrations/sources/jira#experimental-tables for more info.", "default": false } } diff --git a/airbyte-integrations/connectors/source-kafka/src/main/resources/spec.json b/airbyte-integrations/connectors/source-kafka/src/main/resources/spec.json index 4759f724440c..60ddd5e0c343 100644 --- a/airbyte-integrations/connectors/source-kafka/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-kafka/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/kafka", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/kafka", "supportsIncremental": true, "supportsNormalization": false, "supportsDBT": false, diff --git a/airbyte-integrations/connectors/source-klaviyo/acceptance-test-config.yml b/airbyte-integrations/connectors/source-klaviyo/acceptance-test-config.yml index 9b2f83c3fd2f..ae6488f296c7 100644 --- a/airbyte-integrations/connectors/source-klaviyo/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-klaviyo/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-klaviyo:dev tests: diff --git a/airbyte-integrations/connectors/source-klaviyo/source_klaviyo/spec.json b/airbyte-integrations/connectors/source-klaviyo/source_klaviyo/spec.json index 914600ba2a49..82b88ad11c52 100644 --- a/airbyte-integrations/connectors/source-klaviyo/source_klaviyo/spec.json +++ b/airbyte-integrations/connectors/source-klaviyo/source_klaviyo/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/klaviyo", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/klaviyo", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/klaviyo", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/klaviyo", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Klaviyo Spec", @@ -8,7 +8,7 @@ "properties": { "api_key": { "title": "Api Key", - "description": "Klaviyo API Key. See our docs if you need help finding this key.", + "description": "Klaviyo API Key. See our docs if you need help finding this key.", "airbyte_secret": true, "type": "string" }, diff --git a/airbyte-integrations/connectors/source-kustomer-singer/acceptance-test-config.yml b/airbyte-integrations/connectors/source-kustomer-singer/acceptance-test-config.yml index 0cd76d2280ea..887819255c20 100644 --- a/airbyte-integrations/connectors/source-kustomer-singer/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-kustomer-singer/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-kustomer-singer:dev tests: diff --git a/airbyte-integrations/connectors/source-kustomer-singer/source_kustomer_singer/spec.json b/airbyte-integrations/connectors/source-kustomer-singer/source_kustomer_singer/spec.json index 19e62c7c1eb4..dd88b02571ad 100644 --- a/airbyte-integrations/connectors/source-kustomer-singer/source_kustomer_singer/spec.json +++ b/airbyte-integrations/connectors/source-kustomer-singer/source_kustomer_singer/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/kustomer", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/kustomer", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Kustomer Singer Spec", diff --git a/airbyte-integrations/connectors/source-kyriba/acceptance-test-config.yml b/airbyte-integrations/connectors/source-kyriba/acceptance-test-config.yml index 30bfefcffbef..fc4fe8180620 100644 --- a/airbyte-integrations/connectors/source-kyriba/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-kyriba/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-kyriba:dev tests: diff --git a/airbyte-integrations/connectors/source-lemlist/source_lemlist/spec.json b/airbyte-integrations/connectors/source-lemlist/source_lemlist/spec.json index 9aa5133e3c8f..7887c9d90f7d 100644 --- a/airbyte-integrations/connectors/source-lemlist/source_lemlist/spec.json +++ b/airbyte-integrations/connectors/source-lemlist/source_lemlist/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/lemlist", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/lemlist", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Lemlist Spec", diff --git a/airbyte-integrations/connectors/source-lever-hiring/acceptance-test-config.yml b/airbyte-integrations/connectors/source-lever-hiring/acceptance-test-config.yml index b1be4556f38d..312a783149fb 100644 --- a/airbyte-integrations/connectors/source-lever-hiring/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-lever-hiring/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-lever-hiring:dev tests: diff --git a/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json b/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json index 4df7073f2dc7..751b39c470f9 100644 --- a/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/lever-hiring", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/lever-hiring#changelog", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/lever-hiring", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/lever-hiring#changelog", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Lever Hiring Source Spec", diff --git a/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json b/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json index 4df7073f2dc7..751b39c470f9 100644 --- a/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json +++ b/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/lever-hiring", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/lever-hiring#changelog", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/lever-hiring", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/lever-hiring#changelog", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Lever Hiring Source Spec", diff --git a/airbyte-integrations/connectors/source-linkedin-ads/acceptance-test-config.yml b/airbyte-integrations/connectors/source-linkedin-ads/acceptance-test-config.yml index 7a9b8bf20618..068e6ff9719f 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-linkedin-ads/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/contributing-to-airbyte/building-new-connector/source-acceptance-tests) +# See [Source Acceptance Tests](https://docs.airbyte.com/contributing-to-airbyte/building-new-connector/source-acceptance-tests) # for more information about how to configure these tests connector_image: airbyte/source-linkedin-ads:dev tests: diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py index 666a3fb53e53..0274264e6337 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py @@ -78,7 +78,7 @@ def should_retry(self, response: requests.Response) -> bool: f"Stream {self.name}: LinkedIn API requests are rate limited. " f"Rate limits specify the maximum number of API calls that can be made in a 24 hour period. " f"These limits reset at midnight UTC every day. " - f"You can find more information here https://docs.airbyte.io/integrations/sources/linkedin-ads. " + f"You can find more information here https://docs.airbyte.com/integrations/sources/linkedin-ads. " f"Also quotas and usage are here: https://www.linkedin.com/developers/apps." ) self.logger.error(error_message) diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.json b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.json index 56b960c3c3ae..8e8af360e74b 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.json +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/linkedin-ads", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/linkedin-ads", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Linkedin Ads Spec", diff --git a/airbyte-integrations/connectors/source-linkedin-pages/acceptance-test-config.yml b/airbyte-integrations/connectors/source-linkedin-pages/acceptance-test-config.yml index b7023bc46b22..1ede1d61a6a2 100644 --- a/airbyte-integrations/connectors/source-linkedin-pages/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-linkedin-pages/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-linkedin-pages:dev tests: diff --git a/airbyte-integrations/connectors/source-linkedin-pages/source_linkedin_pages/source.py b/airbyte-integrations/connectors/source-linkedin-pages/source_linkedin_pages/source.py index e03bbeecbb50..3ae046808158 100644 --- a/airbyte-integrations/connectors/source-linkedin-pages/source_linkedin_pages/source.py +++ b/airbyte-integrations/connectors/source-linkedin-pages/source_linkedin_pages/source.py @@ -47,7 +47,7 @@ def should_retry(self, response: requests.Response) -> bool: f"Stream {self.name}: LinkedIn API requests are rate limited. " f"Rate limits specify the maximum number of API calls that can be made in a 24 hour period. " f"These limits reset at midnight UTC every day. " - f"You can find more information here https://docs.airbyte.io/integrations/sources/linkedin-pages. " + f"You can find more information here https://docs.airbyte.com/integrations/sources/linkedin-pages. " f"Also quotas and usage are here: https://www.linkedin.com/developers/apps." ) self.logger.error(error_message) diff --git a/airbyte-integrations/connectors/source-linnworks/acceptance-test-config.yml b/airbyte-integrations/connectors/source-linnworks/acceptance-test-config.yml index d7cd5759ed08..eec185a3e833 100644 --- a/airbyte-integrations/connectors/source-linnworks/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-linnworks/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-linnworks:dev tests: diff --git a/airbyte-integrations/connectors/source-linnworks/source_linnworks/spec.json b/airbyte-integrations/connectors/source-linnworks/source_linnworks/spec.json index 7afcdced4eb9..8e99bdcbc648 100644 --- a/airbyte-integrations/connectors/source-linnworks/source_linnworks/spec.json +++ b/airbyte-integrations/connectors/source-linnworks/source_linnworks/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/linnworks", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/linnworks", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Linnworks Spec", diff --git a/airbyte-integrations/connectors/source-looker/acceptance-test-config.yml b/airbyte-integrations/connectors/source-looker/acceptance-test-config.yml index 31daa5289820..637e4607ac75 100644 --- a/airbyte-integrations/connectors/source-looker/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-looker/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-looker:dev tests: diff --git a/airbyte-integrations/connectors/source-looker/source_looker/spec.json b/airbyte-integrations/connectors/source-looker/source_looker/spec.json index 7dc4204a1136..74e9e470e57a 100644 --- a/airbyte-integrations/connectors/source-looker/source_looker/spec.json +++ b/airbyte-integrations/connectors/source-looker/source_looker/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/looker", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/looker", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Looker Spec", @@ -20,7 +20,7 @@ "client_id": { "title": "Client ID", "type": "string", - "description": "The Client ID is first part of an API3 key that is specific to each Looker user. See the docs for more information on how to generate this key." + "description": "The Client ID is first part of an API3 key that is specific to each Looker user. See the docs for more information on how to generate this key." }, "client_secret": { "title": "Client Secret", diff --git a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/spec.json b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/spec.json index ad8dad439c0c..5bf88b220ca1 100644 --- a/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/spec.json +++ b/airbyte-integrations/connectors/source-mailchimp/source_mailchimp/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mailchimp", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mailchimp", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Mailchimp Spec", @@ -54,7 +54,7 @@ "apikey": { "type": "string", "title": "API Key", - "description": "Mailchimp API Key. See the docs for information on how to generate this key.", + "description": "Mailchimp API Key. See the docs for information on how to generate this key.", "airbyte_secret": true } } diff --git a/airbyte-integrations/connectors/source-mailgun/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mailgun/acceptance-test-config.yml index 57c043b5c757..ca2b4ee804f5 100644 --- a/airbyte-integrations/connectors/source-mailgun/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-mailgun/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-mailgun:dev tests: diff --git a/airbyte-integrations/connectors/source-mailgun/source_mailgun/spec.json b/airbyte-integrations/connectors/source-mailgun/source_mailgun/spec.json index 25c41ffa4c49..624f239f7a8a 100644 --- a/airbyte-integrations/connectors/source-mailgun/source_mailgun/spec.json +++ b/airbyte-integrations/connectors/source-mailgun/source_mailgun/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mailgun", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mailgun", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Mailgun Spec", diff --git a/airbyte-integrations/connectors/source-marketo/acceptance-test-config.yml b/airbyte-integrations/connectors/source-marketo/acceptance-test-config.yml index 6a2ba4401610..463cb5f025a0 100644 --- a/airbyte-integrations/connectors/source-marketo/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-marketo/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-marketo:dev tests: diff --git a/airbyte-integrations/connectors/source-marketo/source_marketo/spec.json b/airbyte-integrations/connectors/source-marketo/source_marketo/spec.json index 9af488bf4cdc..5b8a56a730b0 100644 --- a/airbyte-integrations/connectors/source-marketo/source_marketo/spec.json +++ b/airbyte-integrations/connectors/source-marketo/source_marketo/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/marketo", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/marketo", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Marketo Spec", @@ -11,21 +11,21 @@ "title": "Domain URL", "type": "string", "order": 3, - "description": "Your Marketo Base URL. See the docs for info on how to obtain this.", + "description": "Your Marketo Base URL. See the docs for info on how to obtain this.", "examples": ["https://000-AAA-000.mktorest.com"], "airbyte_secret": true }, "client_id": { "title": "Client ID", "type": "string", - "description": "The Client ID of your Marketo developer application. See the docs for info on how to obtain this.", + "description": "The Client ID of your Marketo developer application. See the docs for info on how to obtain this.", "order": 0, "airbyte_secret": true }, "client_secret": { "title": "Client Secret", "type": "string", - "description": "The Client Secret of your Marketo developer application. See the docs for info on how to obtain this.", + "description": "The Client Secret of your Marketo developer application. See the docs for info on how to obtain this.", "order": 1, "airbyte_secret": true }, diff --git a/airbyte-integrations/connectors/source-metabase/source_metabase/spec.yaml b/airbyte-integrations/connectors/source-metabase/source_metabase/spec.yaml index 5e4a40f80547..a094f21d894e 100644 --- a/airbyte-integrations/connectors/source-metabase/source_metabase/spec.yaml +++ b/airbyte-integrations/connectors/source-metabase/source_metabase/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/metabase +documentationUrl: https://docs.airbyte.com/integrations/sources/metabase connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Metabase Source Spec diff --git a/airbyte-integrations/connectors/source-microsoft-teams/acceptance-test-config.yml b/airbyte-integrations/connectors/source-microsoft-teams/acceptance-test-config.yml index ded010235d1f..3ed33bbf1dd1 100644 --- a/airbyte-integrations/connectors/source-microsoft-teams/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-microsoft-teams/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-microsoft-teams:dev tests: diff --git a/airbyte-integrations/connectors/source-microsoft-teams/source_microsoft_teams/spec.json b/airbyte-integrations/connectors/source-microsoft-teams/source_microsoft_teams/spec.json index 442abfde2939..ab4af0e7d1ed 100644 --- a/airbyte-integrations/connectors/source-microsoft-teams/source_microsoft_teams/spec.json +++ b/airbyte-integrations/connectors/source-microsoft-teams/source_microsoft_teams/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/microsoft-teams", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/microsoft-teams", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Microsoft Teams Spec", diff --git a/airbyte-integrations/connectors/source-mixpanel/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mixpanel/acceptance-test-config.yml index 53e8ac399db9..563ea4642c52 100644 --- a/airbyte-integrations/connectors/source-mixpanel/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-mixpanel/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-mixpanel:dev tests: diff --git a/airbyte-integrations/connectors/source-mixpanel/source_mixpanel/spec.json b/airbyte-integrations/connectors/source-mixpanel/source_mixpanel/spec.json index 02cbba4e8942..b7a54cd528db 100644 --- a/airbyte-integrations/connectors/source-mixpanel/source_mixpanel/spec.json +++ b/airbyte-integrations/connectors/source-mixpanel/source_mixpanel/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mixpanel", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mixpanel", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Mixpanel Spec", diff --git a/airbyte-integrations/connectors/source-monday/acceptance-test-config.yml b/airbyte-integrations/connectors/source-monday/acceptance-test-config.yml index 635ebd693d84..bada3d79a93b 100644 --- a/airbyte-integrations/connectors/source-monday/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-monday/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-monday:dev tests: diff --git a/airbyte-integrations/connectors/source-monday/source_monday/spec.json b/airbyte-integrations/connectors/source-monday/source_monday/spec.json index f928a0d9b256..9119d34d41c2 100644 --- a/airbyte-integrations/connectors/source-monday/source_monday/spec.json +++ b/airbyte-integrations/connectors/source-monday/source_monday/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/monday", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/monday", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Monday Spec", diff --git a/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/test-integration/resources/expected_spec.json b/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/test-integration/resources/expected_spec.json index 26dadbda7bb6..2b5821b4f975 100644 --- a/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/test-integration/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/test-integration/resources/expected_spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mongodb-v2", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/mongodb-v2", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mongodb-v2", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/mongodb-v2", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MongoDb Source Spec", diff --git a/airbyte-integrations/connectors/source-mongodb-v2/src/main/resources/spec.json b/airbyte-integrations/connectors/source-mongodb-v2/src/main/resources/spec.json index e78ca96d82d8..fc7959b42274 100644 --- a/airbyte-integrations/connectors/source-mongodb-v2/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-mongodb-v2/src/main/resources/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mongodb-v2", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/mongodb-v2", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mongodb-v2", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/mongodb-v2", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MongoDb Source Spec", @@ -42,7 +42,7 @@ "tls": { "title": "TLS Connection", "type": "boolean", - "description": "Indicates whether TLS encryption protocol will be used to connect to MongoDB. It is recommended to use TLS connection if possible. For more information see documentation.", + "description": "Indicates whether TLS encryption protocol will be used to connect to MongoDB. It is recommended to use TLS connection if possible. For more information see documentation.", "default": false, "order": 2 } diff --git a/airbyte-integrations/connectors/source-mongodb/lib/spec.json b/airbyte-integrations/connectors/source-mongodb/lib/spec.json index 87ec218289f3..390f196ccdfe 100644 --- a/airbyte-integrations/connectors/source-mongodb/lib/spec.json +++ b/airbyte-integrations/connectors/source-mongodb/lib/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mongodb", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/mongodb", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mongodb", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/mongodb", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Mongodb Source Spec", diff --git a/airbyte-integrations/connectors/source-mssql-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mssql-strict-encrypt/acceptance-test-config.yml index f0e10cab2457..2c513e3132ce 100644 --- a/airbyte-integrations/connectors/source-mssql-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-mssql-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-mssql-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/resources/expected_spec.json index bcfb76b07eca..7158e7b18507 100644 --- a/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mssql", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mssql", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MSSQL Source Spec", diff --git a/airbyte-integrations/connectors/source-mssql/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mssql/acceptance-test-config.yml index 081594f7fc03..5aa3dde87597 100644 --- a/airbyte-integrations/connectors/source-mssql/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-mssql/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-mssql:dev tests: diff --git a/airbyte-integrations/connectors/source-mssql/src/main/resources/spec.json b/airbyte-integrations/connectors/source-mssql/src/main/resources/spec.json index 8b8acf4b2630..a76671f2dae6 100644 --- a/airbyte-integrations/connectors/source-mssql/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-mssql/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/mssql", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/mssql", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MSSQL Source Spec", diff --git a/airbyte-integrations/connectors/source-my-hours/acceptance-test-config.yml b/airbyte-integrations/connectors/source-my-hours/acceptance-test-config.yml index f8d73cf0e95d..652b076e81be 100644 --- a/airbyte-integrations/connectors/source-my-hours/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-my-hours/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-my-hours:dev tests: diff --git a/airbyte-integrations/connectors/source-my-hours/source_my_hours/spec.json b/airbyte-integrations/connectors/source-my-hours/source_my_hours/spec.json index d1a739b238be..6ca8a83e3fea 100644 --- a/airbyte-integrations/connectors/source-my-hours/source_my_hours/spec.json +++ b/airbyte-integrations/connectors/source-my-hours/source_my_hours/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/my-hours", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/my-hours", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "My Hours Spec", diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mysql-strict-encrypt/acceptance-test-config.yml index 99b34b82f86a..5dbe535ccb3e 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-mysql-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json index d9568d513664..2336c8b7f893 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mysql", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mysql", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MySql Source Spec", @@ -119,7 +119,7 @@ }, "client_key_password": { "type": "string", - "title": "Client key password (Optional)", + "title": "Client key password", "description": "Password for keystorage. This field is optional. If you do not add it - the password will be generated automatically.", "airbyte_secret": true, "order": 4 @@ -164,7 +164,7 @@ }, "client_key_password": { "type": "string", - "title": "Client key password (Optional)", + "title": "Client key password", "description": "Password for keystorage. This field is optional. If you do not add it - the password will be generated automatically.", "airbyte_secret": true, "order": 4 diff --git a/airbyte-integrations/connectors/source-mysql/acceptance-test-config.yml b/airbyte-integrations/connectors/source-mysql/acceptance-test-config.yml index 7ad2a6022587..cf854c42e505 100644 --- a/airbyte-integrations/connectors/source-mysql/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-mysql/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-mysql:dev tests: diff --git a/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json b/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json index 3d86accf2e05..456f50136b42 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/mysql", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/mysql", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "MySql Source Spec", diff --git a/airbyte-integrations/connectors/source-netsuite/acceptance-test-config.yml b/airbyte-integrations/connectors/source-netsuite/acceptance-test-config.yml index 4dc2f7a489d0..43f0d73ffe32 100644 --- a/airbyte-integrations/connectors/source-netsuite/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-netsuite/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-netsuite:dev tests: diff --git a/airbyte-integrations/connectors/source-notion/acceptance-test-config.yml b/airbyte-integrations/connectors/source-notion/acceptance-test-config.yml index fa3d38b65606..dd9eedd476b0 100644 --- a/airbyte-integrations/connectors/source-notion/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-notion/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-notion:dev tests: diff --git a/airbyte-integrations/connectors/source-notion/source_notion/spec.json b/airbyte-integrations/connectors/source-notion/source_notion/spec.json index 945a0f0815e1..d20142cdd82d 100644 --- a/airbyte-integrations/connectors/source-notion/source_notion/spec.json +++ b/airbyte-integrations/connectors/source-notion/source_notion/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/notion", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/notion", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Notion Source Spec", diff --git a/airbyte-integrations/connectors/source-okta/source_okta/spec.json b/airbyte-integrations/connectors/source-okta/source_okta/spec.json index f5f222761325..27877765ad3d 100644 --- a/airbyte-integrations/connectors/source-okta/source_okta/spec.json +++ b/airbyte-integrations/connectors/source-okta/source_okta/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/okta", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/okta", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Okta Spec", @@ -10,7 +10,7 @@ "domain": { "type": "string", "title": "Okta domain", - "description": "The Okta domain. See the docs for instructions on how to find it.", + "description": "The Okta domain. See the docs for instructions on how to find it.", "airbyte_secret": false }, "start_date": { @@ -72,7 +72,7 @@ "api_token": { "type": "string", "title": "Personal API Token", - "description": "An Okta token. See the docs for instructions on how to generate it.", + "description": "An Okta token. See the docs for instructions on how to generate it.", "airbyte_secret": true } } diff --git a/airbyte-integrations/connectors/source-onesignal/acceptance-test-config.yml b/airbyte-integrations/connectors/source-onesignal/acceptance-test-config.yml index 68ba6b4ee03b..ddd5106f6c99 100644 --- a/airbyte-integrations/connectors/source-onesignal/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-onesignal/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-onesignal:dev tests: diff --git a/airbyte-integrations/connectors/source-onesignal/source_onesignal/spec.json b/airbyte-integrations/connectors/source-onesignal/source_onesignal/spec.json index 1134e5bd1c2e..7ceef935c827 100644 --- a/airbyte-integrations/connectors/source-onesignal/source_onesignal/spec.json +++ b/airbyte-integrations/connectors/source-onesignal/source_onesignal/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/onesignal", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/onesignal", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "OneSignal Source Spec", diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-oracle-strict-encrypt/acceptance-test-config.yml index 94ddb2684397..fd4f0356b9f2 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-oracle-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/resources/expected_spec.json index 07d2e50b1b0b..17856fc32ad8 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/resources/expected_spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/oracle", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/oracle", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Oracle Source Spec", diff --git a/airbyte-integrations/connectors/source-oracle/acceptance-test-config.yml b/airbyte-integrations/connectors/source-oracle/acceptance-test-config.yml index a00943691385..37c40cb71f16 100644 --- a/airbyte-integrations/connectors/source-oracle/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-oracle/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-oracle:dev tests: diff --git a/airbyte-integrations/connectors/source-oracle/src/main/resources/spec.json b/airbyte-integrations/connectors/source-oracle/src/main/resources/spec.json index 3dce50d5be3a..4f1c7b04e01a 100644 --- a/airbyte-integrations/connectors/source-oracle/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-oracle/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/oracle", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/oracle", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Oracle Source Spec", diff --git a/airbyte-integrations/connectors/source-orb/acceptance-test-config.yml b/airbyte-integrations/connectors/source-orb/acceptance-test-config.yml index 64401643d148..86db5f7a0923 100644 --- a/airbyte-integrations/connectors/source-orb/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-orb/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-orb:dev tests: diff --git a/airbyte-integrations/connectors/source-orbit/acceptance-test-config.yml b/airbyte-integrations/connectors/source-orbit/acceptance-test-config.yml index a00444b24a6d..7113ca5ca503 100644 --- a/airbyte-integrations/connectors/source-orbit/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-orbit/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-orbit:dev tests: diff --git a/airbyte-integrations/connectors/source-outreach/acceptance-test-config.yml b/airbyte-integrations/connectors/source-outreach/acceptance-test-config.yml index 4549f5b77166..6d066c01f4ca 100644 --- a/airbyte-integrations/connectors/source-outreach/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-outreach/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-outreach:dev tests: diff --git a/airbyte-integrations/connectors/source-outreach/source_outreach/spec.json b/airbyte-integrations/connectors/source-outreach/source_outreach/spec.json index 149684eaab6d..6cbb614e700d 100644 --- a/airbyte-integrations/connectors/source-outreach/source_outreach/spec.json +++ b/airbyte-integrations/connectors/source-outreach/source_outreach/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/outreach", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/outreach", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Outreach Spec", diff --git a/airbyte-integrations/connectors/source-pardot/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pardot/acceptance-test-config.yml index 838eaac4b95c..dc8ca0fcdce6 100644 --- a/airbyte-integrations/connectors/source-pardot/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pardot/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-pardot:dev tests: diff --git a/airbyte-integrations/connectors/source-paypal-transaction/acceptance-test-config.yml b/airbyte-integrations/connectors/source-paypal-transaction/acceptance-test-config.yml index d324e942b6de..807ccbee333c 100644 --- a/airbyte-integrations/connectors/source-paypal-transaction/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-paypal-transaction/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-paypal-transaction:dev tests: diff --git a/airbyte-integrations/connectors/source-paypal-transaction/source_paypal_transaction/spec.json b/airbyte-integrations/connectors/source-paypal-transaction/source_paypal_transaction/spec.json index 610747b2c203..25b06e158256 100644 --- a/airbyte-integrations/connectors/source-paypal-transaction/source_paypal_transaction/spec.json +++ b/airbyte-integrations/connectors/source-paypal-transaction/source_paypal_transaction/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/paypal-transactions", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/paypal-transactions", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Paypal Transaction Search", diff --git a/airbyte-integrations/connectors/source-paystack/source_paystack/spec.json b/airbyte-integrations/connectors/source-paystack/source_paystack/spec.json index f77473a952a0..da8bb19f9ff7 100644 --- a/airbyte-integrations/connectors/source-paystack/source_paystack/spec.json +++ b/airbyte-integrations/connectors/source-paystack/source_paystack/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/paystack", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/paystack", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Paystack Source Spec", diff --git a/airbyte-integrations/connectors/source-persistiq/acceptance-test-config.yml b/airbyte-integrations/connectors/source-persistiq/acceptance-test-config.yml index 4446ede08e8e..dde9b8947c01 100644 --- a/airbyte-integrations/connectors/source-persistiq/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-persistiq/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-persistiq:dev tests: diff --git a/airbyte-integrations/connectors/source-persistiq/source_persistiq/spec.json b/airbyte-integrations/connectors/source-persistiq/source_persistiq/spec.json index bf3260203357..3c5b18ac0ba9 100644 --- a/airbyte-integrations/connectors/source-persistiq/source_persistiq/spec.json +++ b/airbyte-integrations/connectors/source-persistiq/source_persistiq/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/persistiq", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/persistiq", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Persistiq Spec", diff --git a/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml index 2fb21134f7c6..6cce06b96ad6 100644 --- a/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pinterest/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-pinterest:dev tests: diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/spec.json b/airbyte-integrations/connectors/source-pinterest/source_pinterest/spec.json index d642d95e3e5e..2c8440ec43b9 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/spec.json +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/pinterest", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/pinterest", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Pinterest Spec", diff --git a/airbyte-integrations/connectors/source-pipedrive/source_pipedrive/spec.json b/airbyte-integrations/connectors/source-pipedrive/source_pipedrive/spec.json index d03f5c6e99e7..405305b66b90 100644 --- a/airbyte-integrations/connectors/source-pipedrive/source_pipedrive/spec.json +++ b/airbyte-integrations/connectors/source-pipedrive/source_pipedrive/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/pipedrive", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/pipedrive", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Pipedrive Spec", diff --git a/airbyte-integrations/connectors/source-pivotal-tracker/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pivotal-tracker/acceptance-test-config.yml index d809d7b67525..21de13877195 100644 --- a/airbyte-integrations/connectors/source-pivotal-tracker/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pivotal-tracker/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-pivotal-tracker:dev tests: diff --git a/airbyte-integrations/connectors/source-plaid/acceptance-test-config.yml b/airbyte-integrations/connectors/source-plaid/acceptance-test-config.yml index c021ad8b8dd7..94498591b6c0 100644 --- a/airbyte-integrations/connectors/source-plaid/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-plaid/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-plaid:dev tests: diff --git a/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml index 7cacf3a9388c..ef2899044223 100644 --- a/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-pokeapi:dev tests: diff --git a/airbyte-integrations/connectors/source-pokeapi/source_pokeapi/spec.json b/airbyte-integrations/connectors/source-pokeapi/source_pokeapi/spec.json index 6f0a9b496d79..8798a9470575 100644 --- a/airbyte-integrations/connectors/source-pokeapi/source_pokeapi/spec.json +++ b/airbyte-integrations/connectors/source-pokeapi/source_pokeapi/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/pokeapi", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/pokeapi", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Pokeapi Spec", diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/acceptance-test-config.yml b/airbyte-integrations/connectors/source-postgres-strict-encrypt/acceptance-test-config.yml index a03ef7fed68d..ec3eed823ea1 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-postgres-strict-encrypt:dev tests: diff --git a/airbyte-integrations/connectors/source-postgres/acceptance-test-config.yml b/airbyte-integrations/connectors/source-postgres/acceptance-test-config.yml index 94a547fa654f..beaef784e60c 100644 --- a/airbyte-integrations/connectors/source-postgres/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-postgres/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-postgres:dev tests: diff --git a/airbyte-integrations/connectors/source-prestashop/acceptance-test-config.yml b/airbyte-integrations/connectors/source-prestashop/acceptance-test-config.yml index efe799fccfac..b6a4efe2fbf5 100644 --- a/airbyte-integrations/connectors/source-prestashop/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-prestashop/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-prestashop:dev tests: diff --git a/airbyte-integrations/connectors/source-primetric/acceptance-test-config.yml b/airbyte-integrations/connectors/source-primetric/acceptance-test-config.yml index 44c8a1c6b9d1..6fba1de696bb 100644 --- a/airbyte-integrations/connectors/source-primetric/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-primetric/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-primetric:dev tests: diff --git a/airbyte-integrations/connectors/source-python-http-tutorial/build.gradle b/airbyte-integrations/connectors/source-python-http-tutorial/build.gradle index 6877f691e631..735a02cd833e 100644 --- a/airbyte-integrations/connectors/source-python-http-tutorial/build.gradle +++ b/airbyte-integrations/connectors/source-python-http-tutorial/build.gradle @@ -9,7 +9,7 @@ airbytePython { } airbyteStandardSourceTestFile { - // For more information on standard source tests, see https://docs.airbyte.io/connector-development/testing-connectors + // For more information on standard source tests, see https://docs.airbyte.com/connector-development/testing-connectors // All these input paths must live inside this connector's directory (or subdirectories) // TODO update the spec JSON file diff --git a/airbyte-integrations/connectors/source-python-http-tutorial/source_python_http_tutorial/spec.json b/airbyte-integrations/connectors/source-python-http-tutorial/source_python_http_tutorial/spec.json index 94f00e9b0e16..ccd762b1370f 100644 --- a/airbyte-integrations/connectors/source-python-http-tutorial/source_python_http_tutorial/spec.json +++ b/airbyte-integrations/connectors/source-python-http-tutorial/source_python_http_tutorial/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/exchangeratesapi", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/exchangeratesapi", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Python Http Tutorial Spec", diff --git a/airbyte-integrations/connectors/source-qualaroo/acceptance-test-config.yml b/airbyte-integrations/connectors/source-qualaroo/acceptance-test-config.yml index c133c3628d40..5946cd6f4f0b 100644 --- a/airbyte-integrations/connectors/source-qualaroo/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-qualaroo/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-qualaroo:dev tests: diff --git a/airbyte-integrations/connectors/source-qualaroo/source_qualaroo/spec.json b/airbyte-integrations/connectors/source-qualaroo/source_qualaroo/spec.json index c183880e647a..1939a6016a81 100644 --- a/airbyte-integrations/connectors/source-qualaroo/source_qualaroo/spec.json +++ b/airbyte-integrations/connectors/source-qualaroo/source_qualaroo/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/qualaroo", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/qualaroo", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Qualaroo Spec", diff --git a/airbyte-integrations/connectors/source-quickbooks-singer/acceptance-test-config.yml b/airbyte-integrations/connectors/source-quickbooks-singer/acceptance-test-config.yml index 7a91ffa8a6af..59684025f560 100644 --- a/airbyte-integrations/connectors/source-quickbooks-singer/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-quickbooks-singer/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-quickbooks-singer:dev tests: diff --git a/airbyte-integrations/connectors/source-recurly/acceptance-test-config.yml b/airbyte-integrations/connectors/source-recurly/acceptance-test-config.yml index 509856db22e3..68b00738c9dd 100644 --- a/airbyte-integrations/connectors/source-recurly/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-recurly/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-recurly:dev tests: diff --git a/airbyte-integrations/connectors/source-redshift/src/main/resources/spec.json b/airbyte-integrations/connectors/source-redshift/src/main/resources/spec.json index 8393dea874d3..9a0d010b6575 100644 --- a/airbyte-integrations/connectors/source-redshift/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-redshift/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/redshift", + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/redshift", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Redshift Source Spec", diff --git a/airbyte-integrations/connectors/source-rki-covid/acceptance-test-config.yml b/airbyte-integrations/connectors/source-rki-covid/acceptance-test-config.yml index ad3b3adbb56d..749d5d300264 100644 --- a/airbyte-integrations/connectors/source-rki-covid/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-rki-covid/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-rki-covid:dev tests: diff --git a/airbyte-integrations/connectors/source-s3/acceptance-test-config.yml b/airbyte-integrations/connectors/source-s3/acceptance-test-config.yml index abbd23b5e02d..c85b3712cc75 100644 --- a/airbyte-integrations/connectors/source-s3/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-s3/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-s3:dev tests: diff --git a/airbyte-integrations/connectors/source-s3/integration_tests/spec.json b/airbyte-integrations/connectors/source-s3/integration_tests/spec.json index c97c75bdb75d..b6c53fae4ce3 100644 --- a/airbyte-integrations/connectors/source-s3/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-s3/integration_tests/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/s3", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/s3", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/s3", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/s3", "connectionSpecification": { "title": "S3 Source Spec", "type": "object", diff --git a/airbyte-integrations/connectors/source-s3/source_s3/source.py b/airbyte-integrations/connectors/source-s3/source_s3/source.py index 682ca782ba00..8317f8d89710 100644 --- a/airbyte-integrations/connectors/source-s3/source_s3/source.py +++ b/airbyte-integrations/connectors/source-s3/source_s3/source.py @@ -68,7 +68,7 @@ class Config: class SourceS3(SourceFilesAbstract): stream_class = IncrementalFileStreamS3 spec_class = SourceS3Spec - documentation_url = "https://docs.airbyte.io/integrations/sources/s3" + documentation_url = "https://docs.airbyte.com/integrations/sources/s3" def read_config(self, config_path: str) -> Mapping[str, Any]: config: Mapping[str, Any] = super().read_config(config_path) diff --git a/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/source.py b/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/source.py index 3baafda03e06..10aa6d094fdf 100644 --- a/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/source.py +++ b/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/source.py @@ -41,7 +41,7 @@ def spec_class(self) -> type: @abstractmethod def documentation_url(self) -> str: """ - :return: link to docs page for this source e.g. "https://docs.airbyte.io/integrations/sources/s3" + :return: link to docs page for this source e.g. "https://docs.airbyte.com/integrations/sources/s3" """ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, Optional[Any]]: diff --git a/airbyte-integrations/connectors/source-salesforce/acceptance-test-config.yml b/airbyte-integrations/connectors/source-salesforce/acceptance-test-config.yml index 87ae3d60ce8b..3ad8172e6d61 100644 --- a/airbyte-integrations/connectors/source-salesforce/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-salesforce/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-salesforce:dev tests: diff --git a/airbyte-integrations/connectors/source-salesloft/acceptance-test-config.yml b/airbyte-integrations/connectors/source-salesloft/acceptance-test-config.yml index f177f4636e9c..487833c0fefa 100644 --- a/airbyte-integrations/connectors/source-salesloft/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-salesloft/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-salesloft:dev tests: diff --git a/airbyte-integrations/connectors/source-salesloft/source_salesloft/spec.json b/airbyte-integrations/connectors/source-salesloft/source_salesloft/spec.json index e7f43c8e1c8b..158ed8195aca 100644 --- a/airbyte-integrations/connectors/source-salesloft/source_salesloft/spec.json +++ b/airbyte-integrations/connectors/source-salesloft/source_salesloft/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/salesloft", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/salesloft", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Salesloft Spec", diff --git a/airbyte-integrations/connectors/source-scaffold-java-jdbc/src/main/resources/spec.json b/airbyte-integrations/connectors/source-scaffold-java-jdbc/src/main/resources/spec.json index 106729cac1a1..654f4897b278 100644 --- a/airbyte-integrations/connectors/source-scaffold-java-jdbc/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-scaffold-java-jdbc/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/scaffold_java_jdbc", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/scaffold_java_jdbc", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ScaffoldJavaJdbc Source Spec", diff --git a/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml b/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml index 97cf9c7d8e1c..3d4070bed766 100644 --- a/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-scaffold-source-http:dev tests: diff --git a/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml b/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml index 9e655296a5fa..0f74155d399b 100644 --- a/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-scaffold-source-python:dev tests: diff --git a/airbyte-integrations/connectors/source-search-metrics/acceptance-test-config.yml b/airbyte-integrations/connectors/source-search-metrics/acceptance-test-config.yml index bb9c6666e82a..0dcde7b260d9 100644 --- a/airbyte-integrations/connectors/source-search-metrics/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-search-metrics/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-search-metrics:dev tests: diff --git a/airbyte-integrations/connectors/source-search-metrics/source_search_metrics/spec.json b/airbyte-integrations/connectors/source-search-metrics/source_search_metrics/spec.json index c33c10d6d6a3..b7e56d69da53 100644 --- a/airbyte-integrations/connectors/source-search-metrics/source_search_metrics/spec.json +++ b/airbyte-integrations/connectors/source-search-metrics/source_search_metrics/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/seacrh-metrics", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/seacrh-metrics", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Search Metrics Spec", diff --git a/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json b/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json index b421d84ea849..a1404d7c356e 100644 --- a/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json +++ b/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/sendgrid", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/sendgrid", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Sendgrid Spec", diff --git a/airbyte-integrations/connectors/source-sentry/source_sentry/spec.json b/airbyte-integrations/connectors/source-sentry/source_sentry/spec.json index 7820a6e6bcb3..2b97d1341d05 100644 --- a/airbyte-integrations/connectors/source-sentry/source_sentry/spec.json +++ b/airbyte-integrations/connectors/source-sentry/source_sentry/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/sentry", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/sentry", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Sentry Spec", @@ -28,6 +28,12 @@ "type": "string", "title": "Project", "description": "The name (slug) of the Project you want to sync." + }, + "discover_fields": { + "type": "array", + "item": "string", + "title": "Discover Event Fields", + "description": "Fields to retrieve when fetching discover events" } } } diff --git a/airbyte-integrations/connectors/source-sftp/src/main/resources/spec.json b/airbyte-integrations/connectors/source-sftp/src/main/resources/spec.json index dbe1014cad4f..1be963367b02 100644 --- a/airbyte-integrations/connectors/source-sftp/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-sftp/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/source/sftp", + "documentationUrl": "https://docs.airbyte.com/integrations/source/sftp", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SFTP Source Spec", diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json b/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json index 9ef00e3ed7fa..73ef20e21374 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/shopify", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/shopify", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Shopify Source CDK Specifications", diff --git a/airbyte-integrations/connectors/source-shortio/acceptance-test-config.yml b/airbyte-integrations/connectors/source-shortio/acceptance-test-config.yml index ac3d3dc91801..0db1cc6c8784 100644 --- a/airbyte-integrations/connectors/source-shortio/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-shortio/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-shortio:dev tests: diff --git a/airbyte-integrations/connectors/source-slack/source_slack/spec.json b/airbyte-integrations/connectors/source-slack/source_slack/spec.json index 816d2738860b..dae73c75f9c8 100644 --- a/airbyte-integrations/connectors/source-slack/source_slack/spec.json +++ b/airbyte-integrations/connectors/source-slack/source_slack/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/slack", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/slack", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Slack Spec", @@ -58,28 +58,28 @@ }, "client_id": { "title": "Client ID", - "description": "Slack client_id. See our docs if you need help finding this id.", + "description": "Slack client_id. See our docs if you need help finding this id.", "type": "string", "examples": ["slack-client-id-example"], "airbyte_secret": true }, "client_secret": { "title": "Client Secret", - "description": "Slack client_secret. See our docs if you need help finding this secret.", + "description": "Slack client_secret. See our docs if you need help finding this secret.", "type": "string", "examples": ["slack-client-secret-example"], "airbyte_secret": true }, "access_token": { "title": "Access token", - "description": "Slack access_token. See our docs if you need help generating the token.", + "description": "Slack access_token. See our docs if you need help generating the token.", "type": "string", "examples": ["slack-access-token-example"], "airbyte_secret": true }, "refresh_token": { "title": "Refresh token", - "description": "Slack refresh_token. See our docs if you need help generating the token.", + "description": "Slack refresh_token. See our docs if you need help generating the token.", "type": "string", "examples": ["slack-refresh-token-example"], "airbyte_secret": true @@ -99,7 +99,7 @@ "api_token": { "type": "string", "title": "API Token", - "description": "A Slack bot token. See the docs for instructions on how to generate it.", + "description": "A Slack bot token. See the docs for instructions on how to generate it.", "airbyte_secret": true } }, diff --git a/airbyte-integrations/connectors/source-smartsheets/source_smartsheets/spec.json b/airbyte-integrations/connectors/source-smartsheets/source_smartsheets/spec.json index 838854cb1b8a..77ec847cbeee 100644 --- a/airbyte-integrations/connectors/source-smartsheets/source_smartsheets/spec.json +++ b/airbyte-integrations/connectors/source-smartsheets/source_smartsheets/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/smartsheets", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/smartsheets", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Smartsheets Source Spec", @@ -9,7 +9,7 @@ "properties": { "access_token": { "title": "Access Token", - "description": "The access token to use for accessing your data from Smartsheets. This access token must be generated by a user with at least read access to the data you'd like to replicate. Generate an access token in the Smartsheets main menu by clicking Account > Apps & Integrations > API Access. See the setup guide for information on how to obtain this token.", + "description": "The access token to use for accessing your data from Smartsheets. This access token must be generated by a user with at least read access to the data you'd like to replicate. Generate an access token in the Smartsheets main menu by clicking Account > Apps & Integrations > API Access. See the setup guide for information on how to obtain this token.", "type": "string", "order": 0, "airbyte_secret": true diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/acceptance-test-config.yml b/airbyte-integrations/connectors/source-snapchat-marketing/acceptance-test-config.yml index 16ca7163fe13..f1f9cf3d0b0c 100644 --- a/airbyte-integrations/connectors/source-snapchat-marketing/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-snapchat-marketing/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-snapchat-marketing:dev tests: diff --git a/airbyte-integrations/connectors/source-snapchat-marketing/source_snapchat_marketing/spec.json b/airbyte-integrations/connectors/source-snapchat-marketing/source_snapchat_marketing/spec.json index 1f2a02582f29..9b819d6aac6a 100644 --- a/airbyte-integrations/connectors/source-snapchat-marketing/source_snapchat_marketing/spec.json +++ b/airbyte-integrations/connectors/source-snapchat-marketing/source_snapchat_marketing/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/snapchat-marketing", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/snapchat-marketing", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Snapchat Marketing Spec", diff --git a/airbyte-integrations/connectors/source-snowflake/src/main/resources/spec.json b/airbyte-integrations/connectors/source-snowflake/src/main/resources/spec.json index 467ddcb0487f..40c912a340ba 100644 --- a/airbyte-integrations/connectors/source-snowflake/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-snowflake/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/snowflake", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/snowflake", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Snowflake Source Spec", diff --git a/airbyte-integrations/connectors/source-square/source_square/spec.json b/airbyte-integrations/connectors/source-square/source_square/spec.json index f8719f27c062..b9874cdbc117 100644 --- a/airbyte-integrations/connectors/source-square/source_square/spec.json +++ b/airbyte-integrations/connectors/source-square/source_square/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/square", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/square", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Square Source CDK Specifications", diff --git a/airbyte-integrations/connectors/source-strava/acceptance-test-config.yml b/airbyte-integrations/connectors/source-strava/acceptance-test-config.yml index 04badbe8eed5..b88f3ab663c4 100644 --- a/airbyte-integrations/connectors/source-strava/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-strava/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-strava:dev tests: diff --git a/airbyte-integrations/connectors/source-strava/source_strava/spec.json b/airbyte-integrations/connectors/source-strava/source_strava/spec.json index 5515b5896b90..f5755cd4ab6c 100644 --- a/airbyte-integrations/connectors/source-strava/source_strava/spec.json +++ b/airbyte-integrations/connectors/source-strava/source_strava/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/strava", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/strava", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Strava Spec", diff --git a/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml b/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml index f76a58b7ad46..ae14c3ee4bdc 100644 --- a/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml +++ b/airbyte-integrations/connectors/source-stripe/source_stripe/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/stripe +documentationUrl: https://docs.airbyte.com/integrations/sources/stripe connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Stripe Source Spec diff --git a/airbyte-integrations/connectors/source-surveymonkey/source_surveymonkey/spec.json b/airbyte-integrations/connectors/source-surveymonkey/source_surveymonkey/spec.json index eb697230c76d..e697704667e9 100644 --- a/airbyte-integrations/connectors/source-surveymonkey/source_surveymonkey/spec.json +++ b/airbyte-integrations/connectors/source-surveymonkey/source_surveymonkey/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/surveymonkey", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/surveymonkey", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SurveyMonkey Spec", @@ -12,7 +12,7 @@ "order": 0, "type": "string", "airbyte_secret": true, - "description": "Access Token for making authenticated requests. See the docs for information on how to generate this key." + "description": "Access Token for making authenticated requests. See the docs for information on how to generate this key." }, "start_date": { "title": "Start Date", diff --git a/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml b/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml index 2d5da4d679fd..c6c27a98f3f5 100644 --- a/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte-local/source-talkdesk:tests tests: diff --git a/airbyte-integrations/connectors/source-tempo/acceptance-test-config.yml b/airbyte-integrations/connectors/source-tempo/acceptance-test-config.yml index a1e56a2f166b..b2e97c303c7e 100644 --- a/airbyte-integrations/connectors/source-tempo/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-tempo/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-tempo:dev tests: diff --git a/airbyte-integrations/connectors/source-tempo/source_tempo/spec.json b/airbyte-integrations/connectors/source-tempo/source_tempo/spec.json index 44aedda35730..df349ad41cf8 100644 --- a/airbyte-integrations/connectors/source-tempo/source_tempo/spec.json +++ b/airbyte-integrations/connectors/source-tempo/source_tempo/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Tempo Spec", diff --git a/airbyte-integrations/connectors/source-tidb/src/main/resources/spec.json b/airbyte-integrations/connectors/source-tidb/src/main/resources/spec.json index 79d50a7ab9d3..edad17d6bf24 100755 --- a/airbyte-integrations/connectors/source-tidb/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-tidb/src/main/resources/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/tidb", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/tidb", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "TiDB Source Spec", diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml b/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml index 0c5cb133aa67..8a39ffa837c9 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-tiktok-marketing:dev diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/integration_tests/spec.json b/airbyte-integrations/connectors/source-tiktok-marketing/integration_tests/spec.json index 3ed4e8d1bf02..311650ac1f45 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-tiktok-marketing/integration_tests/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/tiktok-marketing", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/tiktok-marketing", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", "connectionSpecification": { "title": "TikTok Marketing Source Spec", "type": "object", diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/source.py b/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/source.py index e0f18e15f4b9..6d34e5439f90 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/source.py +++ b/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/source.py @@ -33,7 +33,7 @@ ReportGranularity, ) -DOCUMENTATION_URL = "https://docs.airbyte.io/integrations/sources/tiktok-marketing" +DOCUMENTATION_URL = "https://docs.airbyte.com/integrations/sources/tiktok-marketing" def get_report_stream(report: BasicReports, granularity: ReportGranularity) -> BasicReports: diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json b/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json index c6f24b6eb6b8..daaf6e9fdd0b 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json +++ b/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json @@ -1,6 +1,6 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/tiktok-marketing", - "changelogUrl": "https://docs.airbyte.io/integrations/sources/tiktok-marketing", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", "connectionSpecification": { "title": "TikTok Marketing Source Spec", "type": "object", diff --git a/airbyte-integrations/connectors/source-timely/acceptance-test-config.yml b/airbyte-integrations/connectors/source-timely/acceptance-test-config.yml index 5e4f1f0114dd..d59ece835b8c 100644 --- a/airbyte-integrations/connectors/source-timely/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-timely/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-timely:dev tests: diff --git a/airbyte-integrations/connectors/source-tplcentral/acceptance-test-config.yml b/airbyte-integrations/connectors/source-tplcentral/acceptance-test-config.yml index af3b2e7b2df4..89e1e040f2c7 100644 --- a/airbyte-integrations/connectors/source-tplcentral/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-tplcentral/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-tplcentral:dev tests: diff --git a/airbyte-integrations/connectors/source-tplcentral/source_tplcentral/spec.json b/airbyte-integrations/connectors/source-tplcentral/source_tplcentral/spec.json index 41271107aff6..88333ddc7ea6 100644 --- a/airbyte-integrations/connectors/source-tplcentral/source_tplcentral/spec.json +++ b/airbyte-integrations/connectors/source-tplcentral/source_tplcentral/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/tplcentral", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/tplcentral", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Tplcentral Spec", diff --git a/airbyte-integrations/connectors/source-trello/acceptance-test-config.yml b/airbyte-integrations/connectors/source-trello/acceptance-test-config.yml index 72218e268358..b4aca2c682bf 100644 --- a/airbyte-integrations/connectors/source-trello/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-trello/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-trello:dev tests: diff --git a/airbyte-integrations/connectors/source-trello/source_trello/spec.json b/airbyte-integrations/connectors/source-trello/source_trello/spec.json index aac64502708b..67156062cdb1 100644 --- a/airbyte-integrations/connectors/source-trello/source_trello/spec.json +++ b/airbyte-integrations/connectors/source-trello/source_trello/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/trello", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/trello", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Trello Spec", diff --git a/airbyte-integrations/connectors/source-twilio/source_twilio/spec.json b/airbyte-integrations/connectors/source-twilio/source_twilio/spec.json index 8d94200a1d04..b2e362f23cba 100644 --- a/airbyte-integrations/connectors/source-twilio/source_twilio/spec.json +++ b/airbyte-integrations/connectors/source-twilio/source_twilio/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/twilio", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/twilio", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Twilio Spec", diff --git a/airbyte-integrations/connectors/source-typeform/source_typeform/spec.json b/airbyte-integrations/connectors/source-typeform/source_typeform/spec.json index 2d622d7fedc4..9b4115fdb175 100644 --- a/airbyte-integrations/connectors/source-typeform/source_typeform/spec.json +++ b/airbyte-integrations/connectors/source-typeform/source_typeform/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/typeform", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/typeform", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Typeform Spec", diff --git a/airbyte-integrations/connectors/source-us-census/acceptance-test-config.yml b/airbyte-integrations/connectors/source-us-census/acceptance-test-config.yml index c1f43532dc4c..14b72c6317a7 100644 --- a/airbyte-integrations/connectors/source-us-census/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-us-census/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-us-census:dev tests: diff --git a/airbyte-integrations/connectors/source-us-census/source_us_census/spec.json b/airbyte-integrations/connectors/source-us-census/source_us_census/spec.json index 702872593af1..b8c67bbff6af 100644 --- a/airbyte-integrations/connectors/source-us-census/source_us_census/spec.json +++ b/airbyte-integrations/connectors/source-us-census/source_us_census/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/us-census", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/us-census", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "https://api.census.gov/ Source Spec", diff --git a/airbyte-integrations/connectors/source-webflow/acceptance-test-config.yml b/airbyte-integrations/connectors/source-webflow/acceptance-test-config.yml index 8e6e6f5ae782..816c63d8de3a 100644 --- a/airbyte-integrations/connectors/source-webflow/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-webflow/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-webflow:dev tests: diff --git a/airbyte-integrations/connectors/source-webflow/source_webflow/spec.yaml b/airbyte-integrations/connectors/source-webflow/source_webflow/spec.yaml index 7a1754509df5..0fd66a820c1a 100644 --- a/airbyte-integrations/connectors/source-webflow/source_webflow/spec.yaml +++ b/airbyte-integrations/connectors/source-webflow/source_webflow/spec.yaml @@ -1,4 +1,4 @@ -documentationUrl: https://docs.airbyte.io/integrations/sources/webflow +documentationUrl: https://docs.airbyte.com/integrations/sources/webflow connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Webflow Spec diff --git a/airbyte-integrations/connectors/source-woocommerce/acceptance-test-config.yml b/airbyte-integrations/connectors/source-woocommerce/acceptance-test-config.yml index e0d08fdcacf0..3fba9ce508e7 100644 --- a/airbyte-integrations/connectors/source-woocommerce/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-woocommerce/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) +# See [Source Acceptance Tests](https://docs.airbyte.com/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md) # for more information about how to configure these tests connector_image: airbyte/source-woocommerce:dev tests: diff --git a/airbyte-integrations/connectors/source-woocommerce/source_woocommerce/spec.json b/airbyte-integrations/connectors/source-woocommerce/source_woocommerce/spec.json index de32a9b2f54e..f08e87c3d03d 100644 --- a/airbyte-integrations/connectors/source-woocommerce/source_woocommerce/spec.json +++ b/airbyte-integrations/connectors/source-woocommerce/source_woocommerce/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/woocommerce", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/woocommerce", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Woocommerce Source CDK Specifications", diff --git a/airbyte-integrations/connectors/source-wrike/acceptance-test-config.yml b/airbyte-integrations/connectors/source-wrike/acceptance-test-config.yml index 2e89cfb7056c..cebbc69826fc 100644 --- a/airbyte-integrations/connectors/source-wrike/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-wrike/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-wrike:dev tests: diff --git a/airbyte-integrations/connectors/source-yahoo-finance-price/acceptance-test-config.yml b/airbyte-integrations/connectors/source-yahoo-finance-price/acceptance-test-config.yml index 7f5f8904d886..5c0e80150338 100644 --- a/airbyte-integrations/connectors/source-yahoo-finance-price/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-yahoo-finance-price/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-yahoo-finance-price:dev tests: diff --git a/airbyte-integrations/connectors/source-yandex-metrica/acceptance-test-config.yml b/airbyte-integrations/connectors/source-yandex-metrica/acceptance-test-config.yml index 94d80d2474e2..399d1d33adeb 100644 --- a/airbyte-integrations/connectors/source-yandex-metrica/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-yandex-metrica/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-yandex-metrica:dev tests: diff --git a/airbyte-integrations/connectors/source-youtube-analytics/acceptance-test-config.yml b/airbyte-integrations/connectors/source-youtube-analytics/acceptance-test-config.yml index 8b46149cf64d..9c770f1449c4 100644 --- a/airbyte-integrations/connectors/source-youtube-analytics/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-youtube-analytics/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-youtube-analytics:dev tests: diff --git a/airbyte-integrations/connectors/source-youtube-analytics/source_youtube_analytics/spec.json b/airbyte-integrations/connectors/source-youtube-analytics/source_youtube_analytics/spec.json index d0413439a22a..af0ea71afe0f 100644 --- a/airbyte-integrations/connectors/source-youtube-analytics/source_youtube_analytics/spec.json +++ b/airbyte-integrations/connectors/source-youtube-analytics/source_youtube_analytics/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/youtube-analytics", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/youtube-analytics", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "YouTube Analytics Spec", diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json index d145227a8012..8740326bf352 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zendesk-chat", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zendesk-chat", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Zendesk Chat Spec", diff --git a/airbyte-integrations/connectors/source-zendesk-sunshine/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zendesk-sunshine/acceptance-test-config.yml index 5a46bc61d8d4..b443accf0116 100644 --- a/airbyte-integrations/connectors/source-zendesk-sunshine/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zendesk-sunshine/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-zendesk-sunshine:dev tests: diff --git a/airbyte-integrations/connectors/source-zendesk-sunshine/source_zendesk_sunshine/spec.json b/airbyte-integrations/connectors/source-zendesk-sunshine/source_zendesk_sunshine/spec.json index 03a04f15b81a..29e900a4b1a5 100644 --- a/airbyte-integrations/connectors/source-zendesk-sunshine/source_zendesk_sunshine/spec.json +++ b/airbyte-integrations/connectors/source-zendesk-sunshine/source_zendesk_sunshine/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zendesk_sunshine", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zendesk_sunshine", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Zendesk Sunshine Spec", @@ -75,7 +75,7 @@ "api_token": { "type": "string", "title": "API Token", - "description": "API Token. See the docs for information on how to generate this key.", + "description": "API Token. See the docs for information on how to generate this key.", "airbyte_secret": true }, "email": { diff --git a/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml index 6bdcc2ef1a10..561f7675518e 100644 --- a/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zendesk-support/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-zendesk-support:dev tests: diff --git a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json index 7af95d0413e9..ad3d82c1e948 100644 --- a/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json +++ b/airbyte-integrations/connectors/source-zendesk-support/source_zendesk_support/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zendesk-support", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zendesk-support", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Zendesk Support Spec", @@ -38,7 +38,7 @@ "access_token": { "type": "string", "title": "Access Token", - "description": "The value of the API token generated. See the docs for more information.", + "description": "The value of the API token generated. See the docs for more information.", "airbyte_secret": true } } diff --git a/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml index bc80a5ed6add..a4cb208d9cd7 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zendesk-talk/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests # intentionally left out explicit configured_catalog.json to test all streams from discovery connector_image: airbyte/source-zendesk-talk:dev diff --git a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json index 7a6a98c98eef..aa5bb00cdfa7 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json +++ b/airbyte-integrations/connectors/source-zendesk-talk/source_zendesk_talk/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zendesk-talk", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zendesk-talk", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Zendesk Talk Spec", @@ -55,7 +55,7 @@ "access_token": { "type": "string", "title": "Access Token", - "description": "The value of the API token generated. See the docs for more information.", + "description": "The value of the API token generated. See the docs for more information.", "airbyte_secret": true } } diff --git a/airbyte-integrations/connectors/source-zenefits/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zenefits/acceptance-test-config.yml index 97f1a19bae90..1ddf8edf5c2b 100644 --- a/airbyte-integrations/connectors/source-zenefits/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zenefits/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-zenefits:dev tests: diff --git a/airbyte-integrations/connectors/source-zenloop/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zenloop/acceptance-test-config.yml index b2345234ac84..c179240af691 100644 --- a/airbyte-integrations/connectors/source-zenloop/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zenloop/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-zenloop:dev tests: diff --git a/airbyte-integrations/connectors/source-zenloop/source_zenloop/spec.json b/airbyte-integrations/connectors/source-zenloop/source_zenloop/spec.json index 7482894f410f..0e1ed2b3738a 100644 --- a/airbyte-integrations/connectors/source-zenloop/source_zenloop/spec.json +++ b/airbyte-integrations/connectors/source-zenloop/source_zenloop/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zenloop", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zenloop", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Zenloop Spec", diff --git a/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml index b34ac4718fe0..c95fce81818d 100644 --- a/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zoho-crm/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-zoho-crm:dev tests: diff --git a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json b/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json index d1cd60108ec0..ccd2b3111b35 100644 --- a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json +++ b/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zoom", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zoom", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Source Zoom Singer Spec", @@ -10,7 +10,7 @@ "jwt": { "title": "JWT Token", "type": "string", - "description": "Zoom JWT Token. See the docs for more information on how to obtain this key.", + "description": "Zoom JWT Token. See the docs for more information on how to obtain this key.", "airbyte_secret": true } } diff --git a/airbyte-integrations/connectors/source-zuora/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zuora/acceptance-test-config.yml index eb3614e19c67..c633fc3d8c7e 100644 --- a/airbyte-integrations/connectors/source-zuora/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-zuora/acceptance-test-config.yml @@ -1,4 +1,4 @@ -# See [Source Acceptance Tests](https://docs.airbyte.io/contributing-to-airbyte/building-new-connector/source-acceptance-tests) +# See [Source Acceptance Tests](https://docs.airbyte.com/contributing-to-airbyte/building-new-connector/source-acceptance-tests) # for more information about how to configure these tests connector_image: airbyte/source-zuora:dev tests: diff --git a/airbyte-integrations/connectors/source-zuora/source_zuora/spec.json b/airbyte-integrations/connectors/source-zuora/source_zuora/spec.json index dbf8498f0411..f9d2dc6262dc 100644 --- a/airbyte-integrations/connectors/source-zuora/source_zuora/spec.json +++ b/airbyte-integrations/connectors/source-zuora/source_zuora/spec.json @@ -1,5 +1,5 @@ { - "documentationUrl": "https://docs.airbyte.io/integrations/sources/zuora", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/zuora", "connectionSpecification": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "Zuora Connector Configuration", From d30de1bdd3059f5ed0f844d05370f6b6876b2a5a Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Tue, 11 Oct 2022 09:13:57 -0700 Subject: [PATCH 031/498] convert airbyte-metrics-reporter to micronaut (#17365) * sealed class experiment * micronaut the metric reporter service * code format * move event-listeners to EventListeners class * code format * db cleanup; start adding tests * finish porting over MetricsQueriesTest pieces that apply * dedupe/simplify test code * add EmitterTest; misc code clean-up * address pmd warnings * remove code that was moved to the reporter package * update application.yml * Emitter[] -> List * remove unused singleton * formatting * formatting * remove default test creds * update banner * remove commented out code * remove another commented out line --- .../io/airbyte/metrics/lib/MetricQueries.java | 177 ------ .../metrics/lib/OssMetricsRegistry.java | 6 +- .../metrics/lib/MetricsQueriesTest.java | 505 ----------------- airbyte-metrics/reporter/build.gradle | 24 +- .../airbyte/metrics/reporter/Application.java | 20 + .../io/airbyte/metrics/reporter/Emitter.java | 193 +++++++ .../metrics/reporter/EventListeners.java | 56 ++ .../metrics/reporter/MetricRepository.java | 189 +++++++ .../airbyte/metrics/reporter/ReporterApp.java | 68 --- .../metrics/reporter/ReporterFactory.java | 24 + .../io/airbyte/metrics/reporter/ToEmit.java | 112 ---- .../src/main/resources/application.yml | 38 ++ .../src/main/resources/micronaut-banner.txt | 8 + .../airbyte/metrics/reporter/EmitterTest.java | 156 ++++++ .../reporter/MetricRepositoryTest.java | 506 ++++++++++++++++++ 15 files changed, 1215 insertions(+), 867 deletions(-) create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java create mode 100644 airbyte-metrics/reporter/src/main/resources/application.yml create mode 100644 airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt create mode 100644 airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java create mode 100644 airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java index 4a83550a02e8..bb4eeaaa6347 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java @@ -6,19 +6,11 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; -import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; -import static org.jooq.impl.SQLDataType.VARCHAR; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; -import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import java.util.ArrayList; import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; import org.jooq.DSLContext; /** @@ -57,173 +49,4 @@ public static List srcIdAndDestIdToReleaseStages(final DSLContext .or(ACTOR.ID.eq(dstId)).fetch().getValues(ACTOR_DEFINITION.RELEASE_STAGE); } - public static int numberOfPendingJobs(final DSLContext ctx) { - return ctx.selectCount().from(JOBS).where(JOBS.STATUS.eq(JobStatus.pending)).fetchOne(0, int.class); - } - - public static int numberOfRunningJobs(final DSLContext ctx) { - return ctx.selectCount().from(JOBS).join(CONNECTION).on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) - .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) - .fetchOne(0, int.class); - } - - public static int numberOfOrphanRunningJobs(final DSLContext ctx) { - return ctx.selectCount().from(JOBS).join(CONNECTION).on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) - .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.ne(StatusType.active))) - .fetchOne(0, int.class); - } - - public static Long oldestPendingJobAgeSecs(final DSLContext ctx) { - return oldestJobAgeSecs(ctx, JobStatus.pending); - } - - public static Long oldestRunningJobAgeSecs(final DSLContext ctx) { - return oldestJobAgeSecs(ctx, JobStatus.running); - } - - private static Long oldestJobAgeSecs(final DSLContext ctx, final JobStatus status) { - final var readableTimeField = "run_duration"; - final var durationSecField = "run_duration_secs"; - final var query = String.format(""" - WITH - oldest_job AS ( - SELECT id, - age(current_timestamp, created_at) AS %s - FROM jobs - WHERE status = '%s' - ORDER BY run_duration DESC - LIMIT 1) - SELECT id, - run_duration, - extract(epoch from run_duration) as %s - FROM oldest_job""", readableTimeField, status.getLiteral(), durationSecField); - final var res = ctx.fetch(query); - // unfortunately there are no good Jooq methods for retrieving a single record of a single column - // forcing the List cast. - final var duration = res.getValues(durationSecField, Double.class); - - if (duration.size() == 0) { - return 0L; - } - // .get(0) works in the following code due to the query's SELECT 1. - final var id = res.getValues("id", String.class).get(0); - final var readableTime = res.getValues(readableTimeField, String.class).get(0); - log.info("oldest job information - id: {}, readable time: {}", id, readableTime); - - // as double can have rounding errors, round down to remove noise. - return duration.get(0).longValue(); - } - - public static List numberOfActiveConnPerWorkspace(final DSLContext ctx) { - final var countField = "num_conn"; - final var query = String.format(""" - SELECT workspace_id, count(c.id) as %s - FROM actor - INNER JOIN workspace ws ON actor.workspace_id = ws.id - INNER JOIN connection c ON actor.id = c.source_id - WHERE ws.tombstone = false - AND actor.tombstone = false AND actor.actor_type = 'source' - AND c.status = 'active' - GROUP BY workspace_id;""", countField); - return ctx.fetch(query).getValues(countField, long.class); - } - - public static List> overallJobRuntimeForTerminalJobsInLastHour(final DSLContext ctx) { - final var statusField = "status"; - final var timeField = "sec"; - final var query = - String.format(""" - SELECT %s, extract(epoch from age(updated_at, created_at)) AS %s FROM jobs - WHERE updated_at >= NOW() - INTERVAL '1 HOUR' - AND (jobs.status = 'failed' OR jobs.status = 'succeeded' OR jobs.status = 'cancelled');""", statusField, timeField); - final var statuses = ctx.fetch(query).getValues(statusField, JobStatus.class); - final var times = ctx.fetch(query).getValues(timeField, double.class); - - final var pairedRes = new ArrayList>(); - for (int i = 0; i < statuses.size(); i++) { - final var pair = new ImmutablePair<>(statuses.get(i), times.get(i)); - pairedRes.add(pair); - } - - return pairedRes; - } - - /* - * A connection that is not running on schedule is defined in last 24 hours if the number of runs - * are not matching with the number of expected runs according to the schedule settings. Refer to - * runbook for detailed discussion. - * - */ - public static Long numberOfJobsNotRunningOnScheduleInLastDay(final DSLContext ctx) { - final var countField = "cnt"; - // This query finds all sync jobs ran in last 24 hours and count how many times they have run. - // Comparing this to the expected number of runs (24 hours divide by configured cadence in hours), - // if it runs below that expected number it will be considered as abnormal instance. - // For example, if it's configured to run every 6 hours but in last 24 hours it only has 3 runs, - // it will be considered as 1 abnormal instance. - final var queryForAbnormalSyncInHoursInLastDay = - String.format(""" - select count(1) as %s - from - ( - select - c.id, - count(*) as cnt - from - connection c - left join Jobs j on - j.scope::uuid = c.id - where - c.schedule is not null - and c.schedule != 'null' - and j.created_at > now() - interval '24 hours 1 minutes' - and c.status = 'active' - and j.config_type = 'sync' - and c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) = '"hours"' - group by 1 - having count(*) < 24 / cast(c.schedule::jsonb->'units' as integer)) as abnormal_jobs - """, countField); - - // Similar to the query above, this finds if the connection cadence's timeUnit is minutes. - // thus we use 1440 (=24 hours x 60 minutes) to divide the configured cadence. - final var queryForAbnormalSyncInMinutesInLastDay = - String.format(""" - select count(1) as %s from ( - select - c.id, - count(*) as cnt - from - connection c - left join Jobs j on - j.scope::uuid = c.id - where - c.schedule is not null - and c.schedule != 'null' - and j.created_at > now() - interval '24 hours 1 minutes' - and c.status = 'active' - and j.config_type = 'sync' - and c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) = '"minutes"' - group by 1 - having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer)) as abnormal_jobs - """, countField); - return ctx.fetch(queryForAbnormalSyncInHoursInLastDay).getValues(countField, long.class).get(0) - + ctx.fetch(queryForAbnormalSyncInMinutesInLastDay).getValues(countField, long.class).get(0); - } - - public static Long numScheduledActiveConnectionsInLastDay(final DSLContext ctx) { - final var countField = "cnt"; - final var queryForTotalConnections = String.format(""" - select count(1) as %s - from connection c - where - c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) IN ('"hours"', '"minutes"') - and c.status = 'active' - """, countField); - - return ctx.fetch(queryForTotalConnections).getValues(countField, long.class).get(0); - } - } diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java index 67c750bd49f1..75e30ebf335d 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java @@ -9,15 +9,15 @@ /** * Enum source of truth of all Airbyte metrics. Each enum value represent a metric and is linked to * an application and contains a description to make it easier to understand. - * + *

      * Each object of the enum actually represent a metric, so the Registry name is misleading. The * reason 'Registry' is in the name is to emphasize this enum's purpose as a source of truth for all * metrics. This also helps code readability i.e. AirbyteMetricsRegistry.metricA. - * + *

      * Metric Name Convention (adapted from * https://docs.datadoghq.com/developers/guide/what-best-practices-are-recommended-for-naming-metrics-and-tags/): *

      - * - Use lowercase. Metric names are case sensitive. + * - Use lowercase. Metric names are case-sensitive. *

      * - Use underscore to delimit names with multiple words. *

      diff --git a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java index 7184b892ab04..ae94b43f07d5 100644 --- a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java +++ b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java @@ -15,7 +15,6 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; @@ -23,19 +22,13 @@ import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; -import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; import io.airbyte.db.instance.test.TestDatabaseProviders; import io.airbyte.test.utils.DatabaseConnectionHelper; import java.io.IOException; import java.sql.SQLException; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; import javax.sql.DataSource; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.jooq.DSLContext; import org.jooq.JSONB; import org.jooq.SQLDialect; @@ -172,502 +165,4 @@ void shouldReturnNothingIfNotApplicable() throws SQLException { } - @Nested - class numJobs { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - void runningJobsShouldReturnCorrectCount() throws SQLException { - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) - .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) - .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) - .execute()); - final UUID activeConnectionId = UUID.randomUUID(); - final UUID inactiveConnectionId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, - CONNECTION.DESTINATION_ID, CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL) - .values(activeConnectionId, StatusType.active, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) - .values(inactiveConnectionId, StatusType.inactive, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) - .execute()); - - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, activeConnectionId.toString(), JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, activeConnectionId.toString(), JobStatus.failed).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, activeConnectionId.toString(), JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, activeConnectionId.toString(), JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(5L, inactiveConnectionId.toString(), JobStatus.running).execute()); - - assertEquals(2, configDb.query(MetricQueries::numberOfRunningJobs)); - assertEquals(1, configDb.query(MetricQueries::numberOfOrphanRunningJobs)); - } - - @Test - void runningJobsShouldReturnZero() throws SQLException { - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::numberOfRunningJobs); - assertEquals(0, res); - } - - @Test - void pendingJobsShouldReturnCorrectCount() throws SQLException { - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.running).execute()); - - final var res = configDb.query(MetricQueries::numberOfPendingJobs); - assertEquals(2, res); - } - - @Test - void pendingJobsShouldReturnZero() throws SQLException { - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::numberOfPendingJobs); - assertEquals(0, res); - } - - } - - @Nested - class oldestPendingJob { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - @DisplayName("should return only the pending job's age in seconds") - void shouldReturnOnlyPendingSeconds() throws SQLException { - final var expAgeSecs = 1000; - final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - // oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(1L, "", JobStatus.pending, oldestCreateAt) - .execute()); - // second oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(2L, "", JobStatus.pending, OffsetDateTime.now()) - .execute()); - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestPendingJobAgeSecs); - // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(999L, 1000L, 1001L).contains(res)); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.succeeded).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestPendingJobAgeSecs); - assertEquals(0L, res); - } - - } - - @Nested - class oldestRunningJob { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - @DisplayName("should return only the running job's age in seconds") - void shouldReturnOnlyRunningSeconds() throws SQLException { - final var expAgeSecs = 10000; - final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - // oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(1L, "", JobStatus.running, oldestCreateAt) - .execute()); - // second oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(2L, "", JobStatus.running, OffsetDateTime.now()) - .execute()); - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestRunningJobAgeSecs); - // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.succeeded).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestRunningJobAgeSecs); - assertEquals(0L, res); - } - - } - - @Nested - class numActiveConnPerWorkspace { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(CONNECTION).cascade().execute()); - configDb.transaction(ctx -> ctx.truncate(ACTOR).cascade().execute()); - configDb.transaction(ctx -> ctx.truncate(WORKSPACE).cascade().execute()); - } - - @Test - @DisplayName("should return only connections per workspace") - void shouldReturnNumConnectionsBasic() throws SQLException { - final var workspaceId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", false) - .execute()); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .execute()); - - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(1, res.size()); - assertEquals(2, res.get(0)); - } - - @Test - @DisplayName("should ignore deleted connections") - void shouldIgnoreNonRunningConnections() throws SQLException { - final var workspaceId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", false) - .execute()); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.deprecated) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.inactive) - .execute()); - - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(1, res.size()); - assertEquals(2, res.get(0)); - } - - @Test - @DisplayName("should ignore deleted connections") - void shouldIgnoreDeletedWorkspaces() throws SQLException { - final var workspaceId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", true) - .execute()); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .execute()); - - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(0, res.size()); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(0, res.size()); - } - - } - - @Nested - class overallJobRuntimeForTerminalJobsInLastHour { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - @DisplayName("should ignore non terminal jobs") - void shouldIgnoreNonTerminalJobs() throws SQLException { - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.incomplete).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(0, res.size()); - } - - @Test - @DisplayName("should ignore jobs older than 1 hour") - void shouldIgnoreJobsOlderThan1Hour() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.UPDATED_AT).values(1L, "", JobStatus.succeeded, updateAt).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(0, res.size()); - } - - @Test - @DisplayName("should return correct duration for terminal jobs") - void shouldReturnTerminalJobs() throws SQLException { - final var updateAt = OffsetDateTime.now(); - final var expAgeSecs = 10000; - final var createAt = updateAt.minus(expAgeSecs, ChronoUnit.SECONDS); - - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(1L, "", JobStatus.succeeded, createAt, updateAt).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(2L, "", JobStatus.failed, createAt, updateAt).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(3L, "", JobStatus.cancelled, createAt, updateAt).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(3, res.size()); - - final var exp = List.of( - new ImmutablePair<>(JobStatus.succeeded, expAgeSecs * 1.0), - new ImmutablePair<>(JobStatus.cancelled, expAgeSecs * 1.0), - new ImmutablePair<>(JobStatus.failed, expAgeSecs * 1.0)); - assertTrue(res.containsAll(exp) && exp.containsAll(res)); - } - - @Test - @DisplayName("should return correct duration for jobs that terminated in the last hour") - void shouldReturnTerminalJobsComplex() throws SQLException { - final var updateAtNow = OffsetDateTime.now(); - final var expAgeSecs = 10000; - final var createAt = updateAtNow.minus(expAgeSecs, ChronoUnit.SECONDS); - - // terminal jobs in last hour - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(1L, "", JobStatus.succeeded, createAt, updateAtNow).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(2L, "", JobStatus.failed, createAt, updateAtNow).execute()); - - // old terminal jobs - final var updateAtOld = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(3L, "", JobStatus.cancelled, createAt, updateAtOld).execute()); - - // non-terminal jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) - .values(4L, "", JobStatus.running, createAt).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(2, res.size()); - - final var exp = List.of( - new ImmutablePair<>(JobStatus.succeeded, expAgeSecs * 1.0), - new ImmutablePair<>(JobStatus.failed, expAgeSecs * 1.0)); - assertTrue(res.containsAll(exp) && exp.containsAll(res)); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(0, res.size()); - } - - } - - @Nested - class AbnormalJobsInLastDay { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - configDb.transaction(ctx -> ctx.truncate(CONNECTION).cascade().execute()); - } - - @Test - @DisplayName("should return correct number for abnormal jobs") - void shouldCountInJobsWithMissingRun() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); - final var connectionId = UUID.randomUUID(); - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - final var syncConfigType = JobConfigType.sync; - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(connectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 6, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) - .execute()); - - // Jobs running in prior day will not be counted - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(5, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - - final var totalConnectionResult = configDb.query(MetricQueries::numScheduledActiveConnectionsInLastDay); - assertEquals(1, totalConnectionResult); - - final var abnormalConnectionResult = configDb.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); - assertEquals(1, abnormalConnectionResult); - } - - @Test - @DisplayName("normal jobs should not be counted") - void shouldNotCountNormalJobsInAbnormalMetric() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); - final var inactiveConnectionId = UUID.randomUUID(); - final var activeConnectionId = UUID.randomUUID(); - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - final var syncConfigType = JobConfigType.sync; - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(inactiveConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.inactive, updateAt, updateAt) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(activeConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) - .execute()); - - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(1L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, - syncConfigType) - .execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(2L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, - syncConfigType) - .execute()); - - final var totalConnectionResult = configDb.query(MetricQueries::numScheduledActiveConnectionsInLastDay); - assertEquals(1, totalConnectionResult); - - final var abnormalConnectionResult = configDb.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); - assertEquals(0, abnormalConnectionResult); - } - - } - } diff --git a/airbyte-metrics/reporter/build.gradle b/airbyte-metrics/reporter/build.gradle index 806d48a52545..22885a77de9f 100644 --- a/airbyte-metrics/reporter/build.gradle +++ b/airbyte-metrics/reporter/build.gradle @@ -2,18 +2,38 @@ plugins { id 'application' } +configurations { + jdbc +} + dependencies { - implementation libs.flyway.core + annotationProcessor platform(libs.micronaut.bom) + annotationProcessor libs.bundles.micronaut.annotation.processor + + implementation platform(libs.micronaut.bom) + implementation libs.bundles.micronaut implementation project(':airbyte-config:config-models') implementation project(':airbyte-db:jooq') implementation project(':airbyte-db:db-lib') implementation project(':airbyte-metrics:metrics-lib') + + implementation(libs.jooq) { + force = true + } + + testAnnotationProcessor platform(libs.micronaut.bom) + testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor + + testImplementation project(':airbyte-test-utils') + testImplementation libs.bundles.micronaut.test + testImplementation libs.postgresql + testImplementation libs.platform.testcontainers.postgresql } application { applicationName = "airbyte-metrics-reporter" - mainClass = 'io.airbyte.metrics.reporter.ReporterApp' + mainClass = 'io.airbyte.metrics.reporter.Application' applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0'] } diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java new file mode 100644 index 000000000000..f64527149ba7 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.micronaut.runtime.Micronaut; + +/** + * Metric Reporter application. + *

      + * Responsible for emitting metric information on a periodic basis. + */ +public class Application { + + public static void main(final String[] args) { + Micronaut.run(Application.class, args); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java new file mode 100644 index 000000000000..5e21723ad3e4 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.metrics.lib.MetricAttribute; +import io.airbyte.metrics.lib.MetricClient; +import io.airbyte.metrics.lib.MetricTags; +import io.airbyte.metrics.lib.OssMetricsRegistry; +import jakarta.inject.Singleton; +import java.lang.invoke.MethodHandles; +import java.time.Duration; +import java.util.concurrent.Callable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +final class NumPendingJobs extends Emitter { + + public NumPendingJobs(final MetricClient client, final MetricRepository db) { + super(() -> { + final var pending = db.numberOfPendingJobs(); + client.gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pending); + return null; + }); + } + +} + +@Singleton +final class NumRunningJobs extends Emitter { + + public NumRunningJobs(final MetricClient client, final MetricRepository db) { + super(() -> { + final var running = db.numberOfRunningJobs(); + client.gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, running); + return null; + }); + } + +} + +@Singleton +final class NumOrphanRunningJobs extends Emitter { + + NumOrphanRunningJobs(final MetricClient client, final MetricRepository db) { + super(() -> { + final var orphaned = db.numberOfOrphanRunningJobs(); + client.gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, orphaned); + return null; + }); + } + +} + +@Singleton +final class OldestRunningJob extends Emitter { + + OldestRunningJob(final MetricClient client, final MetricRepository db) { + super(() -> { + final var age = db.oldestRunningJobAgeSecs(); + client.gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); + return null; + }); + } + +} + +@Singleton +final class OldestPendingJob extends Emitter { + + OldestPendingJob(final MetricClient client, final MetricRepository db) { + super(() -> { + final var age = db.oldestPendingJobAgeSecs(); + client.gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); + return null; + }); + } + +} + +@Singleton +final class NumActiveConnectionsPerWorkspace extends Emitter { + + NumActiveConnectionsPerWorkspace(final MetricClient client, final MetricRepository db) { + super(() -> { + final var workspaceConns = db.numberOfActiveConnPerWorkspace(); + for (final long numCons : workspaceConns) { + client.distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, numCons); + } + return null; + }); + } + +} + +@Singleton +final class NumAbnormalScheduledSyncs extends Emitter { + + NumAbnormalScheduledSyncs(final MetricClient client, final MetricRepository db) { + super(() -> { + final var count = db.numberOfJobsNotRunningOnScheduleInLastDay(); + client.gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofHours(1); + } + +} + +@Singleton +final class TotalScheduledSyncs extends Emitter { + + TotalScheduledSyncs(final MetricClient client, final MetricRepository db) { + super(() -> { + final var count = db.numScheduledActiveConnectionsInLastDay(); + client.gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofHours(1); + } + +} + +@Singleton +final class TotalJobRuntimeByTerminalState extends Emitter { + + public TotalJobRuntimeByTerminalState(final MetricClient client, final MetricRepository db) { + super(() -> { + db.overallJobRuntimeForTerminalJobsInLastHour() + .forEach((jobStatus, time) -> client.distribution( + OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, + time, + new MetricAttribute(MetricTags.JOB_STATUS, jobStatus.getLiteral()))); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofHours(1); + } + +} + +/** + * Abstract base class for all emitted metrics. + *

      + * As this is a sealed class, all implementations of it are contained within this same file. + */ +sealed class Emitter { + + protected static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + protected final Callable callable; + + Emitter(final Callable callable) { + this.callable = callable; + } + + /** + * Emit the metrics by calling the callable. + *

      + * Any exception thrown by the callable will be logged. + * + * @TODO: replace log message with a published error-event of some kind. + */ + public void Emit() { + try { + callable.call(); + } catch (final Exception e) { + log.error("Exception querying database for metric: ", e); + } + } + + /** + * How often this metric should report, defaults to 15s if not overwritten. + * + * @return Duration of how often this metric should report. + */ + public Duration getDuration() { + return Duration.ofSeconds(15); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java new file mode 100644 index 000000000000..4bf30ef92cbc --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.micronaut.runtime.event.ApplicationShutdownEvent; +import io.micronaut.runtime.event.ApplicationStartupEvent; +import io.micronaut.runtime.event.annotation.EventListener; +import jakarta.inject.Singleton; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * EventListeners registers event listeners for the startup and shutdown events from Micronaut. + */ +@Singleton +class EventListeners { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final List emitters; + private final ScheduledExecutorService executor; + + EventListeners(final List emitters) { + this.emitters = emitters; + this.executor = Executors.newScheduledThreadPool(emitters.size()); + } + + /** + * Manually registers all the emitters to run on startup. + * + * @param event unused but required in order to listen to the startup event. + */ + @EventListener + public void startEmitters(final ApplicationStartupEvent event) { + emitters.forEach(emitter -> executor.scheduleAtFixedRate(emitter::Emit, 0, emitter.getDuration().getSeconds(), TimeUnit.SECONDS)); + log.info("registered {} emitters", emitters.size()); + } + + /** + * Attempts to cleanly shutdown the running emitters + * + * @param event unused but required in order to listen to the shutdown event. + */ + @EventListener + public void stopEmitters(final ApplicationShutdownEvent event) { + log.info("shutting down emitters"); + executor.shutdown(); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java new file mode 100644 index 000000000000..267426209283 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; +import static org.jooq.impl.SQLDataType.VARCHAR; + +import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import jakarta.inject.Singleton; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jooq.DSLContext; + +@Singleton +class MetricRepository { + + private final DSLContext ctx; + + MetricRepository(final DSLContext ctx) { + this.ctx = ctx; + } + + int numberOfPendingJobs() { + return ctx.selectCount() + .from(JOBS) + .where(JOBS.STATUS.eq(JobStatus.pending)) + .fetchOne(0, int.class); + } + + int numberOfRunningJobs() { + return ctx.selectCount() + .from(JOBS) + .join(CONNECTION) + .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) + .fetchOne(0, int.class); + } + + int numberOfOrphanRunningJobs() { + return ctx.selectCount() + .from(JOBS) + .join(CONNECTION) + .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.ne(StatusType.active))) + .fetchOne(0, int.class); + } + + long oldestPendingJobAgeSecs() { + return oldestJobAgeSecs(JobStatus.pending); + } + + long oldestRunningJobAgeSecs() { + return oldestJobAgeSecs(JobStatus.running); + } + + List numberOfActiveConnPerWorkspace() { + final var query = """ + SELECT workspace_id, count(c.id) as num_conn + FROM actor + INNER JOIN workspace ws ON actor.workspace_id = ws.id + INNER JOIN connection c ON actor.id = c.source_id + WHERE ws.tombstone = false + AND actor.tombstone = false AND actor.actor_type = 'source' + AND c.status = 'active' + GROUP BY workspace_id; + """; + return ctx.fetch(query).getValues("num_conn", long.class); + } + + long numScheduledActiveConnectionsInLastDay() { + final var queryForTotalConnections = """ + select count(1) as connection_count + from connection c + where + c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) IN ('"hours"', '"minutes"') + and c.status = 'active' + """; + + return ctx.fetchOne(queryForTotalConnections).get("connection_count", long.class); + } + + long numberOfJobsNotRunningOnScheduleInLastDay() { + // This query finds all sync jobs ran in last 24 hours and count how many times they have run. + // Comparing this to the expected number of runs (24 hours divide by configured cadence in hours), + // if it runs below that expected number it will be considered as abnormal instance. + // For example, if it's configured to run every 6 hours but in last 24 hours it only has 3 runs, + // it will be considered as 1 abnormal instance. + final var queryForAbnormalSyncInHoursInLastDay = """ + select count(1) as cnt + from ( + select + c.id, + count(*) as cnt + from connection c + left join jobs j on j.scope::uuid = c.id + where + c.schedule is not null + and c.schedule != 'null' + and j.created_at > now() - interval '24 hours 1 minutes' + and c.status = 'active' + and j.config_type = 'sync' + and c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) = '"hours"' + group by 1 + having count(*) < 24 / cast(c.schedule::jsonb->'units' as integer) + ) as abnormal_jobs + """; + + // Similar to the query above, this finds if the connection cadence's timeUnit is minutes. + // thus we use 1440 (=24 hours x 60 minutes) to divide the configured cadence. + final var queryForAbnormalSyncInMinutesInLastDay = """ + select count(1) as cnt + from ( + select + c.id, + count(*) as cnt + from + connection c + left join Jobs j on + j.scope::uuid = c.id + where + c.schedule is not null + and c.schedule != 'null' + and j.created_at > now() - interval '24 hours 1 minutes' + and c.status = 'active' + and j.config_type = 'sync' + and c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) = '"minutes"' + group by 1 + having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer) + ) as abnormal_jobs + """; + return ctx.fetchOne(queryForAbnormalSyncInHoursInLastDay).get("cnt", long.class) + + ctx.fetchOne(queryForAbnormalSyncInMinutesInLastDay).get("cnt", long.class); + } + + Map overallJobRuntimeForTerminalJobsInLastHour() { + final var query = """ + SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs + WHERE updated_at >= NOW() - INTERVAL '1 HOUR' + AND (jobs.status = 'failed' OR jobs.status = 'succeeded' OR jobs.status = 'cancelled'); + """; + final var statuses = ctx.fetch(query).getValues("status", JobStatus.class); + final var times = ctx.fetch(query).getValues("sec", double.class); + + final var results = new HashMap(); + for (int i = 0; i < statuses.size(); i++) { + results.put(statuses.get(i), times.get(i)); + } + + return results; + } + + private long oldestJobAgeSecs(final JobStatus status) { + final var readableTimeField = "run_duration"; + final var durationSecField = "run_duration_secs"; + final var query = String.format(""" + WITH + oldest_job AS ( + SELECT id, + age(current_timestamp, created_at) AS %s + FROM jobs + WHERE status = '%s' + ORDER BY run_duration DESC + LIMIT 1) + SELECT id, + run_duration, + extract(epoch from run_duration) as %s + FROM oldest_job""", readableTimeField, status.getLiteral(), durationSecField); + final var res = ctx.fetch(query); + // unfortunately there are no good Jooq methods for retrieving a single record of a single column + // forcing the List cast. + final var duration = res.getValues(durationSecField, Double.class); + + if (duration.size() == 0) { + return 0L; + } + + // as double can have rounding errors, round down to remove noise. + return duration.get(0).longValue(); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java deleted file mode 100644 index 17d8331dcc7b..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.commons.lang.CloseableShutdownHook; -import io.airbyte.config.Configs; -import io.airbyte.config.EnvConfigs; -import io.airbyte.db.Database; -import io.airbyte.db.check.DatabaseCheckException; -import io.airbyte.db.factory.DSLContextFactory; -import io.airbyte.db.factory.DataSourceFactory; -import io.airbyte.db.factory.DatabaseCheckFactory; -import io.airbyte.db.factory.DatabaseDriver; -import io.airbyte.db.factory.FlywayFactory; -import io.airbyte.db.instance.configs.ConfigsDatabaseMigrator; -import io.airbyte.metrics.lib.MetricClientFactory; -import io.airbyte.metrics.lib.MetricEmittingApps; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import javax.sql.DataSource; -import lombok.extern.slf4j.Slf4j; -import org.flywaydb.core.Flyway; -import org.jooq.DSLContext; -import org.jooq.SQLDialect; - -@Slf4j -public class ReporterApp { - - public static Database configDatabase; - - public static void main(final String[] args) throws DatabaseCheckException { - final Configs configs = new EnvConfigs(); - - MetricClientFactory.initialize(MetricEmittingApps.METRICS_REPORTER); - - final DataSource dataSource = DataSourceFactory.create( - configs.getConfigDatabaseUser(), - configs.getConfigDatabasePassword(), - DatabaseDriver.POSTGRESQL.getDriverClassName(), - configs.getConfigDatabaseUrl()); - - try (final DSLContext dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES)) { - - final Flyway flyway = FlywayFactory.create(dataSource, ReporterApp.class.getSimpleName(), - ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); - - // Ensure that the database resources are closed on application shutdown - CloseableShutdownHook.registerRuntimeShutdownHook(dataSource, dslContext); - - // Ensure that the Configuration database is available - DatabaseCheckFactory.createConfigsDatabaseMigrationCheck(dslContext, flyway, configs.getConfigsDatabaseMinimumFlywayMigrationVersion(), - configs.getConfigsDatabaseInitializationTimeoutMs()).check(); - - configDatabase = new Database(dslContext); - - final var toEmits = ToEmit.values(); - final var pollers = Executors.newScheduledThreadPool(toEmits.length); - - log.info("Scheduling {} metrics for emission..", toEmits.length); - for (final ToEmit toEmit : toEmits) { - pollers.scheduleAtFixedRate(toEmit.emit, 0, toEmit.duration.getSeconds(), TimeUnit.SECONDS); - } - } - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java new file mode 100644 index 000000000000..2e5683fb55b1 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.metrics.lib.MetricClient; +import io.airbyte.metrics.lib.MetricClientFactory; +import io.micronaut.context.annotation.Factory; +import jakarta.inject.Singleton; + +/** + * Micronaut factory for creating the appropriate singletons utilized by the metric reporter + * service. + */ +@Factory +class ReporterFactory { + + @Singleton + public MetricClient metricClient() { + return MetricClientFactory.getMetricClient(); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java deleted file mode 100644 index 3313a73da4bc..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import io.airbyte.metrics.lib.MetricAttribute; -import io.airbyte.metrics.lib.MetricClientFactory; -import io.airbyte.metrics.lib.MetricQueries; -import io.airbyte.metrics.lib.MetricTags; -import io.airbyte.metrics.lib.OssMetricsRegistry; -import java.lang.invoke.MethodHandles; -import java.time.Duration; -import java.util.concurrent.Callable; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This class contains all metrics emitted by the {@link ReporterApp}. - */ -public enum ToEmit { - - NUM_PENDING_JOBS(countMetricEmission(() -> { - final var pendingJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfPendingJobs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pendingJobs); - return null; - })), - NUM_RUNNING_JOBS(countMetricEmission(() -> { - final var runningJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfRunningJobs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, runningJobs); - return null; - })), - NUM_ORPHAN_RUNNING_JOB(countMetricEmission(() -> { - final var orphanRunningJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfOrphanRunningJobs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, orphanRunningJobs); - return null; - })), - OLDEST_RUNNING_JOB_AGE_SECS(countMetricEmission(() -> { - final var age = ReporterApp.configDatabase.query(MetricQueries::oldestRunningJobAgeSecs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); - return null; - })), - OLDEST_PENDING_JOB_AGE_SECS(countMetricEmission(() -> { - final var age = ReporterApp.configDatabase.query(MetricQueries::oldestPendingJobAgeSecs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); - return null; - })), - NUM_ACTIVE_CONN_PER_WORKSPACE(countMetricEmission(() -> { - final var age = ReporterApp.configDatabase.query(MetricQueries::numberOfActiveConnPerWorkspace); - for (final long count : age) { - MetricClientFactory.getMetricClient().distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, count); - } - return null; - })), - NUM_ABNORMAL_SCHEDULED_SYNCS_LAST_DAY(Duration.ofHours(1), countMetricEmission(() -> { - final var count = ReporterApp.configDatabase.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); - return null; - })), - NUM_TOTAL_SCHEDULED_SYNCS_LAST_DAY(Duration.ofHours(1), countMetricEmission(() -> { - final var count = ReporterApp.configDatabase.query(MetricQueries::numScheduledActiveConnectionsInLastDay); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); - return null; - })), - OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS(Duration.ofHours(1), countMetricEmission(() -> { - final var times = ReporterApp.configDatabase.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - for (final Pair pair : times) { - MetricClientFactory.getMetricClient().distribution( - OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, pair.getRight(), - new MetricAttribute(MetricTags.JOB_STATUS, MetricTags.getJobStatus(pair.getLeft()))); - } - return null; - })); - - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - // default constructor - /** A runnable that emits a metric. */ - final public Runnable emit; - /** How often this metric would emit data. */ - final public Duration duration; - - ToEmit(final Runnable emit) { - this(Duration.ofSeconds(15), emit); - } - - ToEmit(final Duration duration, final Runnable emit) { - this.duration = duration; - this.emit = emit; - } - - /** - * Wrapper callable to handle 1) query exception logging and 2) counting metric emissions so - * reporter app can be monitored too. - * - * @param metricQuery - * @return - */ - private static Runnable countMetricEmission(final Callable metricQuery) { - return () -> { - try { - metricQuery.call(); - MetricClientFactory.getMetricClient().count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); - } catch (final Exception e) { - log.error("Exception querying database for metric: ", e); - } - }; - } - -} diff --git a/airbyte-metrics/reporter/src/main/resources/application.yml b/airbyte-metrics/reporter/src/main/resources/application.yml new file mode 100644 index 000000000000..49dd8cb8d7ae --- /dev/null +++ b/airbyte-metrics/reporter/src/main/resources/application.yml @@ -0,0 +1,38 @@ +micronaut: + application: + name: airbyte-metrics-reporter + security: + intercept-url-map: + - pattern: /** + httpMethod: GET + access: + - isAnonymous() + server: + port: 9000 + +datasources: + config: + connection-test-query: SELECT 1 + connection-timeout: 30000 + idle-timeout: 600000 + maximum-pool-size: 10 + url: ${DATABASE_URL} + driverClassName: org.postgresql.Driver + username: ${DATABASE_USER} + password: ${DATABASE_PASSWORD} + +jooq: + datasources: + config: + jackson-converter-enabled: true + sql-dialect: POSTGRES + +endpoints: + all: + enabled: true + +logger: + levels: + io.airbyte.bootloader: DEBUG +# Uncomment to help resolve issues with conditional beans +# io.micronaut.context.condition: DEBUG diff --git a/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt b/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt new file mode 100644 index 000000000000..633f73326c1a --- /dev/null +++ b/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt @@ -0,0 +1,8 @@ + + ___ _ __ __ + / | (_)____/ /_ __ __/ /____ + / /| | / / ___/ __ \/ / / / __/ _ \ + / ___ |/ / / / /_/ / /_/ / /_/ __/ +/_/ |_/_/_/ /_.___/\__, /\__/\___/ + /____/ + : airbyte-metrics-reporter : \ No newline at end of file diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java new file mode 100644 index 000000000000..d509ee577668 --- /dev/null +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import io.airbyte.metrics.lib.MetricAttribute; +import io.airbyte.metrics.lib.MetricClient; +import io.airbyte.metrics.lib.MetricTags; +import io.airbyte.metrics.lib.OssMetricsRegistry; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class EmitterTest { + + private MetricClient client; + private MetricRepository repo; + + @BeforeEach + void setUp() { + client = mock(MetricClient.class); + repo = mock(MetricRepository.class); + } + + @Test + void TestNumPendingJobs() { + final var value = 101; + when(repo.numberOfPendingJobs()).thenReturn(value); + + final var emitter = new NumPendingJobs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfPendingJobs(); + verify(client).gauge(OssMetricsRegistry.NUM_PENDING_JOBS, value); + } + + @Test + void TestNumRunningJobs() { + final var value = 101; + when(repo.numberOfRunningJobs()).thenReturn(value); + + final var emitter = new NumRunningJobs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfRunningJobs(); + verify(client).gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, value); + } + + @Test + void TestNumOrphanRunningJobs() { + final var value = 101; + when(repo.numberOfOrphanRunningJobs()).thenReturn(value); + + final var emitter = new NumOrphanRunningJobs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfOrphanRunningJobs(); + verify(client).gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, value); + } + + @Test + void TestOldestRunningJob() { + final var value = 101; + when(repo.oldestRunningJobAgeSecs()).thenReturn((long) value); + + final var emitter = new OldestRunningJob(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).oldestRunningJobAgeSecs(); + verify(client).gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, value); + } + + @Test + void TestOldestPendingJob() { + final var value = 101; + when(repo.oldestPendingJobAgeSecs()).thenReturn((long) value); + + final var emitter = new OldestPendingJob(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).oldestPendingJobAgeSecs(); + verify(client).gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, value); + } + + @Test + void TestNumActiveConnectionsPerWorkspace() { + final var values = List.of(101L, 202L); + when(repo.numberOfActiveConnPerWorkspace()).thenReturn(values); + + final var emitter = new NumActiveConnectionsPerWorkspace(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfActiveConnPerWorkspace(); + for (final var value : values) { + verify(client).distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, value); + } + } + + @Test + void TestNumAbnormalScheduledSyncs() { + final var value = 101; + when(repo.numberOfJobsNotRunningOnScheduleInLastDay()).thenReturn((long) value); + + final var emitter = new NumAbnormalScheduledSyncs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofHours(1), emitter.getDuration()); + verify(repo).numberOfJobsNotRunningOnScheduleInLastDay(); + verify(client).gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, value); + } + + @Test + void TestTotalScheduledSyncs() { + final var value = 101; + when(repo.numScheduledActiveConnectionsInLastDay()).thenReturn((long) value); + + final var emitter = new TotalScheduledSyncs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofHours(1), emitter.getDuration()); + verify(repo).numScheduledActiveConnectionsInLastDay(); + verify(client).gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, value); + } + + @Test + void TestTotalJobRuntimeByTerminalState() { + final var values = Map.of(JobStatus.cancelled, 101.0, JobStatus.succeeded, 202.0, JobStatus.failed, 303.0); + when(repo.overallJobRuntimeForTerminalJobsInLastHour()).thenReturn(values); + + final var emitter = new TotalJobRuntimeByTerminalState(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofHours(1), emitter.getDuration()); + verify(repo).overallJobRuntimeForTerminalJobsInLastHour(); + values.forEach((jobStatus, time) -> { + verify(client).distribution(OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, time, + new MetricAttribute(MetricTags.JOB_STATUS, jobStatus.getLiteral())); + }); + } + +} diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java new file mode 100644 index 000000000000..8ff5f2e8cd69 --- /dev/null +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import static io.airbyte.db.instance.configs.jooq.generated.Keys.ACTOR_CATALOG_FETCH_EVENT__ACTOR_CATALOG_FETCH_EVENT_ACTOR_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Keys.ACTOR__ACTOR_WORKSPACE_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Keys.CONNECTION__CONNECTION_DESTINATION_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Keys.CONNECTION__CONNECTION_SOURCE_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_CATALOG_FETCH_EVENT; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.airbyte.db.factory.DSLContextFactory; +import io.airbyte.db.init.DatabaseInitializationException; +import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; +import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; +import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; +import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import io.airbyte.db.instance.test.TestDatabaseProviders; +import io.airbyte.test.utils.DatabaseConnectionHelper; +import java.io.IOException; +import java.sql.SQLException; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.jooq.DSLContext; +import org.jooq.JSONB; +import org.jooq.SQLDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; + +class MetricRepositoryTest { + + private static final String SRC = "src"; + private static final String DEST = "dst"; + private static final String CONN = "conn"; + private static final UUID SRC_DEF_ID = UUID.randomUUID(); + private static final UUID DST_DEF_ID = UUID.randomUUID(); + private static MetricRepository db; + private static DSLContext ctx; + + @BeforeAll + public static void setUpAll() throws DatabaseInitializationException, IOException { + final var psqlContainer = new PostgreSQLContainer<>("postgres:13-alpine") + .withUsername("user") + .withPassword("hunter2"); + psqlContainer.start(); + + final var dataSource = DatabaseConnectionHelper.createDataSource(psqlContainer); + ctx = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); + final var dbProviders = new TestDatabaseProviders(dataSource, ctx); + dbProviders.createNewConfigsDatabase(); + dbProviders.createNewJobsDatabase(); + + ctx.insertInto(ACTOR_DEFINITION, ACTOR_DEFINITION.ID, ACTOR_DEFINITION.NAME, ACTOR_DEFINITION.DOCKER_REPOSITORY, + ACTOR_DEFINITION.DOCKER_IMAGE_TAG, ACTOR_DEFINITION.SPEC, ACTOR_DEFINITION.ACTOR_TYPE, ACTOR_DEFINITION.RELEASE_STAGE) + .values(SRC_DEF_ID, "srcDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.source, ReleaseStage.beta) + .values(DST_DEF_ID, "dstDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.destination, ReleaseStage.generally_available) + .values(UUID.randomUUID(), "dstDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.destination, ReleaseStage.alpha) + .execute(); + + // drop constraints to simplify test set up + ctx.alterTable(ACTOR).dropForeignKey(ACTOR__ACTOR_WORKSPACE_ID_FKEY.constraint()).execute(); + ctx.alterTable(CONNECTION).dropForeignKey(CONNECTION__CONNECTION_DESTINATION_ID_FKEY.constraint()).execute(); + ctx.alterTable(CONNECTION).dropForeignKey(CONNECTION__CONNECTION_SOURCE_ID_FKEY.constraint()).execute(); + ctx.alterTable(ACTOR_CATALOG_FETCH_EVENT) + .dropForeignKey(ACTOR_CATALOG_FETCH_EVENT__ACTOR_CATALOG_FETCH_EVENT_ACTOR_ID_FKEY.constraint()).execute(); + ctx.alterTable(WORKSPACE).alter(WORKSPACE.SLUG).dropNotNull().execute(); + ctx.alterTable(WORKSPACE).alter(WORKSPACE.INITIAL_SETUP_COMPLETE).dropNotNull().execute(); + + db = new MetricRepository(ctx); + } + + @BeforeEach + void setUp() { + ctx.truncate(ACTOR).execute(); + ctx.truncate(CONNECTION).cascade().execute(); + ctx.truncate(JOBS).cascade().execute(); + ctx.truncate(WORKSPACE).cascade().execute(); + } + + @AfterEach + void tearDown() { + + } + + @Nested + class NumJobs { + + @Test + void shouldReturnReleaseStages() { + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) + .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) + .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) + .execute(); + + final var activeConnectionId = UUID.randomUUID(); + final var inactiveConnectionId = UUID.randomUUID(); + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, + CONNECTION.DESTINATION_ID, CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL) + .values(activeConnectionId, StatusType.active, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) + .values(inactiveConnectionId, StatusType.inactive, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) + .execute(); + + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, activeConnectionId.toString(), JobStatus.pending) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(2L, activeConnectionId.toString(), JobStatus.failed) + .values(3L, activeConnectionId.toString(), JobStatus.running) + .values(4L, activeConnectionId.toString(), JobStatus.running) + .values(5L, inactiveConnectionId.toString(), JobStatus.running) + .execute(); + + assertEquals(2, db.numberOfRunningJobs()); + assertEquals(1, db.numberOfOrphanRunningJobs()); + } + + @Test + void runningJobsShouldReturnZero() throws SQLException { + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute(); + + final var res = db.numberOfRunningJobs(); + assertEquals(0, res); + } + + @Test + void pendingJobsShouldReturnCorrectCount() throws SQLException { + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.pending) + .values(2L, "", JobStatus.failed) + .values(3L, "", JobStatus.pending) + .values(4L, "", JobStatus.running) + .execute(); + + final var res = db.numberOfPendingJobs(); + assertEquals(2, res); + } + + @Test + void pendingJobsShouldReturnZero() throws SQLException { + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.running) + .values(2L, "", JobStatus.failed) + .execute(); + + final var res = db.numberOfPendingJobs(); + assertEquals(0, res); + } + + } + + @Nested + class OldestPendingJob { + + @Test + void shouldReturnOnlyPendingSeconds() throws SQLException { + final var expAgeSecs = 1000; + final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + // oldest pending job + .values(1L, "", JobStatus.pending, oldestCreateAt) + // second-oldest pending job + .values(2L, "", JobStatus.pending, OffsetDateTime.now()) + .execute(); + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(3L, "", JobStatus.running) + .values(4L, "", JobStatus.failed) + .execute(); + + final var res = db.oldestPendingJobAgeSecs(); + // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(List.of(999L, 1000L, 1001L).contains(res)); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.succeeded) + .values(2L, "", JobStatus.running) + .values(3L, "", JobStatus.failed).execute(); + + final var res = db.oldestPendingJobAgeSecs(); + assertEquals(0L, res); + } + + } + + @Nested + class OldestRunningJob { + + @Test + void shouldReturnOnlyRunningSeconds() { + final var expAgeSecs = 10000; + final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + // oldest pending job + .values(1L, "", JobStatus.running, oldestCreateAt) + // second-oldest pending job + .values(2L, "", JobStatus.running, OffsetDateTime.now()) + .execute(); + + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(3L, "", JobStatus.pending) + .values(4L, "", JobStatus.failed) + .execute(); + + final var res = db.oldestRunningJobAgeSecs(); + // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.succeeded) + .values(2L, "", JobStatus.pending) + .values(3L, "", JobStatus.failed) + .execute(); + + final var res = db.oldestRunningJobAgeSecs(); + assertEquals(0L, res); + } + + } + + @Nested + class NumActiveConnsPerWorkspace { + + @Test + void shouldReturnNumConnectionsBasic() { + final var workspaceId = UUID.randomUUID(); + ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) + .values(workspaceId, "test-0", false) + .execute(); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .execute(); + + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(1, res.size()); + assertEquals(2, res.get(0)); + } + + @Test + @DisplayName("should ignore deleted connections") + void shouldIgnoreNonRunningConnections() { + final var workspaceId = UUID.randomUUID(); + ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) + .values(workspaceId, "test-0", false) + .execute(); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.deprecated) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.inactive) + .execute(); + + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(1, res.size()); + assertEquals(2, res.get(0)); + } + + @Test + @DisplayName("should ignore deleted connections") + void shouldIgnoreDeletedWorkspaces() { + final var workspaceId = UUID.randomUUID(); + ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) + .values(workspaceId, "test-0", true) + .execute(); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .execute(); + + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(0, res.size()); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(0, res.size()); + } + + } + + @Nested + class OverallJobRuntimeForTerminalJobsInLastHour { + + @Test + void shouldIgnoreNonTerminalJobs() throws SQLException { + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.running) + .values(2L, "", JobStatus.incomplete) + .values(3L, "", JobStatus.pending) + .execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(0, res.size()); + } + + @Test + void shouldIgnoreJobsOlderThan1Hour() { + final var updateAt = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.UPDATED_AT).values(1L, "", JobStatus.succeeded, updateAt).execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(0, res.size()); + } + + @Test + @DisplayName("should return correct duration for terminal jobs") + void shouldReturnTerminalJobs() { + final var updateAt = OffsetDateTime.now(); + final var expAgeSecs = 10000; + final var createAt = updateAt.minus(expAgeSecs, ChronoUnit.SECONDS); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(1L, "", JobStatus.succeeded, createAt, updateAt) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(2L, "", JobStatus.failed, createAt, updateAt) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(3L, "", JobStatus.cancelled, createAt, updateAt) + .execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(3, res.size()); + + final var exp = Map.of( + JobStatus.succeeded, expAgeSecs * 1.0, + JobStatus.cancelled, expAgeSecs * 1.0, + JobStatus.failed, expAgeSecs * 1.0); + assertEquals(exp, res); + } + + @Test + void shouldReturnTerminalJobsComplex() { + final var updateAtNow = OffsetDateTime.now(); + final var expAgeSecs = 10000; + final var createAt = updateAtNow.minus(expAgeSecs, ChronoUnit.SECONDS); + + // terminal jobs in last hour + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(1L, "", JobStatus.succeeded, createAt, updateAtNow) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(2L, "", JobStatus.failed, createAt, updateAtNow) + .execute(); + + // old terminal jobs + final var updateAtOld = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(3L, "", JobStatus.cancelled, createAt, updateAtOld) + .execute(); + + // non-terminal jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + .values(4L, "", JobStatus.running, createAt) + .execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(2, res.size()); + + final var exp = Map.of( + JobStatus.succeeded, expAgeSecs * 1.0, + JobStatus.failed, expAgeSecs * 1.0); + assertEquals(exp, res); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(0, res.size()); + } + + } + + @Nested + class AbnormalJobsInLastDay { + + @Test + void shouldCountInJobsWithMissingRun() throws SQLException { + final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); + final var connectionId = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(connectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 6, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) + .execute(); + + // Jobs running in prior day will not be counted + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), updateAt, syncConfigType) + .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, syncConfigType) + .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, syncConfigType) + .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(5, ChronoUnit.HOURS), updateAt, syncConfigType) + .execute(); + + final var totalConnectionResult = db.numScheduledActiveConnectionsInLastDay(); + assertEquals(1, totalConnectionResult); + + final var abnormalConnectionResult = db.numberOfJobsNotRunningOnScheduleInLastDay(); + assertEquals(1, abnormalConnectionResult); + } + + @Test + void shouldNotCountNormalJobsInAbnormalMetric() { + final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); + final var inactiveConnectionId = UUID.randomUUID(); + final var activeConnectionId = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(inactiveConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.inactive, updateAt, updateAt) + .values(activeConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) + .execute(); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(1L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, + syncConfigType) + .values(2L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, + syncConfigType) + .execute(); + + final var totalConnectionResult = db.numScheduledActiveConnectionsInLastDay(); + assertEquals(1, totalConnectionResult); + + final var abnormalConnectionResult = db.numberOfJobsNotRunningOnScheduleInLastDay(); + assertEquals(0, abnormalConnectionResult); + } + + } + +} From 286b51ba58d67f17d7146fe27092f86b9ee6148a Mon Sep 17 00:00:00 2001 From: Topher Lubaway Date: Tue, 11 Oct 2022 11:14:31 -0500 Subject: [PATCH 032/498] Version bumps docusaurus because firefox (#17849) firefox was not showing the search bar this update seems to fix the problem https://airbytehq-team.slack.com/archives/C032Y32T065/p1665503058314239 --- docusaurus/package.json | 24 +- docusaurus/yarn.lock | 1284 +++++++++++++++++++++++++++++---------- 2 files changed, 986 insertions(+), 322 deletions(-) diff --git a/docusaurus/package.json b/docusaurus/package.json index a09fdb5b2955..7a403679f7d5 100644 --- a/docusaurus/package.json +++ b/docusaurus/package.json @@ -75,18 +75,18 @@ "@babel/runtime-corejs3": "7.18.6", "@cmfcmf/docusaurus-search-local": "^0.10.0", "@docsearch/react": "3.1.0", - "@docusaurus/core": "^2.0.0-beta.21", - "@docusaurus/cssnano-preset": "2.0.0-beta.21", - "@docusaurus/module-type-aliases": "2.0.0-beta.21", - "@docusaurus/plugin-client-redirects": "2.0.0-beta.21", - "@docusaurus/plugin-debug": "2.0.0-beta.21", - "@docusaurus/plugin-google-analytics": "2.0.0-beta.21", - "@docusaurus/plugin-google-gtag": "2.0.0-beta.21", - "@docusaurus/plugin-sitemap": "2.0.0-beta.21", - "@docusaurus/preset-classic": "2.0.0-beta.21", - "@docusaurus/theme-classic": "2.0.0-beta.21", - "@docusaurus/theme-search-algolia": "2.0.0-beta.21", - "@docusaurus/types": "2.0.0-beta.21", + "@docusaurus/core": "^2.1.0", + "@docusaurus/cssnano-preset": "^2.1.0", + "@docusaurus/module-type-aliases": "^2.1.0", + "@docusaurus/plugin-client-redirects": "^2.1.0", + "@docusaurus/plugin-debug": "^2.1.0", + "@docusaurus/plugin-google-analytics": "^2.1.0", + "@docusaurus/plugin-google-gtag": "^2.1.0", + "@docusaurus/plugin-sitemap": "^2.1.0", + "@docusaurus/preset-classic": "^2.1.0", + "@docusaurus/theme-classic": "^2.1.0", + "@docusaurus/theme-search-algolia": "^2.1.0", + "@docusaurus/types": "^2.1.0", "@mdx-js/react": "^1.6.21", "async": "2.6.4", "autoprefixer": "10.4.7", diff --git a/docusaurus/yarn.lock b/docusaurus/yarn.lock index 4d3b91369813..1923ac4cf4ad 100644 --- a/docusaurus/yarn.lock +++ b/docusaurus/yarn.lock @@ -9,6 +9,13 @@ dependencies: "@algolia/autocomplete-shared" "1.6.3" +"@algolia/autocomplete-core@1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.7.1.tgz#025538b8a9564a9f3dd5bcf8a236d6951c76c7d1" + integrity sha512-eiZw+fxMzNQn01S8dA/hcCpoWCOCwcIIEUtHHdzN5TGB3IpzLbuhqFeTfh2OUhhgkE8Uo17+wH+QJ/wYyQmmzg== + dependencies: + "@algolia/autocomplete-shared" "1.7.1" + "@algolia/autocomplete-js@^1.5.1": version "1.6.3" resolved "https://registry.yarnpkg.com/@algolia/autocomplete-js/-/autocomplete-js-1.6.3.tgz#a5bacfcced057a8587bc7a136a6c4f54052913fe" @@ -27,11 +34,23 @@ dependencies: "@algolia/autocomplete-shared" "1.6.3" +"@algolia/autocomplete-preset-algolia@1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.1.tgz#7dadc5607097766478014ae2e9e1c9c4b3f957c8" + integrity sha512-pJwmIxeJCymU1M6cGujnaIYcY3QPOVYZOXhFkWVM7IxKzy272BwCvMFMyc5NpG/QmiObBxjo7myd060OeTNJXg== + dependencies: + "@algolia/autocomplete-shared" "1.7.1" + "@algolia/autocomplete-shared@1.6.3": version "1.6.3" resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.6.3.tgz#52085ce89a755977841ed0a463aa31ce8f1dea97" integrity sha512-UV46bnkTztyADFaETfzFC5ryIdGVb2zpAoYgu0tfcuYWjhg1KbLXveFffZIrGVoboqmAk1b+jMrl6iCja1i3lg== +"@algolia/autocomplete-shared@1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.1.tgz#95c3a0b4b78858fed730cf9c755b7d1cd0c82c74" + integrity sha512-eTmGVqY3GeyBTT8IWiB2K5EuURAqhnumfktAEoHxfDY2o7vg2rSnO16ZtIG0fMgt3py28Vwgq42/bVEuaQV7pg== + "@algolia/autocomplete-theme-classic@^1.5.1": version "1.6.3" resolved "https://registry.yarnpkg.com/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.6.3.tgz#b659b4535482e1c6195eb1186afe1dc03b237aaa" @@ -166,6 +185,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.6.tgz#8b37d24e88e8e21c499d4328db80577d8882fa53" integrity sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.19.3", "@babel/compat-data@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.4.tgz#95c86de137bf0317f3a570e1b6e996b427299747" + integrity sha512-CHIGpJcUQ5lU9KrPHTjBMhVwQG6CQjxfg36fGXl3qk/Gik1WwWachaXFuo0uCWJT/mStOKtcbFJCaVLihC1CMw== + "@babel/core@7.12.9": version "7.12.9" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" @@ -188,7 +212,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@7.18.6", "@babel/core@^7.15.5", "@babel/core@^7.18.2": +"@babel/core@7.18.6", "@babel/core@^7.15.5": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d" integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ== @@ -209,7 +233,28 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@^7.12.5", "@babel/generator@^7.18.2", "@babel/generator@^7.18.6": +"@babel/core@^7.18.6": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.3.tgz#2519f62a51458f43b682d61583c3810e7dcee64c" + integrity sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.3" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helpers" "^7.19.0" + "@babel/parser" "^7.19.3" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.3" + "@babel/types" "^7.19.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.12.5", "@babel/generator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.6.tgz#9ab2d46d3cbf631f0e80f72e72874a04c3fc12a9" integrity sha512-AIwwoOS8axIC5MZbhNHRLKi3D+DMpvDf9XUcu3pIVAfOHFT45f4AoDAltRbHIQomCipkCZxrNkfpOEHhJz/VKw== @@ -218,6 +263,15 @@ "@jridgewell/gen-mapping" "^0.3.0" jsesc "^2.5.1" +"@babel/generator@^7.18.7", "@babel/generator@^7.19.3", "@babel/generator@^7.19.4": + version "7.19.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.5.tgz#da3f4b301c8086717eee9cab14da91b1fa5dcca7" + integrity sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg== + dependencies: + "@babel/types" "^7.19.4" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -243,6 +297,16 @@ browserslist "^4.20.2" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0", "@babel/helper-compilation-targets@^7.19.3": + version "7.19.3" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.3.tgz#a10a04588125675d7c7ae299af86fa1b2ee038ca" + integrity sha512-65ESqLGyGmLvgR0mst5AdW1FkNlj9rQsCKduzEoEPhBCDFGXvz2jW6bXFG6i0/MrV2s7hhXjjb2yAzcPuQlLwg== + dependencies: + "@babel/compat-data" "^7.19.3" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz#6f15f8459f3b523b39e00a99982e2c040871ed72" @@ -264,6 +328,14 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" +"@babel/helper-create-regexp-features-plugin@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" + integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + "@babel/helper-define-polyfill-provider@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" @@ -278,11 +350,28 @@ resolve "^1.14.2" semver "^6.1.2" +"@babel/helper-define-polyfill-provider@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a" + integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + "@babel/helper-environment-visitor@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + "@babel/helper-explode-assignable-expression@7.18.6", "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" @@ -298,6 +387,14 @@ "@babel/template" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + "@babel/helper-hoist-variables@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" @@ -312,6 +409,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" @@ -333,6 +437,20 @@ "@babel/traverse" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helper-module-transforms@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" + integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -350,6 +468,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== +"@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + "@babel/helper-remap-async-to-generator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz#fa1f81acd19daee9d73de297c0308783cd3cfc23" @@ -360,6 +483,16 @@ "@babel/helper-wrap-function" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + "@babel/helper-replace-supers@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz#efedf51cfccea7b7b8c0f00002ab317e7abfe420" @@ -371,6 +504,17 @@ "@babel/traverse" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helper-replace-supers@^7.18.9": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78" + integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.19.1" + "@babel/types" "^7.19.0" + "@babel/helper-simple-access@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" @@ -385,6 +529,13 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + "@babel/helper-split-export-declaration@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" @@ -392,11 +543,21 @@ dependencies: "@babel/types" "^7.18.6" +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + "@babel/helper-validator-option@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" @@ -412,6 +573,16 @@ "@babel/traverse" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helper-wrap-function@^7.18.9": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1" + integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg== + dependencies: + "@babel/helper-function-name" "^7.19.0" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.0" + "@babel/types" "^7.19.0" + "@babel/helpers@^7.12.5", "@babel/helpers@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" @@ -421,6 +592,15 @@ "@babel/traverse" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helpers@^7.19.0": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.4.tgz#42154945f87b8148df7203a25c31ba9a73be46c5" + integrity sha512-G+z3aOx2nfDHwX/kyVii5fJq+bgscg89/dJNWpYeKeBv3v9xX8EIabmx1k6u9LS04H7nROFVRVK+e3k0VHp+sw== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.4" + "@babel/types" "^7.19.4" + "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -430,11 +610,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.12.7", "@babel/parser@^7.18.3", "@babel/parser@^7.18.6": +"@babel/parser@^7.12.7", "@babel/parser@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc" integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw== +"@babel/parser@^7.18.10", "@babel/parser@^7.18.8", "@babel/parser@^7.19.3", "@babel/parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.4.tgz#03c4339d2b8971eb3beca5252bafd9b9f79db3dc" + integrity sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -451,6 +636,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.6" "@babel/plugin-proposal-optional-chaining" "^7.18.6" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions@7.18.6", "@babel/plugin-proposal-async-generator-functions@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz#aedac81e6fc12bb643374656dd5f2605bf743d17" @@ -461,6 +655,16 @@ "@babel/helper-remap-async-to-generator" "^7.18.6" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-proposal-async-generator-functions@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz#34f6f5174b688529342288cd264f80c9ea9fb4a7" + integrity sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-proposal-class-properties@7.18.6", "@babel/plugin-proposal-class-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" @@ -494,6 +698,14 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-proposal-json-strings@7.18.6", "@babel/plugin-proposal-json-strings@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" @@ -510,6 +722,14 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator@7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" @@ -546,6 +766,17 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.18.6" +"@babel/plugin-proposal-object-rest-spread@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.19.4.tgz#a8fc86e8180ff57290c91a75d83fe658189b642d" + integrity sha512-wHmj6LDxVDnL+3WhXteUBaoM1aVILZODAUjg11kHqG4cOlfgMQGxw6aCgvrXrmaJR3Bn14oZhImyCPZzRpC93Q== + dependencies: + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-proposal-optional-catch-binding@7.18.6", "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" @@ -563,6 +794,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.18.6" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-proposal-private-methods@7.18.6", "@babel/plugin-proposal-private-methods@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" @@ -745,6 +985,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-block-scoping@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.19.4.tgz#315d70f68ce64426db379a3d830e7ac30be02e9b" + integrity sha512-934S2VLLlt2hRJwPf4MczaOr4hYF0z+VKPwqTNxyKX7NthTiPfhuKFWQZHXRM0vh/wo/VyXB3s4bZUNA08l+tQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-transform-classes@7.18.6", "@babel/plugin-transform-classes@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.6.tgz#3501a8f3f4c7d5697c27a3eedbee71d68312669f" @@ -759,6 +1006,21 @@ "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20" + integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-compilation-targets" "^7.19.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@7.18.6", "@babel/plugin-transform-computed-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.6.tgz#5d15eb90e22e69604f3348344c91165c5395d032" @@ -766,6 +1028,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-destructuring@7.18.6", "@babel/plugin-transform-destructuring@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.6.tgz#a98b0e42c7ffbf5eefcbcf33280430f230895c6f" @@ -773,6 +1042,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-destructuring@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.19.4.tgz#46890722687b9b89e1369ad0bd8dc6c5a3b4319d" + integrity sha512-t0j0Hgidqf0aM86dF8U+vXYReUgJnlv4bZLsyoPnwZNrGY+7/38o8YjaELrvHeVfTZao15kjR0PVv0nju2iduA== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" @@ -788,6 +1064,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator@7.18.6", "@babel/plugin-transform-exponentiation-operator@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" @@ -803,6 +1086,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-function-name@7.18.6", "@babel/plugin-transform-function-name@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.6.tgz#6a7e4ae2893d336fd1b8f64c9f92276391d0f1b4" @@ -812,6 +1102,15 @@ "@babel/helper-function-name" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-literals@7.18.6", "@babel/plugin-transform-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.6.tgz#9d6af353b5209df72960baf4492722d56f39a205" @@ -819,6 +1118,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-member-expression-literals@7.18.6", "@babel/plugin-transform-member-expression-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" @@ -856,6 +1162,17 @@ "@babel/helper-validator-identifier" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-systemjs@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz#5f20b471284430f02d9c5059d9b9a16d4b085a1f" + integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-identifier" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-umd@7.18.6", "@babel/plugin-transform-modules-umd@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" @@ -872,6 +1189,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" + integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.19.0" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/plugin-transform-new-target@7.18.6", "@babel/plugin-transform-new-target@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" @@ -894,6 +1219,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-property-literals@7.18.6", "@babel/plugin-transform-property-literals@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" @@ -956,7 +1288,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@7.18.6", "@babel/plugin-transform-runtime@^7.18.2": +"@babel/plugin-transform-runtime@7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.6.tgz#77b14416015ea93367ca06979710f5000ff34ccb" integrity sha512-8uRHk9ZmRSnWqUgyae249EJZ94b0yAGLBIqzZzl+0iEdbno55Pmlt/32JZsHwXD9k/uZj18Aqqk35wBX4CBTXA== @@ -968,6 +1300,18 @@ babel-plugin-polyfill-regenerator "^0.3.1" semver "^6.3.0" +"@babel/plugin-transform-runtime@^7.18.6": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz#a3df2d7312eea624c7889a2dcd37fd1dfd25b2c6" + integrity sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.19.0" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + semver "^6.3.0" + "@babel/plugin-transform-shorthand-properties@7.18.6", "@babel/plugin-transform-shorthand-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" @@ -983,6 +1327,14 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-skip-transparent-expression-wrappers" "^7.18.6" +"@babel/plugin-transform-spread@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" + integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-transform-sticky-regex@7.18.6", "@babel/plugin-transform-sticky-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" @@ -997,6 +1349,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-typeof-symbol@7.18.6", "@babel/plugin-transform-typeof-symbol@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.6.tgz#486bb39d5a18047358e0d04dc0d2f322f0b92e92" @@ -1004,6 +1363,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-typescript@7.18.6", "@babel/plugin-transform-typescript@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.6.tgz#8f4ade1a9cf253e5cf7c7c20173082c2c08a50a7" @@ -1020,6 +1386,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-unicode-regex@7.18.6", "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" @@ -1028,7 +1401,7 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@7.18.6", "@babel/preset-env@^7.15.6", "@babel/preset-env@^7.18.2": +"@babel/preset-env@7.18.6", "@babel/preset-env@^7.15.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.6.tgz#953422e98a5f66bc56cd0b9074eaea127ec86ace" integrity sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw== @@ -1109,6 +1482,87 @@ core-js-compat "^3.22.1" semver "^6.3.0" +"@babel/preset-env@^7.18.6": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.4.tgz#4c91ce2e1f994f717efb4237891c3ad2d808c94b" + integrity sha512-5QVOTXUdqTCjQuh2GGtdd7YEhoRXBMVGROAtsBeLGIbIz3obCBIfRMT1I3ZKkMgNzwkyCkftDXSSkHxnfVf4qg== + dependencies: + "@babel/compat-data" "^7.19.4" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.19.1" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.19.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.19.4" + "@babel/plugin-transform-classes" "^7.19.0" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.19.4" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.19.0" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.19.4" + babel-plugin-polyfill-corejs2 "^0.3.3" + babel-plugin-polyfill-corejs3 "^0.6.0" + babel-plugin-polyfill-regenerator "^0.4.1" + core-js-compat "^3.25.1" + semver "^6.3.0" + "@babel/preset-modules@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" @@ -1120,7 +1574,7 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@7.18.6", "@babel/preset-react@^7.14.5", "@babel/preset-react@^7.17.12": +"@babel/preset-react@7.18.6", "@babel/preset-react@^7.14.5", "@babel/preset-react@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== @@ -1132,7 +1586,7 @@ "@babel/plugin-transform-react-jsx-development" "^7.18.6" "@babel/plugin-transform-react-pure-annotations" "^7.18.6" -"@babel/preset-typescript@7.18.6", "@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.17.12": +"@babel/preset-typescript@7.18.6", "@babel/preset-typescript@^7.15.0", "@babel/preset-typescript@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== @@ -1141,7 +1595,7 @@ "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-transform-typescript" "^7.18.6" -"@babel/runtime-corejs3@7.18.6", "@babel/runtime-corejs3@^7.18.3": +"@babel/runtime-corejs3@7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.18.6.tgz#6f02c5536911f4b445946a2179554b95c8838635" integrity sha512-cOu5wH2JFBgMjje+a+fz2JNIWU4GzYpl05oSob3UDvBEh6EuIn+TXFHMmBbhSb+k/4HMzgKCQfEEDArAWNF9Cw== @@ -1149,13 +1603,28 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.8.4": +"@babel/runtime-corejs3@^7.18.6": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.19.4.tgz#870dbfd9685b3dad5aeb2d00841bb8b6192e3095" + integrity sha512-HzjQ8+dzdx7dmZy4DQ8KV8aHi/74AjEbBGTFutBmg/pd3dY5/q1sfuOGPTFGEytlQhWoeVXqcK5BwMgIkRkNDQ== + dependencies: + core-js-pure "^3.25.1" + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580" integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.18.6": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.12.7", "@babel/template@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" @@ -1165,7 +1634,16 @@ "@babel/parser" "^7.18.6" "@babel/types" "^7.18.6" -"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.6": +"@babel/template@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.6.tgz#a228562d2f46e89258efa4ddd0416942e2fd671d" integrity sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw== @@ -1181,6 +1659,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.18.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.19.3", "@babel/traverse@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.4.tgz#f117820e18b1e59448a6c1fa9d0ff08f7ac459a8" + integrity sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.4" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.19.4" + "@babel/types" "^7.19.4" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.12.7", "@babel/types@^7.15.6", "@babel/types@^7.18.6", "@babel/types@^7.4.4": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.6.tgz#5d781dd10a3f0c9f1f931bd19de5eb26ec31acf0" @@ -1189,6 +1683,15 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" +"@babel/types@^7.18.10", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.19.3", "@babel/types@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + "@cmfcmf/docusaurus-search-local@^0.10.0": version "0.10.0" resolved "https://registry.yarnpkg.com/@cmfcmf/docusaurus-search-local/-/docusaurus-search-local-0.10.0.tgz#0a77847641ec490f4663666e5eee07416f5a0c63" @@ -1213,7 +1716,12 @@ resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.1.0.tgz#6781cad43fc2e034d012ee44beddf8f93ba21f19" integrity sha512-bh5IskwkkodbvC0FzSg1AxMykfDl95hebEKwxNoq4e5QaGzOXSBgW8+jnMFZ7JU4sTBiB04vZWoUSzNrPboLZA== -"@docsearch/react@3.1.0", "@docsearch/react@^3.1.0": +"@docsearch/css@3.2.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.2.1.tgz#c05d7818b0e43b42f9efa2d82a11c36606b37b27" + integrity sha512-gaP6TxxwQC+K8D6TRx5WULUWKrcbzECOPA2KCVMuI+6C7dNiGUk5yXXzVhc5sld79XKYLnO9DRTI4mjXDYkh+g== + +"@docsearch/react@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.1.0.tgz#da943a64c01ee82b04e53b691806469272f943f7" integrity sha512-bjB6ExnZzf++5B7Tfoi6UXgNwoUnNOfZ1NyvnvPhWgCMy5V/biAtLL4o7owmZSYdAKeFSvZ5Lxm0is4su/dBWg== @@ -1222,29 +1730,39 @@ "@docsearch/css" "3.1.0" algoliasearch "^4.0.0" -"@docusaurus/core@2.0.0-beta.21", "@docusaurus/core@^2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.0.0-beta.21.tgz#50897317b22dbd94b1bf91bb30c2a0fddd15a806" - integrity sha512-qysDMVp1M5UozK3u/qOxsEZsHF7jeBvJDS+5ItMPYmNKvMbNKeYZGA0g6S7F9hRDwjIlEbvo7BaX0UMDcmTAWA== +"@docsearch/react@^3.1.1": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.2.1.tgz#112ad88db07367fa6fd933d67d58421d8d8289aa" + integrity sha512-EzTQ/y82s14IQC5XVestiK/kFFMe2aagoYFuTAIfIb/e+4FU7kSMKonRtLwsCiLQHmjvNQq+HO+33giJ5YVtaQ== + dependencies: + "@algolia/autocomplete-core" "1.7.1" + "@algolia/autocomplete-preset-algolia" "1.7.1" + "@docsearch/css" "3.2.1" + algoliasearch "^4.0.0" + +"@docusaurus/core@2.1.0", "@docusaurus/core@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/core/-/core-2.1.0.tgz#4aedc306f4c4cd2e0491b641bf78941d4b480ab6" + integrity sha512-/ZJ6xmm+VB9Izbn0/s6h6289cbPy2k4iYFwWDhjiLsVqwa/Y0YBBcXvStfaHccudUC3OfP+26hMk7UCjc50J6Q== dependencies: - "@babel/core" "^7.18.2" - "@babel/generator" "^7.18.2" + "@babel/core" "^7.18.6" + "@babel/generator" "^7.18.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-transform-runtime" "^7.18.2" - "@babel/preset-env" "^7.18.2" - "@babel/preset-react" "^7.17.12" - "@babel/preset-typescript" "^7.17.12" - "@babel/runtime" "^7.18.3" - "@babel/runtime-corejs3" "^7.18.3" - "@babel/traverse" "^7.18.2" - "@docusaurus/cssnano-preset" "2.0.0-beta.21" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/mdx-loader" "2.0.0-beta.21" + "@babel/plugin-transform-runtime" "^7.18.6" + "@babel/preset-env" "^7.18.6" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.18.6" + "@babel/runtime" "^7.18.6" + "@babel/runtime-corejs3" "^7.18.6" + "@babel/traverse" "^7.18.8" + "@docusaurus/cssnano-preset" "2.1.0" + "@docusaurus/logger" "2.1.0" + "@docusaurus/mdx-loader" "2.1.0" "@docusaurus/react-loadable" "5.5.2" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-common" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" - "@slorber/static-site-generator-webpack-plugin" "^4.0.4" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-common" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" + "@slorber/static-site-generator-webpack-plugin" "^4.0.7" "@svgr/webpack" "^6.2.1" autoprefixer "^10.4.7" babel-loader "^8.2.5" @@ -1257,10 +1775,10 @@ combine-promises "^1.1.0" commander "^5.1.0" copy-webpack-plugin "^11.0.0" - core-js "^3.22.7" + core-js "^3.23.3" css-loader "^6.7.1" css-minimizer-webpack-plugin "^4.0.0" - cssnano "^5.1.9" + cssnano "^5.1.12" del "^6.1.1" detect-port "^1.3.0" escape-html "^1.0.3" @@ -1273,7 +1791,7 @@ import-fresh "^3.3.0" leven "^3.1.0" lodash "^4.17.21" - mini-css-extract-plugin "^2.6.0" + mini-css-extract-plugin "^2.6.1" postcss "^8.4.14" postcss-loader "^7.0.0" prompts "^2.4.2" @@ -1284,49 +1802,48 @@ react-router "^5.3.3" react-router-config "^5.1.1" react-router-dom "^5.3.3" - remark-admonitions "^1.2.1" rtl-detect "^1.0.4" semver "^7.3.7" serve-handler "^6.1.3" shelljs "^0.8.5" - terser-webpack-plugin "^5.3.1" + terser-webpack-plugin "^5.3.3" tslib "^2.4.0" update-notifier "^5.1.0" url-loader "^4.1.1" wait-on "^6.0.1" - webpack "^5.72.1" + webpack "^5.73.0" webpack-bundle-analyzer "^4.5.0" - webpack-dev-server "^4.9.0" + webpack-dev-server "^4.9.3" webpack-merge "^5.8.0" webpackbar "^5.0.2" -"@docusaurus/cssnano-preset@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.0.0-beta.21.tgz#38113877a5857c3f9d493522085d20909dcec474" - integrity sha512-fhTZrg1vc6zYYZIIMXpe1TnEVGEjqscBo0s1uomSwKjjtMgu7wkzc1KKJYY7BndsSA+fVVkZ+OmL/kAsmK7xxw== +"@docusaurus/cssnano-preset@2.1.0", "@docusaurus/cssnano-preset@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/cssnano-preset/-/cssnano-preset-2.1.0.tgz#5b42107769b7cbc61655496090bc262d7788d6ab" + integrity sha512-pRLewcgGhOies6pzsUROfmPStDRdFw+FgV5sMtLr5+4Luv2rty5+b/eSIMMetqUsmg3A9r9bcxHk9bKAKvx3zQ== dependencies: - cssnano-preset-advanced "^5.3.5" + cssnano-preset-advanced "^5.3.8" postcss "^8.4.14" postcss-sort-media-queries "^4.2.1" tslib "^2.4.0" -"@docusaurus/logger@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.0.0-beta.21.tgz#f6ab4133917965349ae03fd9111a940b24d4fd12" - integrity sha512-HTFp8FsSMrAj7Uxl5p72U+P7rjYU/LRRBazEoJbs9RaqoKEdtZuhv8MYPOCh46K9TekaoquRYqag2o23Qt4ggA== +"@docusaurus/logger@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/logger/-/logger-2.1.0.tgz#86c97e948f578814d3e61fc2b2ad283043cbe87a" + integrity sha512-uuJx2T6hDBg82joFeyobywPjSOIfeq05GfyKGHThVoXuXsu1KAzMDYcjoDxarb9CoHCI/Dor8R2MoL6zII8x1Q== dependencies: chalk "^4.1.2" tslib "^2.4.0" -"@docusaurus/mdx-loader@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.0.0-beta.21.tgz#52af341e21f22be882d2155a7349bea10f5d77a3" - integrity sha512-AI+4obJnpOaBOAYV6df2ux5Y1YJCBS+MhXFf0yhED12sVLJi2vffZgdamYd/d/FwvWDw6QLs/VD2jebd7P50yQ== +"@docusaurus/mdx-loader@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/mdx-loader/-/mdx-loader-2.1.0.tgz#3fca9576cc73a22f8e7d9941985590b9e47a8526" + integrity sha512-i97hi7hbQjsD3/8OSFhLy7dbKGH8ryjEzOfyhQIn2CFBYOY3ko0vMVEf3IY9nD3Ld7amYzsZ8153RPkcnXA+Lg== dependencies: - "@babel/parser" "^7.18.3" - "@babel/traverse" "^7.18.2" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" + "@babel/parser" "^7.18.8" + "@babel/traverse" "^7.18.8" + "@docusaurus/logger" "2.1.0" + "@docusaurus/utils" "2.1.0" "@mdx-js/mdx" "^1.6.22" escape-html "^1.0.3" file-loader "^6.2.0" @@ -1336,151 +1853,162 @@ remark-emoji "^2.2.0" stringify-object "^3.3.0" tslib "^2.4.0" + unified "^9.2.2" unist-util-visit "^2.0.3" url-loader "^4.1.1" - webpack "^5.72.1" + webpack "^5.73.0" -"@docusaurus/module-type-aliases@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.0.0-beta.21.tgz#345f1c1a99407775d1d3ffc1a90c2df93d50a9b8" - integrity sha512-gRkWICgQZiqSJgrwRKWjXm5gAB+9IcfYdUbCG0PRPP/G8sNs9zBIOY4uT4Z5ox2CWFEm44U3RTTxj7BiLVMBXw== +"@docusaurus/module-type-aliases@2.1.0", "@docusaurus/module-type-aliases@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/module-type-aliases/-/module-type-aliases-2.1.0.tgz#322f8fd5b436af2154c0dddfa173435730e66261" + integrity sha512-Z8WZaK5cis3xEtyfOT817u9xgGUauT0PuuVo85ysnFRX8n7qLN1lTPCkC+aCmFm/UcV8h/W5T4NtIsst94UntQ== dependencies: - "@docusaurus/types" "2.0.0-beta.21" + "@docusaurus/react-loadable" "5.5.2" + "@docusaurus/types" "2.1.0" + "@types/history" "^4.7.11" "@types/react" "*" "@types/react-router-config" "*" "@types/react-router-dom" "*" react-helmet-async "*" + react-loadable "npm:@docusaurus/react-loadable@5.5.2" -"@docusaurus/plugin-client-redirects@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.21.tgz#bf0c3e94d89c7bd15933dacdfefc17fec22c5d76" - integrity sha512-4xzrti0au7SaQT/cxr+FM9b+R5gfOSFODwQJ2QeTXbkdiz1+9DV3bp8nB/2CmzZ9ApY5lsueXNpa4n7+UAngrA== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-common" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" +"@docusaurus/plugin-client-redirects@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.1.0.tgz#4141040552faad48aefc5bc8f3827c3c4eba1ab8" + integrity sha512-3PhzwHSyZWqBAFPJuLJE3dZVuKWQEj9ReQP85Z3/2hpnQoVNBgAqc+64FIko0FvvK1iluLeasO7NWGyuATngvw== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/logger" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-common" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" eta "^1.12.3" fs-extra "^10.1.0" lodash "^4.17.21" tslib "^2.4.0" -"@docusaurus/plugin-content-blog@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.21.tgz#86211deeea901ddcd77ca387778e121e93ee8d01" - integrity sha512-IP21yJViP3oBmgsWBU5LhrG1MZXV4mYCQSoCAboimESmy1Z11RCNP2tXaqizE3iTmXOwZZL+SNBk06ajKCEzWg== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/mdx-loader" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-common" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" - cheerio "^1.0.0-rc.11" +"@docusaurus/plugin-content-blog@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.1.0.tgz#32b1a7cd4b0026f4a76fce4edc5cfdd0edb1ec42" + integrity sha512-xEp6jlu92HMNUmyRBEeJ4mCW1s77aAEQO4Keez94cUY/Ap7G/r0Awa6xSLff7HL0Fjg8KK1bEbDy7q9voIavdg== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/logger" "2.1.0" + "@docusaurus/mdx-loader" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-common" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" + cheerio "^1.0.0-rc.12" feed "^4.2.2" fs-extra "^10.1.0" lodash "^4.17.21" reading-time "^1.5.0" - remark-admonitions "^1.2.1" tslib "^2.4.0" unist-util-visit "^2.0.3" utility-types "^3.10.0" - webpack "^5.72.1" - -"@docusaurus/plugin-content-docs@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.0.0-beta.21.tgz#b3171fa9aed99e367b6eb7111187bd0e3dcf2949" - integrity sha512-aa4vrzJy4xRy81wNskyhE3wzRf3AgcESZ1nfKh8xgHUkT7fDTZ1UWlg50Jb3LBCQFFyQG2XQB9N6llskI/KUnw== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/mdx-loader" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" + webpack "^5.73.0" + +"@docusaurus/plugin-content-docs@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-docs/-/plugin-content-docs-2.1.0.tgz#3fcdf258c13dde27268ce7108a102b74ca4c279b" + integrity sha512-Rup5pqXrXlKGIC4VgwvioIhGWF7E/NNSlxv+JAxRYpik8VKlWsk9ysrdHIlpX+KJUCO9irnY21kQh2814mlp/Q== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/logger" "2.1.0" + "@docusaurus/mdx-loader" "2.1.0" + "@docusaurus/module-type-aliases" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" + "@types/react-router-config" "^5.0.6" combine-promises "^1.1.0" fs-extra "^10.1.0" import-fresh "^3.3.0" js-yaml "^4.1.0" lodash "^4.17.21" - remark-admonitions "^1.2.1" tslib "^2.4.0" utility-types "^3.10.0" - webpack "^5.72.1" + webpack "^5.73.0" -"@docusaurus/plugin-content-pages@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.0.0-beta.21.tgz#df6b4c5c4cde8a0ea491a30002e84941ca7bf0cf" - integrity sha512-DmXOXjqNI+7X5hISzCvt54QIK6XBugu2MOxjxzuqI7q92Lk/EVdraEj5mthlH8IaEH/VlpWYJ1O9TzLqX5vH2g== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/mdx-loader" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" +"@docusaurus/plugin-content-pages@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-content-pages/-/plugin-content-pages-2.1.0.tgz#714d24f71d49dbfed888f50c15e975c2154c3ce8" + integrity sha512-SwZdDZRlObHNKXTnFo7W2aF6U5ZqNVI55Nw2GCBryL7oKQSLeI0lsrMlMXdzn+fS7OuBTd3MJBO1T4Zpz0i/+g== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/mdx-loader" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" fs-extra "^10.1.0" - remark-admonitions "^1.2.1" tslib "^2.4.0" - webpack "^5.72.1" + webpack "^5.73.0" -"@docusaurus/plugin-debug@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.0.0-beta.21.tgz#dfa212fd90fe2f54439aacdc8c143e8ce96b0d27" - integrity sha512-P54J4q4ecsyWW0Jy4zbimSIHna999AfbxpXGmF1IjyHrjoA3PtuakV1Ai51XrGEAaIq9q6qMQkEhbUd3CffGAw== +"@docusaurus/plugin-debug@2.1.0", "@docusaurus/plugin-debug@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-debug/-/plugin-debug-2.1.0.tgz#b3145affb40e25cf342174638952a5928ddaf7dc" + integrity sha512-8wsDq3OIfiy6440KLlp/qT5uk+WRHQXIXklNHEeZcar+Of0TZxCNe2FBpv+bzb/0qcdP45ia5i5WmR5OjN6DPw== dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" + "@docusaurus/core" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils" "2.1.0" fs-extra "^10.1.0" react-json-view "^1.21.3" tslib "^2.4.0" -"@docusaurus/plugin-google-analytics@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.0.0-beta.21.tgz#5475c58fb23603badf41d84298569f6c46b4e6b2" - integrity sha512-+5MS0PeGaJRgPuNZlbd/WMdQSpOACaxEz7A81HAxm6kE+tIASTW3l8jgj1eWFy/PGPzaLnQrEjxI1McAfnYmQw== +"@docusaurus/plugin-google-analytics@2.1.0", "@docusaurus/plugin-google-analytics@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-2.1.0.tgz#c9a7269817b38e43484d38fad9996e39aac4196c" + integrity sha512-4cgeqIly/wcFVbbWP03y1QJJBgH8W+Bv6AVbWnsXNOZa1yB3AO6hf3ZdeQH9x20v9T2pREogVgAH0rSoVnNsgg== dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" + "@docusaurus/core" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" tslib "^2.4.0" -"@docusaurus/plugin-google-gtag@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.0.0-beta.21.tgz#a4a101089994a7103c1cc7cddb15170427b185d6" - integrity sha512-4zxKZOnf0rfh6myXLG7a6YZfQcxYDMBsWqANEjCX77H5gPdK+GHZuDrxK6sjFvRBv4liYCrNjo7HJ4DpPoT0zA== +"@docusaurus/plugin-google-gtag@2.1.0", "@docusaurus/plugin-google-gtag@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-2.1.0.tgz#e4f351dcd98b933538d55bb742650a2a36ca9a32" + integrity sha512-/3aDlv2dMoCeiX2e+DTGvvrdTA+v3cKQV3DbmfsF4ENhvc5nKV23nth04Z3Vq0Ci1ui6Sn80TkhGk/tiCMW2AA== dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" + "@docusaurus/core" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" tslib "^2.4.0" -"@docusaurus/plugin-sitemap@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.0.0-beta.21.tgz#8bfa695eada2ec95c9376a884641237ffca5dd3d" - integrity sha512-/ynWbcXZXcYZ6sT2X6vAJbnfqcPxwdGEybd0rcRZi4gBHq6adMofYI25AqELmnbBDxt0If+vlAeUHFRG5ueP7Q== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-common" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" +"@docusaurus/plugin-sitemap@2.1.0", "@docusaurus/plugin-sitemap@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/plugin-sitemap/-/plugin-sitemap-2.1.0.tgz#b316bb9a42a1717845e26bd4e2d3071748a54b47" + integrity sha512-2Y6Br8drlrZ/jN9MwMBl0aoi9GAjpfyfMBYpaQZXimbK+e9VjYnujXlvQ4SxtM60ASDgtHIAzfVFBkSR/MwRUw== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/logger" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-common" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" fs-extra "^10.1.0" sitemap "^7.1.1" tslib "^2.4.0" -"@docusaurus/preset-classic@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.0.0-beta.21.tgz#1362d8650ebed22633db411caaba80075f7c86ce" - integrity sha512-KvBnIUu7y69pNTJ9UhX6SdNlK6prR//J3L4rhN897tb8xx04xHHILlPXko2Il+C3Xzgh3OCgyvkoz9K6YlFTDw== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/plugin-content-blog" "2.0.0-beta.21" - "@docusaurus/plugin-content-docs" "2.0.0-beta.21" - "@docusaurus/plugin-content-pages" "2.0.0-beta.21" - "@docusaurus/plugin-debug" "2.0.0-beta.21" - "@docusaurus/plugin-google-analytics" "2.0.0-beta.21" - "@docusaurus/plugin-google-gtag" "2.0.0-beta.21" - "@docusaurus/plugin-sitemap" "2.0.0-beta.21" - "@docusaurus/theme-classic" "2.0.0-beta.21" - "@docusaurus/theme-common" "2.0.0-beta.21" - "@docusaurus/theme-search-algolia" "2.0.0-beta.21" +"@docusaurus/preset-classic@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/preset-classic/-/preset-classic-2.1.0.tgz#45b23c8ec10c96ded9ece128fac3a39b10bcbc56" + integrity sha512-NQMnaq974K4BcSMXFSJBQ5itniw6RSyW+VT+6i90kGZzTwiuKZmsp0r9lC6BYAvvVMQUNJQwrETmlu7y2XKW7w== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/plugin-content-blog" "2.1.0" + "@docusaurus/plugin-content-docs" "2.1.0" + "@docusaurus/plugin-content-pages" "2.1.0" + "@docusaurus/plugin-debug" "2.1.0" + "@docusaurus/plugin-google-analytics" "2.1.0" + "@docusaurus/plugin-google-gtag" "2.1.0" + "@docusaurus/plugin-sitemap" "2.1.0" + "@docusaurus/theme-classic" "2.1.0" + "@docusaurus/theme-common" "2.1.0" + "@docusaurus/theme-search-algolia" "2.1.0" + "@docusaurus/types" "2.1.0" "@docusaurus/react-loadable@5.5.2", "react-loadable@npm:@docusaurus/react-loadable@5.5.2": version "5.5.2" @@ -1490,115 +2018,125 @@ "@types/react" "*" prop-types "^15.6.2" -"@docusaurus/theme-classic@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.0.0-beta.21.tgz#6df5b9ea2d389dafb6f59badeabb3eda060b5017" - integrity sha512-Ge0WNdTefD0VDQfaIMRRWa8tWMG9+8/OlBRd5MK88/TZfqdBq7b/gnCSaalQlvZwwkj6notkKhHx72+MKwWUJA== - dependencies: - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/plugin-content-blog" "2.0.0-beta.21" - "@docusaurus/plugin-content-docs" "2.0.0-beta.21" - "@docusaurus/plugin-content-pages" "2.0.0-beta.21" - "@docusaurus/theme-common" "2.0.0-beta.21" - "@docusaurus/theme-translations" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-common" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" +"@docusaurus/theme-classic@2.1.0", "@docusaurus/theme-classic@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-classic/-/theme-classic-2.1.0.tgz#d957a907ea8dd035c1cf911d0fbe91d8f24aef3f" + integrity sha512-xn8ZfNMsf7gaSy9+ClFnUu71o7oKgMo5noYSS1hy3svNifRTkrBp6+MReLDsmIaj3mLf2e7+JCBYKBFbaGzQng== + dependencies: + "@docusaurus/core" "2.1.0" + "@docusaurus/mdx-loader" "2.1.0" + "@docusaurus/module-type-aliases" "2.1.0" + "@docusaurus/plugin-content-blog" "2.1.0" + "@docusaurus/plugin-content-docs" "2.1.0" + "@docusaurus/plugin-content-pages" "2.1.0" + "@docusaurus/theme-common" "2.1.0" + "@docusaurus/theme-translations" "2.1.0" + "@docusaurus/types" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-common" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" "@mdx-js/react" "^1.6.22" - clsx "^1.1.1" + clsx "^1.2.1" copy-text-to-clipboard "^3.0.1" - infima "0.2.0-alpha.39" + infima "0.2.0-alpha.42" lodash "^4.17.21" nprogress "^0.2.0" postcss "^8.4.14" - prism-react-renderer "^1.3.3" + prism-react-renderer "^1.3.5" prismjs "^1.28.0" react-router-dom "^5.3.3" rtlcss "^3.5.0" tslib "^2.4.0" + utility-types "^3.10.0" -"@docusaurus/theme-common@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.0.0-beta.21.tgz#508478251982d01655ef505ccb2420db38623db8" - integrity sha512-fTKoTLRfjuFG6c3iwnVjIIOensxWMgdBKLfyE5iih3Lq7tQgkE7NyTGG9BKLrnTJ7cAD2UXdXM9xbB7tBf1qzg== - dependencies: - "@docusaurus/module-type-aliases" "2.0.0-beta.21" - "@docusaurus/plugin-content-blog" "2.0.0-beta.21" - "@docusaurus/plugin-content-docs" "2.0.0-beta.21" - "@docusaurus/plugin-content-pages" "2.0.0-beta.21" - clsx "^1.1.1" +"@docusaurus/theme-common@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-common/-/theme-common-2.1.0.tgz#dff4d5d1e29efc06125dc06f7b259f689bb3f24d" + integrity sha512-vT1otpVPbKux90YpZUnvknsn5zvpLf+AW1W0EDcpE9up4cDrPqfsh0QoxGHFJnobE2/qftsBFC19BneN4BH8Ag== + dependencies: + "@docusaurus/mdx-loader" "2.1.0" + "@docusaurus/module-type-aliases" "2.1.0" + "@docusaurus/plugin-content-blog" "2.1.0" + "@docusaurus/plugin-content-docs" "2.1.0" + "@docusaurus/plugin-content-pages" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-config" "*" + clsx "^1.2.1" parse-numeric-range "^1.3.0" - prism-react-renderer "^1.3.3" + prism-react-renderer "^1.3.5" tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-search-algolia@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.0.0-beta.21.tgz#2891f11372e2542e4e1426c3100b72c2d30d4d68" - integrity sha512-T1jKT8MVSSfnztSqeebUOpWHPoHKtwDXtKYE0xC99JWoZ+mMfv8AFhVSoSddn54jLJjV36mxg841eHQIySMCpQ== - dependencies: - "@docsearch/react" "^3.1.0" - "@docusaurus/core" "2.0.0-beta.21" - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/plugin-content-docs" "2.0.0-beta.21" - "@docusaurus/theme-common" "2.0.0-beta.21" - "@docusaurus/theme-translations" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" - "@docusaurus/utils-validation" "2.0.0-beta.21" +"@docusaurus/theme-search-algolia@2.1.0", "@docusaurus/theme-search-algolia@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-search-algolia/-/theme-search-algolia-2.1.0.tgz#e7cdf64b6f7a15b07c6dcf652fd308cfdaabb0ee" + integrity sha512-rNBvi35VvENhucslEeVPOtbAzBdZY/9j55gdsweGV5bYoAXy4mHB6zTGjealcB4pJ6lJY4a5g75fXXMOlUqPfg== + dependencies: + "@docsearch/react" "^3.1.1" + "@docusaurus/core" "2.1.0" + "@docusaurus/logger" "2.1.0" + "@docusaurus/plugin-content-docs" "2.1.0" + "@docusaurus/theme-common" "2.1.0" + "@docusaurus/theme-translations" "2.1.0" + "@docusaurus/utils" "2.1.0" + "@docusaurus/utils-validation" "2.1.0" algoliasearch "^4.13.1" - algoliasearch-helper "^3.8.2" - clsx "^1.1.1" + algoliasearch-helper "^3.10.0" + clsx "^1.2.1" eta "^1.12.3" fs-extra "^10.1.0" lodash "^4.17.21" tslib "^2.4.0" utility-types "^3.10.0" -"@docusaurus/theme-translations@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.0.0-beta.21.tgz#5da60ffc58de256b96316c5e0fe2733c1e83f22c" - integrity sha512-dLVT9OIIBs6MpzMb1bAy+C0DPJK3e3DNctG+ES0EP45gzEqQxzs4IsghpT+QDaOsuhNnAlosgJpFWX3rqxF9xA== +"@docusaurus/theme-translations@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/theme-translations/-/theme-translations-2.1.0.tgz#ce9a2955afd49bff364cfdfd4492b226f6dd3b6e" + integrity sha512-07n2akf2nqWvtJeMy3A+7oSGMuu5F673AovXVwY0aGAux1afzGCiqIFlYW3EP0CujvDJAEFSQi/Tetfh+95JNg== dependencies: fs-extra "^10.1.0" tslib "^2.4.0" -"@docusaurus/types@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.0.0-beta.21.tgz#36659c6c012663040dcd4cbc97b5d7a555dae229" - integrity sha512-/GH6Npmq81eQfMC/ikS00QSv9jNyO1RXEpNSx5GLA3sFX8Iib26g2YI2zqNplM8nyxzZ2jVBuvUoeODTIbTchQ== +"@docusaurus/types@2.1.0", "@docusaurus/types@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-2.1.0.tgz#01e13cd9adb268fffe87b49eb90302d5dc3edd6b" + integrity sha512-BS1ebpJZnGG6esKqsjtEC9U9qSaPylPwlO7cQ1GaIE7J/kMZI3FITnNn0otXXu7c7ZTqhb6+8dOrG6fZn6fqzQ== dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" commander "^5.1.0" - history "^4.9.0" joi "^17.6.0" react-helmet-async "^1.3.0" utility-types "^3.10.0" - webpack "^5.72.1" + webpack "^5.73.0" webpack-merge "^5.8.0" -"@docusaurus/utils-common@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.0.0-beta.21.tgz#81e86ed04ad62b75e9ba6a5e7689dc23d5f36a0a" - integrity sha512-5w+6KQuJb6pUR2M8xyVuTMvO5NFQm/p8TOTDFTx60wt3p0P1rRX00v6FYsD4PK6pgmuoKjt2+Ls8dtSXc4qFpQ== +"@docusaurus/utils-common@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-common/-/utils-common-2.1.0.tgz#248434751096f8c6c644ed65eed2a5a070a227f8" + integrity sha512-F2vgmt4yRFgRQR2vyEFGTWeyAdmgKbtmu3sjHObF0tjjx/pN0Iw/c6eCopaH34E6tc9nO0nvp01pwW+/86d1fg== dependencies: tslib "^2.4.0" -"@docusaurus/utils-validation@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.0.0-beta.21.tgz#10169661be5f8a233f4c12202ee5802ccb77400f" - integrity sha512-6NG1FHTRjv1MFzqW//292z7uCs77vntpWEbZBHk3n67aB1HoMn5SOwjLPtRDjbCgn6HCHFmdiJr6euCbjhYolg== +"@docusaurus/utils-validation@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils-validation/-/utils-validation-2.1.0.tgz#c8cf1d8454d924d9a564fefa86436268f43308e3" + integrity sha512-AMJzWYKL3b7FLltKtDXNLO9Y649V2BXvrnRdnW2AA+PpBnYV78zKLSCz135cuWwRj1ajNtP4onbXdlnyvCijGQ== dependencies: - "@docusaurus/logger" "2.0.0-beta.21" - "@docusaurus/utils" "2.0.0-beta.21" + "@docusaurus/logger" "2.1.0" + "@docusaurus/utils" "2.1.0" joi "^17.6.0" js-yaml "^4.1.0" tslib "^2.4.0" -"@docusaurus/utils@2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.0.0-beta.21.tgz#8fc4499c4cfedd29805025d930f8008cad255044" - integrity sha512-M/BrVCDmmUPZLxtiStBgzpQ4I5hqkggcpnQmEN+LbvbohjbtVnnnZQ0vptIziv1w8jry/woY+ePsyOO7O/yeLQ== +"@docusaurus/utils@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@docusaurus/utils/-/utils-2.1.0.tgz#b77b45b22e61eb6c2dcad8a7e96f6db0409b655f" + integrity sha512-fPvrfmAuC54n8MjZuG4IysaMdmvN5A/qr7iFLbSGSyDrsbP4fnui6KdZZIa/YOLIPLec8vjZ8RIITJqF18mx4A== dependencies: - "@docusaurus/logger" "2.0.0-beta.21" + "@docusaurus/logger" "2.1.0" "@svgr/webpack" "^6.2.1" file-loader "^6.2.0" fs-extra "^10.1.0" @@ -1612,7 +2150,7 @@ shelljs "^0.8.5" tslib "^2.4.0" url-loader "^4.1.1" - webpack "^5.72.1" + webpack "^5.73.0" "@hapi/hoek@^9.0.0": version "9.3.0" @@ -1634,7 +2172,7 @@ "@jridgewell/set-array" "^1.0.0" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/gen-mapping@^0.3.0": +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== @@ -1643,6 +2181,11 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/resolve-uri@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + "@jridgewell/resolve-uri@^3.0.3": version "3.0.8" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" @@ -1661,11 +2204,19 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@^0.3.14": + version "0.3.16" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.16.tgz#a7982f16c18cae02be36274365433e5b49d7b23f" + integrity sha512-LCQ+NeThyJ4k1W2d+vIKdxuSt9R3pQSZ4P92m7EakaYuXcVWbHuT5bjNcqLd4Rdgi6xYWYDvBJZJLZSLanjDcA== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": version "0.3.14" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" @@ -1788,7 +2339,7 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@slorber/static-site-generator-webpack-plugin@^4.0.4": +"@slorber/static-site-generator-webpack-plugin@^4.0.7": version "4.0.7" resolved "https://registry.yarnpkg.com/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz#fc1678bddefab014e2145cbe25b3ce4e1cfc36f3" integrity sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA== @@ -2064,7 +2615,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-router-config@*": +"@types/react-router-config@*", "@types/react-router-config@^5.0.6": version "5.0.6" resolved "https://registry.yarnpkg.com/@types/react-router-config/-/react-router-config-5.0.6.tgz#87c5c57e72d241db900d9734512c50ccec062451" integrity sha512-db1mx37a1EJDf1XeX8jJN7R3PZABmJQXR8r28yUjVMFSjkmnQo6X6pOEEmNl+Tp2gYQOGPdYbFIipBtdElZ3Yg== @@ -2299,11 +2850,16 @@ acorn-walk@^8.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^8.0.4, acorn@^8.4.1, acorn@^8.5.0: +acorn@^8.0.4, acorn@^8.5.0: version "8.7.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== +acorn@^8.7.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + address@^1.0.1, address@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/address/-/address-1.2.0.tgz#d352a62c92fee90f89a693eccd2a8b2139ab02d9" @@ -2356,10 +2912,10 @@ ajv@^8.0.0, ajv@^8.8.0: require-from-string "^2.0.2" uri-js "^4.2.2" -algoliasearch-helper@^3.8.2: - version "3.10.0" - resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.10.0.tgz#59a0f645dd3c7e55cf01faa568d1af50c49d36f6" - integrity sha512-4E4od8qWWDMVvQ3jaRX6Oks/k35ywD011wAA4LbYMMjOtaZV6VWaTjRr4iN2bdaXP2o1BP7SLFMBf3wvnHmd8Q== +algoliasearch-helper@^3.10.0: + version "3.11.1" + resolved "https://registry.yarnpkg.com/algoliasearch-helper/-/algoliasearch-helper-3.11.1.tgz#d83ab7f1a2a374440686ef7a144b3c288b01188a" + integrity sha512-mvsPN3eK4E0bZG0/WlWJjeqe/bUD2KOEVOl0GyL/TGXn6wcpZU8NOuztGHCUKXkyg5gq6YzUakVTmnmSSO5Yiw== dependencies: "@algolia/events" "^4.0.1" @@ -2541,6 +3097,15 @@ babel-plugin-polyfill-corejs2@^0.3.1: "@babel/helper-define-polyfill-provider" "^0.3.1" semver "^6.1.1" +babel-plugin-polyfill-corejs2@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122" + integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.3" + semver "^6.1.1" + babel-plugin-polyfill-corejs3@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" @@ -2549,6 +3114,14 @@ babel-plugin-polyfill-corejs3@^0.5.2: "@babel/helper-define-polyfill-provider" "^0.3.1" core-js-compat "^3.21.0" +babel-plugin-polyfill-corejs3@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a" + integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + core-js-compat "^3.25.1" + babel-plugin-polyfill-regenerator@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" @@ -2556,6 +3129,13 @@ babel-plugin-polyfill-regenerator@^0.3.1: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" +babel-plugin-polyfill-regenerator@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747" + integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.3" + bail@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" @@ -2672,6 +3252,16 @@ browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4 node-releases "^2.0.5" update-browserslist-db "^1.0.0" +browserslist@^4.21.3, browserslist@^4.21.4: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -2746,7 +3336,12 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001358: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz#a1c1cbe1c2da9e689638813618b4219acbd4925e" integrity sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw== -ccount@^1.0.0, ccount@^1.0.3: +caniuse-lite@^1.0.30001400: + version "1.0.30001418" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz#5f459215192a024c99e3e3a53aac310fc7cf24e6" + integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg== + +ccount@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== @@ -2795,7 +3390,7 @@ cheerio-select@^2.1.0: domhandler "^5.0.3" domutils "^3.0.1" -cheerio@^1.0.0-rc.11: +cheerio@^1.0.0-rc.12: version "1.0.0-rc.12" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== @@ -2899,6 +3494,11 @@ clsx@^1.1.1: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +clsx@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + collapse-white-space@^1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" @@ -3015,6 +3615,11 @@ connect-history-api-fallback@^1.6.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + consola@^2.15.3: version "2.15.3" resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" @@ -3079,16 +3684,33 @@ core-js-compat@^3.21.0, core-js-compat@^3.22.1: browserslist "^4.21.0" semver "7.0.0" +core-js-compat@^3.25.1: + version "3.25.5" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.5.tgz#0016e8158c904f7b059486639e6e82116eafa7d9" + integrity sha512-ovcyhs2DEBUIE0MGEKHP4olCUW/XYte3Vroyxuh38rD1wAO4dHohsovUC4eAOuzFxE6b+RXvBU3UZ9o0YhUTkA== + dependencies: + browserslist "^4.21.4" + core-js-pure@^3.20.2: version "3.23.3" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.3.tgz#bcd02d3d8ec68ad871ef50d5ccbb248ddb54f401" integrity sha512-XpoouuqIj4P+GWtdyV8ZO3/u4KftkeDVMfvp+308eGMhCrA3lVDSmAxO0c6GGOcmgVlaKDrgWVMo49h2ab/TDA== -core-js@3.23.3, core-js@^3.22.7: +core-js-pure@^3.25.1: + version "3.25.5" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.25.5.tgz#79716ba54240c6aa9ceba6eee08cf79471ba184d" + integrity sha512-oml3M22pHM+igfWHDfdLVq2ShWmjM2V4L+dQEBs0DWVIqEm9WHCwGAlZ6BmyBQGy5sFrJmcx+856D9lVKyGWYg== + +core-js@3.23.3: version "3.23.3" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.3.tgz#3b977612b15da6da0c9cc4aec487e8d24f371112" integrity sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q== +core-js@^3.23.3: + version "3.25.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.5.tgz#e86f651a2ca8a0237a5f064c2fe56cef89646e27" + integrity sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw== + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -3208,7 +3830,7 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-advanced@5.3.8, cssnano-preset-advanced@^5.3.5: +cssnano-preset-advanced@5.3.8, cssnano-preset-advanced@^5.3.8: version "5.3.8" resolved "https://registry.yarnpkg.com/cssnano-preset-advanced/-/cssnano-preset-advanced-5.3.8.tgz#027b1d05ef896d908178c483f0ec4190cb50ef9a" integrity sha512-xUlLLnEB1LjpEik+zgRNlk8Y/koBPPtONZjp7JKbXigeAmCrFvq9H0pXW5jJV45bQWAlmJ0sKy+IMr0XxLYQZg== @@ -3260,7 +3882,7 @@ cssnano-utils@^3.1.0: resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano@5.1.12, cssnano@^5.1.8, cssnano@^5.1.9: +cssnano@5.1.12, cssnano@^5.1.8: version "5.1.12" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.12.tgz#bcd0b64d6be8692de79332c501daa7ece969816c" integrity sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ== @@ -3269,6 +3891,15 @@ cssnano@5.1.12, cssnano@^5.1.8, cssnano@^5.1.9: lilconfig "^2.0.3" yaml "^1.10.2" +cssnano@^5.1.12: + version "5.1.13" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.13.tgz#83d0926e72955332dc4802a7070296e6258efc0a" + integrity sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ== + dependencies: + cssnano-preset-default "^5.2.12" + lilconfig "^2.0.3" + yaml "^1.10.2" + csso@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" @@ -3527,6 +4158,11 @@ electron-to-chromium@^1.4.164: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.171.tgz#2beb4e03744ef24a90580074edb87c06fbc86ed7" integrity sha512-DmVNWJlGfCYtdgyxp24Aznd7Ou6a+XBSawxODh4lc39OHdh1a49Sitw8oxwMqzzm8n9m2P93OelvIF09SOTNwA== +electron-to-chromium@^1.4.251: + version "1.4.277" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.277.tgz#6dc3d9724a0a19b7ab155bf8e37967357a081dc5" + integrity sha512-Ej4VyUfGdVY5D2J5WHAVNqrEFBKgeNcX7p/bBQU4x/VKwvnyEvGd62NEkIK3lykLEe9Cg4MCcoWAa+u97o0u/A== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -3559,10 +4195,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.9.3: - version "5.9.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" - integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== +enhanced-resolve@^5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" + integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -4187,17 +4823,6 @@ hast-to-hyperscript@^9.0.0: unist-util-is "^4.0.0" web-namespaces "^1.0.0" -hast-util-from-parse5@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz#3089dc0ee2ccf6ec8bc416919b51a54a589e097c" - integrity sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA== - dependencies: - ccount "^1.0.3" - hastscript "^5.0.0" - property-information "^5.0.0" - web-namespaces "^1.1.2" - xtend "^4.0.1" - hast-util-from-parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" @@ -4242,16 +4867,6 @@ hast-util-to-parse5@^6.0.0: xtend "^4.0.0" zwitch "^1.0.0" -hastscript@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-5.1.2.tgz#bde2c2e56d04c62dd24e8c5df288d050a355fb8a" - integrity sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ== - dependencies: - comma-separated-tokens "^1.0.0" - hast-util-parse-selector "^2.0.0" - property-information "^5.0.0" - space-separated-tokens "^1.0.0" - hastscript@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" @@ -4474,10 +5089,10 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -infima@0.2.0-alpha.39: - version "0.2.0-alpha.39" - resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.39.tgz#054b13ac44f3e9a42bc083988f1a1586add2f59c" - integrity sha512-UyYiwD3nwHakGhuOUfpe3baJ8gkiPpRVx4a4sE/Ag+932+Y6swtLsdPoRR8ezhwqGnduzxmFkjumV9roz6QoLw== +infima@0.2.0-alpha.42: + version "0.2.0-alpha.42" + resolved "https://registry.yarnpkg.com/infima/-/infima-0.2.0-alpha.42.tgz#f6e86a655ad40877c6b4d11b2ede681eb5470aa5" + integrity sha512-ift8OXNbQQwtbIt6z16KnSWP7uJ/SysSMFI4F87MNRTicypfl4Pv3E2OGVv6N3nSZFJvA8imYulCBS64iyHYww== inflight@^1.0.4: version "1.0.6" @@ -5114,7 +5729,7 @@ mini-create-react-context@^0.4.0: "@babel/runtime" "^7.12.1" tiny-warning "^1.0.3" -mini-css-extract-plugin@^2.6.0: +mini-css-extract-plugin@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz#9a1251d15f2035c342d99a468ab9da7a0451b71e" integrity sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg== @@ -5220,6 +5835,11 @@ node-releases@^2.0.5: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -5456,11 +6076,6 @@ parse5-htmlparser2-tree-adapter@^7.0.0: domhandler "^5.0.2" parse5 "^7.0.0" -parse5@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" - integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== - parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" @@ -5875,11 +6490,6 @@ pretty-time@^1.1.0: resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-1.1.0.tgz#ffb7429afabb8535c346a34e41873adf3d74dd0e" integrity sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA== -prism-react-renderer@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.3.tgz#9b5a4211a6756eee3c96fee9a05733abc0b0805c" - integrity sha512-Viur/7tBTCH2HmYzwCHmt2rEFn+rdIWNIINXyg0StiISbDiIhHKhrFuEK8eMkKgvsIYSjgGqy/hNyucHp6FpoQ== - prism-react-renderer@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" @@ -6280,29 +6890,11 @@ regjsparser@^0.8.2: dependencies: jsesc "~0.5.0" -rehype-parse@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-6.0.2.tgz#aeb3fdd68085f9f796f1d3137ae2b85a98406964" - integrity sha512-0S3CpvpTAgGmnz8kiCyFLGuW5yA4OQhyNTm/nwPopZ7+PI11WnGl1TTWTGv/2hPEe/g2jRLlhVVSsoDH8waRug== - dependencies: - hast-util-from-parse5 "^5.0.0" - parse5 "^5.0.0" - xtend "^4.0.0" - relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== -remark-admonitions@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/remark-admonitions/-/remark-admonitions-1.2.1.tgz#87caa1a442aa7b4c0cafa04798ed58a342307870" - integrity sha512-Ji6p68VDvD+H1oS95Fdx9Ar5WA2wcDA4kwrrhVU7fGctC6+d3uiMICu7w7/2Xld+lnU7/gi+432+rRbup5S8ow== - dependencies: - rehype-parse "^6.0.2" - unified "^8.4.2" - unist-util-visit "^2.0.1" - remark-emoji@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/remark-emoji/-/remark-emoji-2.2.0.tgz#1c702090a1525da5b80e15a8f963ef2c8236cac7" @@ -6548,6 +7140,13 @@ selfsigned@^2.0.1: dependencies: node-forge "^1" +selfsigned@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.1.1.tgz#18a7613d714c0cd3385c48af0075abf3f266af61" + integrity sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ== + dependencies: + node-forge "^1" + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -6980,7 +7579,7 @@ tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.1: +terser-webpack-plugin@^5.1.3: version "5.3.3" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz#8033db876dd5875487213e87c627bca323e5ed90" integrity sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ== @@ -6991,6 +7590,17 @@ terser-webpack-plugin@^5.1.3, terser-webpack-plugin@^5.3.1: serialize-javascript "^6.0.0" terser "^5.7.2" +terser-webpack-plugin@^5.3.3: + version "5.3.6" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" + integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.14" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.14.1" + terser@^5.10.0, terser@^5.7.2: version "5.14.1" resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.1.tgz#7c95eec36436cb11cf1902cc79ac564741d19eca" @@ -7001,6 +7611,16 @@ terser@^5.10.0, terser@^5.7.2: commander "^2.20.0" source-map-support "~0.5.20" +terser@^5.14.1: + version "5.15.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.1.tgz#8561af6e0fd6d839669c73b92bdd5777d870ed6c" + integrity sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -7151,13 +7771,14 @@ unified@9.2.0: trough "^1.0.0" vfile "^4.0.0" -unified@^8.4.2: - version "8.4.2" - resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.2.tgz#13ad58b4a437faa2751a4a4c6a16f680c500fff1" - integrity sha512-JCrmN13jI4+h9UAyKEoGcDZV+i1E7BLFuG7OsaDvTXI5P0qhHX+vZO/kOhz9jn8HGENDKbwSeB0nVOg4gVStGA== +unified@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.2.tgz#67649a1abfc3ab85d2969502902775eb03146975" + integrity sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ== dependencies: bail "^1.0.0" extend "^3.0.0" + is-buffer "^2.0.0" is-plain-obj "^2.0.0" trough "^1.0.0" vfile "^4.0.0" @@ -7218,7 +7839,7 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" -unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.1, unist-util-visit@^2.0.3: +unist-util-visit@2.0.3, unist-util-visit@^2.0.0, unist-util-visit@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== @@ -7245,6 +7866,14 @@ update-browserslist-db@^1.0.0: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + update-notifier@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-5.1.0.tgz#4ab0d7c7f36a231dd7316cf7729313f0214d9ad9" @@ -7374,7 +8003,7 @@ wait-on@^6.0.1: minimist "^1.2.5" rxjs "^7.5.4" -watchpack@^2.3.1: +watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== @@ -7389,7 +8018,7 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -web-namespaces@^1.0.0, web-namespaces@^1.1.2: +web-namespaces@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== @@ -7425,7 +8054,7 @@ webpack-dev-middleware@^5.3.1: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@4.9.2, webpack-dev-server@^4.9.0: +webpack-dev-server@4.9.2: version "4.9.2" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.2.tgz#c188db28c7bff12f87deda2a5595679ebbc3c9bc" integrity sha512-H95Ns95dP24ZsEzO6G9iT+PNw4Q7ltll1GfJHV4fKphuHWgKFzGHWi4alTlTnpk1SPPk41X+l2RB7rLfIhnB9Q== @@ -7460,6 +8089,41 @@ webpack-dev-server@4.9.2, webpack-dev-server@^4.9.0: webpack-dev-middleware "^5.3.1" ws "^8.4.2" +webpack-dev-server@^4.9.3: + version "4.11.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5" + integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.1" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" + webpack-merge@^5.8.0: version "5.8.0" resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" @@ -7473,21 +8137,21 @@ webpack-sources@^3.2.2, webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.72.1: - version "5.73.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" - integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== +webpack@^5.73.0: + version "5.74.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980" + integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" "@webassemblyjs/ast" "1.11.1" "@webassemblyjs/wasm-edit" "1.11.1" "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" + acorn "^8.7.1" acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.3" + enhanced-resolve "^5.10.0" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" @@ -7500,7 +8164,7 @@ webpack@^5.72.1: schema-utils "^3.1.0" tapable "^2.1.1" terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" + watchpack "^2.4.0" webpack-sources "^3.2.3" webpackbar@^5.0.2: From 9c46e5a5600ff9bdb897c9025106524235e6f843 Mon Sep 17 00:00:00 2001 From: Topher Lubaway Date: Tue, 11 Oct 2022 11:36:33 -0500 Subject: [PATCH 033/498] Fixes relative links above docs to be URIs (#17850) also adds a missing exit 1 to deploy docs when the deploy SHOULD fail --- .../database-data-catalog.md | 16 ++++++++-------- tools/bin/deploy_docusaurus | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/understanding-airbyte/database-data-catalog.md b/docs/understanding-airbyte/database-data-catalog.md index 88bb34b8ea00..543dca7d8cfc 100644 --- a/docs/understanding-airbyte/database-data-catalog.md +++ b/docs/understanding-airbyte/database-data-catalog.md @@ -8,7 +8,7 @@ * The `release_stage` describes the certification level of the connector (e.g. Alpha, Beta, Generally Available). * The `docker_repository` field is the name of the docker image associated with the connector definition. `docker_image_tag` is the tag of the docker image and the version of the connector definition. * The `source_type` field is only used for Sources, and represents the category of the connector definition (e.g. API, Database). - * The `resource_requirements` field sets a default resource requirement for any connector of this type. This overrides the default we set for all connector definitions, and it can be overridden by a connection-specific resource requirement. The column is a JSON blob with the schema defined in [ActorDefinitionResourceRequirements.yaml](airbyte-config/config-models/src/main/resources/types/ActorDefinitionResourceRequirements.yaml) + * The `resource_requirements` field sets a default resource requirement for any connector of this type. This overrides the default we set for all connector definitions, and it can be overridden by a connection-specific resource requirement. The column is a JSON blob with the schema defined in [ActorDefinitionResourceRequirements.yaml](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/ActorDefinitionResourceRequirements.yaml) * The `public` boolean column, describes if a connector is available to all workspaces or not. For non, `public` connector definitions, they can be provisioned to a workspace using the `actor_definition_workspace_grant` table. `custom` means that the connector is written by a user of the platform (and not packaged into the Airbyte product). * Each record contains additional metadata and display data about a connector (e.g. `name` and `icon`), and we should add additional metadata here over time. * `actor_definition_workspace_grant` @@ -31,9 +31,9 @@ * todo (cgardens) - should we remove the `modified_at` column? These records should be immutable. * `connection` * Each record in this table configures a connection (`source_id`, `destination_id`, and relevant configuration). - * The `resource_requirements` field sets a default resource requirement for the connection. This overrides the default we set for all connector definitions and the default set for the connector definitions. The column is a JSON blob with the schema defined in [ResourceRequirements.yaml](airbyte-config/config-models/src/main/resources/types/ResourceRequirements.yaml). + * The `resource_requirements` field sets a default resource requirement for the connection. This overrides the default we set for all connector definitions and the default set for the connector definitions. The column is a JSON blob with the schema defined in [ResourceRequirements.yaml](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/ResourceRequirements.yaml). * The `source_catalog_id` column is a foreign key to the `sourc_catalog` table and represents the catalog that was used to configure the connection. This should not be confused with the `catalog` column which contains the [ConfiguredCatalog](airbyte-protocol.md#catalog) for the connection. - * The `schedule_type` column defines what type of schedule is being used. If the `type` is manual, then `schedule_data` will be null. Otherwise, `schedule_data` column is a JSON blob with the schema of [StandardSync#scheduleData](airbyte-config/config-models/src/main/resources/types/StandardSync.yaml#79) that defines the actual schedule. The columns `manual` and `schedule` are deprecated and should be ignored (they will be dropped soon). + * The `schedule_type` column defines what type of schedule is being used. If the `type` is manual, then `schedule_data` will be null. Otherwise, `schedule_data` column is a JSON blob with the schema of [StandardSync#scheduleData](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml#L74) that defines the actual schedule. The columns `manual` and `schedule` are deprecated and should be ignored (they will be dropped soon). * The `namespace_type` column configures whether the namespace for the connection should use that defined by the source, the destination, or a user-defined format (`custom`). If `custom` the `namespace_format` column defines the string that will be used as the namespace. * The `status` column describes the activity level of the connector: `active` - current schedule is respected, `inactive` - current schedule is ignored (the connection does not run) but it could be switched back to active, and `deprecated` - the connection is permanently off (cannot be moved to active or inactive). * `state` @@ -49,8 +49,8 @@ * Each record in this table represents a stream in a connection that is enqueued to be reset or is currently being reset. It can be thought of as a queue. Once the stream is reset, the record is removed from the table. * `operation` * The `operation` table transformations for a connection beyond the raw output produced by the destination. The two options are: `normalization`, which outputs Airbyte's basic normalization. The second is `dbt`, which allows a user to configure their own custom dbt transformation. A connection can have multiple operations (e.g. it can do `normalization` and `dbt`). - * If the `operation` is `dbt`, then the `operator_dbt` column will be populated with a JSON blob with the schema from [OperatorDbt](airbyte-config/config-models/src/main/resources/types/OperatorDbt.yaml). - * If the `operation` is `normalization`, then the `operator_dbt` column will be populated with a JSON blob with the scehma from [OperatorNormalization](airbyte-config/config-models/src/main/resources/types/OperatorNormalization.yaml). + * If the `operation` is `dbt`, then the `operator_dbt` column will be populated with a JSON blob with the schema from [OperatorDbt](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/OperatorDbt.yaml). + * If the `operation` is `normalization`, then the `operator_dbt` column will be populated with a JSON blob with the scehma from [OperatorNormalization](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/OperatorNormalization.yaml). * Operations are scoped by workspace, using the `workspace_id` column. * `connection_operation` * This table joins the `operation` table to the `connection` for which it is configured. @@ -72,7 +72,7 @@ * Each record in this table represents a job. * The `config_type` column captures the type of job. We only make jobs for `sync` and `reset` (we do not use them for `spec`, `check`, `discover`). * A job represents an attempt to use a connector (or a pair of connectors). The goal of this model is to capture the input of that run. A job can have multiple attempts (see the `attempts` table). The guarantee across all attempts is that the input into each attempt will be the same. - * That input is captured in the `config` column. This column is a JSON Blob with the schema of a [JobConfig](airbyte-config/config-models/src/main/resources/types/JobConfig.yaml). Only `sync` and `resetConnection` are ever used in that model. + * That input is captured in the `config` column. This column is a JSON Blob with the schema of a [JobConfig](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/JobConfig.yaml). Only `sync` and `resetConnection` are ever used in that model. * The other top-level fields are vestigial from when `spec`, `check`, `discover` were used in this model (we will eventually remove them). * The `scope` column contains the `connection_id` for the relevant connection of the job. * Context: It is called `scope` and not `connection_id`, because, this table was originally used for `spec`, `check`, and `discover`, and in those cases the `scope` referred to the relevant actor or actor definition. At this point the scope is always a `connection_id`. @@ -81,10 +81,10 @@ * Each record in this table represents an attempt. * Each attempt belongs to a job--this is captured by the `job_id` column. All attempts for a job will run on the same input. * The `id` column is a unique id across all attempts while the `attempt_number` is an ascending number of the attempts for a job. - * The output of each attempt, however, can be different. The `output` column is a JSON blob with the schema of a [JobOutput](airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml). Only `sync` is used in that model. Reset jobs will also use the `sync` field, because under the hood `reset` jobs end up just doing a `sync` with special inputs. This object contains all the output info for a sync including stats on how much data was moved. + * The output of each attempt, however, can be different. The `output` column is a JSON blob with the schema of a [JobOutput](ahttps://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml). Only `sync` is used in that model. Reset jobs will also use the `sync` field, because under the hood `reset` jobs end up just doing a `sync` with special inputs. This object contains all the output info for a sync including stats on how much data was moved. * The other top-level fields are vestigial from when `spec`, `check`, `discover` were used in this model (we will eventually remove them). * The `status` column contains the attempt status. The lifecycle of a job / attempt is explained in detail in the [Jobs & Workers documentation](jobs.md#job-state-machine). - * If the attempt fails, the `failure_summary` column will be populated. The column is a JSON blob with the schema of (AttemptFailureReason)[airbyte-config/config-models/src/main/resources/types/AttemptFailureSummary.yaml]. + * If the attempt fails, the `failure_summary` column will be populated. The column is a JSON blob with the schema of [AttemptFailureReason](https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/resources/types/AttemptFailureSummary.yaml). * The `log_path` column captures where logs for the attempt will be written. * `created_at`, `started_at`, and `ended_at` track the run time. * The `temporal_workflow_id` column keeps track of what temporal execution is associated with the attempt. diff --git a/tools/bin/deploy_docusaurus b/tools/bin/deploy_docusaurus index 0d250229dcf3..fcfaa900ae68 100755 --- a/tools/bin/deploy_docusaurus +++ b/tools/bin/deploy_docusaurus @@ -76,6 +76,7 @@ yarn install if ! yarn build; then echo -e "$red_text""yarn build has failed. Documentation probably has broken links""$default_text" echo -e "$red_text""please fix the links, commit, and try again""$default_text" + exit 1 fi # Check tty for local/remote deploys (we expect cloud to be non-interactive) From 834ea961cdd73520c9013c56fa8fb6b47729a45f Mon Sep 17 00:00:00 2001 From: Yashkumar Makwana <64660381+makyash@users.noreply.github.com> Date: Tue, 11 Oct 2022 11:48:24 -0500 Subject: [PATCH 034/498] Documentation Update for Timely (#17755) * Updating timely.md * Documentation update for Timely source connector. * Updated timely.md * Updated Timely.md * Timely Readme.md Update --- .../connectors/source-timely/README.md | 137 ++++++++++++++++-- docs/integrations/sources/timely.md | 38 ++++- 2 files changed, 155 insertions(+), 20 deletions(-) diff --git a/airbyte-integrations/connectors/source-timely/README.md b/airbyte-integrations/connectors/source-timely/README.md index f96212704b20..5b4f10657b1e 100644 --- a/airbyte-integrations/connectors/source-timely/README.md +++ b/airbyte-integrations/connectors/source-timely/README.md @@ -1,19 +1,132 @@ -This is the first python custom source connector which is made for Timely.
      -To get started using this connector, you will be needing three things.
      +# Timely Source - 1. Account ID - 2. Bearer Token - 3. Start-date +This is the repository for the Timely source connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/timely). -**Account ID** - Anyone who has admin access to Timelyapp.com you can find your account id, on the URL of your home page. +## Local development -Once, you have the account create an application on the Timelyapp.com where you need to specify your application name, this will generate Client_secret, Client_id, and redirect_uri. +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** -**Bearer Token** - To connect to the timelyapp API, I recommend using Postman or any other open source application that will get you the bearer token. -For Postman users, it will ask you to enter Auth url, Token url, Client_id, Client secret. For more details on how to work with timelyapp, please click [here](https://dev.timelyapp.com/#introduction) +#### Minimum Python version required `= 3.7.0` -**Start-date** - Please enter the start date in yy-mm--dd format to get the enteries from the start-date, this will pull all the record from the date entered to the present date. +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python -m venv .venv +``` -That's all you need to get this connector working +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +pip install '.[tests]' +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. -**Working locally**- navigate yourself to the source-timely/sample_files/config.json, and enter the ID, token and date to get this connector working. +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-timely:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/timely) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_timely/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source timely test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-timely:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-timely:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-timely:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-timely:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-timely:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-timely:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-timely:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-timely:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/docs/integrations/sources/timely.md b/docs/integrations/sources/timely.md index 34869e4e778e..09bb0d659ac9 100644 --- a/docs/integrations/sources/timely.md +++ b/docs/integrations/sources/timely.md @@ -1,16 +1,38 @@ ---- -description: 'This connector extracts "events" from Timely' ---- - # Timely -Timely can track time spent in every web and desktop app automatically for you. Get a precise daily record of all the time you spend in documents, meetings, emails, websites and video calls with zero effort.[Timely APIs](https://dev.timelyapp.com/). +This page contains the setup guide and reference information for the Timely source connector. + +## Prerequisites + +1. Please follow these [steps](https://dev.timelyapp.com/#authorization) to obtain `Bearer_token` for your account.
      +2. Login into your `https://app.timelyapp.com` portal, fetch the `account-id` present in the URL.
      + URL `https://app.timelyapp.com/12345/calendar`
      + account-id `12345`
      +3. Get a start-date to your events.
      + Dateformat `YYYY-MM-DD` + +## Setup guide +## Step 1: Set up the Timely connector in Airbyte + +### For Airbyte OSS: + +1. Navigate to the Airbyte Open Source dashboard. +2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. +3. On the Set up the source page, enter the name for the Timely connector and select **Timely** from the Source type dropdown. +4. Enter your `Bearer_token`, `account-id`, and `start-date`. +5. Select `Authenticate your account`. +6. Click **Set up source**. + +## Supported sync modes -Timely uses [Events](https://dev.timelyapp.com/#events) to store all the entries a user makes. Users can add, delete and edit all entries. Some user’s actions are restricted based on their access level in Timely. +The Timely source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -# Timely credentials +| Feature | Supported? | +| :---------------- | :--------- | +| Full Refresh Sync | Yes | +| Incremental Sync | No | -You should be able to create a Timely `Bearer Token` as described in [Intro to the Timely API](https://dev.timelyapp.com/#authorization) +## Changelog | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :-------------- | From ae30773a5b5b8ecc67efdfa31d843b849cfa42a0 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 11 Oct 2022 12:52:19 -0400 Subject: [PATCH 035/498] Add base Java image (#17847) * Add base Java image * Fix typo --- airbyte-base-java-image/Dockerfile | 13 +++++++++++++ airbyte-base-java-image/README.md | 24 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 airbyte-base-java-image/Dockerfile create mode 100644 airbyte-base-java-image/README.md diff --git a/airbyte-base-java-image/Dockerfile b/airbyte-base-java-image/Dockerfile new file mode 100644 index 000000000000..db890363e143 --- /dev/null +++ b/airbyte-base-java-image/Dockerfile @@ -0,0 +1,13 @@ +FROM amazoncorretto:19 + +ARG DOCKER_BUILD_ARCH=amd64 + +WORKDIR /app + +RUN yum install -y tar + +# Add the DataDaog Java APM agent +ADD https://dtdg.co/latest-java-tracer dd-java-agent.jar + +# Add the OpenTelemetry Java APM agent +ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar opentelemetry-javaagent.jar diff --git a/airbyte-base-java-image/README.md b/airbyte-base-java-image/README.md new file mode 100644 index 000000000000..78bc7ccceb9e --- /dev/null +++ b/airbyte-base-java-image/README.md @@ -0,0 +1,24 @@ +# Base Docker Image for Java + +This Docker image provides the base for any Java-based Airbyte module. It is currently based on the [Amazon Corretto](https://aws.amazon.com/corretto/?filtered-posts.sort-by=item.additionalFields.createdDate&filtered-posts.sort-order=desc) +distribution of [OpenJDK](https://openjdk.org/). + +# Releasing + +To release a new version of this base image, use the following steps: + +1. Log in to [Dockerhub](https://hub.docker.com/) via the Docker CLI (`docker login`). +2. Run `docker buildx create --use` to enable Docker `buildx` if you have not used it previously. +3. Run the following to build and push a new version of this image (replace `` with a new version!) : + ``` + docker buildx build --push \ + --tag airbyte/airbyte-base-java-image: \ + --platform linux/amd64,linux/arm64 . + ``` + To see existing versions, [view the image on Dockerhub](https://hub.docker.com/r/airbyte/airbyte-base-java-image). +4. Update base Docker image tag to the new version in all Dockerfiles that depend on the base image: + ```bash + FROM airbyte/java-datadog-tracer-base: + ``` + +[dockerhub]: https://hub.docker.com/repository/registry-1.docker.io/airbyte/airbyte-base-java-image/general From ec5496840df4b0af93afa341d820268d0212c7e9 Mon Sep 17 00:00:00 2001 From: Lake Mossman Date: Tue, 11 Oct 2022 09:55:39 -0700 Subject: [PATCH 036/498] Add workspace ID to connector check calls to fix segment tracking (#17816) * add workspace ID to source/dest check and discover endpoints * pass workspaceId to API from frontend * make it work for destinations too --- airbyte-api/src/main/openapi/config.yaml | 5 +++++ .../io/airbyte/server/handlers/SchedulerHandler.java | 9 ++++++--- .../io/airbyte/server/handlers/SchedulerHandlerTest.java | 9 ++++++--- .../src/core/domain/connector/SourceService.ts | 1 + airbyte-webapp/src/hooks/services/useConnector.tsx | 5 +++++ .../views/Connector/ConnectorCard/useTestConnector.tsx | 3 +++ docs/reference/api/generated-api-html/index.html | 2 ++ 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index dd4fd4d4253a..a5ef224bc982 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -2731,11 +2731,14 @@ components: required: - sourceDefinitionId - connectionConfiguration + - workspaceId properties: sourceDefinitionId: $ref: "#/components/schemas/SourceDefinitionId" connectionConfiguration: $ref: "#/components/schemas/SourceConfiguration" + workspaceId: + $ref: "#/components/schemas/WorkspaceId" SourceCreate: type: object required: @@ -3027,6 +3030,8 @@ components: $ref: "#/components/schemas/DestinationDefinitionId" connectionConfiguration: $ref: "#/components/schemas/DestinationConfiguration" + workspaceId: + $ref: "#/components/schemas/WorkspaceId" DestinationCreate: type: object required: diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java index dfe277c1704e..7892919d6213 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java @@ -146,7 +146,8 @@ public CheckConnectionRead checkSourceConnectionFromSourceCreate(final SourceCor // technically declared as required. final SourceConnection source = new SourceConnection() .withSourceDefinitionId(sourceConfig.getSourceDefinitionId()) - .withConfiguration(partialConfig); + .withConfiguration(partialConfig) + .withWorkspaceId(sourceConfig.getWorkspaceId()); final String imageName = DockerUtils.getTaggedImageName(sourceDef.getDockerRepository(), sourceDef.getDockerImageTag()); return reportConnectionStatus(synchronousSchedulerClient.createSourceCheckConnectionJob(source, imageName)); @@ -186,7 +187,8 @@ public CheckConnectionRead checkDestinationConnectionFromDestinationCreate(final // technically declared as required. final DestinationConnection destination = new DestinationConnection() .withDestinationDefinitionId(destinationConfig.getDestinationDefinitionId()) - .withConfiguration(partialConfig); + .withConfiguration(partialConfig) + .withWorkspaceId(destinationConfig.getWorkspaceId()); final String imageName = DockerUtils.getTaggedImageName(destDef.getDockerRepository(), destDef.getDockerImageTag()); return reportConnectionStatus(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, imageName)); @@ -250,7 +252,8 @@ public SourceDiscoverSchemaRead discoverSchemaForSourceFromSourceCreate(final So // technically declared as required. final SourceConnection source = new SourceConnection() .withSourceDefinitionId(sourceCreate.getSourceDefinitionId()) - .withConfiguration(partialConfig); + .withConfiguration(partialConfig) + .withWorkspaceId(sourceCreate.getWorkspaceId()); final SynchronousResponse response = synchronousSchedulerClient.createDiscoverSchemaJob(source, imageName, sourceDef.getDockerImageTag()); return retrieveDiscoveredSchema(response); } diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java index 33a8bae6fd2a..d58958c4cfac 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java @@ -189,7 +189,8 @@ void testCheckSourceConnectionFromSourceCreate() throws JsonValidationException, final SourceCoreConfig sourceCoreConfig = new SourceCoreConfig() .sourceDefinitionId(source.getSourceDefinitionId()) - .connectionConfiguration(source.getConfiguration()); + .connectionConfiguration(source.getConfiguration()) + .workspaceId(source.getWorkspaceId()); when(configRepository.getStandardSourceDefinition(source.getSourceDefinitionId())) .thenReturn(new StandardSourceDefinition() @@ -505,7 +506,8 @@ void testDiscoverSchemaForSourceFromSourceCreate() throws JsonValidationExceptio final SourceCoreConfig sourceCoreConfig = new SourceCoreConfig() .sourceDefinitionId(source.getSourceDefinitionId()) - .connectionConfiguration(source.getConfiguration()); + .connectionConfiguration(source.getConfiguration()) + .workspaceId(source.getWorkspaceId()); final ActorCatalog actorCatalog = new ActorCatalog() .withCatalog(Jsons.jsonNode(airbyteCatalog)) .withCatalogHash("") @@ -538,7 +540,8 @@ void testDiscoverSchemaForSourceFromSourceCreateFailed() throws JsonValidationEx final SourceCoreConfig sourceCoreConfig = new SourceCoreConfig() .sourceDefinitionId(source.getSourceDefinitionId()) - .connectionConfiguration(source.getConfiguration()); + .connectionConfiguration(source.getConfiguration()) + .workspaceId(source.getWorkspaceId()); when(configRepository.getStandardSourceDefinition(source.getSourceDefinitionId())) .thenReturn(new StandardSourceDefinition() diff --git a/airbyte-webapp/src/core/domain/connector/SourceService.ts b/airbyte-webapp/src/core/domain/connector/SourceService.ts index 7b23ca9e1522..2a25072b01ab 100644 --- a/airbyte-webapp/src/core/domain/connector/SourceService.ts +++ b/airbyte-webapp/src/core/domain/connector/SourceService.ts @@ -25,6 +25,7 @@ export class SourceService extends AirbyteRequestService { params: { sourceId?: string; connectionConfiguration?: ConnectionConfiguration; + workspaceId?: string; }, requestParams?: RequestInit ) { diff --git a/airbyte-webapp/src/hooks/services/useConnector.tsx b/airbyte-webapp/src/hooks/services/useConnector.tsx index 2f67754028d7..0cdf939de2ac 100644 --- a/airbyte-webapp/src/hooks/services/useConnector.tsx +++ b/airbyte-webapp/src/hooks/services/useConnector.tsx @@ -101,6 +101,7 @@ export type CheckConnectorParams = { signal: AbortSignal } & ( | { selectedConnectorDefinitionId: string; connectionConfiguration: ConnectionConfiguration; + workspaceId: string; } ); @@ -119,6 +120,10 @@ export const useCheckConnector = (formType: "source" | "destination") => { payload.name = params.name; } + if ("workspaceId" in params) { + payload.workspaceId = params.workspaceId; + } + if (formType === "destination") { if ("selectedConnectorId" in params) { payload.destinationId = params.selectedConnectorId; diff --git a/airbyte-webapp/src/views/Connector/ConnectorCard/useTestConnector.tsx b/airbyte-webapp/src/views/Connector/ConnectorCard/useTestConnector.tsx index 4c586af3fa39..9fd789fbfaff 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorCard/useTestConnector.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorCard/useTestConnector.tsx @@ -3,6 +3,7 @@ import { useRef } from "react"; import { ConnectorHelper } from "core/domain/connector"; import { ConnectorT } from "core/domain/connector/types"; import { CheckConnectorParams, useCheckConnector } from "hooks/services/useConnector"; +import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; import { ServiceFormValues } from "views/Connector/ServiceForm"; import { CheckConnectionRead } from "../../../core/request/AirbyteClient"; @@ -25,6 +26,7 @@ export const useTestConnector = ( reset: () => void; } => { const { mutateAsync, isLoading, error, isSuccess, reset } = useCheckConnector(props.formType); + const workspace = useCurrentWorkspace(); const abortControllerRef = useRef(null); @@ -66,6 +68,7 @@ export const useTestConnector = ( connectionConfiguration: values.connectionConfiguration, signal: controller.signal, selectedConnectorDefinitionId: values.serviceType, + workspaceId: workspace.workspaceId, }; } diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 770274d10481..2801d6cbc913 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -10356,6 +10356,7 @@

      DestinationCoreConfig -
      destinationDefinitionId
      UUID format: uuid
      connectionConfiguration
      +
      workspaceId
      UUID format: uuid

    @@ -11017,6 +11018,7 @@

    SourceCoreConfig -
    sourceDefinitionId
    UUID format: uuid
    connectionConfiguration
    +
    workspaceId
    UUID format: uuid

    From 2d049f18cfd0e2f44a242303387db7415f8e9140 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Tue, 11 Oct 2022 10:32:57 -0700 Subject: [PATCH 037/498] Use locally-installed qemu rather than docker-installed version (#17848) * Use locally-installed qemu rather than docker-installed version * Bump faker for test * Update docs/integrations/sources/faker.md Co-authored-by: Pedro S. Lopez * auto-bump connector version [ci skip] Co-authored-by: Pedro S. Lopez Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-faker/Dockerfile | 2 +- docs/integrations/sources/faker.md | 3 ++- tools/integrations/manage.sh | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 077d3e164de9..8b50b86ceeec 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -295,7 +295,7 @@ - name: Faker sourceDefinitionId: dfd88b22-b603-4c3d-aad7-3701784586b1 dockerRepository: airbyte/source-faker - dockerImageTag: 0.1.6 + dockerImageTag: 0.1.7 documentationUrl: https://docs.airbyte.com/integrations/sources/faker sourceType: api releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index b734bd35b586..4bfb004a986d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2949,7 +2949,7 @@ oauthFlowInitParameters: [] oauthFlowOutputParameters: - - "access_token" -- dockerImage: "airbyte/source-faker:0.1.6" +- dockerImage: "airbyte/source-faker:0.1.7" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/faker" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-faker/Dockerfile b/airbyte-integrations/connectors/source-faker/Dockerfile index 4f217b1e4d41..05fea6d1438c 100644 --- a/airbyte-integrations/connectors/source-faker/Dockerfile +++ b/airbyte-integrations/connectors/source-faker/Dockerfile @@ -34,5 +34,5 @@ COPY source_faker ./source_faker ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.6 +LABEL io.airbyte.version=0.1.7 LABEL io.airbyte.name=airbyte/source-faker diff --git a/docs/integrations/sources/faker.md b/docs/integrations/sources/faker.md index f3f1b1879b70..5afaaf181154 100644 --- a/docs/integrations/sources/faker.md +++ b/docs/integrations/sources/faker.md @@ -40,7 +40,8 @@ N/A ## Changelog | Version | Date | Pull Request | Subject | -|:--------| :--------- | :------------------------------------------------------- | :-------------------------------------------------------- | +| :------ | :--------- | :------------------------------------------------------- | :-------------------------------------------------------- | +| 0.1.7 | 2022-10-11 | [17848](https://github.com/airbytehq/airbyte/pull/17848) | Bump to test publish command | | 0.1.6 | 2022-09-07 | [16418](https://github.com/airbytehq/airbyte/pull/16418) | Log start of each stream | | 0.1.5 | 2022-06-10 | [13695](https://github.com/airbytehq/airbyte/pull/13695) | Emit timestamps in the proper ISO format | | 0.1.4 | 2022-05-27 | [13298](https://github.com/airbytehq/airbyte/pull/13298) | Test publication flow | diff --git a/tools/integrations/manage.sh b/tools/integrations/manage.sh index a9f2875b9024..bd74a75edf38 100755 --- a/tools/integrations/manage.sh +++ b/tools/integrations/manage.sh @@ -220,7 +220,7 @@ cmd_publish() { # Install docker emulators # TODO: Don't run this command on M1 macs locally (it won't work and isn't needed) - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + apt-get install -y qemu-user-static # log into docker if test -z "${DOCKER_HUB_USERNAME}"; then From f5d339a260473a4f10cacd73757bcf20ebf9b566 Mon Sep 17 00:00:00 2001 From: HongChe Lin Date: Wed, 12 Oct 2022 01:37:54 +0800 Subject: [PATCH 038/498] fix condition for KubePortManagerSingleton init twice (#16213) * add test for init ports manager twice * fix bug for init more then onece * formatter * fix typo by pr suggestions Co-authored-by: Davin Chia --- .../process/KubePortManagerSingleton.java | 2 +- .../KubePodProcessIntegrationTest.java | 83 ++++++++----------- 2 files changed, 37 insertions(+), 48 deletions(-) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java b/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java index 206da25b8729..b85746536a15 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java @@ -56,7 +56,7 @@ public static synchronized KubePortManagerSingleton getInstance() { public static synchronized void init(final Set ports) { if (instance == null) { instance = new KubePortManagerSingleton(ports); - } else if (Sets.intersection(instance.getAllPorts(), ports).size() != ports.size()) { + } else if (Sets.intersection(instance.getAllPorts(), ports).size() == ports.size()) { LOGGER.info("Skipping initializing KubePortManagerSingleton since ports specified are the same."); } else { throw new RuntimeException("Cannot initialize twice with different ports!"); diff --git a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java index 55035ad738b0..9ea78f2f4ac6 100644 --- a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java +++ b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -102,14 +103,31 @@ public void setup() throws Exception { final WorkerConfigs workerConfigs = spy(new WorkerConfigs(new EnvConfigs())); when(workerConfigs.getEnvMap()).thenReturn(Map.of("ENV_VAR_1", "ENV_VALUE_1")); - processFactory = - new KubeProcessFactory( - workerConfigs, - "default", - fabricClient, - heartbeatUrl, - getHost(), - false); + processFactory = new KubeProcessFactory(workerConfigs, "default", fabricClient, heartbeatUrl, getHost(), false); + } + + @RetryingTest(3) + public void testInitKubePortManagerSingletonTwice() throws Exception { + /** + * Test init KubePortManagerSingleton twice: 1. with same ports should succeed 2. with different + * port should fail + * + * Every test has been init once in BeforeAll with getOpenPorts(30) + */ + + KubePortManagerSingleton originalKubePortManager = KubePortManagerSingleton.getInstance(); + + // init the second time with the same ports + final List theSameOpenPorts = new ArrayList<>(getOpenPorts(30)); + KubePortManagerSingleton.init(new HashSet<>(theSameOpenPorts.subList(1, theSameOpenPorts.size() - 1))); + assertEquals(originalKubePortManager, KubePortManagerSingleton.getInstance()); + + // init the second time with different ports + final List differentOpenPorts = new ArrayList<>(getOpenPorts(32)); + Exception exception = assertThrows(RuntimeException.class, () -> { + KubePortManagerSingleton.init(new HashSet<>(differentOpenPorts.subList(1, differentOpenPorts.size() - 1))); + }); + assertTrue(exception.getMessage().contains("Cannot initialize twice with different ports!")); } /** @@ -283,21 +301,14 @@ public void testDeletingPodImmediatelyAfterCompletion() throws Exception { final var uuid = UUID.randomUUID(); final Process process = getProcess(Map.of("uuid", uuid.toString()), "sleep 1 && exit 10"); - final var pod = fabricClient.pods().list().getItems().stream() - .filter(p -> p.getMetadata() != null && p.getMetadata().getLabels() != null) + final var pod = fabricClient.pods().list().getItems().stream().filter(p -> p.getMetadata() != null && p.getMetadata().getLabels() != null) .filter(p -> p.getMetadata().getLabels().containsKey("uuid") && p.getMetadata().getLabels().get("uuid").equals(uuid.toString())) .collect(Collectors.toList()).get(0); - final SharedIndexInformer podInformer = fabricClient.pods() - .inNamespace(pod.getMetadata().getNamespace()) - .withName(pod.getMetadata().getName()) - .inform(); - podInformer.addEventHandler(new ExitCodeWatcher( - pod.getMetadata().getName(), - pod.getMetadata().getNamespace(), - exitCode -> { - fabricClient.pods().delete(pod); - }, - () -> {})); + final SharedIndexInformer podInformer = + fabricClient.pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName()).inform(); + podInformer.addEventHandler(new ExitCodeWatcher(pod.getMetadata().getName(), pod.getMetadata().getNamespace(), exitCode -> { + fabricClient.pods().delete(pod); + }, () -> {})); process.waitFor(); @@ -342,14 +353,7 @@ public void testKillingWithoutHeartbeat() throws Exception { final WorkerConfigs workerConfigs = spy(new WorkerConfigs(new EnvConfigs())); when(workerConfigs.getEnvMap()).thenReturn(Map.of("ENV_VAR_1", "ENV_VALUE_1")); - processFactory = - new KubeProcessFactory( - workerConfigs, - "default", - fabricClient, - heartbeatUrl, - getHost(), - false); + processFactory = new KubeProcessFactory(workerConfigs, "default", fabricClient, heartbeatUrl, getHost(), false); // start an infinite process final var availablePortsBefore = KubePortManagerSingleton.getInstance().getNumAvailablePorts(); @@ -412,11 +416,7 @@ private static String getRandomFile(final int lines) { private Process getProcess(final String entrypoint) throws WorkerException { // these files aren't used for anything, it's just to check for exceptions when uploading - final var files = ImmutableMap.of( - "file0", "fixed str", - "file1", getRandomFile(1), - "file2", getRandomFile(100), - "file3", getRandomFile(1000)); + final var files = ImmutableMap.of("file0", "fixed str", "file1", getRandomFile(1), "file2", getRandomFile(100), "file3", getRandomFile(1000)); return getProcess(entrypoint, files); } @@ -431,19 +431,8 @@ private Process getProcess(final String entrypoint, final Map fi private Process getProcess(final Map customLabels, final String entrypoint, final Map files) throws WorkerException { - return processFactory.create( - "tester", - "some-id", - 0, - Path.of("/tmp/job-root"), - "busybox:latest", - false, - files, - entrypoint, - DEFAULT_RESOURCE_REQUIREMENTS, - customLabels, - Collections.emptyMap(), - Collections.emptyMap()); + return processFactory.create("tester", "some-id", 0, Path.of("/tmp/job-root"), "busybox:latest", false, files, entrypoint, + DEFAULT_RESOURCE_REQUIREMENTS, customLabels, Collections.emptyMap(), Collections.emptyMap()); } private static Set getOpenPorts(final int count) { From fbead4d7d15d2faa43fad19ed306c2891cedbdcf Mon Sep 17 00:00:00 2001 From: Xiaohan Song Date: Tue, 11 Oct 2022 11:27:53 -0700 Subject: [PATCH 039/498] Add a new column in attempts table (#17806) * migration attempts * migrator script * version fix --- .../airbyte/bootloader/BootloaderAppTest.java | 2 +- ...001__AddProcessingTaskQueueInAttempts.java | 40 +++++++++++++++++++ .../resources/jobs_database/schema_dump.txt | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/jobs/migrations/V0_40_14_001__AddProcessingTaskQueueInAttempts.java diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index 37602c7c27a5..d05940ba056f 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -134,7 +134,7 @@ void testBootloaderAppBlankDb() throws Exception { bootloader.load(); val jobsMigrator = new JobsDatabaseMigrator(jobDatabase, jobsFlyway); - assertEquals("0.40.4.002", jobsMigrator.getLatestMigration().getVersion().getVersion()); + assertEquals("0.40.14.001", jobsMigrator.getLatestMigration().getVersion().getVersion()); val configsMigrator = new ConfigsDatabaseMigrator(configDatabase, configsFlyway); // this line should change with every new migration diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/jobs/migrations/V0_40_14_001__AddProcessingTaskQueueInAttempts.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/jobs/migrations/V0_40_14_001__AddProcessingTaskQueueInAttempts.java new file mode 100644 index 000000000000..df849e113917 --- /dev/null +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/jobs/migrations/V0_40_14_001__AddProcessingTaskQueueInAttempts.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.db.instance.jobs.migrations; + +import org.flywaydb.core.api.migration.BaseJavaMigration; +import org.flywaydb.core.api.migration.Context; +import org.jooq.DSLContext; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// TODO: update migration description in the class name +public class V0_40_14_001__AddProcessingTaskQueueInAttempts extends BaseJavaMigration { + + private static final Logger LOGGER = LoggerFactory.getLogger( + V0_40_14_001__AddProcessingTaskQueueInAttempts.class); + + @Override + public void migrate(final Context context) throws Exception { + LOGGER.info("Running migration: {}", this.getClass().getSimpleName()); + + // Warning: please do not use any jOOQ generated code to write a migration. + // As database schema changes, the generated jOOQ code can be deprecated. So + // old migration may not compile if there is any generated code. + final DSLContext ctx = DSL.using(context.getConnection()); + addProtocolVersionColumn(ctx); + } + + private static void addProtocolVersionColumn(final DSLContext ctx) { + ctx.alterTable("attempts") + .addColumnIfNotExists(DSL.field( + "processing_task_queue", + SQLDataType.VARCHAR(255).nullable(true))) + .execute(); + } + +} diff --git a/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt b/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt index 4129b16d6f27..15cd985a9118 100644 --- a/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt +++ b/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt @@ -34,6 +34,7 @@ create table "public"."attempts"( "ended_at" timestamptz(35) null, "temporal_workflow_id" varchar(256) null, "failure_summary" jsonb null, + "processing_task_queue" varchar(255) null, constraint "attempts_pkey" primary key ("id") ); From 6e42afb735f61fcf758fd8ca686d996348aca352 Mon Sep 17 00:00:00 2001 From: Christophe Duong Date: Tue, 11 Oct 2022 20:57:53 +0200 Subject: [PATCH 040/498] Update and publish source-orb (#17838) * Update docs for source-orb * auto-bump connector version [ci skip] Co-authored-by: Alexandre Girard Co-authored-by: Octavia Squidington III --- .../source-orb/unit_tests/test_incremental_streams.py | 2 +- docs/integrations/sources/orb.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py b/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py index f471f1cfd205..ebffe43548d1 100644 --- a/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py +++ b/airbyte-integrations/connectors/source-orb/unit_tests/test_incremental_streams.py @@ -259,6 +259,7 @@ def test_credits_ledger_entries_enriches_selected_property_keys( # Does not enrich, but still passes back, irrelevant (for enrichment purposes) ledger entry assert enriched_entries[1] == original_entry_1 + @responses.activate def test_credits_ledger_entries_enriches_with_multiple_entries_per_event(mocker): stream = CreditsLedgerEntries(string_event_properties_keys=["ping"]) @@ -285,7 +286,6 @@ def test_credits_ledger_entries_enriches_with_multiple_entries_per_event(mocker) ] - def test_supports_incremental(patch_incremental_base_class, mocker): mocker.patch.object(IncrementalOrbStream, "cursor_field", "dummy_field") stream = IncrementalOrbStream() diff --git a/docs/integrations/sources/orb.md b/docs/integrations/sources/orb.md index e9422b5f40e0..c0c5cd099ded 100644 --- a/docs/integrations/sources/orb.md +++ b/docs/integrations/sources/orb.md @@ -52,7 +52,7 @@ an Orb Account and API Key. | Version | Date | Pull Request | Subject | | --- | --- | --- | --- | -| 0.1.4 | 2022-10-07 | [17761](https://github.com/airbytehq/airbyte/pull/17761) | Fix bug with enriching ledger entries +| 0.1.4 | 2022-10-07 | [17761](https://github.com/airbytehq/airbyte/pull/17761) | Fix bug with enriching ledger entries with multiple credit blocks | 0.1.3 | 2022-08-26 | [16017](https://github.com/airbytehq/airbyte/pull/16017) | Add credit block id to ledger entries | 0.1.2 | 2022-04-20 | [11528](https://github.com/airbytehq/airbyte/pull/11528) | Add cost basis to ledger entries, update expiration date, sync only committed entries | 0.1.1 | 2022-03-03 | [10839](https://github.com/airbytehq/airbyte/pull/10839) | Support ledger entries with numeric properties + schema fixes From 1a36074818ae7f860310446a6ef1205f2ad2cb11 Mon Sep 17 00:00:00 2001 From: Augustin Date: Tue, 11 Oct 2022 21:08:35 +0200 Subject: [PATCH 041/498] source-salesforce: add EventWhoRelation to UNSUPPORTED_BULK_API_SALESFORCE_OBJECTS (#17778) --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-salesforce/Dockerfile | 2 +- .../connectors/source-salesforce/source_salesforce/api.py | 1 + docs/integrations/sources/salesforce.md | 3 ++- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 8b50b86ceeec..f247941efd3a 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -931,7 +931,7 @@ - name: Salesforce sourceDefinitionId: b117307c-14b6-41aa-9422-947e34922962 dockerRepository: airbyte/source-salesforce - dockerImageTag: 1.0.20 + dockerImageTag: 1.0.21 documentationUrl: https://docs.airbyte.com/integrations/sources/salesforce icon: salesforce.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 4bfb004a986d..15b02f528426 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9775,7 +9775,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-salesforce:1.0.20" +- dockerImage: "airbyte/source-salesforce:1.0.21" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/salesforce" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-salesforce/Dockerfile b/airbyte-integrations/connectors/source-salesforce/Dockerfile index 0c49fbe795eb..9c1fb17676f3 100644 --- a/airbyte-integrations/connectors/source-salesforce/Dockerfile +++ b/airbyte-integrations/connectors/source-salesforce/Dockerfile @@ -13,6 +13,6 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=1.0.20 +LABEL io.airbyte.version=1.0.21 LABEL io.airbyte.name=airbyte/source-salesforce diff --git a/airbyte-integrations/connectors/source-salesforce/source_salesforce/api.py b/airbyte-integrations/connectors/source-salesforce/source_salesforce/api.py index 0232c804d6d1..6241aa4a2633 100644 --- a/airbyte-integrations/connectors/source-salesforce/source_salesforce/api.py +++ b/airbyte-integrations/connectors/source-salesforce/source_salesforce/api.py @@ -134,6 +134,7 @@ "CaseStatus", "ContractStatus", "DeclinedEventRelation", + "EventWhoRelation", "FieldSecurityClassification", "KnowledgeArticle", "KnowledgeArticleVersion", diff --git a/docs/integrations/sources/salesforce.md b/docs/integrations/sources/salesforce.md index 872364ea7098..5c1a730337fe 100644 --- a/docs/integrations/sources/salesforce.md +++ b/docs/integrations/sources/salesforce.md @@ -120,7 +120,8 @@ Now that you have set up the Salesforce source connector, check out the followin ## Changelog | Version | Date | Pull Request | Subject | -|:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------| +| :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | +| 1.0.21 | 2022-10-10 | [17778](https://github.com/airbytehq/airbyte/pull/17778) | Add `EventWhoRelation` to the list of unsupported Bulk API objects. | | 1.0.20 | 2022-09-30 | [17453](https://github.com/airbytehq/airbyte/pull/17453) | Check objects that are not supported by the Bulk API (v52.0) | | 1.0.19 | 2022-09-29 | [17314](https://github.com/airbytehq/airbyte/pull/17314) | Fixed bug with decoding response | | 1.0.18 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream states. | From e4d1da5948390f5c41291383afeab7c3604cb2a6 Mon Sep 17 00:00:00 2001 From: Augustin Date: Tue, 11 Oct 2022 21:08:55 +0200 Subject: [PATCH 042/498] source-recharge: do not parse json in `should_retry` (#17822) --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-recharge/Dockerfile | 2 +- .../source-recharge/source_recharge/api.py | 3 +- docs/integrations/sources/recharge.md | 39 ++++++++++--------- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index f247941efd3a..4b514d884d6d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -876,7 +876,7 @@ - name: Recharge sourceDefinitionId: 45d2e135-2ede-49e1-939f-3e3ec357a65e dockerRepository: airbyte/source-recharge - dockerImageTag: 0.2.3 + dockerImageTag: 0.2.4 documentationUrl: https://docs.airbyte.com/integrations/sources/recharge icon: recharge.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 15b02f528426..b4f2afca068d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9168,7 +9168,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-recharge:0.2.3" +- dockerImage: "airbyte/source-recharge:0.2.4" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/recharge" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-recharge/Dockerfile b/airbyte-integrations/connectors/source-recharge/Dockerfile index de7ae7e7e0f5..69848f6bd939 100644 --- a/airbyte-integrations/connectors/source-recharge/Dockerfile +++ b/airbyte-integrations/connectors/source-recharge/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.3 +LABEL io.airbyte.version=0.2.4 LABEL io.airbyte.name=airbyte/source-recharge diff --git a/airbyte-integrations/connectors/source-recharge/source_recharge/api.py b/airbyte-integrations/connectors/source-recharge/source_recharge/api.py index 408f1d57f700..73627d359a7f 100644 --- a/airbyte-integrations/connectors/source-recharge/source_recharge/api.py +++ b/airbyte-integrations/connectors/source-recharge/source_recharge/api.py @@ -64,11 +64,10 @@ def get_stream_data(self, response_data: Any) -> List[dict]: def should_retry(self, response: requests.Response) -> bool: content_length = int(response.headers.get("Content-Length", 0)) incomplete_data_response = response.status_code == 200 and content_length > len(response.content) - forbidden_error = isinstance(response.json(), dict) and response.status_code == requests.codes.FORBIDDEN if incomplete_data_response: return True - elif forbidden_error: + elif response.status_code == requests.codes.FORBIDDEN: setattr(self, "raise_on_http_errors", False) self.logger.error(f"Skiping stream {self.name} because of a 403 error.") return False diff --git a/docs/integrations/sources/recharge.md b/docs/integrations/sources/recharge.md index 247099bd2c24..61f5247cb572 100644 --- a/docs/integrations/sources/recharge.md +++ b/docs/integrations/sources/recharge.md @@ -35,11 +35,11 @@ Please read [How to generate your API token](https://support.rechargepayments.co The Recharge supports full refresh and incremental sync. -| Feature | Supported? | -| :--- | :--- | -| Full Refresh Sync | Yes | -| Incremental Sync | Yes | -| SSL connection | Yes | +| Feature | Supported? | +| :---------------- | :--------- | +| Full Refresh Sync | Yes | +| Incremental Sync | Yes | +| SSL connection | Yes | ## Supported Streams @@ -65,18 +65,19 @@ The Recharge connector should gracefully handle Recharge API limitations under n ## Changelog -| Version | Date | Pull Request | Subject | -| :--- | :--- | :--- | :--- | -| 0.2.2 | 2022-10-05 | [17608](https://github.com/airbytehq/airbyte/pull/17608) | Skip stream if we receive 403 error -| 0.2.2 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. -| 0.2.1 | 2022-09-23 | [17080](https://github.com/airbytehq/airbyte/pull/17080) | Fix `total_weight` value to be `int` instead of `float` -| 0.2.0 | 2022-09-21 | [16959](https://github.com/airbytehq/airbyte/pull/16959) | Use TypeTransformer to reliably convert to schema declared data types -| 0.1.8 | 2022-08-27 | [16045](https://github.com/airbytehq/airbyte/pull/16045) | Force total_weight to be an integer | -| 0.1.7 | 2022-07-24 | [14978](https://github.com/airbytehq/airbyte/pull/14978) | Set `additionalProperties` to True, to guarantee backward cababilities | -| 0.1.6 | 2022-07-21 | [14902](https://github.com/airbytehq/airbyte/pull/14902) | Increased test coverage, fixed broken `charges`, `orders` schemas, added state checkpoint | -| 0.1.5 | 2022-01-26 | [9808](https://github.com/airbytehq/airbyte/pull/9808) | Update connector fields title/description | -| 0.1.4 | 2021-11-05 | [7626](https://github.com/airbytehq/airbyte/pull/7626) | Improve 'backoff' for HTTP requests | -| 0.1.3 | 2021-09-17 | [6149](https://github.com/airbytehq/airbyte/pull/6149) | Update `discount` and `order` schema | -| 0.1.2 | 2021-09-17 | [6149](https://github.com/airbytehq/airbyte/pull/6149) | Change `cursor_field` for Incremental streams | -| | | | | +| Version | Date | Pull Request | Subject | +| :------ | :--------- | :------------------------------------------------------- | :---------------------------------------------------------------------------------------- | +| 0.2.3 | 2022-10-11 | [17822](https://github.com/airbytehq/airbyte/pull/17822) | Do not parse JSON in `should_retry` | +| 0.2.2 | 2022-10-05 | [17608](https://github.com/airbytehq/airbyte/pull/17608) | Skip stream if we receive 403 error | +| 0.2.2 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | +| 0.2.1 | 2022-09-23 | [17080](https://github.com/airbytehq/airbyte/pull/17080) | Fix `total_weight` value to be `int` instead of `float` | +| 0.2.0 | 2022-09-21 | [16959](https://github.com/airbytehq/airbyte/pull/16959) | Use TypeTransformer to reliably convert to schema declared data types | +| 0.1.8 | 2022-08-27 | [16045](https://github.com/airbytehq/airbyte/pull/16045) | Force total_weight to be an integer | +| 0.1.7 | 2022-07-24 | [14978](https://github.com/airbytehq/airbyte/pull/14978) | Set `additionalProperties` to True, to guarantee backward cababilities | +| 0.1.6 | 2022-07-21 | [14902](https://github.com/airbytehq/airbyte/pull/14902) | Increased test coverage, fixed broken `charges`, `orders` schemas, added state checkpoint | +| 0.1.5 | 2022-01-26 | [9808](https://github.com/airbytehq/airbyte/pull/9808) | Update connector fields title/description | +| 0.1.4 | 2021-11-05 | [7626](https://github.com/airbytehq/airbyte/pull/7626) | Improve 'backoff' for HTTP requests | +| 0.1.3 | 2021-09-17 | [6149](https://github.com/airbytehq/airbyte/pull/6149) | Update `discount` and `order` schema | +| 0.1.2 | 2021-09-17 | [6149](https://github.com/airbytehq/airbyte/pull/6149) | Change `cursor_field` for Incremental streams | +| | | | | From a8461a54c35cdefcc89c9473ce0f54b2fbd279ad Mon Sep 17 00:00:00 2001 From: Liren Tu Date: Tue, 11 Oct 2022 12:14:28 -0700 Subject: [PATCH 043/498] =?UTF-8?q?=F0=9F=90=9E=20Source=20google=20sheets?= =?UTF-8?q?:=20fix=20nonetype=20exception=20when=20no=20spreadsheet=20id?= =?UTF-8?q?=20is=20found=20(#17766)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Check if the match is not none * Bump version * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-google-sheets/Dockerfile | 2 +- .../source-google-sheets/google_sheets_source/helpers.py | 2 +- docs/integrations/sources/google-sheets.md | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 4b514d884d6d..eb022795e328 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -418,7 +418,7 @@ - name: Google Sheets sourceDefinitionId: 71607ba1-c0ac-4799-8049-7f4b90dd50f7 dockerRepository: airbyte/source-google-sheets - dockerImageTag: 0.2.19 + dockerImageTag: 0.2.20 documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets icon: google-sheets.svg sourceType: file diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index b4f2afca068d..e1486202b755 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -4354,7 +4354,7 @@ oauthFlowOutputParameters: - - "access_token" - - "refresh_token" -- dockerImage: "airbyte/source-google-sheets:0.2.19" +- dockerImage: "airbyte/source-google-sheets:0.2.20" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-sheets" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-google-sheets/Dockerfile b/airbyte-integrations/connectors/source-google-sheets/Dockerfile index 9e3cf439c68a..83bdc7857668 100644 --- a/airbyte-integrations/connectors/source-google-sheets/Dockerfile +++ b/airbyte-integrations/connectors/source-google-sheets/Dockerfile @@ -34,5 +34,5 @@ COPY google_sheets_source ./google_sheets_source ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.19 +LABEL io.airbyte.version=0.2.20 LABEL io.airbyte.name=airbyte/source-google-sheets diff --git a/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py b/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py index 33874db0a4ba..b22c4aa1521f 100644 --- a/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py +++ b/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py @@ -202,7 +202,7 @@ def get_spreadsheet_id(id_or_url: str) -> str: if re.match(r"(http://)|(https://)", id_or_url): # This is a URL m = re.search(r"(/)([-\w]{40,})([/]?)", id_or_url) - if m.group(2): + if m is not None and m.group(2): return m.group(2) else: return id_or_url diff --git a/docs/integrations/sources/google-sheets.md b/docs/integrations/sources/google-sheets.md index 5669f68e83aa..f78f741a8f62 100644 --- a/docs/integrations/sources/google-sheets.md +++ b/docs/integrations/sources/google-sheets.md @@ -71,6 +71,7 @@ The [Google API rate limit](https://developers.google.com/sheets/api/limits) is | Version | Date | Pull Request | Subject | | ------- | ---------- | -------------------------------------------------------- | ----------------------------------------------------------------------------- | +| 0.2.20 | 2022-10-10 | [17766](https://github.com/airbytehq/airbyte/pull/17766) | Fix null pointer exception when parsing the spreadsheet id. | | 0.2.19 | 2022-09-29 | [17410](https://github.com/airbytehq/airbyte/pull/17410) | Use latest CDK. | | 0.2.18 | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states. | | 0.2.17 | 2022-08-03 | [15107](https://github.com/airbytehq/airbyte/pull/15107) | Expose Row Batch Size in Connector Specification | From e07530a81a53d66e10841c7b94dae1d6f5b04b1a Mon Sep 17 00:00:00 2001 From: Ivan Krylov Date: Tue, 11 Oct 2022 21:44:30 +0100 Subject: [PATCH 044/498] Helm Chart: remove worker tag (#17834) --- charts/airbyte/values.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/charts/airbyte/values.yaml b/charts/airbyte/values.yaml index 4b599a899312..7e9103a2ea77 100644 --- a/charts/airbyte/values.yaml +++ b/charts/airbyte/values.yaml @@ -689,7 +689,6 @@ worker: image: repository: airbyte/worker pullPolicy: IfNotPresent - tag: 0.40.14 ## worker.podAnnotations [object] Add extra annotations to the worker pod(s) ## From 30d752369e79efceb56b4eb1a8315c4a6861055f Mon Sep 17 00:00:00 2001 From: William Chhim Date: Tue, 11 Oct 2022 22:46:33 +0200 Subject: [PATCH 045/498] :bug: Helm Chart: fix the service name used by the helm test (#17667) --- charts/airbyte/templates/tests/test-webapp.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/airbyte/templates/tests/test-webapp.yaml b/charts/airbyte/templates/tests/test-webapp.yaml index 823da96dc134..a459dddd7c64 100644 --- a/charts/airbyte/templates/tests/test-webapp.yaml +++ b/charts/airbyte/templates/tests/test-webapp.yaml @@ -12,5 +12,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "common.names.fullname" . }}-webapp:{{ .Values.webapp.service.port }}'] + args: ['{{ .Release.Name }}-airbyte-webapp-svc:{{ .Values.webapp.service.port }}'] restartPolicy: Never From bd54de05d1483f93f3c723485e8a22b7a101cb7f Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Tue, 11 Oct 2022 14:10:30 -0700 Subject: [PATCH 046/498] reuse existing ports for test (#17856) --- .../airbyte/workers/process/KubePodProcessIntegrationTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java index 9ea78f2f4ac6..7dcbdb3cdfb5 100644 --- a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java +++ b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/KubePodProcessIntegrationTest.java @@ -118,8 +118,7 @@ public void testInitKubePortManagerSingletonTwice() throws Exception { KubePortManagerSingleton originalKubePortManager = KubePortManagerSingleton.getInstance(); // init the second time with the same ports - final List theSameOpenPorts = new ArrayList<>(getOpenPorts(30)); - KubePortManagerSingleton.init(new HashSet<>(theSameOpenPorts.subList(1, theSameOpenPorts.size() - 1))); + KubePortManagerSingleton.init(new HashSet<>(openPorts.subList(1, openPorts.size() - 1))); assertEquals(originalKubePortManager, KubePortManagerSingleton.getInstance()); // init the second time with different ports From e2a5c3768d535ab15a9d889a0e25048277e00173 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 11 Oct 2022 23:42:00 +0200 Subject: [PATCH 047/498] Add debug output for Osano (#17857) --- airbyte-webapp/src/utils/dataPrivacy.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/airbyte-webapp/src/utils/dataPrivacy.ts b/airbyte-webapp/src/utils/dataPrivacy.ts index 371cf6ca2dd6..428b0e31fa77 100644 --- a/airbyte-webapp/src/utils/dataPrivacy.ts +++ b/airbyte-webapp/src/utils/dataPrivacy.ts @@ -4,6 +4,7 @@ declare global { cm: { mode: "production" | "debug"; showDrawer: (type: string) => void; + addEventListener: (event: string, callback: (event: unknown) => void) => void; }; }; } @@ -63,6 +64,14 @@ export const loadOsano = (): void => { // Create and append the script tag to load osano const script = document.createElement("script"); script.src = `https://cmp.osano.com/${process.env.REACT_APP_OSANO}/osano.js`; + script.addEventListener("load", () => { + window.Osano?.cm.addEventListener("osano-cm-script-blocked", (item) => { + console.debug(`Script blocked by Osano: ${item}`); + }); + window.Osano?.cm.addEventListener("osano-cm-cookie-blocked", (item) => { + console.debug(`Cookie blocked by Osano: ${item}`); + }); + }); document.head.appendChild(script); }; From 4652173311d4988bfe0bd415bdc063c980a5d6ff Mon Sep 17 00:00:00 2001 From: Subodh Kant Chaturvedi Date: Wed, 12 Oct 2022 03:33:35 +0530 Subject: [PATCH 048/498] postgres-source: handle 24:00:00 value for time column (#17782) * postgres-source: handle 24:00:00 value for time column * address review comment * fix test + use LocalTime.MAX * bump version * update alloy db as well * auto-bump connector version [ci skip] * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../main/resources/seed/source_definitions.yaml | 4 ++-- .../init/src/main/resources/seed/source_specs.yaml | 4 ++-- .../java/io/airbyte/db/jdbc/DateTimeConverter.java | 14 ++++++++++---- .../source-alloydb-strict-encrypt/Dockerfile | 2 +- .../connectors/source-alloydb/Dockerfile | 2 +- .../source-postgres-strict-encrypt/Dockerfile | 2 +- .../connectors/source-postgres/Dockerfile | 2 +- .../AbstractPostgresSourceDatatypeTest.java | 4 ++-- docs/integrations/sources/alloydb.md | 8 +++++--- docs/integrations/sources/postgres.md | 1 + 10 files changed, 26 insertions(+), 17 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index eb022795e328..357a4d54d941 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -17,7 +17,7 @@ - name: AlloyDB for PostgreSQL sourceDefinitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 dockerRepository: airbyte/source-alloydb - dockerImageTag: 1.0.0 + dockerImageTag: 1.0.15 documentationUrl: https://docs.airbyte.com/integrations/sources/alloydb icon: alloydb.svg sourceType: database @@ -836,7 +836,7 @@ - name: Postgres sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres - dockerImageTag: 1.0.14 + dockerImageTag: 1.0.15 documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index e1486202b755..8cb1bf1561c4 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -198,7 +198,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-alloydb:1.0.0" +- dockerImage: "airbyte/source-alloydb:1.0.15" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: @@ -8590,7 +8590,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-postgres:1.0.14" +- dockerImage: "airbyte/source-postgres:1.0.15" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DateTimeConverter.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DateTimeConverter.java index a20e63d75948..5c8b6a306b07 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DateTimeConverter.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DateTimeConverter.java @@ -145,11 +145,11 @@ public static String convertToTime(final Object time) { return localTime.format(TIME_FORMATTER); } else if (time instanceof java.time.Duration) { long value = ((Duration) time).toNanos(); - if (value >= 0 && value <= TimeUnit.DAYS.toNanos(1)) { + if (value >= 0 && value < TimeUnit.DAYS.toNanos(1)) { return LocalTime.ofNanoOfDay(value).format(TIME_FORMATTER); } else { - final long updatedValue = 0 > value ? Math.abs(value) : TimeUnit.DAYS.toNanos(1); - LOGGER.debug("Time values must use number of milliseconds greater than 0 and less than 86400000000000 but its {}, converting to {} ", value, + final long updatedValue = Math.min(Math.abs(value), LocalTime.MAX.toNanoOfDay()); + LOGGER.debug("Time values must use number of nanoseconds greater than 0 and less than 86400000000000 but its {}, converting to {} ", value, updatedValue); return LocalTime.ofNanoOfDay(updatedValue).format(TIME_FORMATTER); } @@ -158,7 +158,13 @@ public static String convertToTime(final Object time) { LOGGER.info("Unknown class for Time data type" + time.getClass()); loggedUnknownTimeClass = true; } - return LocalTime.parse(time.toString()).format(TIME_FORMATTER); + + final String valueAsString = time.toString(); + if (valueAsString.startsWith("24")) { + LOGGER.debug("Time value {} is above range, converting to 23:59:59", valueAsString); + return LocalTime.MAX.format(TIME_FORMATTER); + } + return LocalTime.parse(valueAsString).format(TIME_FORMATTER); } } diff --git a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile index 58276e0b4c6b..8410e032a8cd 100644 --- a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-alloydb-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.0 +LABEL io.airbyte.version=1.0.15 LABEL io.airbyte.name=airbyte/source-alloydb-strict-encrypt diff --git a/airbyte-integrations/connectors/source-alloydb/Dockerfile b/airbyte-integrations/connectors/source-alloydb/Dockerfile index 892cfc54321a..284a1d21d21f 100644 --- a/airbyte-integrations/connectors/source-alloydb/Dockerfile +++ b/airbyte-integrations/connectors/source-alloydb/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-alloydb COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.0 +LABEL io.airbyte.version=1.0.15 LABEL io.airbyte.name=airbyte/source-alloydb diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile index 4789c139f502..5cc9e8cb9e8e 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.14 +LABEL io.airbyte.version=1.0.15 LABEL io.airbyte.name=airbyte/source-postgres-strict-encrypt diff --git a/airbyte-integrations/connectors/source-postgres/Dockerfile b/airbyte-integrations/connectors/source-postgres/Dockerfile index 6918bbe9b972..8bb028fafeaa 100644 --- a/airbyte-integrations/connectors/source-postgres/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.14 +LABEL io.airbyte.version=1.0.15 LABEL io.airbyte.name=airbyte/source-postgres diff --git a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java index ab56d6b287b1..776aa8414f9f 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java @@ -404,9 +404,9 @@ protected void initTests() { .fullSourceDataType(fullSourceType) .airbyteType(JsonSchemaType.STRING_TIME_WITHOUT_TIMEZONE) // time column will ignore time zone - .addInsertValues("'13:00:01'", "'13:00:02+8'", "'13:00:03-8'", "'13:00:04Z'", "'13:00:05.01234Z+8'", "'13:00:00Z-8'") + .addInsertValues("'13:00:01'", "'13:00:02+8'", "'13:00:03-8'", "'13:00:04Z'", "'13:00:05.01234Z+8'", "'13:00:00Z-8'", "'24:00:00'") .addExpectedValues("13:00:01.000000", "13:00:02.000000", "13:00:03.000000", "13:00:04.000000", "13:00:05.012340", - "13:00:00.000000") + "13:00:00.000000", "23:59:59.999999") .build()); } diff --git a/docs/integrations/sources/alloydb.md b/docs/integrations/sources/alloydb.md index 33fcda0a7072..289a79262ae4 100644 --- a/docs/integrations/sources/alloydb.md +++ b/docs/integrations/sources/alloydb.md @@ -331,12 +331,14 @@ According to Postgres [documentation](https://www.postgresql.org/docs/14/datatyp | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------| +| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Align with Postgres source v.1.0.15 | | 1.0.0 | 2022-09-15 | [16776](https://github.com/airbytehq/airbyte/pull/16776) | Align with strict-encrypt version | | 0.1.0 | 2022-09-05 | [16323](https://github.com/airbytehq/airbyte/pull/16323) | Initial commit. Based on source-postgres v.1.0.7 | ## Changelog (Strict Encrypt) -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------| -| 1.0.0 | 2022-09-15 | [16776](https://github.com/airbytehq/airbyte/pull/16776) | Initial commit. Based on source-postgres-strict-encrypt | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------| +| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Align with Postgres source v.1.0.15 | +| 1.0.0 | 2022-09-15 | [16776](https://github.com/airbytehq/airbyte/pull/16776) | Initial commit. Based on source-postgres-strict-encrypt | diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index 77aab90597ef..fd6b229a3098 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -398,6 +398,7 @@ The root causes is that the WALs needed for the incremental sync has been remove | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | | 1.0.14 | 2022-10-03 | [17515](https://github.com/airbytehq/airbyte/pull/17515) | Fix an issue preventing connection using client certificate | | 1.0.13 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | | 1.0.12 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt postgres source | From 63e39e6142f8a4accd42994068315168789d41f4 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Tue, 11 Oct 2022 16:03:02 -0700 Subject: [PATCH 049/498] fix metric reporter not producing metrics (#17863) * init metric client before fetching it * move init to Application * semi-colon * remove double query --- .../main/java/io/airbyte/metrics/reporter/Application.java | 3 +++ .../java/io/airbyte/metrics/reporter/MetricRepository.java | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java index f64527149ba7..f8af2de13b23 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java @@ -4,6 +4,8 @@ package io.airbyte.metrics.reporter; +import io.airbyte.metrics.lib.MetricClientFactory; +import io.airbyte.metrics.lib.MetricEmittingApps; import io.micronaut.runtime.Micronaut; /** @@ -14,6 +16,7 @@ public class Application { public static void main(final String[] args) { + MetricClientFactory.initialize(MetricEmittingApps.METRICS_REPORTER); Micronaut.run(Application.class, args); } diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java index 267426209283..b36f6ad15b30 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java @@ -146,8 +146,9 @@ SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs WHERE updated_at >= NOW() - INTERVAL '1 HOUR' AND (jobs.status = 'failed' OR jobs.status = 'succeeded' OR jobs.status = 'cancelled'); """; - final var statuses = ctx.fetch(query).getValues("status", JobStatus.class); - final var times = ctx.fetch(query).getValues("sec", double.class); + final var queryResults = ctx.fetch(query); + final var statuses = queryResults.getValues("status", JobStatus.class); + final var times = queryResults.getValues("sec", double.class); final var results = new HashMap(); for (int i = 0; i < statuses.size(); i++) { From f650005f2a7fa502df1d2d0eb0e3b16376884c6b Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Tue, 11 Oct 2022 17:12:00 -0700 Subject: [PATCH 050/498] Remove the dependency between the orchestrator container and the worker app (#17570) * test [ci skip] * Autogenerated files * Add missing annotation * Remove unused json2Schema block from worker * Move tess * Missing deps and format * Fix test build * Add missing dependencies * PR comments * Fix acceptance test and add the seed dependency * Fix build * Fix build again --- .../temporal/config/CommonFactory.java | 20 +++++++ .../temporal/sync/OrchestratorConstants.java | 2 +- airbyte-commons-worker/build.gradle | 55 +++++++++++++++++++ .../workers/ContainerOrchestratorConfig.java | 2 +- .../workers/RecordSchemaValidator.java | 0 .../main/java/io/airbyte/workers/Worker.java | 1 - .../io/airbyte/workers/WorkerConfigs.java | 0 .../io/airbyte/workers/WorkerConstants.java | 2 + .../airbyte/workers/WorkerMetricReporter.java | 0 .../java/io/airbyte/workers/WorkerUtils.java | 0 .../io/airbyte/workers/config/WorkerMode.java | 0 .../RecordSchemaValidationException.java | 0 .../workers/exception/WorkerException.java | 0 .../general/DbtTransformationRunner.java | 0 .../general/DbtTransformationWorker.java | 0 .../general/DefaultNormalizationWorker.java | 0 .../general/DefaultReplicationWorker.java | 4 +- .../workers/general/ReplicationWorker.java | 0 .../airbyte/workers/helper/FailureHelper.java | 0 .../workers/internal/AirbyteDestination.java | 0 .../workers/internal/AirbyteMapper.java | 0 .../internal/AirbyteMessageTracker.java | 0 .../internal/AirbyteProtocolPredicate.java | 0 .../workers/internal/AirbyteSource.java | 0 .../internal/AirbyteStreamFactory.java | 0 .../internal/DefaultAirbyteDestination.java | 0 .../internal/DefaultAirbyteSource.java | 0 .../internal/DefaultAirbyteStreamFactory.java | 0 .../workers/internal/EmptyAirbyteSource.java | 0 .../workers/internal/HeartbeatMonitor.java | 0 .../workers/internal/MessageTracker.java | 0 .../workers/internal/NamespacingMapper.java | 0 .../workers/internal/StateDeltaTracker.java | 0 .../workers/internal/StateMetricsTracker.java | 0 .../VersionedAirbyteStreamFactory.java | 0 .../DefaultStateAggregator.java | 0 .../SingleStateAggregator.java | 0 .../state_aggregator/StateAggregator.java | 0 .../StreamStateAggregator.java | 0 .../DefaultNormalizationRunner.java | 0 .../NormalizationAirbyteStreamFactory.java | 0 .../normalization/NormalizationRunner.java | 0 .../NormalizationRunnerFactory.java | 0 .../normalization/NormalizationWorker.java | 0 .../process/AirbyteIntegrationLauncher.java | 0 .../workers/process/AsyncKubePodStatus.java | 0 .../process/AsyncOrchestratorPodProcess.java | 2 +- .../workers/process/DockerProcessFactory.java | 0 .../workers/process/ExitCodeWatcher.java | 0 .../workers/process/IntegrationLauncher.java | 0 .../workers/process/KubeContainerInfo.java | 0 .../io/airbyte/workers/process/KubePod.java | 0 .../airbyte/workers/process/KubePodInfo.java | 0 .../workers/process/KubePodProcess.java | 0 .../workers/process/KubePodProcessInfo.java | 0 .../process/KubePodResourceHelper.java | 0 .../process/KubePortManagerSingleton.java | 0 .../workers/process/KubeProcessFactory.java | 0 .../workers/process/ProcessFactory.java | 0 .../DockerComposeDocumentStoreClient.java | 1 - .../workers/storage}/DocumentStoreClient.java | 2 +- .../storage/GcsDocumentStoreClient.java | 1 - .../storage/S3DocumentStoreClient.java | 1 - .../airbyte/workers/storage/StateClients.java | 1 - .../workers}/sync/DbtLauncherWorker.java | 2 +- .../airbyte/workers}/sync/LauncherWorker.java | 7 ++- .../sync/NormalizationLauncherWorker.java | 2 +- .../sync/ReplicationLauncherWorker.java | 2 +- .../test_utils}/AirbyteMessageUtils.java | 2 +- .../test_utils}/TestConfigHelpers.java | 2 +- .../dbt_transformation_entrypoint.sh | 0 .../main/resources/entrypoints/sync/check.sh | 0 .../main/resources/entrypoints/sync/init.sh | 0 .../main/resources/entrypoints/sync/main.sh | 0 .../src/main/resources/image_exists.sh | 0 .../src/main/resources/sshtunneling.sh | 0 .../IntegrationLauncherConfig.yaml | 0 .../workers/RecordSchemaValidatorTest.java | 3 +- .../io/airbyte/workers/WorkerConfigsTest.java | 0 .../io/airbyte/workers/WorkerUtilsTest.java | 1 + .../DefaultNormalizationWorkerTest.java | 4 +- .../general/DefaultReplicationWorkerTest.java | 3 +- .../workers/helper/FailureHelperTest.java | 2 +- .../internal/AirbyteMessageTrackerTest.java | 1 + .../AirbyteProtocolPredicateTest.java | 3 +- .../DefaultAirbyteDestinationTest.java | 3 +- .../internal/DefaultAirbyteSourceTest.java | 1 + .../DefaultAirbyteStreamFactoryTest.java | 1 + .../internal/EmptyAirbyteSourceTest.java | 0 .../internal/HeartbeatMonitorTest.java | 0 .../internal/NamespacingMapperTest.java | 1 + .../internal/StateDeltaTrackerTest.java | 0 .../internal/StateMetricsTrackerTest.java | 1 + .../state_aggregator/StateAggregatorTest.java | 0 .../DefaultNormalizationRunnerTest.java | 0 .../NormalizationRunnerFactoryTest.java | 0 .../AirbyteIntegrationLauncherTest.java | 0 .../process/DockerProcessFactoryTest.java | 0 .../workers/process/KubePodProcessTest.java | 0 .../workers/process/ProcessFactoryTest.java | 0 .../DockerComposeDocumentStoreClientTest.java | 0 .../storage/GcsDocumentStoreClientTest.java | 0 .../storage/S3DocumentStoreClientTest.java | 0 airbyte-container-orchestrator/build.gradle | 3 +- .../ContainerOrchestratorApp.java | 8 +-- .../DbtJobOrchestrator.java | 2 +- .../DefaultAsyncStateManager.java | 2 +- .../JobOrchestrator.java | 2 +- .../NormalizationJobOrchestrator.java | 2 +- .../ReplicationJobOrchestrator.java | 2 +- .../DefaultAsyncStateManagerTest.java | 2 +- .../bases/base-normalization/build.gradle | 5 +- .../standard-destination-test/build.gradle | 1 + .../bases/standard-source-test/build.gradle | 1 + .../connectors/destination-gcs/build.gradle | 1 + airbyte-server/build.gradle | 1 + airbyte-workers/build.gradle | 18 +----- .../config/ApplicationBeanFactory.java | 7 --- ...ontainerOrchestratorConfigBeanFactory.java | 2 +- .../WorkerConfigurationBeanFactory.java | 4 +- .../sync/DbtTransformationActivityImpl.java | 1 + .../sync/NormalizationActivityImpl.java | 1 + .../sync/ReplicationActivityImpl.java | 1 + ...OrchestratorPodProcessIntegrationTest.java | 4 +- .../DefaultCheckConnectionWorkerTest.java | 2 +- .../DefaultDiscoverCatalogWorkerTest.java | 2 +- .../general/DefaultGetSpecWorkerTest.java | 2 +- .../temporal/sync/SyncWorkflowTest.java | 2 +- settings.gradle | 2 +- 129 files changed, 143 insertions(+), 72 deletions(-) create mode 100644 airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/CommonFactory.java rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/sync/OrchestratorConstants.java (98%) create mode 100644 airbyte-commons-worker/build.gradle rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java (94%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/RecordSchemaValidator.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/Worker.java (91%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/WorkerConfigs.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/WorkerConstants.java (89%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/WorkerMetricReporter.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/WorkerUtils.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/config/WorkerMode.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/exception/RecordSchemaValidationException.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/exception/WorkerException.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DbtTransformationRunner.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/ReplicationWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/helper/FailureHelper.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/AirbyteDestination.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/AirbyteMapper.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/AirbyteSource.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/AirbyteStreamFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/HeartbeatMonitor.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/MessageTracker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/NamespacingMapper.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/StateMetricsTracker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/state_aggregator/DefaultStateAggregator.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/state_aggregator/StateAggregator.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/normalization/NormalizationAirbyteStreamFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/normalization/NormalizationRunner.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/normalization/NormalizationWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/AsyncKubePodStatus.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/DockerProcessFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/ExitCodeWatcher.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/IntegrationLauncher.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubeContainerInfo.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubePod.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubePodInfo.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubePodProcess.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubePodProcessInfo.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubePodResourceHelper.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/KubeProcessFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/process/ProcessFactory.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java (97%) rename {airbyte-workers/src/main/java/io/airbyte/workers/general => airbyte-commons-worker/src/main/java/io/airbyte/workers/storage}/DocumentStoreClient.java (95%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java (97%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java (98%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/storage/StateClients.java (94%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal => airbyte-commons-worker/src/main/java/io/airbyte/workers}/sync/DbtLauncherWorker.java (97%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal => airbyte-commons-worker/src/main/java/io/airbyte/workers}/sync/LauncherWorker.java (97%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal => airbyte-commons-worker/src/main/java/io/airbyte/workers}/sync/NormalizationLauncherWorker.java (97%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal => airbyte-commons-worker/src/main/java/io/airbyte/workers}/sync/ReplicationLauncherWorker.java (98%) rename {airbyte-workers/src/test/java/io/airbyte/workers/internal => airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils}/AirbyteMessageUtils.java (99%) rename {airbyte-workers/src/test/java/io/airbyte/workers => airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils}/TestConfigHelpers.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/dbt_transformation_entrypoint.sh (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/entrypoints/sync/check.sh (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/entrypoints/sync/init.sh (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/entrypoints/sync/main.sh (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/image_exists.sh (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/sshtunneling.sh (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/resources/workers_models/IntegrationLauncherConfig.yaml (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java (94%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/WorkerConfigsTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/WorkerUtilsTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java (98%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java (94%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java (98%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java (98%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/EmptyAirbyteSourceTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/HeartbeatMonitorTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/StateDeltaTrackerTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/internal/state_aggregator/StateAggregatorTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/normalization/DefaultNormalizationRunnerTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/normalization/NormalizationRunnerFactoryTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/process/AirbyteIntegrationLauncherTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/process/DockerProcessFactoryTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/process/KubePodProcessTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/process/ProcessFactoryTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClientTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/storage/GcsDocumentStoreClientTest.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/test/java/io/airbyte/workers/storage/S3DocumentStoreClientTest.java (100%) diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/CommonFactory.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/CommonFactory.java new file mode 100644 index 000000000000..a337af2c055b --- /dev/null +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/CommonFactory.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.temporal.config; + +import io.airbyte.config.Configs.WorkerEnvironment; +import io.micronaut.context.annotation.Factory; +import io.micronaut.context.env.Environment; +import jakarta.inject.Singleton; + +@Factory +public class CommonFactory { + + @Singleton + public WorkerEnvironment workerEnvironment(final Environment environment) { + return environment.getActiveNames().contains(Environment.KUBERNETES) ? WorkerEnvironment.KUBERNETES : WorkerEnvironment.DOCKER; + } + +} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/OrchestratorConstants.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/sync/OrchestratorConstants.java similarity index 98% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/OrchestratorConstants.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/sync/OrchestratorConstants.java index 02afc3b94516..7a7c7806d7d0 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/OrchestratorConstants.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/sync/OrchestratorConstants.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.sync; +package io.airbyte.commons.temporal.sync; import com.uber.m3.util.ImmutableSet; import io.airbyte.commons.features.EnvVariableFeatureFlags; diff --git a/airbyte-commons-worker/build.gradle b/airbyte-commons-worker/build.gradle new file mode 100644 index 000000000000..b0fde55849bb --- /dev/null +++ b/airbyte-commons-worker/build.gradle @@ -0,0 +1,55 @@ +import org.jsonschema2pojo.SourceType + +plugins { + id "java-library" + id 'com.github.eirnym.js2p' version '1.0' +} + +dependencies { + annotationProcessor platform(libs.micronaut.bom) + annotationProcessor libs.bundles.micronaut.annotation.processor + + implementation platform(libs.micronaut.bom) + implementation libs.bundles.micronaut + + implementation 'io.fabric8:kubernetes-client:5.12.2' + implementation 'io.temporal:temporal-sdk:1.8.1' + implementation 'org.apache.ant:ant:1.10.10' + implementation 'org.apache.commons:commons-text:1.9' + + implementation project(':airbyte-commons-protocol') + implementation project(':airbyte-commons-temporal') + implementation project(':airbyte-config:config-models') + implementation project(':airbyte-json-validation') + implementation project(':airbyte-metrics:metrics-lib') + implementation project(':airbyte-persistence:job-persistence') + implementation project(':airbyte-protocol:protocol-models') + + testAnnotationProcessor platform(libs.micronaut.bom) + testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor + + testImplementation libs.bundles.micronaut.test + testImplementation 'com.jayway.jsonpath:json-path:2.7.0' + testImplementation 'org.mockito:mockito-inline:4.7.0' + testImplementation libs.postgresql + testImplementation libs.platform.testcontainers + testImplementation libs.platform.testcontainers.postgresql + + testImplementation project(':airbyte-commons-docker') +} + +jsonSchema2Pojo { + sourceType = SourceType.YAMLSCHEMA + source = files("${sourceSets.main.output.resourcesDir}/workers_models") + targetDirectory = new File(project.buildDir, 'generated/src/gen/java/') + removeOldOutput = true + + targetPackage = 'io.airbyte.persistence.job.models' + + useLongIntegers = true + generateBuilders = true + includeConstructors = false + includeSetters = true +} + +Task publishArtifactsTask = getPublishArtifactsTask("$rootProject.ext.version", project) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java similarity index 94% rename from airbyte-workers/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java index c3f29292e7cf..17d8c2569b49 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java @@ -5,7 +5,7 @@ package io.airbyte.workers; import io.airbyte.config.Configs.WorkerEnvironment; -import io.airbyte.workers.general.DocumentStoreClient; +import io.airbyte.workers.storage.DocumentStoreClient; import io.fabric8.kubernetes.client.KubernetesClient; public record ContainerOrchestratorConfig( diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/RecordSchemaValidator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/RecordSchemaValidator.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/RecordSchemaValidator.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/RecordSchemaValidator.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/Worker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/Worker.java similarity index 91% rename from airbyte-workers/src/main/java/io/airbyte/workers/Worker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/Worker.java index 1a8b28bae57d..22890fa633fd 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/Worker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/Worker.java @@ -5,7 +5,6 @@ package io.airbyte.workers; import io.airbyte.workers.exception.WorkerException; -import io.airbyte.workers.general.DefaultReplicationWorker; import java.nio.file.Path; public interface Worker { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/WorkerConfigs.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerConfigs.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/WorkerConfigs.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerConfigs.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/WorkerConstants.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerConstants.java similarity index 89% rename from airbyte-workers/src/main/java/io/airbyte/workers/WorkerConstants.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerConstants.java index 0b3fd4a213ff..3e5a63504bd9 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/WorkerConstants.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerConstants.java @@ -15,4 +15,6 @@ public class WorkerConstants { public static final String RESET_JOB_SOURCE_DOCKER_IMAGE_STUB = "airbyte_empty"; + public static final String WORKER_ENVIRONMENT = "WORKER_ENVIRONMENT"; + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/WorkerMetricReporter.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerMetricReporter.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/WorkerMetricReporter.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerMetricReporter.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/WorkerUtils.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerUtils.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/WorkerUtils.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerUtils.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerMode.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/config/WorkerMode.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerMode.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/config/WorkerMode.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/exception/RecordSchemaValidationException.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/exception/RecordSchemaValidationException.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/exception/RecordSchemaValidationException.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/exception/RecordSchemaValidationException.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/exception/WorkerException.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/exception/WorkerException.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/exception/WorkerException.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/exception/WorkerException.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DbtTransformationRunner.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationRunner.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DbtTransformationRunner.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationRunner.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java similarity index 99% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java index 7ce35ae0b9ee..d92a88e7bcda 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java @@ -19,7 +19,9 @@ import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteMessage.Type; import io.airbyte.protocol.models.AirbyteRecordMessage; -import io.airbyte.workers.*; +import io.airbyte.workers.RecordSchemaValidator; +import io.airbyte.workers.WorkerMetricReporter; +import io.airbyte.workers.WorkerUtils; import io.airbyte.workers.exception.RecordSchemaValidationException; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.helper.FailureHelper; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/ReplicationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/ReplicationWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/ReplicationWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/ReplicationWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/helper/FailureHelper.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/FailureHelper.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/helper/FailureHelper.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/FailureHelper.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteDestination.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteDestination.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteDestination.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteDestination.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteMapper.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMapper.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteMapper.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMapper.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteSource.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteSource.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteSource.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteSource.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteStreamFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/AirbyteStreamFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteStreamFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/HeartbeatMonitor.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/HeartbeatMonitor.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/HeartbeatMonitor.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/HeartbeatMonitor.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/MessageTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/MessageTracker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/MessageTracker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/MessageTracker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/NamespacingMapper.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/NamespacingMapper.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/NamespacingMapper.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/NamespacingMapper.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/StateMetricsTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateMetricsTracker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/StateMetricsTracker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateMetricsTracker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/DefaultStateAggregator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/DefaultStateAggregator.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/DefaultStateAggregator.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/DefaultStateAggregator.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/StateAggregator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StateAggregator.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/StateAggregator.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StateAggregator.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/DefaultNormalizationRunner.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationAirbyteStreamFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationAirbyteStreamFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationAirbyteStreamFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationRunner.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunner.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationRunner.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunner.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/AsyncKubePodStatus.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncKubePodStatus.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/AsyncKubePodStatus.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncKubePodStatus.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java similarity index 99% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java index a1017cac2891..49db733cfbd8 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java @@ -10,7 +10,7 @@ import io.airbyte.config.EnvConfigs; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.helpers.LogClientSingleton; -import io.airbyte.workers.general.DocumentStoreClient; +import io.airbyte.workers.storage.DocumentStoreClient; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.DeletionPropagation; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/DockerProcessFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/DockerProcessFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/DockerProcessFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/DockerProcessFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/ExitCodeWatcher.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/ExitCodeWatcher.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/ExitCodeWatcher.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/ExitCodeWatcher.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/IntegrationLauncher.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/IntegrationLauncher.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/IntegrationLauncher.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/IntegrationLauncher.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubeContainerInfo.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubeContainerInfo.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubeContainerInfo.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubeContainerInfo.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePod.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePod.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubePod.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePod.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodInfo.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodInfo.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodInfo.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodInfo.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodProcess.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodProcess.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodProcess.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodProcess.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodProcessInfo.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodProcessInfo.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodProcessInfo.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodProcessInfo.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodResourceHelper.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodResourceHelper.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubePodResourceHelper.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePodResourceHelper.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubePortManagerSingleton.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/KubeProcessFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubeProcessFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/KubeProcessFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/KubeProcessFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/process/ProcessFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/ProcessFactory.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/process/ProcessFactory.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/process/ProcessFactory.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java similarity index 97% rename from airbyte-workers/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java index c6d2c57ab50b..56ddd8584bcb 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClient.java @@ -5,7 +5,6 @@ package io.airbyte.workers.storage; import io.airbyte.commons.io.IOs; -import io.airbyte.workers.general.DocumentStoreClient; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DocumentStoreClient.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/DocumentStoreClient.java similarity index 95% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DocumentStoreClient.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/DocumentStoreClient.java index d92c960f9f9e..01e9e0566ea1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/general/DocumentStoreClient.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/DocumentStoreClient.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.general; +package io.airbyte.workers.storage; import java.util.Optional; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java similarity index 97% rename from airbyte-workers/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java index d0dab2f0dac5..0a720628f1c7 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/GcsDocumentStoreClient.java @@ -10,7 +10,6 @@ import com.google.cloud.storage.Storage; import io.airbyte.config.storage.CloudStorageConfigs.GcsConfig; import io.airbyte.config.storage.DefaultGcsClientFactory; -import io.airbyte.workers.general.DocumentStoreClient; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Optional; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java similarity index 98% rename from airbyte-workers/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java index 592cf9d08dea..12e107411ec4 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/S3DocumentStoreClient.java @@ -8,7 +8,6 @@ import io.airbyte.config.storage.CloudStorageConfigs.S3Config; import io.airbyte.config.storage.DefaultS3ClientFactory; import io.airbyte.config.storage.MinioS3ClientFactory; -import io.airbyte.workers.general.DocumentStoreClient; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Optional; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/storage/StateClients.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/StateClients.java similarity index 94% rename from airbyte-workers/src/main/java/io/airbyte/workers/storage/StateClients.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/StateClients.java index b6bc7bd1ba45..1b713fc572c2 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/storage/StateClients.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/storage/StateClients.java @@ -5,7 +5,6 @@ package io.airbyte.workers.storage; import io.airbyte.config.storage.CloudStorageConfigs; -import io.airbyte.workers.general.DocumentStoreClient; import java.nio.file.Path; public class StateClients { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtLauncherWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/DbtLauncherWorker.java similarity index 97% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtLauncherWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/DbtLauncherWorker.java index 52ebaa9ce3a9..184b1f7ea23a 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtLauncherWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/DbtLauncherWorker.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.sync; +package io.airbyte.workers.sync; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalUtils; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/LauncherWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java similarity index 97% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/LauncherWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java index 7b1d848ad958..a02552917074 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/LauncherWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java @@ -2,18 +2,19 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.sync; +package io.airbyte.workers.sync; import com.google.common.base.Stopwatch; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; import io.airbyte.commons.temporal.TemporalUtils; +import io.airbyte.commons.temporal.sync.OrchestratorConstants; import io.airbyte.config.ResourceRequirements; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.ContainerOrchestratorConfig; import io.airbyte.workers.Worker; -import io.airbyte.workers.config.WorkerConfigurationBeanFactory; +import io.airbyte.workers.WorkerConstants; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.process.AsyncKubePodStatus; import io.airbyte.workers.process.AsyncOrchestratorPodProcess; @@ -104,7 +105,7 @@ public OUTPUT run(final INPUT input, final Path jobRoot) throws WorkerException .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); // Manually add the worker environment to the env var map - envMap.put(WorkerConfigurationBeanFactory.WORKER_ENVIRONMENT, containerOrchestratorConfig.workerEnvironment().name()); + envMap.put(WorkerConstants.WORKER_ENVIRONMENT, containerOrchestratorConfig.workerEnvironment().name()); final Map fileMap = new HashMap<>(additionalFileMap); fileMap.putAll(Map.of( diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationLauncherWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/NormalizationLauncherWorker.java similarity index 97% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationLauncherWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/NormalizationLauncherWorker.java index 62bbaadbdb36..92149919037f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationLauncherWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/NormalizationLauncherWorker.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.sync; +package io.airbyte.workers.sync; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalUtils; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationLauncherWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/ReplicationLauncherWorker.java similarity index 98% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationLauncherWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/ReplicationLauncherWorker.java index c8da41af2bf3..0851322dae44 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationLauncherWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/ReplicationLauncherWorker.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.sync; +package io.airbyte.workers.sync; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalUtils; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteMessageUtils.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils/AirbyteMessageUtils.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteMessageUtils.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils/AirbyteMessageUtils.java index e39d240dc352..2aede7159739 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteMessageUtils.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils/AirbyteMessageUtils.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.internal; +package io.airbyte.workers.test_utils; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/TestConfigHelpers.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils/TestConfigHelpers.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/TestConfigHelpers.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils/TestConfigHelpers.java index 6ccbd7f1faa6..30e055982e79 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/TestConfigHelpers.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/test_utils/TestConfigHelpers.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers; +package io.airbyte.workers.test_utils; import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.json.Jsons; diff --git a/airbyte-workers/src/main/resources/dbt_transformation_entrypoint.sh b/airbyte-commons-worker/src/main/resources/dbt_transformation_entrypoint.sh similarity index 100% rename from airbyte-workers/src/main/resources/dbt_transformation_entrypoint.sh rename to airbyte-commons-worker/src/main/resources/dbt_transformation_entrypoint.sh diff --git a/airbyte-workers/src/main/resources/entrypoints/sync/check.sh b/airbyte-commons-worker/src/main/resources/entrypoints/sync/check.sh similarity index 100% rename from airbyte-workers/src/main/resources/entrypoints/sync/check.sh rename to airbyte-commons-worker/src/main/resources/entrypoints/sync/check.sh diff --git a/airbyte-workers/src/main/resources/entrypoints/sync/init.sh b/airbyte-commons-worker/src/main/resources/entrypoints/sync/init.sh similarity index 100% rename from airbyte-workers/src/main/resources/entrypoints/sync/init.sh rename to airbyte-commons-worker/src/main/resources/entrypoints/sync/init.sh diff --git a/airbyte-workers/src/main/resources/entrypoints/sync/main.sh b/airbyte-commons-worker/src/main/resources/entrypoints/sync/main.sh similarity index 100% rename from airbyte-workers/src/main/resources/entrypoints/sync/main.sh rename to airbyte-commons-worker/src/main/resources/entrypoints/sync/main.sh diff --git a/airbyte-workers/src/main/resources/image_exists.sh b/airbyte-commons-worker/src/main/resources/image_exists.sh similarity index 100% rename from airbyte-workers/src/main/resources/image_exists.sh rename to airbyte-commons-worker/src/main/resources/image_exists.sh diff --git a/airbyte-workers/src/main/resources/sshtunneling.sh b/airbyte-commons-worker/src/main/resources/sshtunneling.sh similarity index 100% rename from airbyte-workers/src/main/resources/sshtunneling.sh rename to airbyte-commons-worker/src/main/resources/sshtunneling.sh diff --git a/airbyte-workers/src/main/resources/workers_models/IntegrationLauncherConfig.yaml b/airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml similarity index 100% rename from airbyte-workers/src/main/resources/workers_models/IntegrationLauncherConfig.yaml rename to airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java similarity index 94% rename from airbyte-workers/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java index e3365e466f41..3412b5573e0f 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/RecordSchemaValidatorTest.java @@ -10,7 +10,8 @@ import io.airbyte.config.StandardSyncInput; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.workers.exception.RecordSchemaValidationException; -import io.airbyte.workers.internal.AirbyteMessageUtils; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; +import io.airbyte.workers.test_utils.TestConfigHelpers; import org.apache.commons.lang3.tuple.ImmutablePair; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/WorkerConfigsTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/WorkerConfigsTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/WorkerConfigsTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/WorkerConfigsTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/WorkerUtilsTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/WorkerUtilsTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/WorkerUtilsTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/WorkerUtilsTest.java index 39da15dd4cf1..b0f51c46f962 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/WorkerUtilsTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/WorkerUtilsTest.java @@ -17,6 +17,7 @@ import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncInput; import io.airbyte.workers.internal.HeartbeatMonitor; +import io.airbyte.workers.test_utils.TestConfigHelpers; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.Map; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java similarity index 98% rename from airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java index c4f8e2fc20dd..fdd702c3d743 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultNormalizationWorkerTest.java @@ -20,11 +20,11 @@ import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncInput; import io.airbyte.protocol.models.AirbyteTraceMessage; -import io.airbyte.workers.TestConfigHelpers; import io.airbyte.workers.WorkerConfigs; import io.airbyte.workers.exception.WorkerException; -import io.airbyte.workers.internal.AirbyteMessageUtils; import io.airbyte.workers.normalization.NormalizationRunner; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; +import io.airbyte.workers.test_utils.TestConfigHelpers; import java.nio.file.Files; import java.nio.file.Path; import java.util.stream.Stream; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java index 95b2fcc91933..5f722b3e95ed 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java @@ -50,9 +50,10 @@ import io.airbyte.workers.helper.FailureHelper; import io.airbyte.workers.internal.AirbyteDestination; import io.airbyte.workers.internal.AirbyteMessageTracker; -import io.airbyte.workers.internal.AirbyteMessageUtils; import io.airbyte.workers.internal.AirbyteSource; import io.airbyte.workers.internal.NamespacingMapper; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; +import io.airbyte.workers.test_utils.TestConfigHelpers; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java index cb0e374e96f4..edbad7d065f8 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/helper/FailureHelperTest.java @@ -14,7 +14,7 @@ import io.airbyte.protocol.models.AirbyteErrorTraceMessage; import io.airbyte.protocol.models.AirbyteTraceMessage; import io.airbyte.workers.helper.FailureHelper.ConnectorCommand; -import io.airbyte.workers.internal.AirbyteMessageUtils; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.util.List; import java.util.Map; import java.util.Set; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java index 6eb5882d670f..313debe985eb 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/AirbyteMessageTrackerTest.java @@ -14,6 +14,7 @@ import io.airbyte.workers.helper.FailureHelper; import io.airbyte.workers.internal.StateDeltaTracker.StateDeltaTrackerException; import io.airbyte.workers.internal.state_aggregator.StateAggregator; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.BeforeEach; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java similarity index 94% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java index 9750e4fbf7b3..aa6194ae1b16 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/AirbyteProtocolPredicateTest.java @@ -4,10 +4,11 @@ package io.airbyte.workers.internal; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import io.airbyte.commons.json.Jsons; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java similarity index 98% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java index 9a2eef1cf3a6..a9464b477e2c 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteDestinationTest.java @@ -25,11 +25,12 @@ import io.airbyte.config.helpers.LogClientSingleton; import io.airbyte.config.helpers.LogConfigs; import io.airbyte.protocol.models.AirbyteMessage; -import io.airbyte.workers.TestConfigHelpers; import io.airbyte.workers.WorkerConstants; import io.airbyte.workers.WorkerUtils; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.process.IntegrationLauncher; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; +import io.airbyte.workers.test_utils.TestConfigHelpers; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java index 38fcf48daf1d..0aadd7aa265a 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteSourceTest.java @@ -33,6 +33,7 @@ import io.airbyte.workers.WorkerConstants; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.process.IntegrationLauncher; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java similarity index 98% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java index 226fd06fdc4b..ceba51531b91 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactoryTest.java @@ -17,6 +17,7 @@ import io.airbyte.commons.logging.MdcScope.Builder; import io.airbyte.protocol.models.AirbyteLogMessage; import io.airbyte.protocol.models.AirbyteMessage; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/EmptyAirbyteSourceTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/EmptyAirbyteSourceTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/EmptyAirbyteSourceTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/EmptyAirbyteSourceTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/HeartbeatMonitorTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/HeartbeatMonitorTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/HeartbeatMonitorTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/HeartbeatMonitorTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java index 8c19f199fc8a..5bfba9bfc36c 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/NamespacingMapperTest.java @@ -13,6 +13,7 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.Field; import io.airbyte.protocol.models.JsonSchemaType; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import org.junit.jupiter.api.Test; class NamespacingMapperTest { diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/StateDeltaTrackerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/StateDeltaTrackerTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/StateDeltaTrackerTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/StateDeltaTrackerTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java similarity index 99% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java index dd17e18566af..8c2759e83651 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/StateMetricsTrackerTest.java @@ -11,6 +11,7 @@ import io.airbyte.protocol.models.AirbyteStateMessage; import io.airbyte.workers.internal.StateMetricsTracker.StateMetricsTrackerNoStateMatchException; import io.airbyte.workers.internal.StateMetricsTracker.StateMetricsTrackerOomException; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import org.junit.jupiter.api.BeforeEach; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/internal/state_aggregator/StateAggregatorTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/state_aggregator/StateAggregatorTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/internal/state_aggregator/StateAggregatorTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/state_aggregator/StateAggregatorTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/normalization/DefaultNormalizationRunnerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/normalization/DefaultNormalizationRunnerTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/normalization/DefaultNormalizationRunnerTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/normalization/DefaultNormalizationRunnerTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/normalization/NormalizationRunnerFactoryTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/normalization/NormalizationRunnerFactoryTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/normalization/NormalizationRunnerFactoryTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/normalization/NormalizationRunnerFactoryTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/process/AirbyteIntegrationLauncherTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/process/AirbyteIntegrationLauncherTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/process/AirbyteIntegrationLauncherTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/process/AirbyteIntegrationLauncherTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/process/DockerProcessFactoryTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/process/DockerProcessFactoryTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/process/DockerProcessFactoryTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/process/DockerProcessFactoryTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/process/KubePodProcessTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/process/KubePodProcessTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/process/KubePodProcessTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/process/KubePodProcessTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/process/ProcessFactoryTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/process/ProcessFactoryTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/process/ProcessFactoryTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/process/ProcessFactoryTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClientTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClientTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClientTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/storage/DockerComposeDocumentStoreClientTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/storage/GcsDocumentStoreClientTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/storage/GcsDocumentStoreClientTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/storage/GcsDocumentStoreClientTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/storage/GcsDocumentStoreClientTest.java diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/storage/S3DocumentStoreClientTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/storage/S3DocumentStoreClientTest.java similarity index 100% rename from airbyte-workers/src/test/java/io/airbyte/workers/storage/S3DocumentStoreClientTest.java rename to airbyte-commons-worker/src/test/java/io/airbyte/workers/storage/S3DocumentStoreClientTest.java diff --git a/airbyte-container-orchestrator/build.gradle b/airbyte-container-orchestrator/build.gradle index d088356a5047..98c4b42bf3f1 100644 --- a/airbyte-container-orchestrator/build.gradle +++ b/airbyte-container-orchestrator/build.gradle @@ -14,11 +14,12 @@ dependencies { implementation project(':airbyte-config:config-models') implementation project(':airbyte-config:config-persistence') implementation project(':airbyte-commons-temporal') + implementation project(':airbyte-commons-worker') + implementation project(':airbyte-config:init') implementation project(':airbyte-db:db-lib') implementation project(':airbyte-json-validation') implementation project(':airbyte-protocol:protocol-models') implementation project(':airbyte-persistence:job-persistence') - implementation project(':airbyte-workers') implementation project(':airbyte-metrics:metrics-lib') testImplementation 'org.mockito:mockito-inline:2.13.0' diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java index 7b9a5d9b6fba..1d22e8af5f6b 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java @@ -8,6 +8,7 @@ import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.logging.LoggingHelper; import io.airbyte.commons.logging.MdcScope; +import io.airbyte.commons.temporal.sync.OrchestratorConstants; import io.airbyte.config.Configs; import io.airbyte.config.EnvConfigs; import io.airbyte.config.helpers.LogClientSingleton; @@ -23,10 +24,9 @@ import io.airbyte.workers.process.KubeProcessFactory; import io.airbyte.workers.process.ProcessFactory; import io.airbyte.workers.storage.StateClients; -import io.airbyte.workers.temporal.sync.DbtLauncherWorker; -import io.airbyte.workers.temporal.sync.NormalizationLauncherWorker; -import io.airbyte.workers.temporal.sync.OrchestratorConstants; -import io.airbyte.workers.temporal.sync.ReplicationLauncherWorker; +import io.airbyte.workers.sync.DbtLauncherWorker; +import io.airbyte.workers.sync.NormalizationLauncherWorker; +import io.airbyte.workers.sync.ReplicationLauncherWorker; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; import java.io.IOException; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java index b46a34d9acf7..b8c659e566f2 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java @@ -15,7 +15,7 @@ import io.airbyte.workers.normalization.NormalizationRunnerFactory; import io.airbyte.workers.process.KubePodProcess; import io.airbyte.workers.process.ProcessFactory; -import io.airbyte.workers.temporal.sync.ReplicationLauncherWorker; +import io.airbyte.workers.sync.ReplicationLauncherWorker; import java.nio.file.Path; import java.util.Optional; import lombok.extern.slf4j.Slf4j; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DefaultAsyncStateManager.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DefaultAsyncStateManager.java index 2114066d5495..0bd3cb081716 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DefaultAsyncStateManager.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DefaultAsyncStateManager.java @@ -4,9 +4,9 @@ package io.airbyte.container_orchestrator; -import io.airbyte.workers.general.DocumentStoreClient; import io.airbyte.workers.process.AsyncKubePodStatus; import io.airbyte.workers.process.KubePodInfo; +import io.airbyte.workers.storage.DocumentStoreClient; import java.util.List; import lombok.extern.slf4j.Slf4j; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/JobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/JobOrchestrator.java index 65395b7547a7..6c3b3897ab20 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/JobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/JobOrchestrator.java @@ -5,11 +5,11 @@ package io.airbyte.container_orchestrator; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.sync.OrchestratorConstants; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.process.AsyncOrchestratorPodProcess; import io.airbyte.workers.process.KubePodInfo; import io.airbyte.workers.process.KubePodProcess; -import io.airbyte.workers.temporal.sync.OrchestratorConstants; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java index 2427ccafe06b..bf3387a1a11c 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java @@ -16,7 +16,7 @@ import io.airbyte.workers.normalization.NormalizationWorker; import io.airbyte.workers.process.KubePodProcess; import io.airbyte.workers.process.ProcessFactory; -import io.airbyte.workers.temporal.sync.ReplicationLauncherWorker; +import io.airbyte.workers.sync.ReplicationLauncherWorker; import java.nio.file.Path; import java.util.Optional; import lombok.extern.slf4j.Slf4j; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java index 53d575ff2e2c..728851a740a0 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java @@ -30,7 +30,7 @@ import io.airbyte.workers.process.IntegrationLauncher; import io.airbyte.workers.process.KubePodProcess; import io.airbyte.workers.process.ProcessFactory; -import io.airbyte.workers.temporal.sync.ReplicationLauncherWorker; +import io.airbyte.workers.sync.ReplicationLauncherWorker; import java.nio.file.Path; import java.util.Optional; import lombok.extern.slf4j.Slf4j; diff --git a/airbyte-container-orchestrator/src/test/java/io/airbyte/container_orchestrator/DefaultAsyncStateManagerTest.java b/airbyte-container-orchestrator/src/test/java/io/airbyte/container_orchestrator/DefaultAsyncStateManagerTest.java index 76b2b85b929c..bbde4fd333c0 100644 --- a/airbyte-container-orchestrator/src/test/java/io/airbyte/container_orchestrator/DefaultAsyncStateManagerTest.java +++ b/airbyte-container-orchestrator/src/test/java/io/airbyte/container_orchestrator/DefaultAsyncStateManagerTest.java @@ -9,10 +9,10 @@ import static org.mockito.Mockito.*; import static org.mockito.Mockito.when; -import io.airbyte.workers.general.DocumentStoreClient; import io.airbyte.workers.process.AsyncKubePodStatus; import io.airbyte.workers.process.KubeContainerInfo; import io.airbyte.workers.process.KubePodInfo; +import io.airbyte.workers.storage.DocumentStoreClient; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/airbyte-integrations/bases/base-normalization/build.gradle b/airbyte-integrations/bases/base-normalization/build.gradle index 0361d80f5316..5236723887e1 100644 --- a/airbyte-integrations/bases/base-normalization/build.gradle +++ b/airbyte-integrations/bases/base-normalization/build.gradle @@ -11,11 +11,12 @@ airbytePython { dependencies { implementation project(':airbyte-workers') + implementation project(':airbyte-commons-worker') } // we need to access the sshtunneling script from airbyte-workers for ssh support -task copySshScript(type: Copy, dependsOn: [project(':airbyte-workers').processResources]) { - from "${project(':airbyte-workers').buildDir}/resources/main" +task copySshScript(type: Copy, dependsOn: [project(':airbyte-commons-worker').processResources]) { + from "${project(':airbyte-commons-worker').buildDir}/resources/main" into "${buildDir}" include "sshtunneling.sh" } diff --git a/airbyte-integrations/bases/standard-destination-test/build.gradle b/airbyte-integrations/bases/standard-destination-test/build.gradle index bc9aee6076fe..edc9bdb84971 100644 --- a/airbyte-integrations/bases/standard-destination-test/build.gradle +++ b/airbyte-integrations/bases/standard-destination-test/build.gradle @@ -3,6 +3,7 @@ plugins { } dependencies { implementation project(':airbyte-db:db-lib') + implementation project(':airbyte-commons-worker') implementation project(':airbyte-config:config-models') implementation project(':airbyte-integrations:bases:base-java') implementation project(':airbyte-protocol:protocol-models') diff --git a/airbyte-integrations/bases/standard-source-test/build.gradle b/airbyte-integrations/bases/standard-source-test/build.gradle index f6d787893dc9..b27667a27491 100644 --- a/airbyte-integrations/bases/standard-source-test/build.gradle +++ b/airbyte-integrations/bases/standard-source-test/build.gradle @@ -13,6 +13,7 @@ import org.jsoup.Jsoup; dependencies { implementation project(':airbyte-db:db-lib') + implementation project(':airbyte-commons-worker') implementation project(':airbyte-config:config-models') implementation project(':airbyte-config:config-persistence') implementation project(':airbyte-protocol:protocol-models') diff --git a/airbyte-integrations/connectors/destination-gcs/build.gradle b/airbyte-integrations/connectors/destination-gcs/build.gradle index 726c923f69a8..590e3d849500 100644 --- a/airbyte-integrations/connectors/destination-gcs/build.gradle +++ b/airbyte-integrations/connectors/destination-gcs/build.gradle @@ -44,4 +44,5 @@ dependencies { integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-gcs') integrationTestJavaImplementation project(':airbyte-workers') + integrationTestJavaImplementation project(':airbyte-commons-worker') } diff --git a/airbyte-server/build.gradle b/airbyte-server/build.gradle index e3dddbe3ca50..b39aad432696 100644 --- a/airbyte-server/build.gradle +++ b/airbyte-server/build.gradle @@ -11,6 +11,7 @@ dependencies { implementation project(':airbyte-api') implementation project(':airbyte-commons-docker') implementation project(':airbyte-commons-temporal') + implementation project(':airbyte-commons-worker') implementation project(':airbyte-config:init') implementation project(':airbyte-config:config-models') implementation project(':airbyte-config:config-persistence') diff --git a/airbyte-workers/build.gradle b/airbyte-workers/build.gradle index 2e740fe29aa9..94626fdaab9f 100644 --- a/airbyte-workers/build.gradle +++ b/airbyte-workers/build.gradle @@ -1,8 +1,5 @@ -import org.jsonschema2pojo.SourceType - plugins { id 'application' - id 'com.github.eirnym.js2p' version '1.0' id 'airbyte-integration-test-java' } @@ -44,6 +41,7 @@ dependencies { implementation project(':airbyte-commons-docker') implementation project(':airbyte-commons-protocol') implementation project(':airbyte-commons-temporal') + implementation project(':airbyte-commons-worker') implementation project(':airbyte-config:config-models') implementation project(':airbyte-config:config-persistence') implementation project(':airbyte-config:init') @@ -83,20 +81,6 @@ dependencies { integrationTestJavaImplementation libs.bundles.micronaut.test } -jsonSchema2Pojo { - sourceType = SourceType.YAMLSCHEMA - source = files("${sourceSets.main.output.resourcesDir}/workers_models") - targetDirectory = new File(project.buildDir, 'generated/src/gen/java/') - removeOldOutput = true - - targetPackage = 'io.airbyte.persistence.job.models' - - useLongIntegers = true - generateBuilders = true - includeConstructors = false - includeSetters = true -} - mainClassName = 'io.airbyte.workers.Application' application { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java index c14c2431cfa9..e0e7c650cacf 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java @@ -12,7 +12,6 @@ import io.airbyte.config.Configs.DeploymentMode; import io.airbyte.config.Configs.SecretPersistenceType; import io.airbyte.config.Configs.TrackingStrategy; -import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.StatePersistence; import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; @@ -28,7 +27,6 @@ import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Requires; import io.micronaut.context.annotation.Value; -import io.micronaut.context.env.Environment; import io.micronaut.core.util.StringUtils; import jakarta.inject.Named; import jakarta.inject.Singleton; @@ -68,11 +66,6 @@ public TrackingStrategy trackingStrategy(@Value("${airbyte.tracking-strategy}") return convertToEnum(trackingStrategy, TrackingStrategy::valueOf, TrackingStrategy.LOGGING); } - @Singleton - public WorkerEnvironment workerEnvironment(final Environment environment) { - return environment.getActiveNames().contains(Environment.KUBERNETES) ? WorkerEnvironment.KUBERNETES : WorkerEnvironment.DOCKER; - } - @Singleton @Named("workspaceRoot") public Path workspaceRoot(@Value("${airbyte.workspace.root}") final String workspaceRoot) { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java index e77a5d735326..23e3fa1c6a9d 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java @@ -7,7 +7,7 @@ import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.storage.CloudStorageConfigs; import io.airbyte.workers.ContainerOrchestratorConfig; -import io.airbyte.workers.general.DocumentStoreClient; +import io.airbyte.workers.storage.DocumentStoreClient; import io.airbyte.workers.storage.StateClients; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.micronaut.context.annotation.Factory; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java index a943cc211553..f418b84235a6 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java @@ -12,6 +12,7 @@ import io.airbyte.config.ResourceRequirements; import io.airbyte.config.TolerationPOJO; import io.airbyte.workers.WorkerConfigs; +import io.airbyte.workers.WorkerConstants; import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Requires; import io.micronaut.context.annotation.Value; @@ -40,7 +41,6 @@ public class WorkerConfigurationBeanFactory { private static final String DOCKER = "DOCKER"; private static final String JOB_DEFAULT_ENV_PREFIX = "JOB_DEFAULT_ENV_"; private static final String KUBERNETES = "KUBERNETES"; - public static final String WORKER_ENVIRONMENT = "WORKER_ENVIRONMENT"; @Singleton @Named("checkJobKubeAnnotations") @@ -104,7 +104,7 @@ public Map jobDefaultEnvMap( final Map jobSharedEnvMap = Map.of(AIRBYTE_ROLE, airbyteRole, AIRBYTE_VERSION, airbyteVersion, DEPLOYMENT_MODE, deploymentMode.name(), - WORKER_ENVIRONMENT, environment.getActiveNames().contains(Environment.KUBERNETES) ? KUBERNETES : DOCKER); + WorkerConstants.WORKER_ENVIRONMENT, environment.getActiveNames().contains(Environment.KUBERNETES) ? KUBERNETES : DOCKER); return MoreMaps.merge(jobPrefixedEnvMap, jobSharedEnvMap); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java index 1ee47429ae79..eee02e8c70a1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java @@ -27,6 +27,7 @@ import io.airbyte.workers.general.DbtTransformationWorker; import io.airbyte.workers.normalization.NormalizationRunnerFactory; import io.airbyte.workers.process.ProcessFactory; +import io.airbyte.workers.sync.DbtLauncherWorker; import io.airbyte.workers.temporal.TemporalAttemptExecution; import io.micronaut.context.annotation.Value; import io.temporal.activity.Activity; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java index cfa4a4659334..9b5fa6381223 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java @@ -29,6 +29,7 @@ import io.airbyte.workers.general.DefaultNormalizationWorker; import io.airbyte.workers.normalization.NormalizationRunnerFactory; import io.airbyte.workers.process.ProcessFactory; +import io.airbyte.workers.sync.NormalizationLauncherWorker; import io.airbyte.workers.temporal.TemporalAttemptExecution; import io.micronaut.context.annotation.Value; import io.temporal.activity.Activity; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java index 955f18bc6bb6..6b308968c509 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java @@ -43,6 +43,7 @@ import io.airbyte.workers.process.AirbyteIntegrationLauncher; import io.airbyte.workers.process.IntegrationLauncher; import io.airbyte.workers.process.ProcessFactory; +import io.airbyte.workers.sync.ReplicationLauncherWorker; import io.airbyte.workers.temporal.TemporalAttemptExecution; import io.micronaut.context.annotation.Value; import io.temporal.activity.Activity; diff --git a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java index d3b794315811..0e0b2b87e639 100644 --- a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java +++ b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java @@ -8,13 +8,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.sync.OrchestratorConstants; import io.airbyte.config.EnvConfigs; import io.airbyte.config.storage.CloudStorageConfigs; import io.airbyte.config.storage.MinioS3ClientFactory; import io.airbyte.workers.WorkerConfigs; -import io.airbyte.workers.general.DocumentStoreClient; +import io.airbyte.workers.storage.DocumentStoreClient; import io.airbyte.workers.storage.S3DocumentStoreClient; -import io.airbyte.workers.temporal.sync.OrchestratorConstants; import io.fabric8.kubernetes.api.model.ContainerBuilder; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.EnvVar; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultCheckConnectionWorkerTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultCheckConnectionWorkerTest.java index 0095be0c6b0a..e1443fbbb91a 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultCheckConnectionWorkerTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultCheckConnectionWorkerTest.java @@ -29,9 +29,9 @@ import io.airbyte.protocol.models.AirbyteMessage.Type; import io.airbyte.workers.WorkerConstants; import io.airbyte.workers.exception.WorkerException; -import io.airbyte.workers.internal.AirbyteMessageUtils; import io.airbyte.workers.internal.AirbyteStreamFactory; import io.airbyte.workers.process.IntegrationLauncher; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorkerTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorkerTest.java index 94b5868af0b3..ff7534073108 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorkerTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorkerTest.java @@ -38,9 +38,9 @@ import io.airbyte.protocol.models.JsonSchemaType; import io.airbyte.workers.WorkerConstants; import io.airbyte.workers.exception.WorkerException; -import io.airbyte.workers.internal.AirbyteMessageUtils; import io.airbyte.workers.internal.AirbyteStreamFactory; import io.airbyte.workers.process.IntegrationLauncher; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.file.Files; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultGetSpecWorkerTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultGetSpecWorkerTest.java index 2b06a7669fab..787641266f72 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultGetSpecWorkerTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/general/DefaultGetSpecWorkerTest.java @@ -26,8 +26,8 @@ import io.airbyte.protocol.models.AirbyteMessage.Type; import io.airbyte.protocol.models.ConnectorSpecification; import io.airbyte.workers.exception.WorkerException; -import io.airbyte.workers.internal.AirbyteMessageUtils; import io.airbyte.workers.process.IntegrationLauncher; +import io.airbyte.workers.test_utils.AirbyteMessageUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java index 1e29735bc316..8ac4fff6b202 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java @@ -29,8 +29,8 @@ import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; -import io.airbyte.workers.TestConfigHelpers; import io.airbyte.workers.temporal.support.TemporalProxyHelper; +import io.airbyte.workers.test_utils.TestConfigHelpers; import io.micronaut.context.BeanRegistration; import io.micronaut.inject.BeanIdentifier; import io.temporal.activity.ActivityCancellationType; diff --git a/settings.gradle b/settings.gradle index 3dd9eb7c9b1d..8653fd11da50 100644 --- a/settings.gradle +++ b/settings.gradle @@ -79,6 +79,7 @@ include ':airbyte-test-utils' include ':airbyte-workers' // reused by acceptance tests in connector base. include ':airbyte-analytics' // transitively used by airbyte-workers. include ':airbyte-commons-temporal' +include ':airbyte-commons-worker' include ':airbyte-config:config-persistence' // transitively used by airbyte-workers. include ':airbyte-persistence:job-persistence' // transitively used by airbyte-workers. include ':airbyte-db:jooq' // transitively used by airbyte-workers. @@ -151,4 +152,3 @@ if (!System.getenv().containsKey("SUB_BUILD") || System.getenv().get("SUB_BUILD" } } } - From f1c0c555c013f16a1d5cc7e80eb2087a6754d608 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Wed, 12 Oct 2022 11:30:41 +0300 Subject: [PATCH 051/498] Source Facebook Marketing: remove "format" from optional datetime "end_date" field (#17869) Signed-off-by: Sergey Chvalyuk --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 3 +-- .../connectors/source-facebook-marketing/Dockerfile | 2 +- .../source-facebook-marketing/acceptance-test-config.yml | 2 ++ .../source-facebook-marketing/integration_tests/spec.json | 3 +-- .../source_facebook_marketing/spec.py | 6 +++++- docs/integrations/sources/facebook-marketing.md | 1 + 7 files changed, 12 insertions(+), 7 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 357a4d54d941..1446c36113e8 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -279,7 +279,7 @@ - name: Facebook Marketing sourceDefinitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c dockerRepository: airbyte/source-facebook-marketing - dockerImageTag: 0.2.67 + dockerImageTag: 0.2.68 documentationUrl: https://docs.airbyte.com/integrations/sources/facebook-marketing icon: facebook.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 8cb1bf1561c4..63156d4c0468 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2561,7 +2561,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-facebook-marketing:0.2.67" +- dockerImage: "airbyte/source-facebook-marketing:0.2.68" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" changelogUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" @@ -2599,7 +2599,6 @@ examples: - "2017-01-26T00:00:00Z" type: "string" - format: "date-time" access_token: title: "Access Token" description: "The value of the access token generated. See the None: + schema["properties"]["end_date"].pop("format") + account_id: str = Field( title="Account ID", order=0, diff --git a/docs/integrations/sources/facebook-marketing.md b/docs/integrations/sources/facebook-marketing.md index 7bccc771b1ae..5a935aaf20ec 100644 --- a/docs/integrations/sources/facebook-marketing.md +++ b/docs/integrations/sources/facebook-marketing.md @@ -121,6 +121,7 @@ Please be informed that the connector uses the `lookback_window` parameter to pe | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 0.2.68 | 2022-10-12 | [17869](https://github.com/airbytehq/airbyte/pull/17869) | Remove "format" from optional datetime `end_date` field | | 0.2.67 | 2022-10-04 | [17551](https://github.com/airbytehq/airbyte/pull/17551) | Add `cursor_field` for custom_insights stream schema | | 0.2.65 | 2022-09-29 | [17371](https://github.com/airbytehq/airbyte/pull/17371) | Fix stream CustomConversions `enable_deleted=False` | | 0.2.64 | 2022-09-22 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | From feab43a9edb76caa6f8f210570154fd9566cfa91 Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Wed, 12 Oct 2022 11:34:22 +0300 Subject: [PATCH 052/498] Source Recharge: do not call response.json() when handling http errors (#17840) * #733 source recharge: do not call response.json when handling http errors * source recharge: upd changelog * source recharge: rm unused import * source recharge: tune unit tests --- .../source-recharge/unit_tests/test_api.py | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/airbyte-integrations/connectors/source-recharge/unit_tests/test_api.py b/airbyte-integrations/connectors/source-recharge/unit_tests/test_api.py index 8b5a5adaa738..0607572a2c81 100644 --- a/airbyte-integrations/connectors/source-recharge/unit_tests/test_api.py +++ b/airbyte-integrations/connectors/source-recharge/unit_tests/test_api.py @@ -3,7 +3,7 @@ # from http import HTTPStatus -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest import requests @@ -166,19 +166,22 @@ def test_path(self, config, stream_cls, stream_type, expected): assert expected == result @pytest.mark.parametrize( - ("http_status", "should_retry"), + ("http_status", "headers", "should_retry"), [ - (HTTPStatus.OK, True), - (HTTPStatus.BAD_REQUEST, False), - (HTTPStatus.TOO_MANY_REQUESTS, True), - (HTTPStatus.INTERNAL_SERVER_ERROR, True), + (HTTPStatus.OK, {"Content-Length": 256}, True), + (HTTPStatus.BAD_REQUEST, {}, False), + (HTTPStatus.TOO_MANY_REQUESTS, {}, True), + (HTTPStatus.INTERNAL_SERVER_ERROR, {}, True), + (HTTPStatus.FORBIDDEN, {}, False), ], ) - def test_should_retry(patch_base_class, http_status, should_retry): - response_mock = MagicMock() - response_mock.status_code = http_status + def test_should_retry(self, http_status, headers, should_retry): + response = requests.Response() + response.status_code = http_status + response._content = b"" + response.headers = headers stream = RechargeStream() - assert stream.should_retry(response_mock) == should_retry + assert stream.should_retry(response) == should_retry class TestFullRefreshStreams: From 8e6fb793bb9b1bc2f2dd75f7001aff511d072fa4 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Wed, 12 Oct 2022 11:35:42 +0300 Subject: [PATCH 053/498] Source Github: Use default behaviour, retry on 429 and all 5XX errors (#17852) Signed-off-by: Sergey Chvalyuk --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-github/Dockerfile | 2 +- .../source-github/source_github/streams.py | 30 +++++++----------- .../source-github/unit_tests/test_stream.py | 31 +++++++++++++++++-- docs/integrations/sources/github.md | 1 + 6 files changed, 45 insertions(+), 23 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 1446c36113e8..0cefad4524aa 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -354,7 +354,7 @@ - name: GitHub sourceDefinitionId: ef69ef6e-aa7f-4af1-a01d-ef775033524e dockerRepository: airbyte/source-github - dockerImageTag: 0.3.5 + dockerImageTag: 0.3.6 documentationUrl: https://docs.airbyte.com/integrations/sources/github icon: github.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 63156d4c0468..666bbc8c2f78 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3502,7 +3502,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-github:0.3.5" +- dockerImage: "airbyte/source-github:0.3.6" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/github" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-github/Dockerfile b/airbyte-integrations/connectors/source-github/Dockerfile index a4ff557eba40..f1ecd0d3b013 100644 --- a/airbyte-integrations/connectors/source-github/Dockerfile +++ b/airbyte-integrations/connectors/source-github/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.3.5 +LABEL io.airbyte.version=0.3.6 LABEL io.airbyte.name=airbyte/source-github diff --git a/airbyte-integrations/connectors/source-github/source_github/streams.py b/airbyte-integrations/connectors/source-github/source_github/streams.py index fe915c05d585..15d5c4c54666 100644 --- a/airbyte-integrations/connectors/source-github/source_github/streams.py +++ b/airbyte-integrations/connectors/source-github/source_github/streams.py @@ -4,7 +4,7 @@ import time from abc import ABC, abstractmethod -from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Union +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional from urllib import parse import pendulum @@ -66,8 +66,9 @@ def check_graphql_rate_limited(self, response_json) -> bool: return False def should_retry(self, response: requests.Response) -> bool: - # We don't call `super()` here because we have custom error handling and GitHub API sometimes returns strange - # errors. So in `read_records()` we have custom error handling which don't require to call `super()` here. + if super().should_retry(response): + return True + retry_flag = ( # The GitHub GraphQL API has limitations # https://docs.github.com/en/graphql/overview/resource-limitations @@ -77,12 +78,7 @@ def should_retry(self, response: requests.Response) -> bool: or response.headers.get("X-RateLimit-Remaining") == "0" # Secondary rate limits # https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits - or response.headers.get("Retry-After") - or response.status_code - in ( - requests.codes.SERVER_ERROR, - requests.codes.BAD_GATEWAY, - ) + or "Retry-After" in response.headers ) if retry_flag: self.logger.info( @@ -91,22 +87,20 @@ def should_retry(self, response: requests.Response) -> bool: return retry_flag - def backoff_time(self, response: requests.Response) -> Union[int, float]: + def backoff_time(self, response: requests.Response) -> Optional[float]: # This method is called if we run into the rate limit. GitHub limits requests to 5000 per hour and provides # `X-RateLimit-Reset` header which contains time when this hour will be finished and limits will be reset so # we again could have 5000 per another hour. - if response.status_code in (requests.codes.SERVER_ERROR, requests.codes.BAD_GATEWAY): - return None + min_backoff_time = 60.0 - retry_after = int(response.headers.get("Retry-After", 0)) - if retry_after: - return retry_after + retry_after = response.headers.get("Retry-After") + if retry_after is not None: + return max(float(retry_after), min_backoff_time) reset_time = response.headers.get("X-RateLimit-Reset") - backoff_time = float(reset_time) - time.time() if reset_time else 60 - - return max(backoff_time, 60) # This is a guarantee that no negative value will be returned. + if reset_time: + return max(float(reset_time) - time.time(), min_backoff_time) def get_error_display_message(self, exception: BaseException) -> Optional[str]: if ( diff --git a/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py b/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py index 546c7e3057fa..8e879a4c4bd2 100644 --- a/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py +++ b/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py @@ -34,6 +34,7 @@ PullRequestStats, Releases, Repositories, + RepositoryStats, Reviews, Stargazers, Tags, @@ -72,8 +73,12 @@ def test_internal_server_error_retry(time_mock): [ (HTTPStatus.BAD_GATEWAY, {}, None), (HTTPStatus.INTERNAL_SERVER_ERROR, {}, None), - (HTTPStatus.FORBIDDEN, {"Retry-After": 120}, 120), - (HTTPStatus.FORBIDDEN, {"X-RateLimit-Reset": 1655804724}, 300.0), + (HTTPStatus.SERVICE_UNAVAILABLE, {}, None), + (HTTPStatus.FORBIDDEN, {"Retry-After": "0"}, 60), + (HTTPStatus.FORBIDDEN, {"Retry-After": "30"}, 60), + (HTTPStatus.FORBIDDEN, {"Retry-After": "120"}, 120), + (HTTPStatus.FORBIDDEN, {"X-RateLimit-Reset": "1655804454"}, 60.0), + (HTTPStatus.FORBIDDEN, {"X-RateLimit-Reset": "1655804724"}, 300.0), ], ) @patch("time.time", return_value=1655804424.0) @@ -86,6 +91,28 @@ def test_backoff_time(time_mock, http_status, response_headers, expected_backoff assert stream.backoff_time(response_mock) == expected_backoff_time +@pytest.mark.parametrize( + ("http_status", "response_headers", "text"), + [ + (HTTPStatus.OK, {"X-RateLimit-Resource": "graphql"}, '{"errors": [{"type": "RATE_LIMITED"}]}'), + (HTTPStatus.OK, {"X-RateLimit-Remaining": "0"}, ""), + (HTTPStatus.FORBIDDEN, {"Retry-After": "0"}, ""), + (HTTPStatus.FORBIDDEN, {"Retry-After": "60"}, ""), + (HTTPStatus.INTERNAL_SERVER_ERROR, {}, ""), + (HTTPStatus.BAD_GATEWAY, {}, ""), + (HTTPStatus.SERVICE_UNAVAILABLE, {}, ""), + ], +) +def test_should_retry(http_status, response_headers, text): + stream = RepositoryStats(repositories=["test_repo"], page_size_for_large_streams=30) + response_mock = MagicMock() + response_mock.status_code = http_status + response_mock.headers = response_headers + response_mock.text = text + response_mock.json = lambda: json.loads(text) + assert stream.should_retry(response_mock) + + @responses.activate @patch("time.sleep") def test_retry_after(time_mock): diff --git a/docs/integrations/sources/github.md b/docs/integrations/sources/github.md index 9e7f752168e5..8f5027b95719 100644 --- a/docs/integrations/sources/github.md +++ b/docs/integrations/sources/github.md @@ -147,6 +147,7 @@ The GitHub connector should not run into GitHub API limitations under normal usa | Version | Date | Pull Request | Subject | | :------ | :--------- | :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 0.3.6 | 2022-10-11 | [17852](https://github.com/airbytehq/airbyte/pull/17852) | Use default behaviour, retry on 429 and all 5XX errors | | 0.3.5 | 2022-10-07 | [17715](https://github.com/airbytehq/airbyte/pull/17715) | Improve 502 handling for `comments` stream | | 0.3.4 | 2022-10-04 | [17555](https://github.com/airbytehq/airbyte/pull/17555) | Skip repository if got HTTP 500 for WorkflowRuns stream | | 0.3.3 | 2022-09-28 | [17287](https://github.com/airbytehq/airbyte/pull/17287) | Fix problem with "null" `cursor_field` for WorkflowJobs stream | From b2b9a3ec9aecade958eb225870a5f678b46ed31d Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 12 Oct 2022 10:56:29 +0200 Subject: [PATCH 054/498] :window: :art: Fix styling of dropdown (#17872) * Fix styling of dropdown * Adjust jest tests --- .../CreateConnectionForm.test.tsx | 6 +- .../CreateConnectionForm.test.tsx.snap | 1384 +++++++++-------- .../components/ui/DropDown/CustomSelect.tsx | 3 +- .../ui/DropDown/components/Option.tsx | 7 +- .../ConnectionReplicationTab.test.tsx | 4 +- .../ConnectionReplicationTab.test.tsx.snap | 1154 +++++++------- .../Controls/ConnectorServiceTypeControl.tsx | 7 +- 7 files changed, 1292 insertions(+), 1273 deletions(-) diff --git a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx index b55dde8ab39f..7be178b5a9ff 100644 --- a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx +++ b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx @@ -44,7 +44,7 @@ describe("CreateConnectionForm", () => { it("should render", async () => { jest.spyOn(sourceHook, "useDiscoverSchema").mockImplementationOnce(() => baseUseDiscoverSchema); const renderResult = await render(); - expect(renderResult.container).toMatchSnapshot(); + expect(renderResult).toMatchSnapshot(); expect(renderResult.queryByText("Please wait a little bit more…")).toBeFalsy(); }); @@ -54,7 +54,7 @@ describe("CreateConnectionForm", () => { .mockImplementationOnce(() => ({ ...baseUseDiscoverSchema, isLoading: true })); const renderResult = await render(); - expect(renderResult.container).toMatchSnapshot(); + expect(renderResult).toMatchSnapshot(); }); it("should render with an error", async () => { @@ -64,6 +64,6 @@ describe("CreateConnectionForm", () => { })); const renderResult = await render(); - expect(renderResult.container).toMatchSnapshot(); + expect(renderResult).toMatchSnapshot(); }); }); diff --git a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap index f32652563982..4bd204d77b07 100644 --- a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap +++ b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap @@ -1,184 +1,61 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CreateConnectionForm should render 1`] = ` -
    -
    -
    +
    +
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - Transfer -
    - - -
    -
    -
    -
    -
    - Every 24 hours -
    -
    -
    - -
    -
    - -
    -
    @@ -186,77 +63,78 @@ exports[`CreateConnectionForm should render 1`] = `
    -
    - Streams -
    - +
    + Transfer +
    - Mirror source structure + Every 24 hours
    @@ -265,8 +143,8 @@ exports[`CreateConnectionForm should render 1`] = ` aria-expanded="false" aria-haspopup="true" aria-readonly="true" - class="css-mohuvp-dummyInput-DummyInput" - id="react-select-3-input" + class="" + id="react-select-2-input" inputmode="none" role="combobox" tabindex="0" @@ -274,11 +152,11 @@ exports[`CreateConnectionForm should render 1`] = ` />
    - +
    +
    +
    -
    + Streams + +
    -
    +
    +
    + +
    - Add a prefix to stream names (ex. “airbyte_” causes “projects” => “airbyte_projects”) - - - +
    +
    +
    +
    + Mirror source structure +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    -
    +
    - +
    + +
    +
    +
    +
    + +
    -
    -
    -
    - Activate the streams you want to sync -
    - -
    -
    -
    - + + Refresh source schema +
    +
    -
    -
    - -
    -
    -
    - Sync +
    - Source
    + +
    +
    +
    + Sync +
    +
    + Source
    - - - + + + +
    -
    -
    -
    - Sync mode
    +
    + Sync mode
    - - - + + + +
    -
    -
    - Cursor field
    + Cursor field
    - - - + + + +
    -
    -
    - Primary key
    + Primary key
    - - - + + + +
    -
    -
    - Destination
    + Destination
    - - - + + + +
    +
    -
    -
    -
    - Namespace -
    -
    - Stream name -
    -
    - Source | Destination -
    -
    -
    -
    - Namespace -
    -
    + `; exports[`CreateConnectionForm should render when loading 1`] = ` -
    -
    + +
    -
    - Please wait a little bit more… +
    +
    + Please wait a little bit more… +
    -
    -
    - We are fetching the schema of your data source. +
    + We are fetching the schema of your data source. This should take less than a minute, but may take a few minutes on slow internet connections or data sources with a large amount of tables. +
    -
    + `; exports[`CreateConnectionForm should render with an error 1`] = ` -
    -
    + +
    - -
    -

    - Failed to fetch schema. Please try again -

    - +

    + Failed to fetch schema. Please try again +

    + +
    -
    + `; diff --git a/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx b/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx index 95af34eead81..7482c87af1f6 100644 --- a/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx +++ b/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx @@ -25,7 +25,8 @@ export const CustomSelect = styled(Select)< border-color: ${({ theme, $error }) => ($error ? theme.dangerColor : theme.greyColor10)}; } - &.react-select__control--menu-is-open { + &.react-select__control--menu-is-open, + &:focus-within { border: 1px solid ${({ theme }) => theme.primaryColor}; box-shadow: none; } diff --git a/airbyte-webapp/src/components/ui/DropDown/components/Option.tsx b/airbyte-webapp/src/components/ui/DropDown/components/Option.tsx index 5de3503e106c..175d7669b676 100644 --- a/airbyte-webapp/src/components/ui/DropDown/components/Option.tsx +++ b/airbyte-webapp/src/components/ui/DropDown/components/Option.tsx @@ -25,6 +25,7 @@ export interface DropDownOptionDataItem { } export const OptionView = styled.div<{ + isFocused?: boolean; isSelected?: boolean; isDisabled?: boolean; }>` @@ -34,14 +35,15 @@ export const OptionView = styled.div<{ align-items: center; cursor: pointer; color: ${({ isSelected, theme }) => (isSelected ? theme.primaryColor : theme.textColor)}; - background: ${({ isSelected, theme }) => (isSelected ? theme.primaryColor12 : theme.whiteColor)}; + background: ${({ isSelected, isFocused, theme }) => + isSelected ? theme.primaryColor12 : isFocused ? theme.grey100 : theme.whiteColor}; border: none; padding: 10px 16px; font-size: 14px; line-height: 19px; &:hover { - background: ${({ isSelected, theme }) => (isSelected ? theme.primaryColor12 : theme.greyColor0)}; + background: ${({ isSelected, theme }) => (isSelected ? theme.primaryColor12 : theme.grey100)}; } `; @@ -58,6 +60,7 @@ export const DropDownOption: React.FC = (props) => { data-testid={dataTestId} isSelected={props.isSelected && !props.isMulti} isDisabled={props.isDisabled} + isFocused={props.isFocused} > {props.isMulti && ( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx index 87a355ee8f37..a110635e8c6c 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx @@ -57,7 +57,7 @@ describe("ConnectionReplicationTab", () => { setupSpies(); const renderResult = await render(); - expect(renderResult.container).toMatchSnapshot(); + expect(renderResult).toMatchSnapshot(); }); it("should show an error if there is a schemaError", async () => { @@ -68,7 +68,7 @@ describe("ConnectionReplicationTab", () => { await act(async () => { renderResult.queryByText("Refresh source schema")?.click(); }); - expect(renderResult.container).toMatchSnapshot(); + expect(renderResult).toMatchSnapshot(); }); it("should show loading if the schema is refreshing", async () => { diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap index cbe0bbfc3187..39598ee8fa73 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap @@ -1,209 +1,87 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ConnectionReplicationTab should render 1`] = ` -
    -
    -
    +
    +
    -
    -
    - Transfer -
    -
    -
    - -
    -
    + Transfer +
    - -
    -
    -
    -
    -
    - Manual -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    - Streams -
    - -
    -
    -
    - Mirror source structure + Manual
    @@ -212,8 +90,8 @@ exports[`ConnectionReplicationTab should render 1`] = ` aria-expanded="false" aria-haspopup="true" aria-readonly="true" - class="css-mohuvp-dummyInput-DummyInput" - id="react-select-3-input" + class="" + id="react-select-2-input" inputmode="none" role="combobox" tabindex="0" @@ -221,11 +99,11 @@ exports[`ConnectionReplicationTab should render 1`] = ` />
    - +
    +
    +
    -
    + Streams + +
    -
    +
    +
    + +
    - Add a prefix to stream names (ex. “airbyte_” causes “projects” => “airbyte_projects”) - - - +
    +
    +
    +
    + Mirror source structure +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    -
    +
    - +
    + +
    +
    +
    +
    + +
    -
    -
    -
    - Activate the streams you want to sync -
    - -
    -
    -
    - + + Refresh source schema +
    +
    -
    -
    - -
    -
    -
    - Sync +
    - Source
    + +
    +
    +
    + Sync +
    +
    + Source
    - - - + + + +
    -
    -
    -
    - Sync mode
    +
    + Sync mode
    - - - + + + +
    -
    -
    - Cursor field
    + Cursor field
    - - - + + + +
    -
    -
    - Primary key
    + Primary key
    - - - + + + +
    -
    -
    - Destination
    + Destination
    - - - + + + +
    +
    -
    -
    -
    - Namespace -
    -
    - Stream name -
    -
    - Source | Destination -
    -
    -
    -
    - Namespace -
    -
    - Stream name +
    + Namespace +
    +
    + Stream name +
    +
    + Source | Destination +
    +
    +
    +
    + Namespace +
    +
    + Stream name +
    -
    -
    - -
    -
    - - - -
    -
    - No namespace +
    - pokemon -
    -
    + +
    +
    - + class="" + > + No namespace + +
    +
    + pokemon +
    +
    + +
    -
    - Full refresh -
    - | -
    -
    - Append +
    + Full refresh +
    +
    + | +
    +
    + Append +
    +
    - -
    -
    -
    -
    -
    -
    - '<source schema> -
    -
    - pokemon +
    +
    +
    + '<source schema> +
    +
    + pokemon +
    @@ -749,88 +751,90 @@ exports[`ConnectionReplicationTab should render 1`] = `
    -
    -
    - - +
    - +
    + Save changes +
    + +
    -
    - + +
    -
    + `; exports[`ConnectionReplicationTab should show an error if there is a schemaError 1`] = ` -
    -
    + +
    - -
    -

    - Failed to fetch schema. Please try again -

    - +

    + Failed to fetch schema. Please try again +

    + +
    -
    + `; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx index 3fe8921b2db1..9b8c3ebd7e16 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx @@ -130,7 +130,12 @@ const StageLabel: React.FC<{ releaseStage?: ReleaseStage }> = ({ releaseStage }) const Option: React.FC = (props) => { return ( - + {props.data.img || null} From 0abeff928976b79083fa1a8f9221f2b7ff1b02aa Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 12 Oct 2022 12:43:41 +0300 Subject: [PATCH 055/498] [17495] Source-oracle: fixed build and integration test (#17769) * [17495] Source-oracle: fixed build and integration test --- .../source-oracle-strict-encrypt/build.gradle | 1 + ...r.java => AirbyteOracleTestContainer.java} | 20 +++++++++---------- ...StrictEncryptJdbcSourceAcceptanceTest.java | 8 +++++--- ...acleStrictEncryptSourceAcceptanceTest.java | 4 ++-- .../connectors/source-oracle/build.gradle | 1 + ...AbstractSshOracleSourceAcceptanceTest.java | 6 +++--- ...r.java => AirbyteOracleTestContainer.java} | 20 +++++++++---------- .../OracleJdbcSourceAcceptanceTest.java | 13 ++++++++---- .../oracle/OracleSourceAcceptanceTest.java | 4 ++-- .../oracle/OracleSourceDatatypeTest.java | 20 ++++++++++--------- .../source/oracle/OracleSourceTest.java | 20 +++++++------------ 11 files changed, 61 insertions(+), 56 deletions(-) rename airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/{OracleContainer.java => AirbyteOracleTestContainer.java} (87%) rename airbyte-integrations/connectors/source-oracle-strict-encrypt/src/{test => test-integration}/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java (98%) rename airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/{OracleContainer.java => AirbyteOracleTestContainer.java} (87%) rename airbyte-integrations/connectors/source-oracle/src/{test => test-integration}/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java (97%) rename airbyte-integrations/connectors/source-oracle/src/{test => test-integration}/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java (90%) diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/build.gradle b/airbyte-integrations/connectors/source-oracle-strict-encrypt/build.gradle index ed3602b5cf9a..69ae71a6f8cc 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/build.gradle +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/build.gradle @@ -30,6 +30,7 @@ dependencies { testImplementation libs.connectors.source.testcontainers.oracle.xe integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-source-test') + integrationTestJavaImplementation project(':airbyte-integrations:connectors:source-oracle-strict-encrypt') implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleContainer.java b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/AirbyteOracleTestContainer.java similarity index 87% rename from airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleContainer.java rename to airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/AirbyteOracleTestContainer.java index f534e1ce2d5c..989efbf78b44 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleContainer.java +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/AirbyteOracleTestContainer.java @@ -17,7 +17,7 @@ import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import org.testcontainers.utility.DockerImageName; -public class OracleContainer extends JdbcDatabaseContainer { +public class AirbyteOracleTestContainer extends JdbcDatabaseContainer { public static final String NAME = "oracle"; private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gvenzl/oracle-xe"); @@ -49,21 +49,21 @@ public class OracleContainer extends JdbcDatabaseContainer { private String password = APP_USER_PASSWORD; private boolean usingSid = false; - public OracleContainer() { + public AirbyteOracleTestContainer() { this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - public OracleContainer(final String dockerImageName) { + public AirbyteOracleTestContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } - public OracleContainer(final DockerImageName dockerImageName) { + public AirbyteOracleTestContainer(final DockerImageName dockerImageName) { super(dockerImageName); dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); preconfigure(); } - public OracleContainer(final Future dockerImageName) { + public AirbyteOracleTestContainer(final Future dockerImageName) { super(dockerImageName); preconfigure(); } @@ -120,7 +120,7 @@ protected boolean isUsingSid() { } @Override - public OracleContainer withUsername(final String username) { + public AirbyteOracleTestContainer withUsername(final String username) { if (StringUtils.isEmpty(username)) { throw new IllegalArgumentException("Username cannot be null or empty"); } @@ -132,7 +132,7 @@ public OracleContainer withUsername(final String username) { } @Override - public OracleContainer withPassword(final String password) { + public AirbyteOracleTestContainer withPassword(final String password) { if (StringUtils.isEmpty(password)) { throw new IllegalArgumentException("Password cannot be null or empty"); } @@ -141,7 +141,7 @@ public OracleContainer withPassword(final String password) { } @Override - public OracleContainer withDatabaseName(final String databaseName) { + public AirbyteOracleTestContainer withDatabaseName(final String databaseName) { if (StringUtils.isEmpty(databaseName)) { throw new IllegalArgumentException("Database name cannot be null or empty"); } @@ -154,13 +154,13 @@ public OracleContainer withDatabaseName(final String databaseName) { return self(); } - public OracleContainer usingSid() { + public AirbyteOracleTestContainer usingSid() { this.usingSid = true; return self(); } @Override - public OracleContainer withUrlParam(final String paramName, final String paramValue) { + public AirbyteOracleTestContainer withUrlParam(final String paramName, final String paramValue) { throw new UnsupportedOperationException("The Oracle Database driver does not support this"); } diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java similarity index 98% rename from airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java rename to airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java index 48e9ef6d4d98..bdee5069be37 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java @@ -53,12 +53,11 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.OracleContainer; class OracleStrictEncryptJdbcSourceAcceptanceTest extends JdbcSourceAcceptanceTest { private static final Logger LOGGER = LoggerFactory.getLogger(OracleStrictEncryptJdbcSourceAcceptanceTest.class); - private static OracleContainer ORACLE_DB; + private static AirbyteOracleTestContainer ORACLE_DB; @BeforeAll static void init() { @@ -86,7 +85,10 @@ static void init() { CREATE_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "CREATE TABLE %s (%s VARCHAR(20))"; INSERT_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES('Hello world :)')"; - ORACLE_DB = new OracleContainer("epiclabs/docker-oracle-xe-11g") + ORACLE_DB = new AirbyteOracleTestContainer() + .withUsername("test") + .withPassword("oracle") + .usingSid() .withEnv("NLS_DATE_FORMAT", "YYYY-MM-DD") .withEnv("RELAX_SECURITY", "1"); ORACLE_DB.start(); diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptSourceAcceptanceTest.java index 064d0275c1c3..8e36d522ad33 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptSourceAcceptanceTest.java @@ -33,12 +33,12 @@ public class OracleStrictEncryptSourceAcceptanceTest extends SourceAcceptanceTes private static final String STREAM_NAME = "JDBC_SPACE.ID_AND_NAME"; private static final String STREAM_NAME2 = "JDBC_SPACE.STARSHIPS"; - protected OracleContainer container; + protected AirbyteOracleTestContainer container; protected JsonNode config; @Override protected void setupEnvironment(final TestDestinationEnv environment) throws Exception { - container = new OracleContainer() + container = new AirbyteOracleTestContainer() .withUsername("test") .withPassword("oracle") .usingSid();; diff --git a/airbyte-integrations/connectors/source-oracle/build.gradle b/airbyte-integrations/connectors/source-oracle/build.gradle index 1b961a1f51b6..435b7625e749 100644 --- a/airbyte-integrations/connectors/source-oracle/build.gradle +++ b/airbyte-integrations/connectors/source-oracle/build.gradle @@ -30,6 +30,7 @@ dependencies { testImplementation libs.connectors.source.testcontainers.oracle.xe integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-source-test') + integrationTestJavaImplementation project(':airbyte-integrations:connectors:source-oracle') implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) diff --git a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AbstractSshOracleSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AbstractSshOracleSourceAcceptanceTest.java index 3c410eee49a5..16eda8bdc340 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AbstractSshOracleSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AbstractSshOracleSourceAcceptanceTest.java @@ -36,7 +36,7 @@ public abstract class AbstractSshOracleSourceAcceptanceTest extends SourceAccept private static final String STREAM_NAME2 = "JDBC_SPACE.STARSHIPS"; private static final Network network = Network.newNetwork(); private final SshBastionContainer sshBastionContainer = new SshBastionContainer(); - private OracleContainer db; + private AirbyteOracleTestContainer db; private JsonNode config; @@ -90,7 +90,7 @@ private void startTestContainers() { } private void initAndStartJdbcContainer() { - db = new OracleContainer() + db = new AirbyteOracleTestContainer() .withUsername("test") .withPassword("oracle") .usingSid() @@ -108,7 +108,7 @@ protected ConnectorSpecification getSpec() throws Exception { return SshHelpers.getSpecAndInjectSsh(); } - public ImmutableMap.Builder getBasicOracleDbConfigBuider(final OracleContainer db) { + public ImmutableMap.Builder getBasicOracleDbConfigBuider(final AirbyteOracleTestContainer db) { return ImmutableMap.builder() .put("host", Objects.requireNonNull(db.getContainerInfo().getNetworkSettings() .getNetworks() diff --git a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleContainer.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AirbyteOracleTestContainer.java similarity index 87% rename from airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleContainer.java rename to airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AirbyteOracleTestContainer.java index a83102f768a0..8b42af1d32b9 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleContainer.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/AirbyteOracleTestContainer.java @@ -17,7 +17,7 @@ import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import org.testcontainers.utility.DockerImageName; -public class OracleContainer extends JdbcDatabaseContainer { +public class AirbyteOracleTestContainer extends JdbcDatabaseContainer { public static final String NAME = "oracle"; private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("gvenzl/oracle-xe"); @@ -49,21 +49,21 @@ public class OracleContainer extends JdbcDatabaseContainer { private String password = APP_USER_PASSWORD; private boolean usingSid = false; - public OracleContainer() { + public AirbyteOracleTestContainer() { this(DEFAULT_IMAGE_NAME.withTag(DEFAULT_TAG)); } - public OracleContainer(final String dockerImageName) { + public AirbyteOracleTestContainer(final String dockerImageName) { this(DockerImageName.parse(dockerImageName)); } - public OracleContainer(final DockerImageName dockerImageName) { + public AirbyteOracleTestContainer(final DockerImageName dockerImageName) { super(dockerImageName); dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME); preconfigure(); } - public OracleContainer(final Future dockerImageName) { + public AirbyteOracleTestContainer(final Future dockerImageName) { super(dockerImageName); preconfigure(); } @@ -120,7 +120,7 @@ protected boolean isUsingSid() { } @Override - public OracleContainer withUsername(final String username) { + public AirbyteOracleTestContainer withUsername(final String username) { if (StringUtils.isEmpty(username)) { throw new IllegalArgumentException("Username cannot be null or empty"); } @@ -132,7 +132,7 @@ public OracleContainer withUsername(final String username) { } @Override - public OracleContainer withPassword(final String password) { + public AirbyteOracleTestContainer withPassword(final String password) { if (StringUtils.isEmpty(password)) { throw new IllegalArgumentException("Password cannot be null or empty"); } @@ -141,7 +141,7 @@ public OracleContainer withPassword(final String password) { } @Override - public OracleContainer withDatabaseName(final String databaseName) { + public AirbyteOracleTestContainer withDatabaseName(final String databaseName) { if (StringUtils.isEmpty(databaseName)) { throw new IllegalArgumentException("Database name cannot be null or empty"); } @@ -154,13 +154,13 @@ public OracleContainer withDatabaseName(final String databaseName) { return self(); } - public OracleContainer usingSid() { + public AirbyteOracleTestContainer usingSid() { this.usingSid = true; return self(); } @Override - public OracleContainer withUrlParam(final String paramName, final String paramValue) { + public AirbyteOracleTestContainer withUrlParam(final String paramName, final String paramValue) { throw new UnsupportedOperationException("The Oracle Database driver does not support this"); } diff --git a/airbyte-integrations/connectors/source-oracle/src/test/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java similarity index 97% rename from airbyte-integrations/connectors/source-oracle/src/test/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java rename to airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java index 714ca43d55eb..dcaf1803f431 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java @@ -55,14 +55,13 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.OracleContainer; class OracleJdbcSourceAcceptanceTest extends JdbcSourceAcceptanceTest { private static final Logger LOGGER = LoggerFactory.getLogger(OracleJdbcSourceAcceptanceTest.class); protected static final String USERNAME_WITHOUT_PERMISSION = "new_user"; protected static final String PASSWORD_WITHOUT_PERMISSION = "new_password"; - private static OracleContainer ORACLE_DB; + private static AirbyteOracleTestContainer ORACLE_DB; @BeforeAll static void init() { @@ -91,8 +90,12 @@ static void init() { CREATE_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "CREATE TABLE %s (%s VARCHAR(20))"; INSERT_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES('Hello world :)')"; - ORACLE_DB = new OracleContainer("epiclabs/docker-oracle-xe-11g") + ORACLE_DB = new AirbyteOracleTestContainer() .withEnv("NLS_DATE_FORMAT", "YYYY-MM-DD") + .withEnv("RELAX_SECURITY", "1") + .withUsername("TEST_ORA") + .withPassword("oracle") + .usingSid() .withEnv("RELAX_SECURITY", "1"); ORACLE_DB.start(); } @@ -398,8 +401,10 @@ void testCheckIncorrectPasswordFailure() throws Exception { ((ObjectNode) config).put(JdbcUtils.USERNAME_KEY, "locked_user"); ((ObjectNode) config).put(JdbcUtils.PASSWORD_KEY, "fake"); final AirbyteConnectionStatus status = source.check(config); + Assertions.assertEquals(AirbyteConnectionStatus.Status.FAILED, status.getStatus()); - assertTrue(status.getMessage().contains("State code: 99999; Error code: 28000;")); + assertEquals("State code: 72000; Error code: 1017; Message: ORA-01017: invalid username/password; logon denied\n", + status.getMessage()); } @Test diff --git a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceAcceptanceTest.java index 1de56a02dde5..498e5c329a37 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceAcceptanceTest.java @@ -31,12 +31,12 @@ public class OracleSourceAcceptanceTest extends SourceAcceptanceTest { private static final String STREAM_NAME = "JDBC_SPACE.ID_AND_NAME"; private static final String STREAM_NAME2 = "JDBC_SPACE.STARSHIPS"; - protected OracleContainer container; + protected AirbyteOracleTestContainer container; protected JsonNode config; @Override protected void setupEnvironment(final TestDestinationEnv environment) throws Exception { - container = new OracleContainer() + container = new AirbyteOracleTestContainer() .withUsername("test") .withPassword("oracle") .usingSid(); diff --git a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceDatatypeTest.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceDatatypeTest.java index 64e69c208569..7d8541597a85 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceDatatypeTest.java @@ -25,11 +25,10 @@ import org.jooq.DSLContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.containers.OracleContainer; public class OracleSourceDatatypeTest extends AbstractSourceDatabaseTypeTest { - private OracleContainer container; + private AirbyteOracleTestContainer container; private JsonNode config; private DSLContext dslContext; @@ -37,7 +36,10 @@ public class OracleSourceDatatypeTest extends AbstractSourceDatabaseTypeTest { @Override protected Database setupDatabase() throws Exception { - container = new OracleContainer("epiclabs/docker-oracle-xe-11g") + container = new AirbyteOracleTestContainer() + .withUsername("TEST_ORA") + .withPassword("oracle") + .usingSid() .withEnv("RELAX_SECURITY", "1"); container.start(); @@ -64,7 +66,7 @@ protected Database setupDatabase() throws Exception { final Database database = new Database(dslContext); LOGGER.warn("config: " + config); - database.query(ctx -> ctx.fetch("CREATE USER test IDENTIFIED BY test DEFAULT TABLESPACE USERS QUOTA UNLIMITED ON USERS")); + database.query(ctx -> ctx.fetch("CREATE USER TEST IDENTIFIED BY TEST DEFAULT TABLESPACE USERS QUOTA UNLIMITED ON USERS")); return database; } @@ -286,11 +288,11 @@ protected void initTests() { "1\n" + "2\n" + "')") - .addExpectedValues("" + - "\n" + - " 1\n" + - " 2\n" + - "") + .addExpectedValues("\n" + + "\n" + + " 1\n" + + " 2\n" + + "\n") .build()); } diff --git a/airbyte-integrations/connectors/source-oracle/src/test/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java similarity index 90% rename from airbyte-integrations/connectors/source-oracle/src/test/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java rename to airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java index 4ef627107197..35679e3e1905 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleSourceTest.java @@ -36,7 +36,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.testcontainers.containers.OracleContainer; class OracleSourceTest { @@ -48,18 +47,23 @@ class OracleSourceTest { Field.of("NAME", JsonSchemaType.STRING), Field.of("IMAGE", JsonSchemaType.STRING)) .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH, SyncMode.INCREMENTAL)))); + private static final ConfiguredAirbyteCatalog CONFIGURED_CATALOG = CatalogHelpers.toDefaultConfiguredCatalog(CATALOG); private static final Set ASCII_MESSAGES = Sets.newHashSet(createRecord(STREAM_NAME, map("ID", new BigDecimal("1.0"), "NAME", "user", "IMAGE", "last_summer.png".getBytes(StandardCharsets.UTF_8)))); - private static OracleContainer ORACLE_DB; + private static AirbyteOracleTestContainer ORACLE_DB; private static JsonNode config; @BeforeAll static void init() { - ORACLE_DB = new OracleContainer("epiclabs/docker-oracle-xe-11g") + ORACLE_DB = new AirbyteOracleTestContainer() + .withUsername("TEST_ORA") + .withPassword("oracle") + .usingSid() .withEnv("RELAX_SECURITY", "1"); + ORACLE_DB.start(); } @@ -97,16 +101,6 @@ void setup() throws Exception { } } - private JsonNode getConfig(final OracleContainer oracleDb) { - return Jsons.jsonNode(ImmutableMap.builder() - .put("host", oracleDb.getHost()) - .put("port", oracleDb.getFirstMappedPort()) - .put("sid", oracleDb.getSid()) - .put("username", oracleDb.getUsername()) - .put("password", oracleDb.getPassword()) - .build()); - } - @AfterAll static void cleanUp() { ORACLE_DB.close(); From 33dfd5d6befcd10181d6a50ad546fe865fac9d9d Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 12 Oct 2022 15:06:28 +0300 Subject: [PATCH 056/498] Fixed "Ensure no file change" build issue. Only checkstyle changes, no logic chnages (#17885) --- .../AwsDatalakeTestDataComparator.java | 5 + .../testdata/dataMaxNestedDepth.json | 2 +- .../testdata/dataTooDeepNestedDepth.json | 2 +- .../testdata/schemaMaxNestedDepth.json | 71 +- .../testdata/schemaTooDeepNestedDepth.json | 64 +- .../ClickhouseDestinationAcceptanceTest.java | 1 - .../src/main/resources/spec.json | 9 +- .../mariadb_columnstore/MariadbSpecTest.java | 20 +- .../mongodb/MongodbDestination.java | 2 +- .../mysql/MySqlTestDataComparator.java | 7 +- .../PostgresDestinationStrictEncrypt.java | 9 +- ...estinationStrictEncryptAcceptanceTest.java | 9 +- .../postgres/PostgresDestination.java | 4 +- .../postgres/PostgresDestinationTest.java | 1 + .../destination/s3/S3DestinationRunner.java | 1 + .../s3/S3DestinationStrictEncrypt.java | 1 + .../s3/S3DestinationStrictEncryptTest.java | 13 +- .../destination/s3/S3DestinationTest.java | 2 + .../integration_tests/configured_catalog.json | 5 +- .../source-adjust/source_adjust/spec.yaml | 193 +- .../alloydb/AlloyDbStrictEncryptSource.java | 4 + ...ured_catalog_sales_and_traffic_report.json | 185 +- ...gured_catalog_vendor_inventory_report.json | 185 +- ...onfigured_catalog_vendor_sales_report.json | 135 +- ...figured_report_get_afn_inventory_data.json | 48 +- ...ort_get_afn_inventory_data_by_country.json | 48 +- .../GET_AFN_INVENTORY_DATA_BY_COUNTRY.json | 25 +- .../schemas/GET_SALES_AND_TRAFFIC_REPORT.json | 153 +- .../GET_SALES_AND_TRAFFIC_REPORT_BY_DATE.json | 264 +- .../schemas/GET_VENDOR_INVENTORY_REPORT.json | 161 +- .../schemas/GET_VENDOR_SALES_REPORT.json | 105 +- .../integration_tests/catalog.json | 400 +-- .../integration_tests/configured_catalog.json | 403 ++-- .../source_bigcommerce/schemas/channels.json | 76 +- .../source_bigcommerce/schemas/store.json | 346 +-- .../integration_tests/invalid_config.json | 6 +- .../source-cart/source_cart/spec.json | 4 +- .../source_chargebee/schemas/addon.json | 8 +- .../schemas/attached_item.json | 10 +- .../ClickHouseJdbcSourceAcceptanceTest.java | 2 +- .../integration_tests/spec.json | 11 +- .../source-file/source_file/spec.json | 11 +- .../source_github/schemas/workflow_jobs.json | 52 +- .../integration_tests/abnormal_state.json | 40 +- .../source/jdbc/AbstractJdbcSource.java | 38 +- .../source/jdbc/JdbcSSLConnectionUtils.java | 1 - .../MongodbSourceStrictEncrypt.java | 3 +- .../MongoDbSource.java | 4 +- .../MongoDbSourceUtils.java | 5 + ...MongoDbSourceStandaloneAcceptanceTest.java | 2 +- .../MySqlStrictEncryptSource.java | 12 +- ...cateStrictEncryptSourceAcceptanceTest.java | 5 +- ...ySqlStrictEncryptSourceAcceptanceTest.java | 4 +- ...StrictEncryptJdbcSourceAcceptanceTest.java | 1 + .../source/mysql/MySqlSource.java | 1 - .../source/mysql/MySqlSourceOperations.java | 3 - .../mysql/helpers/CdcConfigurationHelper.java | 4 +- .../AbstractMySqlSourceDatatypeTest.java | 2 +- ...SqlSslCertificateSourceAcceptanceTest.java | 4 +- .../AbstractSshMySqlSourceAcceptanceTest.java | 1 + .../CdcBinlogsMySqlSourceDatatypeTest.java | 4 +- ...nitialSnapshotMySqlSourceDatatypeTest.java | 1 + ...lSslCaCertificateSourceAcceptanceTest.java | 4 +- .../sources/MySqlSslSourceAcceptanceTest.java | 4 +- .../sources/utils/TestConstants.java | 5 + .../mysql/CdcConfigurationHelperTest.java | 4 + .../mysql/MySqlJdbcSourceAcceptanceTest.java | 4 +- .../integration_tests/configured_catalog.json | 457 ++-- .../source-netsuite/sample_files/config.json | 2 +- .../sample_files/invalid_config.json | 2 +- .../sample_files/sample_state.json | 68 +- .../source/oracle/OracleSource.java | 5 +- .../postgres/PostgresSourceStrictEncrypt.java | 16 +- .../postgres/PostgresCdcProperties.java | 4 +- .../source/postgres/PostgresSource.java | 18 +- .../postgres/PostgresSourceStrictEncrypt.java | 1 - ...actCdcPostgresSourceSslAcceptanceTest.java | 5 +- .../AbstractPostgresSourceDatatypeTest.java | 1 - ...sSourceCaCertificateSslAcceptanceTest.java | 2 + ...ourceFullCertificateSslAcceptanceTest.java | 1 + .../source/postgres/PostgresSourceTest.java | 10 +- .../integration_tests/configured_catalog.json | 2144 +++++++---------- .../sample_files/configured_catalog.json | 2144 +++++++---------- .../source_primetric/schemas/assignments.json | 695 ++---- .../source_primetric/schemas/employees.json | 110 +- .../source_primetric/schemas/hashtags.json | 14 +- .../schemas/organization_clients.json | 14 +- .../schemas/organization_company_groups.json | 14 +- .../schemas/organization_departments.json | 14 +- .../organization_identity_providers.json | 22 +- .../schemas/organization_positions.json | 14 +- .../schemas/organization_rag_scopes.json | 34 +- .../schemas/organization_roles.json | 16 +- .../schemas/organization_seniorities.json | 18 +- .../schemas/organization_tags.json | 14 +- .../schemas/organization_teams.json | 14 +- .../schemas/organization_timeoff_types.json | 14 +- .../source_primetric/schemas/people.json | 52 +- .../source_primetric/schemas/projects.json | 210 +- .../schemas/projects_vacancies.json | 57 +- .../source_primetric/schemas/rag_ratings.json | 23 +- .../schemas/shared/custom_attributes.json | 23 +- .../schemas/shared/money_object.json | 28 +- .../source_primetric/schemas/skills.json | 48 +- .../source_primetric/schemas/timeoffs.json | 70 +- .../source_primetric/schemas/worklogs.json | 81 +- .../source_primetric/spec.yaml | 2 +- .../src/test/resources/config-test.json | 6 +- .../source/relationaldb/AbstractDbSource.java | 3 +- .../relationaldb/InvalidCursorException.java | 11 +- .../integration_tests/invalid_time.json | 2 +- .../source-sendgrid/source_sendgrid/spec.json | 7 +- .../SnowflakeJdbcSourceAcceptanceTest.java | 13 +- .../source_tiktok_marketing/spec.json | 319 ++- .../integration_tests/invalid_config.json | 2 +- .../integration_tests/catalog.json | 308 +-- .../integration_tests/configured_catalog.json | 98 +- .../schemas/custom_field_values.json | 78 +- .../schemas/custom_fields.json | 164 +- .../source_zenefits/schemas/departments.json | 102 +- .../source_zenefits/schemas/employments.json | 136 +- .../schemas/labor_group_types.json | 78 +- .../source_zenefits/schemas/labor_groups.json | 116 +- .../source_zenefits/schemas/locations.json | 144 +- .../source_zenefits/schemas/people.json | 446 ++-- .../schemas/time_durations.json | 140 +- .../schemas/vacation_requests.json | 200 +- .../schemas/vacation_types.json | 122 +- .../source-zenefits/source_zenefits/source.py | 12 +- .../source-zenefits/source_zenefits/spec.json | 4 +- 130 files changed, 5161 insertions(+), 7050 deletions(-) diff --git a/airbyte-integrations/connectors/destination-aws-datalake/src/test-integration/java/io/airbyte/integrations/destination/aws_datalake/AwsDatalakeTestDataComparator.java b/airbyte-integrations/connectors/destination-aws-datalake/src/test-integration/java/io/airbyte/integrations/destination/aws_datalake/AwsDatalakeTestDataComparator.java index 90c0617df60a..7674404d80e2 100644 --- a/airbyte-integrations/connectors/destination-aws-datalake/src/test-integration/java/io/airbyte/integrations/destination/aws_datalake/AwsDatalakeTestDataComparator.java +++ b/airbyte-integrations/connectors/destination-aws-datalake/src/test-integration/java/io/airbyte/integrations/destination/aws_datalake/AwsDatalakeTestDataComparator.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.destination.aws_datalake; import io.airbyte.integrations.standardtest.destination.comparator.AdvancedTestDataComparator; @@ -14,4 +18,5 @@ protected List resolveIdentifier(String identifier) { result.add(identifier.toLowerCase(Locale.ROOT)); return result; } + } diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataMaxNestedDepth.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataMaxNestedDepth.json index 1462597b8f10..206c7feb6315 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataMaxNestedDepth.json +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataMaxNestedDepth.json @@ -13,7 +13,7 @@ "rec_lvl_12": { "rec_lvl_13": { "rec_lvl_14": { - "str_value" : "test_value" + "str_value": "test_value" } } } diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataTooDeepNestedDepth.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataTooDeepNestedDepth.json index 3f0f2468cbc9..5bdc95d388aa 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataTooDeepNestedDepth.json +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/dataTooDeepNestedDepth.json @@ -14,7 +14,7 @@ "rec_lvl_13": { "rec_lvl_14": { "rec_lvl_15": { - "str_value" : "test_value" + "str_value": "test_value" } } } diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaMaxNestedDepth.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaMaxNestedDepth.json index 8570c57e1f2f..a6bf911b74e9 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaMaxNestedDepth.json +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaMaxNestedDepth.json @@ -1,85 +1,50 @@ { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_1": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_2": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_3": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_4": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_5": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_6": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_7": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_8": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_9": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_10": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_11": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_12": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_13": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_14": { - "type": [ - "object" - ], + "type": ["object"], "properties": { - - "str_value" : { - "type": [ - "string" - ] - - + "str_value": { + "type": ["string"] } } } @@ -110,4 +75,4 @@ } } } -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaTooDeepNestedDepth.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaTooDeepNestedDepth.json index 4eda1667967d..2e6c71cdfbc8 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaTooDeepNestedDepth.json +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/resources/testdata/schemaTooDeepNestedDepth.json @@ -1,84 +1,54 @@ { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_1": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_2": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_3": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_4": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_5": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_6": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_7": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_8": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_9": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_10": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_11": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_12": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_13": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_14": { - "type": [ - "object" - ], + "type": ["object"], "properties": { "rec_lvl_15": { "type": [ "object" ], "properties": { - "str_value" : { + "str_value": { "type": [ "string" ] @@ -114,4 +84,4 @@ } } } -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/destination-clickhouse/src/test-integration/java/io/airbyte/integrations/destination/clickhouse/ClickhouseDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-clickhouse/src/test-integration/java/io/airbyte/integrations/destination/clickhouse/ClickhouseDestinationAcceptanceTest.java index 0dfd1db487fb..60c9f5b43206 100644 --- a/airbyte-integrations/connectors/destination-clickhouse/src/test-integration/java/io/airbyte/integrations/destination/clickhouse/ClickhouseDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-clickhouse/src/test-integration/java/io/airbyte/integrations/destination/clickhouse/ClickhouseDestinationAcceptanceTest.java @@ -24,7 +24,6 @@ import java.sql.SQLException; import java.time.Duration; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; diff --git a/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json index 6fb91d335c02..3667ed0ea0b6 100644 --- a/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-kinesis/src/main/resources/spec.json @@ -8,7 +8,14 @@ "$schema": "http://json-schema.org/draft-07/schema#", "title": "Kinesis Destination Spec", "type": "object", - "required": ["endpoint", "region", "shardCount", "accessKey", "privateKey", "bufferSize"], + "required": [ + "endpoint", + "region", + "shardCount", + "accessKey", + "privateKey", + "bufferSize" + ], "additionalProperties": true, "properties": { "endpoint": { diff --git a/airbyte-integrations/connectors/destination-mariadb-columnstore/src/test/java/io/airbyte/integrations/destination/mariadb_columnstore/MariadbSpecTest.java b/airbyte-integrations/connectors/destination-mariadb-columnstore/src/test/java/io/airbyte/integrations/destination/mariadb_columnstore/MariadbSpecTest.java index 63f3ceb12840..9285c919d7c2 100644 --- a/airbyte-integrations/connectors/destination-mariadb-columnstore/src/test/java/io/airbyte/integrations/destination/mariadb_columnstore/MariadbSpecTest.java +++ b/airbyte-integrations/connectors/destination-mariadb-columnstore/src/test/java/io/airbyte/integrations/destination/mariadb_columnstore/MariadbSpecTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; public class MariadbSpecTest { + private static JsonNode schema; private static JsonNode config; private static String configText; @@ -32,15 +33,15 @@ public class MariadbSpecTest { @BeforeAll static void init() throws IOException { configText = """ - { - "host": "localhost", - "port": 1521, - "username": "mariadb", - "password": "password", - "database": "db", - "jdbc_url_params": "property1=pValue1&property2=pValue2" - } - """; + { + "host": "localhost", + "port": 1521, + "username": "mariadb", + "password": "password", + "database": "db", + "jdbc_url_params": "property1=pValue1&property2=pValue2" + } + """; final String spec = MoreResources.readResource("spec.json"); final File schemaFile = IOs.writeFile(Files.createTempDirectory(Path.of("/tmp"), "spec-test"), "schema.json", spec).toFile(); schema = JsonSchemaValidator.getSchema(schemaFile).get("connectionSpecification"); @@ -92,4 +93,5 @@ void testJdbcAdditionalProperty() throws Exception { final ConnectorSpecification spec = new MariadbColumnstoreDestination().spec(); assertNotNull(spec.getConnectionSpecification().get("properties").get("jdbc_url_params")); } + } diff --git a/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java b/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java index 21ee4c5c0ed0..09f51393edf2 100644 --- a/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java +++ b/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java @@ -9,10 +9,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; -import com.mongodb.client.MongoCollection; import com.mongodb.MongoCommandException; import com.mongodb.MongoException; import com.mongodb.MongoSecurityException; +import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import io.airbyte.commons.util.MoreIterators; import io.airbyte.db.exception.ConnectionErrorException; diff --git a/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/mysql/MySqlTestDataComparator.java b/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/mysql/MySqlTestDataComparator.java index 53d7c52fe12a..a938966602c7 100644 --- a/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/mysql/MySqlTestDataComparator.java +++ b/airbyte-integrations/connectors/destination-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/mysql/MySqlTestDataComparator.java @@ -6,7 +6,6 @@ import io.airbyte.integrations.destination.ExtendedNameTransformer; import io.airbyte.integrations.standardtest.destination.comparator.AdvancedTestDataComparator; - import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -34,7 +33,7 @@ protected boolean compareBooleanValues(String firstBooleanValue, String secondBo return super.compareBooleanValues(firstBooleanValue, secondBooleanValue); } else { return super.compareBooleanValues(firstBooleanValue, - String.valueOf(secondBooleanValue.equals("1"))); + String.valueOf(secondBooleanValue.equals("1"))); } } @@ -42,14 +41,14 @@ protected boolean compareBooleanValues(String firstBooleanValue, String secondBo protected boolean compareDateTimeValues(String expectedValue, String actualValue) { var destinationDate = parseLocalDateTime(actualValue); var expectedDate = LocalDate.parse(expectedValue, - DateTimeFormatter.ofPattern(AIRBYTE_DATETIME_FORMAT)); + DateTimeFormatter.ofPattern(AIRBYTE_DATETIME_FORMAT)); return expectedDate.equals(destinationDate); } private LocalDate parseLocalDateTime(String dateTimeValue) { if (dateTimeValue != null) { return LocalDate.parse(dateTimeValue, - DateTimeFormatter.ofPattern(getFormat(dateTimeValue))); + DateTimeFormatter.ofPattern(getFormat(dateTimeValue))); } else { return null; } diff --git a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncrypt.java b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncrypt.java index 6fdb53da53c6..cdb9806fab7e 100644 --- a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncrypt.java +++ b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncrypt.java @@ -5,7 +5,6 @@ package io.airbyte.integrations.destination.postgres; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.airbyte.commons.json.Jsons; import io.airbyte.db.jdbc.JdbcUtils; @@ -31,7 +30,6 @@ public class PostgresDestinationStrictEncrypt extends SpecModifyingDestination i public static final String SSL_MODE_PREFER = "prefer"; public static final String SSL_MODE_DISABLE = "disable"; - public PostgresDestinationStrictEncrypt() { super(PostgresDestination.sshWrappedDestination()); } @@ -48,13 +46,14 @@ public AirbyteConnectionStatus check(final JsonNode config) throws Exception { if (config.has(TUNNEL_METHOD) && config.get(TUNNEL_METHOD).has(TUNNEL_METHOD) && config.get(TUNNEL_METHOD).get(TUNNEL_METHOD).asText().equals(NO_TUNNEL)) { - //If no SSH tunnel + // If no SSH tunnel if (config.has(SSL_MODE) && config.get(SSL_MODE).has(MODE)) { if (Set.of(SSL_MODE_DISABLE, SSL_MODE_ALLOW, SSL_MODE_PREFER).contains(config.get(SSL_MODE).get(MODE).asText())) { - //Fail in case SSL mode is disable, allow or prefer + // Fail in case SSL mode is disable, allow or prefer return new AirbyteConnectionStatus() .withStatus(Status.FAILED) - .withMessage("Unsecured connection not allowed. If no SSH Tunnel set up, please use one of the following SSL modes: require, verify-ca, verify-full"); + .withMessage( + "Unsecured connection not allowed. If no SSH Tunnel set up, please use one of the following SSL modes: require, verify-ca, verify-full"); } } } diff --git a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncryptAcceptanceTest.java b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncryptAcceptanceTest.java index 48f8dfd99fcf..a7b3ad196c91 100644 --- a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncryptAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/postgres/PostgresDestinationStrictEncryptAcceptanceTest.java @@ -175,9 +175,9 @@ void testStrictSSLUnsecuredNoTunnel() throws WorkerException { .put(JdbcUtils.SSL_MODE_KEY, ImmutableMap.builder() .put("mode", "prefer") .build()) - .put("tunnel_method", ImmutableMap.builder() - .put("tunnel_method", "NO_TUNNEL") - .build()) + .put("tunnel_method", ImmutableMap.builder() + .put("tunnel_method", "NO_TUNNEL") + .build()) .build()); final var actual = runCheck(config); @@ -223,7 +223,8 @@ void testStrictSSLUnsecuredWithTunnel() throws WorkerException { .build()) .build()); final var actual = runCheck(config); - //DefaultCheckConnectionWorker is swallowing the NullPointerException + // DefaultCheckConnectionWorker is swallowing the NullPointerException assertNull(actual); } + } diff --git a/airbyte-integrations/connectors/destination-postgres/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestination.java b/airbyte-integrations/connectors/destination-postgres/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestination.java index 4810fa692b38..2bdd6a5ba403 100644 --- a/airbyte-integrations/connectors/destination-postgres/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestination.java +++ b/airbyte-integrations/connectors/destination-postgres/src/main/java/io/airbyte/integrations/destination/postgres/PostgresDestination.java @@ -19,10 +19,8 @@ import io.airbyte.integrations.base.IntegrationRunner; import io.airbyte.integrations.base.ssh.SshWrappedDestination; import io.airbyte.integrations.destination.jdbc.AbstractJdbcDestination; - import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -77,7 +75,7 @@ public JsonNode toJdbcConfig(final JsonNode config) { final String jdbcUrl = String.format("jdbc:postgresql://%s:%s/%s?", config.get(JdbcUtils.HOST_KEY).asText(), config.get(JdbcUtils.PORT_KEY).asText(), - encodedDatabase); + encodedDatabase); final ImmutableMap.Builder configBuilder = ImmutableMap.builder() .put(JdbcUtils.USERNAME_KEY, config.get(JdbcUtils.USERNAME_KEY).asText()) diff --git a/airbyte-integrations/connectors/destination-postgres/src/test/java/io/airbyte/integrations/destination/postgres/PostgresDestinationTest.java b/airbyte-integrations/connectors/destination-postgres/src/test/java/io/airbyte/integrations/destination/postgres/PostgresDestinationTest.java index a68cbe21b3b8..eb8f5f40ceb3 100644 --- a/airbyte-integrations/connectors/destination-postgres/src/test/java/io/airbyte/integrations/destination/postgres/PostgresDestinationTest.java +++ b/airbyte-integrations/connectors/destination-postgres/src/test/java/io/airbyte/integrations/destination/postgres/PostgresDestinationTest.java @@ -74,6 +74,7 @@ private JsonNode buildConfigNoJdbcParameters() { } private static final String EXPECTED_JDBC_ESCAPED_URL = "jdbc:postgresql://localhost:1337/db%2Ffoo?"; + private JsonNode buildConfigEscapingNeeded() { return Jsons.jsonNode(ImmutableMap.of( JdbcUtils.HOST_KEY, "localhost", diff --git a/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationRunner.java b/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationRunner.java index be5bcd52c7ec..7160df7e7414 100644 --- a/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationRunner.java +++ b/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationRunner.java @@ -14,4 +14,5 @@ public static void main(final String[] args) throws Exception { .withCloudDestination(S3DestinationStrictEncrypt::new) .run(args); } + } diff --git a/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncrypt.java b/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncrypt.java index 6ee8729903d4..de0e398a9c61 100644 --- a/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncrypt.java +++ b/airbyte-integrations/connectors/destination-s3/src/main/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncrypt.java @@ -32,4 +32,5 @@ public AirbyteConnectionStatus check(final JsonNode config) { } return super.check(config); } + } diff --git a/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncryptTest.java b/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncryptTest.java index 9c4c92b52176..038af3e3e5c6 100644 --- a/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncryptTest.java +++ b/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationStrictEncryptTest.java @@ -34,6 +34,7 @@ public void setup() { when(s3.initiateMultipartUpload(any(InitiateMultipartUploadRequest.class))).thenReturn(uploadResult); factoryConfig = new S3DestinationConfigFactory() { + public S3DestinationConfig getS3DestinationConfig(final JsonNode config, final StorageProvider storageProvider) { return S3DestinationConfig.create("fake-bucket", "fake-bucketPath", "fake-region") .withEndpoint("https://s3.example.com") @@ -41,10 +42,10 @@ public S3DestinationConfig getS3DestinationConfig(final JsonNode config, final S .withS3Client(s3) .get(); } + }; } - /** * Test that checks if user is using a connection that is HTTPS only */ @@ -56,12 +57,16 @@ public void checksCustomEndpointIsHttpsOnly() { } /** - * Test that checks if user is using a connection that is deemed insecure since it does not always enforce HTTPS only - *

    https://docs.aws.amazon.com/general/latest/gr/s3.html

    + * Test that checks if user is using a connection that is deemed insecure since it does not always + * enforce HTTPS only + *

    + * https://docs.aws.amazon.com/general/latest/gr/s3.html + *

    */ @Test public void checksCustomEndpointIsNotHttpsOnly() { final S3Destination destinationWithStandardUnsecuredEndpoint = new S3DestinationStrictEncrypt(new S3DestinationConfigFactory() { + public S3DestinationConfig getS3DestinationConfig(final JsonNode config, final StorageProvider storageProvider) { return S3DestinationConfig.create("fake-bucket", "fake-bucketPath", "fake-region") .withEndpoint("s3.us-west-1.amazonaws.com") @@ -69,8 +74,10 @@ public S3DestinationConfig getS3DestinationConfig(final JsonNode config, final S .withS3Client(s3) .get(); } + }); final AirbyteConnectionStatus status = destinationWithStandardUnsecuredEndpoint.check(null); assertEquals(Status.FAILED, status.getStatus()); } + } diff --git a/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationTest.java b/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationTest.java index 9eaf74554b96..0aae04639cd4 100644 --- a/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationTest.java +++ b/airbyte-integrations/connectors/destination-s3/src/test/java/io/airbyte/integrations/destination/s3/S3DestinationTest.java @@ -51,6 +51,7 @@ public void setup() { .get(); factoryConfig = new S3DestinationConfigFactory() { + public S3DestinationConfig getS3DestinationConfig(final JsonNode config, final StorageProvider storageProvider) { return S3DestinationConfig.create("fake-bucket", "fake-bucketPath", "fake-region") .withEndpoint("https://s3.example.com") @@ -58,6 +59,7 @@ public S3DestinationConfig getS3DestinationConfig(final JsonNode config, final S .withS3Client(s3) .get(); } + }; } diff --git a/airbyte-integrations/connectors/source-adjust/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-adjust/integration_tests/configured_catalog.json index 2e00e4f9126c..84383992bb4a 100644 --- a/airbyte-integrations/connectors/source-adjust/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-adjust/integration_tests/configured_catalog.json @@ -39,10 +39,7 @@ "required": ["day"], "$schema": "http://json-schema.org/draft-07/schema#" }, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] + "supported_sync_modes": ["full_refresh", "incremental"] } } ] diff --git a/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml b/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml index 854e485ddf5c..2e590559a175 100644 --- a/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml +++ b/airbyte-integrations/connectors/source-adjust/source_adjust/spec.yaml @@ -3,7 +3,8 @@ connectionSpecification: description: Adjust reporting API connector. properties: additional_metrics: - description: Metrics names that are not pre-defined, such as cohort metrics + description: + Metrics names that are not pre-defined, such as cohort metrics or app specific metrics. items: type: string @@ -17,38 +18,39 @@ connectionSpecification: title: API Token type: string dimensions: - description: Dimensions allow a user to break down metrics into groups using + description: + Dimensions allow a user to break down metrics into groups using one or several parameters. For example, the number of installs by date, country and network. See https://help.adjust.com/en/article/reports-endpoint#dimensions for more information about the dimensions. items: enum: - - os_name - - device_type - - app - - app_token - - store_id - - store_type - - app_network - - currency - - currency_code - - network - - campaign - - campaign_network - - campaign_id_network - - adgroup - - adgroup_network - - adgroup_id_network - - source_network - - source_id_network - - creative - - creative_network - - creative_id_network - - country - - country_code - - region - - partner_name - - partner_id + - os_name + - device_type + - app + - app_token + - store_id + - store_type + - app_network + - currency + - currency_code + - network + - campaign + - campaign_network + - campaign_id_network + - adgroup + - adgroup_network + - adgroup_id_network + - source_network + - source_id_network + - creative + - creative_network + - creative_id_network + - country + - country_code + - region + - partner_name + - partner_id type: string minItems: 1 order: 4 @@ -65,68 +67,68 @@ connectionSpecification: description: Select at least one metric to query. items: enum: - - network_cost - - network_cost_diff - - network_clicks - - network_impressions - - network_installs - - network_installs_diff - - network_ecpc - - network_ecpi - - network_ecpm - - arpdau_ad - - arpdau - - arpdau_iap - - ad_impressions - - ad_rpm - - ad_revenue - - cohort_ad_revenue - - cost - - adjust_cost - - all_revenue - - cohort_all_revenue - - daus - - maus - - waus - - base_sessions - - ctr - - click_conversion_rate - - click_cost - - clicks - - paid_clicks - - deattributions - - ecpc - - gdpr_forgets - - gross_profit - - cohort_gross_profit - - impression_conversion_rate - - impression_cost - - impressions - - paid_impressions - - install_cost - - installs - - paid_installs - - installs_per_mile - - limit_ad_tracking_installs - - limit_ad_tracking_install_rate - - limit_ad_tracking_reattribution_rate - - limit_ad_tracking_reattributions - - non_organic_installs - - organic_installs - - roas_ad - - roas - - roas_iap - - reattributions - - return_on_investment - - revenue - - cohort_revenue - - revenue_events - - revenue_to_cost - - sessions - - events - - ecpi_all - - ecpi - - ecpm + - network_cost + - network_cost_diff + - network_clicks + - network_impressions + - network_installs + - network_installs_diff + - network_ecpc + - network_ecpi + - network_ecpm + - arpdau_ad + - arpdau + - arpdau_iap + - ad_impressions + - ad_rpm + - ad_revenue + - cohort_ad_revenue + - cost + - adjust_cost + - all_revenue + - cohort_all_revenue + - daus + - maus + - waus + - base_sessions + - ctr + - click_conversion_rate + - click_cost + - clicks + - paid_clicks + - deattributions + - ecpc + - gdpr_forgets + - gross_profit + - cohort_gross_profit + - impression_conversion_rate + - impression_cost + - impressions + - paid_impressions + - install_cost + - installs + - paid_installs + - installs_per_mile + - limit_ad_tracking_installs + - limit_ad_tracking_install_rate + - limit_ad_tracking_reattribution_rate + - limit_ad_tracking_reattributions + - non_organic_installs + - organic_installs + - roas_ad + - roas + - roas_iap + - reattributions + - return_on_investment + - revenue + - cohort_revenue + - revenue_events + - revenue_to_cost + - sessions + - events + - ecpi_all + - ecpi + - ecpm type: string minItems: 1 order: 2 @@ -135,16 +137,17 @@ connectionSpecification: uniqueItems: true until_today: default: false - description: Syncs data up until today. Useful when running daily incremental + description: + Syncs data up until today. Useful when running daily incremental syncs, and duplicates are not desired. order: 5 title: Until Today type: boolean required: - - api_token - - ingest_start - - metrics - - dimensions + - api_token + - ingest_start + - metrics + - dimensions title: Adjust Spec type: object documentationUrl: https://docs.airbyte.com/integrations/sources/adjust diff --git a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/src/main/java/io/airbyte/integrations/source/alloydb/AlloyDbStrictEncryptSource.java b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/src/main/java/io/airbyte/integrations/source/alloydb/AlloyDbStrictEncryptSource.java index 651c31b43893..35a33b3177fb 100644 --- a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/src/main/java/io/airbyte/integrations/source/alloydb/AlloyDbStrictEncryptSource.java +++ b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/src/main/java/io/airbyte/integrations/source/alloydb/AlloyDbStrictEncryptSource.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.source.alloydb; import static io.airbyte.integrations.source.relationaldb.state.StateManager.LOGGER; diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_sales_and_traffic_report.json b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_sales_and_traffic_report.json index ae1657bde0d5..b542a7b83679 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_sales_and_traffic_report.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_sales_and_traffic_report.json @@ -1,99 +1,98 @@ { - "streams": [ - { - "stream": { - "name": "GET_SALES_AND_TRAFFIC_REPORT", - "json_schema": { - "title": "Seller Sales and Traffic Business Report", - "description": "Seller retail analytics reports - Sales and Traffic Business Report By Asin", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "parentAsin": { - "type": ["null", "string"] - }, - "childAsin": { - "type": ["null", "string"] - }, - "salesByAsin": { - "type": "object", - "properties": { - "unitsOrdered": { - "type": ["null", "number"] - }, - "orderedProductSales": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } - } - }, - "totalOrderItems": { - "type": ["null", "number"] - } - - } - }, - "trafficByAsin": { + "streams": [ + { + "stream": { + "name": "GET_SALES_AND_TRAFFIC_REPORT", + "json_schema": { + "title": "Seller Sales and Traffic Business Report", + "description": "Seller retail analytics reports - Sales and Traffic Business Report By Asin", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "parentAsin": { + "type": ["null", "string"] + }, + "childAsin": { + "type": ["null", "string"] + }, + "salesByAsin": { + "type": "object", + "properties": { + "unitsOrdered": { + "type": ["null", "number"] + }, + "orderedProductSales": { "type": "object", "properties": { - "browserSessions": { - "type": ["null", "number"] - }, - "mobileAppSessions": { - "type": ["null", "number"] - }, - "sessions": { - "type": ["null", "number"] - }, - "browserSessionPercentage": { - "type": ["null", "number"] - }, - "mobileAppSessionPercentage": { - "type": ["null", "number"] - }, - "sessionPercentage": { - "type": ["null", "number"] - }, - "browserPageViews": { - "type": ["null", "number"] - }, - "mobileAppPageViews": { - "type": ["null", "number"] - }, - "pageViews": { - "type": ["null", "number"] - }, - "browserPageViewsPercentage": { - "type": ["null", "number"] - }, - "mobileAppPageViewsPercentage": { - "type": ["null", "number"] - }, - "pageViewsPercentage": { - "type": ["null", "number"] - }, - "buyBoxPercentage": { - "type": ["null", "number"] - }, - "unitSessionPercentage": { - "type": ["null", "number"] - } + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } + } + }, + "totalOrderItems": { + "type": ["null", "number"] + } + } + }, + "trafficByAsin": { + "type": "object", + "properties": { + "browserSessions": { + "type": ["null", "number"] + }, + "mobileAppSessions": { + "type": ["null", "number"] + }, + "sessions": { + "type": ["null", "number"] + }, + "browserSessionPercentage": { + "type": ["null", "number"] + }, + "mobileAppSessionPercentage": { + "type": ["null", "number"] + }, + "sessionPercentage": { + "type": ["null", "number"] + }, + "browserPageViews": { + "type": ["null", "number"] + }, + "mobileAppPageViews": { + "type": ["null", "number"] + }, + "pageViews": { + "type": ["null", "number"] + }, + "browserPageViewsPercentage": { + "type": ["null", "number"] + }, + "mobileAppPageViewsPercentage": { + "type": ["null", "number"] + }, + "pageViewsPercentage": { + "type": ["null", "number"] + }, + "buyBoxPercentage": { + "type": ["null", "number"] + }, + "unitSessionPercentage": { + "type": ["null", "number"] + } } - } - }, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["endDate"] + } + } }, - "sync_mode": "incremental", - "destination_sync_mode": "append", - "cursor_field": ["endDate"] - } - ] - } \ No newline at end of file + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["endDate"] + }, + "sync_mode": "incremental", + "destination_sync_mode": "append", + "cursor_field": ["endDate"] + } + ] +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_inventory_report.json b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_inventory_report.json index 52b033565c06..134341c5031e 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_inventory_report.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_inventory_report.json @@ -1,101 +1,100 @@ { - "streams": [ - { - "stream": { - "name": "GET_VENDOR_INVENTORY_REPORT", - "json_schema": { - "title": "Vendor Inventory Reports", - "description": "Vendor Inventory Reports", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "startDate": { - "type": ["null", "string"] - }, - "endDate": { - "type": ["null", "string"] - }, - "asin": { - "type": ["null", "string"] - }, - "vendorConfirmationRate": { - "type": ["null", "number"] - }, - "netReceivedAmount": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + "streams": [ + { + "stream": { + "name": "GET_VENDOR_INVENTORY_REPORT", + "json_schema": { + "title": "Vendor Inventory Reports", + "description": "Vendor Inventory Reports", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "startDate": { + "type": ["null", "string"] + }, + "endDate": { + "type": ["null", "string"] + }, + "asin": { + "type": ["null", "string"] + }, + "vendorConfirmationRate": { + "type": ["null", "number"] + }, + "netReceivedAmount": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "netReceivedUnits": { - "type": ["null", "number"] - }, - "openPurchaseOrderQuantity": { - "type": ["null", "number"] - }, - "overallVendorLeadTime": { - "type": ["null", "number"] - }, - "sellThroughRate": { - "type": ["null", "number"] - }, - "sellableOnHandInventory": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "netReceivedUnits": { + "type": ["null", "number"] + }, + "openPurchaseOrderQuantity": { + "type": ["null", "number"] + }, + "overallVendorLeadTime": { + "type": ["null", "number"] + }, + "sellThroughRate": { + "type": ["null", "number"] + }, + "sellableOnHandInventory": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "sellableOnHandUnits": { - "type": ["null", "number"] - }, - "unfilledCustomerOrderedUnits": { - "type": ["null", "number"] - }, - "unsellableOnHandInventory": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "sellableOnHandUnits": { + "type": ["null", "number"] + }, + "unfilledCustomerOrderedUnits": { + "type": ["null", "number"] + }, + "unsellableOnHandInventory": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "aged90PlusDaysSellableUnits": { - "type": ["null", "number"] - }, - "unhealthyInventory": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "aged90PlusDaysSellableUnits": { + "type": ["null", "number"] + }, + "unhealthyInventory": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "unhealthyUnits": { - "type": ["null", "number"] } + }, + "unhealthyUnits": { + "type": ["null", "number"] } - }, - "supported_sync_modes": ["full_refresh"] + } }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - } - ] - } - \ No newline at end of file + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_sales_report.json b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_sales_report.json index 2de492052888..e5e6888fdc6f 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_sales_report.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_catalog_vendor_sales_report.json @@ -1,75 +1,74 @@ { - "streams": [ - { - "stream": { - "name": "GET_VENDOR_SALES_REPORT", - "json_schema": { - "title": "Vendor Sales Reports", - "description": "Vendor Sales Reports", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "startDate": { - "type": ["null", "string"] - }, - "endDate": { - "type": ["null", "string"] - }, - "asin": { - "type": ["null", "string"] - }, - "customerReturns": { - "type": ["null", "number"] - }, - "orderedRevenue": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + "streams": [ + { + "stream": { + "name": "GET_VENDOR_SALES_REPORT", + "json_schema": { + "title": "Vendor Sales Reports", + "description": "Vendor Sales Reports", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "startDate": { + "type": ["null", "string"] + }, + "endDate": { + "type": ["null", "string"] + }, + "asin": { + "type": ["null", "string"] + }, + "customerReturns": { + "type": ["null", "number"] + }, + "orderedRevenue": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "orderedUnits": { - "type": ["null", "number"] - }, - "shippedCogs": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "orderedUnits": { + "type": ["null", "number"] + }, + "shippedCogs": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "shippedRevenue": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "shippedRevenue": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "shippedUnits": { - "type": ["null", "number"] } + }, + "shippedUnits": { + "type": ["null", "number"] } - }, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["endDate"] + } }, - "sync_mode": "incremental", - "destination_sync_mode": "append", - "cursor_field": ["endDate"] - } - ] - } - \ No newline at end of file + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["endDate"] + }, + "sync_mode": "incremental", + "destination_sync_mode": "append", + "cursor_field": ["endDate"] + } + ] +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data.json b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data.json index 2c0dcfb9c629..df1619980630 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data.json @@ -1,26 +1,26 @@ { - "streams": [ - { - "stream": { - "name": "GET_AFN_INVENTORY_DATA", - "json_schema": { - "title": "Seller Sales and Traffic Business Report", - "description": "Seller retail analytics reports - Sales and Traffic Business Report By Asin", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "seller-sku": { "type": ["null", "string"] }, - "fulfillment-channel-sku": { "type": ["null", "string"] }, - "asin": { "type": ["null", "string"] }, - "condition-type": { "type": ["null", "string"] }, - "Warehouse-Condition-code": { "type": ["null", "string"] }, - "Quantity Available": { "type": ["null", "number"] } - } - }, - "supported_sync_modes": ["full_refresh"] + "streams": [ + { + "stream": { + "name": "GET_AFN_INVENTORY_DATA", + "json_schema": { + "title": "Seller Sales and Traffic Business Report", + "description": "Seller retail analytics reports - Sales and Traffic Business Report By Asin", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "seller-sku": { "type": ["null", "string"] }, + "fulfillment-channel-sku": { "type": ["null", "string"] }, + "asin": { "type": ["null", "string"] }, + "condition-type": { "type": ["null", "string"] }, + "Warehouse-Condition-code": { "type": ["null", "string"] }, + "Quantity Available": { "type": ["null", "number"] } + } }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - } - ] - } \ No newline at end of file + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data_by_country.json b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data_by_country.json index d511567c0d10..7abf4eaa3109 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data_by_country.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/integration_tests/configured_report_get_afn_inventory_data_by_country.json @@ -1,26 +1,26 @@ { - "streams": [ - { - "stream": { - "name": "GET_AFN_INVENTORY_DATA_BY_COUNTRY", - "json_schema": { - "title": "Seller Sales and Traffic Business Report", - "description": "Seller retail analytics reports - Sales and Traffic Business Report By Asin", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "seller-sku": { "type": ["null", "string"] }, - "fulfillment-channel-sku": { "type": ["null", "string"] }, - "asin": { "type": ["null", "string"] }, - "condition-type": { "type": ["null", "string"] }, - "Warehouse-Condition-code": { "type": ["null", "string"] }, - "Quantity Available": { "type": ["null", "number"] } - } - }, - "supported_sync_modes": ["full_refresh"] + "streams": [ + { + "stream": { + "name": "GET_AFN_INVENTORY_DATA_BY_COUNTRY", + "json_schema": { + "title": "Seller Sales and Traffic Business Report", + "description": "Seller retail analytics reports - Sales and Traffic Business Report By Asin", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "seller-sku": { "type": ["null", "string"] }, + "fulfillment-channel-sku": { "type": ["null", "string"] }, + "asin": { "type": ["null", "string"] }, + "condition-type": { "type": ["null", "string"] }, + "Warehouse-Condition-code": { "type": ["null", "string"] }, + "Quantity Available": { "type": ["null", "number"] } + } }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - } - ] - } \ No newline at end of file + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_AFN_INVENTORY_DATA_BY_COUNTRY.json b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_AFN_INVENTORY_DATA_BY_COUNTRY.json index efc8a0347d8c..984f466bebc4 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_AFN_INVENTORY_DATA_BY_COUNTRY.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_AFN_INVENTORY_DATA_BY_COUNTRY.json @@ -1,15 +1,14 @@ { - "title": "FBA Multi-Country Inventory Report", - "description": "", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "seller-sku": { "type": ["null", "string"] }, - "fulfillment-channel-sku": { "type": ["null", "string"] }, - "asin": { "type": ["null", "string"] }, - "condition-type": { "type": ["null", "string"] }, - "country": { "type": ["null", "string"] }, - "quantity-for-local-fulfillment": { "type": ["null", "number"] } - } + "title": "FBA Multi-Country Inventory Report", + "description": "", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "seller-sku": { "type": ["null", "string"] }, + "fulfillment-channel-sku": { "type": ["null", "string"] }, + "asin": { "type": ["null", "string"] }, + "condition-type": { "type": ["null", "string"] }, + "country": { "type": ["null", "string"] }, + "quantity-for-local-fulfillment": { "type": ["null", "number"] } } - \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT.json b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT.json index ed29c0d627b3..7df5452f9d1e 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT.json @@ -1,87 +1,86 @@ { - "title": "Seller Sales and Traffic Business Report - By Asin", - "description": "Seller retail analytics reports - Sales and Traffic Business Report", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "queryEndDate": { - "type": ["null", "string"] - }, - "parentAsin": { - "type": ["null", "string"] + "title": "Seller Sales and Traffic Business Report - By Asin", + "description": "Seller retail analytics reports - Sales and Traffic Business Report", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "queryEndDate": { + "type": ["null", "string"] + }, + "parentAsin": { + "type": ["null", "string"] + }, + "childAsin": { + "type": ["null", "string"] + }, + "salesByAsin": { + "type": "object", + "properties": { + "unitsOrdered": { + "type": ["null", "number"] }, - "childAsin": { - "type": ["null", "string"] - }, - "salesByAsin": { + "orderedProductSales": { "type": "object", "properties": { - "unitsOrdered": { + "amount": { "type": ["null", "number"] }, - "orderedProductSales": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } - } - }, - "totalOrderItems": { - "type": ["null", "number"] + "currencyCode": { + "type": ["null", "string"] } - } }, - "trafficByAsin": { - "type": "object", - "properties": { - "browserSessions": { - "type": ["null", "number"] - }, - "mobileAppSessions": { - "type": ["null", "number"] - }, - "sessions": { - "type": ["null", "number"] - }, - "browserSessionPercentage": { - "type": ["null", "number"] - }, - "mobileAppSessionPercentage": { - "type": ["null", "number"] - }, - "sessionPercentage": { - "type": ["null", "number"] - }, - "browserPageViews": { - "type": ["null", "number"] - }, - "mobileAppPageViews": { - "type": ["null", "number"] - }, - "pageViews": { - "type": ["null", "number"] - }, - "browserPageViewsPercentage": { - "type": ["null", "number"] - }, - "mobileAppPageViewsPercentage": { - "type": ["null", "number"] - }, - "pageViewsPercentage": { - "type": ["null", "number"] - }, - "buyBoxPercentage": { - "type": ["null", "number"] - }, - "unitSessionPercentage": { - "type": ["null", "number"] - } - } + "totalOrderItems": { + "type": ["null", "number"] + } + } + }, + "trafficByAsin": { + "type": "object", + "properties": { + "browserSessions": { + "type": ["null", "number"] + }, + "mobileAppSessions": { + "type": ["null", "number"] + }, + "sessions": { + "type": ["null", "number"] + }, + "browserSessionPercentage": { + "type": ["null", "number"] + }, + "mobileAppSessionPercentage": { + "type": ["null", "number"] + }, + "sessionPercentage": { + "type": ["null", "number"] + }, + "browserPageViews": { + "type": ["null", "number"] + }, + "mobileAppPageViews": { + "type": ["null", "number"] + }, + "pageViews": { + "type": ["null", "number"] + }, + "browserPageViewsPercentage": { + "type": ["null", "number"] + }, + "mobileAppPageViewsPercentage": { + "type": ["null", "number"] + }, + "pageViewsPercentage": { + "type": ["null", "number"] + }, + "buyBoxPercentage": { + "type": ["null", "number"] + }, + "unitSessionPercentage": { + "type": ["null", "number"] } - } - } \ No newline at end of file + } + } + } +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT_BY_DATE.json b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT_BY_DATE.json index 41b48643cf9e..f19596b1f8d5 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT_BY_DATE.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_SALES_AND_TRAFFIC_REPORT_BY_DATE.json @@ -1,143 +1,143 @@ { - "title": "Seller Sales and Traffic Business Report - By Date", - "description": "Seller retail analytics reports - Sales and Traffic Business Report", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "date": { - "type": ["null", "string"], - "format": "date" - }, - "salesByDate": { - "type": "object", - "properties": { - "orderedProductSales": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + "title": "Seller Sales and Traffic Business Report - By Date", + "description": "Seller retail analytics reports - Sales and Traffic Business Report", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "date": { + "type": ["null", "string"], + "format": "date" + }, + "salesByDate": { + "type": "object", + "properties": { + "orderedProductSales": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "unitsOrdered": { - "type": ["null", "number"] - }, - "totalOrderItems": { - "type": ["null", "number"] - }, - "averageSalesPerOrderItem": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "unitsOrdered": { + "type": ["null", "number"] + }, + "totalOrderItems": { + "type": ["null", "number"] + }, + "averageSalesPerOrderItem": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "averageUnitsPerOrderItem": { - "type": ["null", "number"] - }, - "averageSellingPrice": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "averageUnitsPerOrderItem": { + "type": ["null", "number"] + }, + "averageSellingPrice": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "unitsRefunded": { - "type": ["null", "number"] - }, - "refundRate": { - "type": ["null", "number"] - }, - "claimsGranted": { - "type": ["null", "number"] - }, - "claimsAmount": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "unitsRefunded": { + "type": ["null", "number"] + }, + "refundRate": { + "type": ["null", "number"] + }, + "claimsGranted": { + "type": ["null", "number"] + }, + "claimsAmount": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "shippedProductSales": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "shippedProductSales": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "unitsShipped": { - "type": ["null", "number"] - }, - "ordersShipped": { - "type": ["null", "number"] } + }, + "unitsShipped": { + "type": ["null", "number"] + }, + "ordersShipped": { + "type": ["null", "number"] } - }, - "trafficByDate": { - "type": "object", - "properties": { - "browserPageViews": { - "type": ["null", "number"] - }, - "mobileAppPageViews": { - "type": ["null", "number"] - }, - "pageViews": { - "type": ["null", "number"] - }, - "browserSessions": { - "type": ["null", "number"] - }, - "mobileAppSessions": { - "type": ["null", "number"] - }, - "sessions": { - "type": ["null", "number"] - }, - "buyBoxPercentage": { - "type": ["null", "number"] - }, - "orderItemSessionPercentage": { - "type": ["null", "number"] - }, - "unitSessionPercentage": { - "type": ["null", "number"] - }, - "averageOfferCount": { - "type": ["null", "number"] - }, - "averageParentItems": { - "type": ["null", "number"] - }, - "feedbackReceived": { - "type": ["null", "number"] - }, - "negativeFeedbackReceived": { - "type": ["null", "number"] - }, - "receivedNegativeFeedbackRate": { - "type": ["null", "number"] - } + } + }, + "trafficByDate": { + "type": "object", + "properties": { + "browserPageViews": { + "type": ["null", "number"] + }, + "mobileAppPageViews": { + "type": ["null", "number"] + }, + "pageViews": { + "type": ["null", "number"] + }, + "browserSessions": { + "type": ["null", "number"] + }, + "mobileAppSessions": { + "type": ["null", "number"] + }, + "sessions": { + "type": ["null", "number"] + }, + "buyBoxPercentage": { + "type": ["null", "number"] + }, + "orderItemSessionPercentage": { + "type": ["null", "number"] + }, + "unitSessionPercentage": { + "type": ["null", "number"] + }, + "averageOfferCount": { + "type": ["null", "number"] + }, + "averageParentItems": { + "type": ["null", "number"] + }, + "feedbackReceived": { + "type": ["null", "number"] + }, + "negativeFeedbackReceived": { + "type": ["null", "number"] + }, + "receivedNegativeFeedbackRate": { + "type": ["null", "number"] } } } - } \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_INVENTORY_REPORT.json b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_INVENTORY_REPORT.json index 6c518a1fcaad..e6d68c2d6bd3 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_INVENTORY_REPORT.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_INVENTORY_REPORT.json @@ -1,89 +1,88 @@ { - "title": "Vendor Inventory Data", - "description": "Vendor Inventory Data Reports", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "startDate": { - "type": ["null", "string"] - }, - "endDate": { - "type": ["null", "string"] - }, - "asin": { - "type": ["null", "string"] - }, - "vendorConfirmationRate": { - "type": ["null", "number"] - }, - "netReceivedAmount": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + "title": "Vendor Inventory Data", + "description": "Vendor Inventory Data Reports", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "startDate": { + "type": ["null", "string"] + }, + "endDate": { + "type": ["null", "string"] + }, + "asin": { + "type": ["null", "string"] + }, + "vendorConfirmationRate": { + "type": ["null", "number"] + }, + "netReceivedAmount": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "netReceivedUnits": { - "type": ["null", "number"] - }, - "openPurchaseOrderQuantity": { - "type": ["null", "number"] - }, - "overallVendorLeadTime": { - "type": ["null", "number"] - }, - "sellThroughRate": { - "type": ["null", "number"] - }, - "sellableOnHandInventory": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "netReceivedUnits": { + "type": ["null", "number"] + }, + "openPurchaseOrderQuantity": { + "type": ["null", "number"] + }, + "overallVendorLeadTime": { + "type": ["null", "number"] + }, + "sellThroughRate": { + "type": ["null", "number"] + }, + "sellableOnHandInventory": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "sellableOnHandUnits": { - "type": ["null", "number"] - }, - "unfilledCustomerOrderedUnits": { - "type": ["null", "number"] - }, - "unsellableOnHandInventory": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "sellableOnHandUnits": { + "type": ["null", "number"] + }, + "unfilledCustomerOrderedUnits": { + "type": ["null", "number"] + }, + "unsellableOnHandInventory": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "aged90PlusDaysSellableUnits": { - "type": ["null", "number"] - }, - "unhealthyInventory": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "aged90PlusDaysSellableUnits": { + "type": ["null", "number"] + }, + "unhealthyInventory": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "unhealthyUnits": { - "type": ["null", "number"] } + }, + "unhealthyUnits": { + "type": ["null", "number"] } } - \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_SALES_REPORT.json b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_SALES_REPORT.json index d8c1310972a6..e63716d0c668 100644 --- a/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_SALES_REPORT.json +++ b/airbyte-integrations/connectors/source-amazon-seller-partner/source_amazon_seller_partner/schemas/GET_VENDOR_SALES_REPORT.json @@ -1,60 +1,59 @@ { - "title": "Vendor Sales Reports", - "description": "Vendor Sales Reports", - "type": "object", - "$schema": "http://json-schema.org/draft-07/schema#", - "properties": { - "startDate": { - "type": ["null", "string"] - }, - "endDate": { - "type": ["null", "string"] - }, - "asin": { - "type": ["null", "string"] - }, - "customerReturns": { - "type": ["null", "number"] - }, - "orderedRevenue": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + "title": "Vendor Sales Reports", + "description": "Vendor Sales Reports", + "type": "object", + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "startDate": { + "type": ["null", "string"] + }, + "endDate": { + "type": ["null", "string"] + }, + "asin": { + "type": ["null", "string"] + }, + "customerReturns": { + "type": ["null", "number"] + }, + "orderedRevenue": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "orderedUnits": { - "type": ["null", "number"] - }, - "shippedCogs": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "orderedUnits": { + "type": ["null", "number"] + }, + "shippedCogs": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "shippedRevenue": { - "type": "object", - "properties": { - "amount": { - "type": ["null", "number"] - }, - "currencyCode": { - "type": ["null", "string"] - } + } + }, + "shippedRevenue": { + "type": "object", + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currencyCode": { + "type": ["null", "string"] } - }, - "shippedUnits": { - "type": ["null", "number"] } + }, + "shippedUnits": { + "type": ["null", "number"] } } - \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/catalog.json b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/catalog.json index af515d39ab52..7ed3a148a430 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/catalog.json +++ b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/catalog.json @@ -1480,41 +1480,41 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { - "id": { - "type": ["null", "number"] - }, - "external_id": { - "type": ["null", "string"] - }, - "is_listable_from_ui": { - "type": ["null", "boolean"] - }, - "is_visible": { - "type": ["null", "boolean"] - }, - "status": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "platform": { - "type": ["null", "boolean"] - }, - "date_created": { - "type": ["null", "string"], - "format": "date-time" - }, - "date_modified": { - "type": ["null", "string"], - "format": "date-time" - }, - "icon_url": { - "type": ["null", "string"] - } + "id": { + "type": ["null", "number"] + }, + "external_id": { + "type": ["null", "string"] + }, + "is_listable_from_ui": { + "type": ["null", "boolean"] + }, + "is_visible": { + "type": ["null", "boolean"] + }, + "status": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "platform": { + "type": ["null", "boolean"] + }, + "date_created": { + "type": ["null", "string"], + "format": "date-time" + }, + "date_modified": { + "type": ["null", "string"], + "format": "date-time" + }, + "icon_url": { + "type": ["null", "string"] + } } } }, @@ -1524,179 +1524,179 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { - "id": { - "type": ["null", "string"] - }, - "account_uuid": { - "type": ["null", "string"] - }, - "domain": { - "type": ["null", "string"] - }, - "secure_url": { - "type": ["null", "string"] - }, - "control_panel_base_url": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "admin_email": { - "type": ["null", "string"] - }, - "order_email": { - "type": ["null", "string"] - }, - "favicon_url": { + "id": { + "type": ["null", "string"] + }, + "account_uuid": { + "type": ["null", "string"] + }, + "domain": { + "type": ["null", "string"] + }, + "secure_url": { + "type": ["null", "string"] + }, + "control_panel_base_url": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "first_name": { + "type": ["null", "string"] + }, + "last_name": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "admin_email": { + "type": ["null", "string"] + }, + "order_email": { + "type": ["null", "string"] + }, + "favicon_url": { + "type": ["null", "string"] + }, + "timezone": { + "type": ["null", "object"], + "properties": { + "name": { "type": ["null", "string"] - }, - "timezone": { + }, + "raw_offset": { + "type": ["null", "number"] + }, + "dst_offset": { + "type": ["null", "number"] + }, + "date_format": { "type": ["null", "object"], "properties": { - "name": { - "type": ["null", "string"] - }, - "raw_offset": { - "type": ["null", "number"] - }, - "dst_offset": { - "type": ["null", "number"] - }, - "date_format": { - "type": ["null", "object"], - "properties": { - "display": { - "type": ["null", "string"] - }, - "export": { - "type": ["null", "string"] - }, - "extended_display": { - "type": ["null", "string"] - } - } - } + "display": { + "type": ["null", "string"] + }, + "export": { + "type": ["null", "string"] + }, + "extended_display": { + "type": ["null", "string"] + } } - }, - "language": { - "type": ["null", "string"] - }, - "currency": { - "type": ["null", "string"] - }, - "currency_symbol": { - "type": ["null", "string"] - }, - "decimal_separator": { - "type": ["null", "string"] - }, - "thousands_separator": { - "type": ["null", "string"] - }, - "decimal_places": { - "type": ["null", "string"] - }, - "currency_symbol_location": { - "type": ["null", "string"] - }, - "weight_units": { - "type": ["null", "string"] - }, - "dimension_units": { - "type": ["null", "string"] - }, - "dimension_decimal_places": { - "type": ["null", "string"] - }, - "dimension_decimal_token": { - "type": ["null", "string"] - }, - "dimension_thousands_token": { - "type": ["null", "string"] - }, - "plan_name": { - "type": ["null", "string"] - }, - "plan_level": { + } + } + }, + "language": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "currency_symbol": { + "type": ["null", "string"] + }, + "decimal_separator": { + "type": ["null", "string"] + }, + "thousands_separator": { + "type": ["null", "string"] + }, + "decimal_places": { + "type": ["null", "string"] + }, + "currency_symbol_location": { + "type": ["null", "string"] + }, + "weight_units": { + "type": ["null", "string"] + }, + "dimension_units": { + "type": ["null", "string"] + }, + "dimension_decimal_places": { + "type": ["null", "string"] + }, + "dimension_decimal_token": { + "type": ["null", "string"] + }, + "dimension_thousands_token": { + "type": ["null", "string"] + }, + "plan_name": { + "type": ["null", "string"] + }, + "plan_level": { + "type": ["null", "string"] + }, + "industry": { + "type": ["null", "string"] + }, + "logo": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "url": { + "type": ["null", "string"] + } + } + } + }, + "is_price_entered_with_tax": { + "type": ["null", "boolean"] + }, + "store_id": { + "type": ["null", "number"] + }, + "default_site_id": { + "type": ["null", "number"] + }, + "default_channel_id": { + "type": ["null", "number"] + }, + "active_comparison_modules": { + "type": ["null", "array"] + }, + "features": { + "type": ["null", "object"], + "properties": { + "stencil_enabled": { + "type": ["null", "boolean"] + }, + "sitewidehttps_enabled": { + "type": ["null", "boolean"] + }, + "facebook_catalog_id": { "type": ["null", "string"] - }, - "industry": { + }, + "checkout_type": { "type": ["null", "string"] - }, - "logo": { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "properties": { - "url": { - "type": ["null", "string"] - } - } - } - }, - "is_price_entered_with_tax": { + }, + "wishlists_enabled": { "type": ["null", "boolean"] - }, - "store_id": { - "type": ["null", "number"] - }, - "default_site_id": { - "type": ["null", "number"] - }, - "default_channel_id": { - "type": ["null", "number"] - }, - "active_comparison_modules": { - "type": ["null", "array"] - }, - "features": { - "type": ["null", "object"], - "properties": { - "stencil_enabled": { - "type": ["null", "boolean"] - }, - "sitewidehttps_enabled": { - "type": ["null", "boolean"] - }, - "facebook_catalog_id": { - "type": ["null", "string"] - }, - "checkout_type": { - "type": ["null", "string"] - }, - "wishlists_enabled": { - "type": ["null", "boolean"] - }, - "graphql_storefront_api_enabled": { - "type": ["null", "boolean"] - }, - "shopper_consent_tracking_enabled": { - "type": ["null", "boolean"] - }, - "multi_storefront_enabled": { - "type": ["null", "boolean"] - } - } + }, + "graphql_storefront_api_enabled": { + "type": ["null", "boolean"] + }, + "shopper_consent_tracking_enabled": { + "type": ["null", "boolean"] + }, + "multi_storefront_enabled": { + "type": ["null", "boolean"] + } } + } } } } diff --git a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/configured_catalog.json index 03ca3ecabbdd..af224c140947 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/configured_catalog.json @@ -1521,41 +1521,41 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { - "id": { - "type": ["null", "number"] - }, - "external_id": { - "type": ["null", "string"] - }, - "is_listable_from_ui": { - "type": ["null", "boolean"] - }, - "is_visible": { - "type": ["null", "boolean"] - }, - "status": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "platform": { - "type": ["null", "boolean"] - }, - "date_created": { - "type": ["null", "string"], - "format": "date-time" - }, - "date_modified": { - "type": ["null", "string"], - "format": "date-time" - }, - "icon_url": { - "type": ["null", "string"] - } + "id": { + "type": ["null", "number"] + }, + "external_id": { + "type": ["null", "string"] + }, + "is_listable_from_ui": { + "type": ["null", "boolean"] + }, + "is_visible": { + "type": ["null", "boolean"] + }, + "status": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "platform": { + "type": ["null", "boolean"] + }, + "date_created": { + "type": ["null", "string"], + "format": "date-time" + }, + "date_modified": { + "type": ["null", "string"], + "format": "date-time" + }, + "icon_url": { + "type": ["null", "string"] + } } }, "supported_sync_modes": ["incremental", "full_refresh"], @@ -1573,182 +1573,181 @@ "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { - "id": { - "type": ["null", "string"] - }, - "account_uuid": { - "type": ["null", "string"] - }, - "domain": { - "type": ["null", "string"] - }, - "secure_url": { - "type": ["null", "string"] - }, - "control_panel_base_url": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "admin_email": { - "type": ["null", "string"] - }, - "order_email": { - "type": ["null", "string"] - }, - "favicon_url": { + "id": { + "type": ["null", "string"] + }, + "account_uuid": { + "type": ["null", "string"] + }, + "domain": { + "type": ["null", "string"] + }, + "secure_url": { + "type": ["null", "string"] + }, + "control_panel_base_url": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "first_name": { + "type": ["null", "string"] + }, + "last_name": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "admin_email": { + "type": ["null", "string"] + }, + "order_email": { + "type": ["null", "string"] + }, + "favicon_url": { + "type": ["null", "string"] + }, + "timezone": { + "type": ["null", "object"], + "properties": { + "name": { "type": ["null", "string"] - }, - "timezone": { + }, + "raw_offset": { + "type": ["null", "number"] + }, + "dst_offset": { + "type": ["null", "number"] + }, + "date_format": { "type": ["null", "object"], "properties": { - "name": { - "type": ["null", "string"] - }, - "raw_offset": { - "type": ["null", "number"] - }, - "dst_offset": { - "type": ["null", "number"] - }, - "date_format": { - "type": ["null", "object"], - "properties": { - "display": { - "type": ["null", "string"] - }, - "export": { - "type": ["null", "string"] - }, - "extended_display": { - "type": ["null", "string"] - } - } - } + "display": { + "type": ["null", "string"] + }, + "export": { + "type": ["null", "string"] + }, + "extended_display": { + "type": ["null", "string"] + } } - }, - "language": { - "type": ["null", "string"] - }, - "currency": { - "type": ["null", "string"] - }, - "currency_symbol": { - "type": ["null", "string"] - }, - "decimal_separator": { - "type": ["null", "string"] - }, - "thousands_separator": { - "type": ["null", "string"] - }, - "decimal_places": { - "type": ["null", "string"] - }, - "currency_symbol_location": { - "type": ["null", "string"] - }, - "weight_units": { - "type": ["null", "string"] - }, - "dimension_units": { - "type": ["null", "string"] - }, - "dimension_decimal_places": { - "type": ["null", "string"] - }, - "dimension_decimal_token": { - "type": ["null", "string"] - }, - "dimension_thousands_token": { - "type": ["null", "string"] - }, - "plan_name": { - "type": ["null", "string"] - }, - "plan_level": { + } + } + }, + "language": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "currency_symbol": { + "type": ["null", "string"] + }, + "decimal_separator": { + "type": ["null", "string"] + }, + "thousands_separator": { + "type": ["null", "string"] + }, + "decimal_places": { + "type": ["null", "string"] + }, + "currency_symbol_location": { + "type": ["null", "string"] + }, + "weight_units": { + "type": ["null", "string"] + }, + "dimension_units": { + "type": ["null", "string"] + }, + "dimension_decimal_places": { + "type": ["null", "string"] + }, + "dimension_decimal_token": { + "type": ["null", "string"] + }, + "dimension_thousands_token": { + "type": ["null", "string"] + }, + "plan_name": { + "type": ["null", "string"] + }, + "plan_level": { + "type": ["null", "string"] + }, + "industry": { + "type": ["null", "string"] + }, + "logo": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "url": { + "type": ["null", "string"] + } + } + } + }, + "is_price_entered_with_tax": { + "type": ["null", "boolean"] + }, + "store_id": { + "type": ["null", "number"] + }, + "default_site_id": { + "type": ["null", "number"] + }, + "default_channel_id": { + "type": ["null", "number"] + }, + "active_comparison_modules": { + "type": ["null", "array"] + }, + "features": { + "type": ["null", "object"], + "properties": { + "stencil_enabled": { + "type": ["null", "boolean"] + }, + "sitewidehttps_enabled": { + "type": ["null", "boolean"] + }, + "facebook_catalog_id": { "type": ["null", "string"] - }, - "industry": { + }, + "checkout_type": { "type": ["null", "string"] - }, - "logo": { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "properties": { - "url": { - "type": ["null", "string"] - } - } - } - }, - "is_price_entered_with_tax": { + }, + "wishlists_enabled": { "type": ["null", "boolean"] - }, - "store_id": { - "type": ["null", "number"] - }, - "default_site_id": { - "type": ["null", "number"] - }, - "default_channel_id": { - "type": ["null", "number"] - }, - "active_comparison_modules": { - "type": ["null", "array"] - }, - "features": { - "type": ["null", "object"], - "properties": { - "stencil_enabled": { - "type": ["null", "boolean"] - }, - "sitewidehttps_enabled": { - "type": ["null", "boolean"] - }, - "facebook_catalog_id": { - "type": ["null", "string"] - }, - "checkout_type": { - "type": ["null", "string"] - }, - "wishlists_enabled": { - "type": ["null", "boolean"] - }, - "graphql_storefront_api_enabled": { - "type": ["null", "boolean"] - }, - "shopper_consent_tracking_enabled": { - "type": ["null", "boolean"] - }, - "multi_storefront_enabled": { - "type": ["null", "boolean"] - } - } + }, + "graphql_storefront_api_enabled": { + "type": ["null", "boolean"] + }, + "shopper_consent_tracking_enabled": { + "type": ["null", "boolean"] + }, + "multi_storefront_enabled": { + "type": ["null", "boolean"] + } } + } } - } - , + }, "supported_sync_modes": ["incremental", "full_refresh"], "source_defined_cursor": true, "default_cursor_field": ["store_id"] diff --git a/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/channels.json b/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/channels.json index 32e58a0055f2..1cc51924ea59 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/channels.json +++ b/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/channels.json @@ -1,41 +1,41 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id": { - "type": ["null", "integer"] - }, - "external_id": { - "type": ["null", "string"] - }, - "is_listable_from_ui": { - "type": ["null", "boolean"] - }, - "is_visible": { - "type": ["null", "boolean"] - }, - "status": { - "type": ["null", "string"] - }, - "name": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "platform": { - "type": ["null", "string"] - }, - "date_created": { - "type": ["null", "string"], - "format": "date-time" - }, - "date_modified": { - "type": ["null", "string"], - "format": "date-time" - }, - "icon_url": { - "type": ["null", "string"] - } + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["null", "integer"] + }, + "external_id": { + "type": ["null", "string"] + }, + "is_listable_from_ui": { + "type": ["null", "boolean"] + }, + "is_visible": { + "type": ["null", "boolean"] + }, + "status": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "platform": { + "type": ["null", "string"] + }, + "date_created": { + "type": ["null", "string"], + "format": "date-time" + }, + "date_modified": { + "type": ["null", "string"], + "format": "date-time" + }, + "icon_url": { + "type": ["null", "string"] } } +} diff --git a/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/store.json b/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/store.json index d49524717519..66955e00e224 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/store.json +++ b/airbyte-integrations/connectors/source-bigcommerce/source_bigcommerce/schemas/store.json @@ -1,179 +1,179 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id": { - "type": ["null", "string"] - }, - "account_uuid": { - "type": ["null", "string"] - }, - "domain": { - "type": ["null", "string"] - }, - "secure_url": { - "type": ["null", "string"] - }, - "control_panel_base_url": { - "type": ["null", "string"] - }, - "status": { - "type": ["null", "string"] - }, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["null", "string"] + }, + "account_uuid": { + "type": ["null", "string"] + }, + "domain": { + "type": ["null", "string"] + }, + "secure_url": { + "type": ["null", "string"] + }, + "control_panel_base_url": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "first_name": { + "type": ["null", "string"] + }, + "last_name": { + "type": ["null", "string"] + }, + "address": { + "type": ["null", "string"] + }, + "country": { + "type": ["null", "string"] + }, + "phone": { + "type": ["null", "string"] + }, + "admin_email": { + "type": ["null", "string"] + }, + "order_email": { + "type": ["null", "string"] + }, + "favicon_url": { + "type": ["null", "string"] + }, + "timezone": { + "type": ["null", "object"], + "properties": { "name": { - "type": ["null", "string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "country": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "admin_email": { - "type": ["null", "string"] - }, - "order_email": { - "type": ["null", "string"] - }, - "favicon_url": { - "type": ["null", "string"] - }, - "timezone": { - "type": ["null", "object"], - "properties": { - "name": { - "type": ["null", "string"] - }, - "raw_offset": { - "type": ["null", "integer"] - }, - "dst_offset": { - "type": ["null", "integer"] - }, - "date_format": { - "type": ["null", "object"], - "properties": { - "display": { - "type": ["null", "string"] - }, - "export": { - "type": ["null", "string"] - }, - "extended_display": { - "type": ["null", "string"] - } - } - } - } - }, - "language": { - "type": ["null", "string"] - }, - "currency": { - "type": ["null", "string"] - }, - "currency_symbol": { - "type": ["null", "string"] - }, - "decimal_separator": { - "type": ["null", "string"] - }, - "thousands_separator": { - "type": ["null", "string"] - }, - "decimal_places": { - "type": ["null", "string"] - }, - "currency_symbol_location": { - "type": ["null", "string"] - }, - "weight_units": { - "type": ["null", "string"] - }, - "dimension_units": { - "type": ["null", "string"] - }, - "dimension_decimal_places": { - "type": ["null", "string"] - }, - "dimension_decimal_token": { - "type": ["null", "string"] - }, - "dimension_thousands_token": { - "type": ["null", "string"] - }, - "plan_name": { - "type": ["null", "string"] - }, - "plan_level": { - "type": ["null", "string"] - }, - "industry": { - "type": ["null", "string"] - }, - "logo": { - "type": ["null", "array"], - "items": { - "type": ["null", "object"], - "properties": { - "url": { - "type": ["null", "string"] - } - } - } - }, - "is_price_entered_with_tax": { - "type": ["null", "boolean"] - }, - "store_id": { - "type": ["null", "integer"] - }, - "default_site_id": { - "type": ["null", "integer"] - }, - "default_channel_id": { - "type": ["null", "integer"] - }, - "active_comparison_modules": { - "type": ["null", "array"] - }, - "features": { - "type": ["null", "object"], - "properties": { - "stencil_enabled": { - "type": ["null", "boolean"] - }, - "sitewidehttps_enabled": { - "type": ["null", "boolean"] - }, - "facebook_catalog_id": { - "type": ["null", "string"] - }, - "checkout_type": { - "type": ["null", "string"] - }, - "wishlists_enabled": { - "type": ["null", "boolean"] - }, - "graphql_storefront_api_enabled": { - "type": ["null", "boolean"] - }, - "shopper_consent_tracking_enabled": { - "type": ["null", "boolean"] - }, - "multi_storefront_enabled": { - "type": ["null", "boolean"] - } + "type": ["null", "string"] + }, + "raw_offset": { + "type": ["null", "integer"] + }, + "dst_offset": { + "type": ["null", "integer"] + }, + "date_format": { + "type": ["null", "object"], + "properties": { + "display": { + "type": ["null", "string"] + }, + "export": { + "type": ["null", "string"] + }, + "extended_display": { + "type": ["null", "string"] } + } + } + } + }, + "language": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "currency_symbol": { + "type": ["null", "string"] + }, + "decimal_separator": { + "type": ["null", "string"] + }, + "thousands_separator": { + "type": ["null", "string"] + }, + "decimal_places": { + "type": ["null", "string"] + }, + "currency_symbol_location": { + "type": ["null", "string"] + }, + "weight_units": { + "type": ["null", "string"] + }, + "dimension_units": { + "type": ["null", "string"] + }, + "dimension_decimal_places": { + "type": ["null", "string"] + }, + "dimension_decimal_token": { + "type": ["null", "string"] + }, + "dimension_thousands_token": { + "type": ["null", "string"] + }, + "plan_name": { + "type": ["null", "string"] + }, + "plan_level": { + "type": ["null", "string"] + }, + "industry": { + "type": ["null", "string"] + }, + "logo": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "url": { + "type": ["null", "string"] + } + } + } + }, + "is_price_entered_with_tax": { + "type": ["null", "boolean"] + }, + "store_id": { + "type": ["null", "integer"] + }, + "default_site_id": { + "type": ["null", "integer"] + }, + "default_channel_id": { + "type": ["null", "integer"] + }, + "active_comparison_modules": { + "type": ["null", "array"] + }, + "features": { + "type": ["null", "object"], + "properties": { + "stencil_enabled": { + "type": ["null", "boolean"] + }, + "sitewidehttps_enabled": { + "type": ["null", "boolean"] + }, + "facebook_catalog_id": { + "type": ["null", "string"] + }, + "checkout_type": { + "type": ["null", "string"] + }, + "wishlists_enabled": { + "type": ["null", "boolean"] + }, + "graphql_storefront_api_enabled": { + "type": ["null", "boolean"] + }, + "shopper_consent_tracking_enabled": { + "type": ["null", "boolean"] + }, + "multi_storefront_enabled": { + "type": ["null", "boolean"] } + } } + } } diff --git a/airbyte-integrations/connectors/source-cart/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-cart/integration_tests/invalid_config.json index 4dd831d43564..85fb4646b93c 100644 --- a/airbyte-integrations/connectors/source-cart/integration_tests/invalid_config.json +++ b/airbyte-integrations/connectors/source-cart/integration_tests/invalid_config.json @@ -1,8 +1,8 @@ { "start_date": "2022-01-01T00:00:00Z", "credentials": { - "auth_type": "SINGLE_STORE_ACCESS_TOKEN", - "store_name": "www.my_pretyy_store.com", - "access_token": "1234567890absc" + "auth_type": "SINGLE_STORE_ACCESS_TOKEN", + "store_name": "www.my_pretyy_store.com", + "access_token": "1234567890absc" } } diff --git a/airbyte-integrations/connectors/source-cart/source_cart/spec.json b/airbyte-integrations/connectors/source-cart/source_cart/spec.json index 05380699879e..d071fa04b998 100644 --- a/airbyte-integrations/connectors/source-cart/source_cart/spec.json +++ b/airbyte-integrations/connectors/source-cart/source_cart/spec.json @@ -71,7 +71,9 @@ "description": "The name of Cart.com Online Store. All API URLs start with https://[mystorename.com]/api/v1/, where [mystorename.com] is the domain name of your store." } } - }]}, + } + ] + }, "start_date": { "title": "Start Date", "type": "string", diff --git a/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/addon.json b/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/addon.json index 222901b1558d..fa609e514917 100644 --- a/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/addon.json +++ b/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/addon.json @@ -177,11 +177,11 @@ }, "custom_fields": { "type": ["null", "array"], - "items" : { - "type" : ["null", "object"], + "items": { + "type": ["null", "object"], "properties": { - "name" : { - "type" : ["null", "string"] + "name": { + "type": ["null", "string"] }, "value": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/attached_item.json b/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/attached_item.json index 70f3cd47ed09..18c27549dddb 100644 --- a/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/attached_item.json +++ b/airbyte-integrations/connectors/source-chargebee/source_chargebee/schemas/attached_item.json @@ -66,12 +66,12 @@ "type": ["null", "array"], "items": { "type": ["null", "object"], - "properties" : { - "name" : { - "type" : ["null", "string"] + "properties": { + "name": { + "type": ["null", "string"] }, - "value" : { - "type" : ["null", "string"] + "value": { + "type": ["null", "string"] } } } diff --git a/airbyte-integrations/connectors/source-clickhouse/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/ClickHouseJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-clickhouse/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/ClickHouseJdbcSourceAcceptanceTest.java index 5a66d267fb11..e82d696bd8a3 100644 --- a/airbyte-integrations/connectors/source-clickhouse/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/ClickHouseJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-clickhouse/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/ClickHouseJdbcSourceAcceptanceTest.java @@ -7,8 +7,8 @@ import static io.airbyte.db.jdbc.JdbcUtils.JDBC_URL_KEY; import static io.airbyte.integrations.source.clickhouse.ClickHouseSource.SSL_MODE; import static java.time.temporal.ChronoUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; diff --git a/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json b/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json index 1653e3eb5d8b..8b3932c079f8 100644 --- a/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-file-secure/integration_tests/spec.json @@ -14,7 +14,16 @@ }, "format": { "type": "string", - "enum": ["csv", "json", "jsonl", "excel", "excel_binary", "feather", "parquet", "yaml"], + "enum": [ + "csv", + "json", + "jsonl", + "excel", + "excel_binary", + "feather", + "parquet", + "yaml" + ], "default": "csv", "title": "File Format", "description": "The Format of the file which should be replicated (Warning: some formats may be experimental, please refer to the docs)." diff --git a/airbyte-integrations/connectors/source-file/source_file/spec.json b/airbyte-integrations/connectors/source-file/source_file/spec.json index 2a6b3ea3e432..d430448dc81b 100644 --- a/airbyte-integrations/connectors/source-file/source_file/spec.json +++ b/airbyte-integrations/connectors/source-file/source_file/spec.json @@ -15,7 +15,16 @@ }, "format": { "type": "string", - "enum": ["csv", "json", "jsonl", "excel", "excel_binary", "feather", "parquet", "yaml"], + "enum": [ + "csv", + "json", + "jsonl", + "excel", + "excel_binary", + "feather", + "parquet", + "yaml" + ], "default": "csv", "title": "File Format", "description": "The Format of the file which should be replicated (Warning: some formats may be experimental, please refer to the docs)." diff --git a/airbyte-integrations/connectors/source-github/source_github/schemas/workflow_jobs.json b/airbyte-integrations/connectors/source-github/source_github/schemas/workflow_jobs.json index a2e009fba5a4..83ba55660f88 100644 --- a/airbyte-integrations/connectors/source-github/source_github/schemas/workflow_jobs.json +++ b/airbyte-integrations/connectors/source-github/source_github/schemas/workflow_jobs.json @@ -24,19 +24,13 @@ "type": "string" }, "html_url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "status": { "type": "string" }, "conclusion": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "started_at": { "format": "date-time", @@ -44,10 +38,7 @@ }, "completed_at": { "format": "date-time", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "name": { "type": "string" @@ -61,10 +52,7 @@ "type": "string" }, "conclusion": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "name": { "type": "string" @@ -74,17 +62,11 @@ }, "started_at": { "format": "date-time", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "completed_at": { "format": "date-time", - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } } } @@ -99,28 +81,16 @@ } }, "runner_id": { - "type": [ - "integer", - "null" - ] + "type": ["integer", "null"] }, "runner_name": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "runner_group_id": { - "type": [ - "integer", - "null" - ] + "type": ["integer", "null"] }, "runner_group_name": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] } } -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-greenhouse/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-greenhouse/integration_tests/abnormal_state.json index 5c4e4b44e5ef..3498542b9ca3 100644 --- a/airbyte-integrations/connectors/source-greenhouse/integration_tests/abnormal_state.json +++ b/airbyte-integrations/connectors/source-greenhouse/integration_tests/abnormal_state.json @@ -30,29 +30,29 @@ "applied_at": "2222-01-21T00:00:00.000Z" }, "jobs_stages": { - "4177046003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "4177048003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "4446240003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "4466310003": {"updated_at": "2222-01-01T00:00:00.000Z"} + "4177046003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "4177048003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "4446240003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "4466310003": { "updated_at": "2222-01-01T00:00:00.000Z" } }, "applications_demographics_answers": { - "19214950003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "19214993003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "19215172003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "19215333003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "44933447003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "44937562003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "47459993003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "48693310003": {"updated_at": "2222-01-01T00:00:00.000Z"} + "19214950003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "19214993003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "19215172003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "19215333003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "44933447003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "44937562003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "47459993003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "48693310003": { "updated_at": "2222-01-01T00:00:00.000Z" } }, "applications_interviews": { - "19214950003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "19214993003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "19215172003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "19215333003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "44933447003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "44937562003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "47459993003": {"updated_at": "2222-01-01T00:00:00.000Z"}, - "48693310003": {"updated_at": "2222-01-01T00:00:00.000Z"} + "19214950003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "19214993003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "19215172003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "19215333003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "44933447003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "44937562003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "47459993003": { "updated_at": "2222-01-01T00:00:00.000Z" }, + "48693310003": { "updated_at": "2222-01-01T00:00:00.000Z" } } } diff --git a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java index 69cabaa1ef40..59611c601fe4 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java +++ b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java @@ -18,7 +18,6 @@ import static io.airbyte.db.jdbc.JdbcConstants.JDBC_COLUMN_SIZE; import static io.airbyte.db.jdbc.JdbcConstants.JDBC_COLUMN_TABLE_NAME; import static io.airbyte.db.jdbc.JdbcConstants.JDBC_COLUMN_TYPE_NAME; -import static io.airbyte.db.jdbc.JdbcUtils.EQUALS; import static io.airbyte.db.jdbc.JdbcConstants.JDBC_IS_NULLABLE; import static io.airbyte.db.jdbc.JdbcUtils.EQUALS; @@ -81,6 +80,7 @@ * for a relational DB which has a JDBC driver, make an effort to use this class. */ public abstract class AbstractJdbcSource extends AbstractRelationalDbSource implements Source { + public static final String SSL_MODE = "sslMode"; public static final String TRUST_KEY_STORE_URL = "trustCertificateKeyStoreUrl"; @@ -117,7 +117,6 @@ public static Optional bySpec(final String spec) { } - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJdbcSource.class); protected final String driverClass; @@ -128,8 +127,8 @@ public static Optional bySpec(final String spec) { protected Collection dataSources = new ArrayList<>(); public AbstractJdbcSource(final String driverClass, - final Supplier streamingQueryConfigProvider, - final JdbcCompatibleSourceOperations sourceOperations) { + final Supplier streamingQueryConfigProvider, + final JdbcCompatibleSourceOperations sourceOperations) { this.driverClass = driverClass; this.streamingQueryConfigProvider = streamingQueryConfigProvider; this.sourceOperations = sourceOperations; @@ -173,10 +172,10 @@ protected List>> discoverInternal(final JdbcData LOGGER.info("Internal schemas to exclude: {}", internalSchemas); final Set tablesWithSelectGrantPrivilege = getPrivilegesTableForCurrentUser(database, schema); return database.bufferedResultSetQuery( - // retrieve column metadata from the database - connection -> connection.getMetaData().getColumns(getCatalog(database), schema, null, null), - // store essential column metadata to a Json object from the result set about each column - this::getColumnMetadata) + // retrieve column metadata from the database + connection -> connection.getMetaData().getColumns(getCatalog(database), schema, null, null), + // store essential column metadata to a Json object from the result set about each column + this::getColumnMetadata) .stream() .filter(excludeNotAccessibleTables(internalSchemas, tablesWithSelectGrantPrivilege)) // group by schema and table name to handle the case where a table with the same name exists in @@ -215,7 +214,7 @@ private List extractCursorFields(final List fields) { } protected Predicate excludeNotAccessibleTables(final Set internalSchemas, - final Set tablesWithSelectGrantPrivilege) { + final Set tablesWithSelectGrantPrivilege) { return jsonNode -> { if (tablesWithSelectGrantPrivilege.isEmpty()) { return isNotInternalSchema(jsonNode, internalSchemas); @@ -223,7 +222,7 @@ protected Predicate excludeNotAccessibleTables(final Set inter return tablesWithSelectGrantPrivilege.stream() .anyMatch(e -> e.getSchemaName().equals(jsonNode.get(INTERNAL_SCHEMA_NAME).asText())) && tablesWithSelectGrantPrivilege.stream() - .anyMatch(e -> e.getTableName().equals(jsonNode.get(INTERNAL_TABLE_NAME).asText())) + .anyMatch(e -> e.getTableName().equals(jsonNode.get(INTERNAL_TABLE_NAME).asText())) && !internalSchemas.contains(jsonNode.get(INTERNAL_SCHEMA_NAME).asText()); }; } @@ -274,7 +273,7 @@ public JsonSchemaType getType(final Datatype columnType) { @Override protected Map> discoverPrimaryKeys(final JdbcDatabase database, - final List>> tableInfos) { + final List>> tableInfos) { LOGGER.info("Discover primary keys for tables: " + tableInfos.stream().map(TableInfo::getName).collect( Collectors.toSet())); try { @@ -326,12 +325,12 @@ public boolean isCursorType(final Datatype type) { @Override public AutoCloseableIterator queryTableIncremental(final JdbcDatabase database, - final List columnNames, - final String schemaName, - final String tableName, - final String cursorField, - final Datatype cursorFieldType, - final String cursorValue) { + final List columnNames, + final String schemaName, + final String tableName, + final String cursorField, + final Datatype cursorFieldType, + final String cursorValue) { LOGGER.info("Queueing query for table: {}", tableName); return AutoCloseableIterators.lazyIterator(() -> { try { @@ -409,7 +408,7 @@ protected Map getConnectionProperties(final JsonNode config) { * @throws IllegalArgumentException */ protected static void assertCustomParametersDontOverwriteDefaultParameters(final Map customParameters, - final Map defaultParameters) { + final Map defaultParameters) { for (final String key : defaultParameters.keySet()) { if (customParameters.containsKey(key) && !Objects.equals(customParameters.get(key), defaultParameters.get(key))) { throw new IllegalArgumentException("Cannot overwrite default JDBC parameter " + key); @@ -527,8 +526,6 @@ protected String toSslJdbcParam(final SslMode sslMode) { return sslMode.name(); } - - protected List identifyStreamsToSnapshot(final ConfiguredAirbyteCatalog catalog, final StateManager stateManager) { final Set alreadySyncedStreams = stateManager.getCdcStateManager().getInitialStreamsSynced(); if (alreadySyncedStreams.isEmpty() && (stateManager.getCdcStateManager().getCdcState() == null @@ -545,4 +542,5 @@ protected List identifyStreamsToSnapshot(final Configur .map(Jsons::clone) .collect(Collectors.toList()); } + } diff --git a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/JdbcSSLConnectionUtils.java b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/JdbcSSLConnectionUtils.java index 2bce94a32991..682b78734a73 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/JdbcSSLConnectionUtils.java +++ b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/JdbcSSLConnectionUtils.java @@ -9,7 +9,6 @@ import io.airbyte.db.util.SSLCertificateUtils; import java.io.IOException; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStoreException; diff --git a/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/main/java/io.airbyte.integrations.source.mongodb/MongodbSourceStrictEncrypt.java b/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/main/java/io.airbyte.integrations.source.mongodb/MongodbSourceStrictEncrypt.java index ceb48df72cef..4d460be24483 100644 --- a/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/main/java/io.airbyte.integrations.source.mongodb/MongodbSourceStrictEncrypt.java +++ b/airbyte-integrations/connectors/source-mongodb-strict-encrypt/src/main/java/io.airbyte.integrations.source.mongodb/MongodbSourceStrictEncrypt.java @@ -29,7 +29,8 @@ public MongodbSourceStrictEncrypt() { public AirbyteConnectionStatus check(final JsonNode config) throws Exception { final JsonNode instanceConfig = config.get(MongoDbSourceUtils.INSTANCE_TYPE); final MongoInstanceType instance = MongoInstanceType.fromValue(instanceConfig.get(MongoDbSourceUtils.INSTANCE).asText()); - // If the MongoDb source connector is not set up to use a TLS connection, then we should fail the check. + // If the MongoDb source connector is not set up to use a TLS connection, then we should fail the + // check. if (instance.equals(MongoInstanceType.STANDALONE) && !MongoDbSourceUtils.tlsEnabledForStandaloneInstance(config, instanceConfig)) { return new AirbyteConnectionStatus() .withStatus(Status.FAILED) diff --git a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java index 69d0255f2d01..45d8ba700445 100644 --- a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java +++ b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java @@ -128,8 +128,8 @@ private Set getAuthorizedCollections(final MongoDatabase database) { */ try { final Document document = database.getDatabase().runCommand(new Document("listCollections", 1) - .append("authorizedCollections", true) - .append("nameOnly", true)) + .append("authorizedCollections", true) + .append("nameOnly", true)) .append("filter", "{ 'type': 'collection' }"); return document.toBsonDocument() .get("cursor").asDocument() diff --git a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSourceUtils.java b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSourceUtils.java index 44a9f8f18040..6939ddf9b0ba 100644 --- a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSourceUtils.java +++ b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSourceUtils.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.source.mongodb; import static org.bson.BsonType.DATE_TIME; @@ -40,4 +44,5 @@ public static boolean tlsEnabledForStandaloneInstance(final JsonNode config, fin return config.has(JdbcUtils.TLS_KEY) ? config.get(JdbcUtils.TLS_KEY).asBoolean() : (instanceConfig.has(JdbcUtils.TLS_KEY) ? instanceConfig.get(JdbcUtils.TLS_KEY).asBoolean() : true); } + } diff --git a/airbyte-integrations/connectors/source-mongodb-v2/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MongoDbSourceStandaloneAcceptanceTest.java b/airbyte-integrations/connectors/source-mongodb-v2/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MongoDbSourceStandaloneAcceptanceTest.java index 9213dbaf744a..9fa9cd30819c 100644 --- a/airbyte-integrations/connectors/source-mongodb-v2/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MongoDbSourceStandaloneAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mongodb-v2/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MongoDbSourceStandaloneAcceptanceTest.java @@ -17,8 +17,8 @@ import io.airbyte.integrations.source.mongodb.MongoDbSource; import io.airbyte.integrations.standardtest.source.TestDestinationEnv; import io.airbyte.protocol.models.AirbyteCatalog; -import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.AirbyteConnectionStatus; +import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.CatalogHelpers; import io.airbyte.protocol.models.Field; import io.airbyte.protocol.models.JsonSchemaType; diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/main/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSource.java b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/main/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSource.java index 18a6ce917c57..4038360f4eb8 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/main/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSource.java +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/main/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSource.java @@ -5,7 +5,6 @@ package io.airbyte.integrations.source.mysql_strict_encrypt; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.airbyte.commons.json.Jsons; import io.airbyte.db.jdbc.JdbcUtils; @@ -26,6 +25,7 @@ * internet without encryption. */ public class MySqlStrictEncryptSource extends SpecModifyingSource implements Source { + public static final String TUNNEL_METHOD = "tunnel_method"; public static final String NO_TUNNEL = "NO_TUNNEL"; public static final String SSL_MODE = "ssl_mode"; @@ -52,17 +52,19 @@ public ConnectorSpecification modifySpec(final ConnectorSpecification originalSp @Override public AirbyteConnectionStatus check(final JsonNode config) throws Exception { - // #15808 Disallow connecting to db with disable, prefer or allow SSL mode when connecting directly and not over SSH tunnel + // #15808 Disallow connecting to db with disable, prefer or allow SSL mode when connecting directly + // and not over SSH tunnel if (config.has(TUNNEL_METHOD) && config.get(TUNNEL_METHOD).has(TUNNEL_METHOD) && config.get(TUNNEL_METHOD).get(TUNNEL_METHOD).asText().equals(NO_TUNNEL)) { - //If no SSH tunnel + // If no SSH tunnel if (config.has(SSL_MODE) && config.get(SSL_MODE).has(MODE)) { if (Set.of(SSL_MODE_PREFERRED).contains(config.get(SSL_MODE).get(MODE).asText())) { - //Fail in case SSL mode is preferred + // Fail in case SSL mode is preferred return new AirbyteConnectionStatus() .withStatus(Status.FAILED) - .withMessage("Unsecured connection not allowed. If no SSH Tunnel set up, please use one of the following SSL modes: required, verify-ca, verify-identity"); + .withMessage( + "Unsecured connection not allowed. If no SSH Tunnel set up, please use one of the following SSL modes: required, verify-ca, verify-identity"); } } } diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/AbstractMySqlSslCertificateStrictEncryptSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/AbstractMySqlSslCertificateStrictEncryptSourceAcceptanceTest.java index b637cf07a011..821a70d8b706 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/AbstractMySqlSslCertificateStrictEncryptSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/AbstractMySqlSslCertificateStrictEncryptSourceAcceptanceTest.java @@ -12,7 +12,6 @@ import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.DatabaseDriver; import io.airbyte.db.jdbc.JdbcUtils; -import io.airbyte.integrations.source.mysql.MySqlSource.ReplicationMethod; import io.airbyte.integrations.standardtest.source.TestDestinationEnv; import org.jooq.DSLContext; import org.jooq.SQLDialect; @@ -33,8 +32,8 @@ protected void setupEnvironment(final TestDestinationEnv environment) throws Exc var sslMode = getSslConfig(); final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() - .put("method", "STANDARD") - .build()); + .put("method", "STANDARD") + .build()); config = Jsons.jsonNode(ImmutableMap.builder() .put(JdbcUtils.HOST_KEY, container.getHost()) .put(JdbcUtils.PORT_KEY, container.getFirstMappedPort()) diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSourceAcceptanceTest.java index 79ecc9f29c14..568afc8af48b 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptSourceAcceptanceTest.java @@ -45,8 +45,8 @@ protected void setupEnvironment(final TestDestinationEnv environment) throws Exc container.start(); var sslMode = ImmutableMap.builder() - .put(JdbcUtils.MODE_KEY, "required") - .build(); + .put(JdbcUtils.MODE_KEY, "required") + .build(); final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() .put("method", "STANDARD") .build()); diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java index 09d346761ffc..51767bd1a8e0 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java @@ -328,4 +328,5 @@ void testStrictSSLUnsecuredWithTunnel() throws Exception { protected boolean supportsPerStream() { return true; } + } diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java index 340ebe499f6b..b1268f1c8630 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java @@ -48,7 +48,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSourceOperations.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSourceOperations.java index 4a16bd29c6bf..2730b68f6b5f 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSourceOperations.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSourceOperations.java @@ -30,7 +30,6 @@ import static com.mysql.cj.MysqlType.TINYTEXT; import static com.mysql.cj.MysqlType.VARCHAR; import static com.mysql.cj.MysqlType.YEAR; -import static io.airbyte.db.DataTypeUtils.TIME_FORMATTER; import static io.airbyte.db.jdbc.JdbcConstants.INTERNAL_COLUMN_NAME; import static io.airbyte.db.jdbc.JdbcConstants.INTERNAL_COLUMN_SIZE; import static io.airbyte.db.jdbc.JdbcConstants.INTERNAL_COLUMN_TYPE; @@ -44,7 +43,6 @@ import com.mysql.cj.MysqlType; import com.mysql.cj.jdbc.result.ResultSetMetaData; import com.mysql.cj.result.Field; -import io.airbyte.commons.json.Jsons; import io.airbyte.db.DataTypeUtils; import io.airbyte.db.SourceOperations; import io.airbyte.db.jdbc.AbstractJdbcCompatibleSourceOperations; @@ -58,7 +56,6 @@ import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.format.DateTimeParseException; -import java.util.Collections; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java index a7938581e9a0..555544107260 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java @@ -22,8 +22,8 @@ * 2. checking whether binlog required from saved cdc offset is available on mysql server * #checkBinlog method *

    - * 3. configuring initial CDC wait time. TODO : There is a lot of shared logic for this functionality - * between MySQL and Postgres. Refactor it to reduce code de-duplication. + * 3. configuring initial CDC wait time. TODO : There is a lot of shared logic for this + * functionality between MySQL and Postgres. Refactor it to reduce code de-duplication. */ public class CdcConfigurationHelper { diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java index e7ebeac8ee68..55ed3a3654d4 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java @@ -279,7 +279,7 @@ protected void initTests() { .fullSourceDataType(fullSourceType) .airbyteType(JsonSchemaType.STRING_TIME_WITHOUT_TIMEZONE) // JDBC driver can process only "clock"(00:00:00-23:59:59) values. - .addInsertValues("'-22:59:59'","'23:59:59'", "'00:00:00'") + .addInsertValues("'-22:59:59'", "'23:59:59'", "'00:00:00'") .addExpectedValues("22:59:59.000000", "23:59:59.000000", "00:00:00.000000") .build()); diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSslCertificateSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSslCertificateSourceAcceptanceTest.java index 0086d0045d04..d56a70e4053a 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSslCertificateSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSslCertificateSourceAcceptanceTest.java @@ -33,8 +33,8 @@ protected void setupEnvironment(final TestDestinationEnv environment) throws Exc var sslMode = getSslConfig(); final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() - .put("method", "STANDARD") - .build()); + .put("method", "STANDARD") + .build()); config = Jsons.jsonNode(ImmutableMap.builder() .put(JdbcUtils.HOST_KEY, container.getHost()) .put(JdbcUtils.PORT_KEY, container.getFirstMappedPort()) diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractSshMySqlSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractSshMySqlSourceAcceptanceTest.java index 14c45a6bbbf2..87ffa50164a0 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractSshMySqlSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractSshMySqlSourceAcceptanceTest.java @@ -89,4 +89,5 @@ protected JsonNode getState() { protected boolean supportsPerStream() { return true; } + } diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcBinlogsMySqlSourceDatatypeTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcBinlogsMySqlSourceDatatypeTest.java index da034fbb8e55..b0bbe03185f5 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcBinlogsMySqlSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcBinlogsMySqlSourceDatatypeTest.java @@ -84,8 +84,8 @@ protected Database setupDatabase() throws Exception { container = new MySQLContainer<>("mysql:8.0"); container.start(); final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() - .put("method", "CDC") - .put("initial_waiting_seconds", INITIAL_CDC_WAITING_SECONDS) + .put("method", "CDC") + .put("initial_waiting_seconds", INITIAL_CDC_WAITING_SECONDS) .build()); config = Jsons.jsonNode(ImmutableMap.builder() .put(JdbcUtils.HOST_KEY, container.getHost()) diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcInitialSnapshotMySqlSourceDatatypeTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcInitialSnapshotMySqlSourceDatatypeTest.java index 46c1b2ec03f7..484dc2fa810a 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcInitialSnapshotMySqlSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcInitialSnapshotMySqlSourceDatatypeTest.java @@ -21,6 +21,7 @@ public class CdcInitialSnapshotMySqlSourceDatatypeTest extends AbstractMySqlSourceDatatypeTest { private DSLContext dslContext; + @Override protected void tearDown(final TestDestinationEnv testEnv) { dslContext.close(); diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcMySqlSslCaCertificateSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcMySqlSslCaCertificateSourceAcceptanceTest.java index cd05af84dd1e..af43c65d956d 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcMySqlSslCaCertificateSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CdcMySqlSslCaCertificateSourceAcceptanceTest.java @@ -111,8 +111,8 @@ protected void setupEnvironment(final TestDestinationEnv environment) throws Exc .put("client_key_password", "Passw0rd") .build(); final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() - .put("method", "CDC") - .put("initial_waiting_seconds", INITIAL_CDC_WAITING_SECONDS) + .put("method", "CDC") + .put("initial_waiting_seconds", INITIAL_CDC_WAITING_SECONDS) .build()); config = Jsons.jsonNode(ImmutableMap.builder() diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MySqlSslSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MySqlSslSourceAcceptanceTest.java index 9fadbb08b494..ce4b05d733aa 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MySqlSslSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/MySqlSslSourceAcceptanceTest.java @@ -23,8 +23,8 @@ protected void setupEnvironment(final TestDestinationEnv environment) throws Exc container = new MySQLContainer<>("mysql:8.0"); container.start(); final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() - .put("method", "STANDARD") - .build()); + .put("method", "STANDARD") + .build()); var sslMode = ImmutableMap.builder() .put(JdbcUtils.MODE_KEY, "required") diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/utils/TestConstants.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/utils/TestConstants.java index 723b6b97f4e9..adcbc1c7f3e7 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/utils/TestConstants.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/utils/TestConstants.java @@ -1,6 +1,11 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.io.airbyte.integration_tests.sources.utils; public class TestConstants { + public static final int INITIAL_CDC_WAITING_SECONDS = 5; } diff --git a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java index 18bb7a4781a0..594252bdd6c1 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.source.mysql; import static io.airbyte.integrations.source.mysql.helpers.CdcConfigurationHelper.MAX_FIRST_RECORD_WAIT_TIME; diff --git a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java index d819f9c6027f..fdbe4551a8d2 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java @@ -161,7 +161,8 @@ public void testCheckIncorrectUsernameFailure() throws Exception { ((ObjectNode) config).put(JdbcUtils.USERNAME_KEY, "fake"); final AirbyteConnectionStatus status = source.check(config); assertEquals(AirbyteConnectionStatus.Status.FAILED, status.getStatus()); - // do not test for message since there seems to be flakiness where sometimes the test will get the message with + // do not test for message since there seems to be flakiness where sometimes the test will get the + // message with // State code: 08001 or State code: 28000 } @@ -293,5 +294,4 @@ protected boolean supportsPerStream() { return true; } - } diff --git a/airbyte-integrations/connectors/source-netsuite/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-netsuite/integration_tests/configured_catalog.json index e2a5c8d2ef89..62499ac8e539 100644 --- a/airbyte-integrations/connectors/source-netsuite/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-netsuite/integration_tests/configured_catalog.json @@ -1,293 +1,170 @@ { "streams": [ - { - "stream": { - "name": "customrecord01", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] - }, - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "customer", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "customersubsidiaryrelationship", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "vendorsubsidiaryrelationship", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "vendorbill", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "contact", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "message", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "subsidiary", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "task", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "salesorder", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "employee", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "billingaccount", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "journalentry", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "vendor", - "json_schema": {}, - "supported_sync_modes": [ - "full_refresh", - "incremental" - ] - }, - "source_defined_cursor": true, - "default_cursor_field": [ - "lastModifiedDate" - ], - "source_defined_primary_key": [ - [ - "id" - ] - ], - "sync_mode": "incremental", - "destination_sync_mode": "append" - } + { + "stream": { + "name": "customrecord01", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "source_defined_primary_key": [["id"]], + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "customer", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "customersubsidiaryrelationship", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "vendorsubsidiaryrelationship", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "vendorbill", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "contact", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "message", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "subsidiary", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "task", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "salesorder", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "employee", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "billingaccount", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "journalentry", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "vendor", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "source_defined_cursor": true, + "default_cursor_field": ["lastModifiedDate"], + "source_defined_primary_key": [["id"]], + "sync_mode": "incremental", + "destination_sync_mode": "append" + } ] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-netsuite/sample_files/config.json b/airbyte-integrations/connectors/source-netsuite/sample_files/config.json index 45f298b46ce8..ec04b4473bf5 100644 --- a/airbyte-integrations/connectors/source-netsuite/sample_files/config.json +++ b/airbyte-integrations/connectors/source-netsuite/sample_files/config.json @@ -7,4 +7,4 @@ "start_datetime": "2022-06-01T00:00:01Z", "object_types": ["customer"], "window_in_days": 30 -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-netsuite/sample_files/invalid_config.json b/airbyte-integrations/connectors/source-netsuite/sample_files/invalid_config.json index 45f298b46ce8..ec04b4473bf5 100644 --- a/airbyte-integrations/connectors/source-netsuite/sample_files/invalid_config.json +++ b/airbyte-integrations/connectors/source-netsuite/sample_files/invalid_config.json @@ -7,4 +7,4 @@ "start_datetime": "2022-06-01T00:00:01Z", "object_types": ["customer"], "window_in_days": 30 -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-netsuite/sample_files/sample_state.json b/airbyte-integrations/connectors/source-netsuite/sample_files/sample_state.json index 452e936dc03f..77c9bae4eba7 100644 --- a/airbyte-integrations/connectors/source-netsuite/sample_files/sample_state.json +++ b/airbyte-integrations/connectors/source-netsuite/sample_files/sample_state.json @@ -1,36 +1,36 @@ { - "customer": { - "lastModifiedDate": "2022-08-19T14:01:00Z" - }, - "customersubsidiaryrelationship": { - "lastModifiedDate": "2022-08-19T14:01:00Z" - }, - "vendorsubsidiaryrelationship": { - "lastModifiedDate": "2022-08-22T09:21:00Z" - }, - "charge": {}, - "vendorbill": { - "lastModifiedDate": "2022-08-22T09:21:00Z" - }, - "contact": { - "lastModifiedDate": "2022-08-19T14:13:00Z" - }, - "message": {}, - "subsidiary": { - "lastModifiedDate": "2022-08-22T08:21:00Z" - }, - "task": { - "lastModifiedDate": "2022-08-18T08:11:00Z" - }, - "salesorder": {}, - "employee": { - "lastModifiedDate": "2022-08-18T12:05:00Z" - }, - "billingaccount": {}, - "journalentry": { - "lastModifiedDate": "2022-08-22T10:57:00Z" - }, - "vendor": { - "lastModifiedDate": "2022-08-22T08:21:00Z" - } + "customer": { + "lastModifiedDate": "2022-08-19T14:01:00Z" + }, + "customersubsidiaryrelationship": { + "lastModifiedDate": "2022-08-19T14:01:00Z" + }, + "vendorsubsidiaryrelationship": { + "lastModifiedDate": "2022-08-22T09:21:00Z" + }, + "charge": {}, + "vendorbill": { + "lastModifiedDate": "2022-08-22T09:21:00Z" + }, + "contact": { + "lastModifiedDate": "2022-08-19T14:13:00Z" + }, + "message": {}, + "subsidiary": { + "lastModifiedDate": "2022-08-22T08:21:00Z" + }, + "task": { + "lastModifiedDate": "2022-08-18T08:11:00Z" + }, + "salesorder": {}, + "employee": { + "lastModifiedDate": "2022-08-18T12:05:00Z" + }, + "billingaccount": {}, + "journalentry": { + "lastModifiedDate": "2022-08-22T10:57:00Z" + }, + "vendor": { + "lastModifiedDate": "2022-08-22T08:21:00Z" + } } diff --git a/airbyte-integrations/connectors/source-oracle/src/main/java/io/airbyte/integrations/source/oracle/OracleSource.java b/airbyte-integrations/connectors/source-oracle/src/main/java/io/airbyte/integrations/source/oracle/OracleSource.java index ec4be9ef636e..b0f1ebc2ce26 100644 --- a/airbyte-integrations/connectors/source-oracle/src/main/java/io/airbyte/integrations/source/oracle/OracleSource.java +++ b/airbyte-integrations/connectors/source-oracle/src/main/java/io/airbyte/integrations/source/oracle/OracleSource.java @@ -210,7 +210,10 @@ public static void main(final String[] args) throws Exception { LOGGER.info("completed source: {}", OracleSource.class); } - private String buildConnectionString(final JsonNode config, final String protocol, final String connectionTypeName, final String connectionTypeValue) { + private String buildConnectionString(final JsonNode config, + final String protocol, + final String connectionTypeName, + final String connectionTypeValue) { return String.format( "jdbc:oracle:thin:@(DESCRIPTION=(ADDRESS=(PROTOCOL=%s)(HOST=%s)(PORT=%s))(CONNECT_DATA=(%s=%s)))", protocol, diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java b/airbyte-integrations/connectors/source-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java index 1329dfab8f75..17e0b11ceb6d 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java @@ -5,7 +5,6 @@ package io.airbyte.integrations.source.postgres; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.airbyte.commons.json.Jsons; import io.airbyte.db.jdbc.JdbcUtils; @@ -15,8 +14,6 @@ import io.airbyte.protocol.models.AirbyteConnectionStatus; import io.airbyte.protocol.models.AirbyteConnectionStatus.Status; import io.airbyte.protocol.models.ConnectorSpecification; -import java.util.Arrays; -import java.util.Collections; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,17 +46,19 @@ public ConnectorSpecification modifySpec(final ConnectorSpecification originalSp @Override public AirbyteConnectionStatus check(final JsonNode config) throws Exception { - // #15808 Disallow connecting to db with disable, prefer or allow SSL mode when connecting directly and not over SSH tunnel + // #15808 Disallow connecting to db with disable, prefer or allow SSL mode when connecting directly + // and not over SSH tunnel if (config.has(TUNNEL_METHOD) && config.get(TUNNEL_METHOD).has(TUNNEL_METHOD) && config.get(TUNNEL_METHOD).get(TUNNEL_METHOD).asText().equals(NO_TUNNEL)) { - //If no SSH tunnel + // If no SSH tunnel if (config.has(SSL_MODE) && config.get(SSL_MODE).has(MODE)) { - if (Set.of(SSL_MODE_DISABLE,SSL_MODE_ALLOW, SSL_MODE_PREFER).contains(config.get(SSL_MODE).get(MODE).asText())) { - //Fail in case SSL mode is disable, allow or prefer + if (Set.of(SSL_MODE_DISABLE, SSL_MODE_ALLOW, SSL_MODE_PREFER).contains(config.get(SSL_MODE).get(MODE).asText())) { + // Fail in case SSL mode is disable, allow or prefer return new AirbyteConnectionStatus() .withStatus(Status.FAILED) - .withMessage("Unsecured connection not allowed. If no SSH Tunnel set up, please use one of the following SSL modes: require, verify-ca, verify-full"); + .withMessage( + "Unsecured connection not allowed. If no SSH Tunnel set up, please use one of the following SSL modes: require, verify-ca, verify-full"); } } } @@ -72,4 +71,5 @@ public static void main(final String[] args) throws Exception { new IntegrationRunner(source).run(args); LOGGER.info("completed source: {}", PostgresSourceStrictEncrypt.class); } + } diff --git a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresCdcProperties.java b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresCdcProperties.java index 19cf76edb417..37c2d6ab7b37 100644 --- a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresCdcProperties.java +++ b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresCdcProperties.java @@ -8,13 +8,11 @@ import static io.airbyte.integrations.source.jdbc.AbstractJdbcSource.CLIENT_KEY_STORE_URL; import static io.airbyte.integrations.source.jdbc.AbstractJdbcSource.SSL_MODE; import static io.airbyte.integrations.source.jdbc.AbstractJdbcSource.TRUST_KEY_STORE_PASS; -import static io.airbyte.integrations.source.jdbc.AbstractJdbcSource.TRUST_KEY_STORE_URL; import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.db.jdbc.JdbcDatabase; import io.airbyte.db.jdbc.JdbcUtils; import io.airbyte.integrations.debezium.internals.PostgresConverter; -import io.airbyte.integrations.source.jdbc.AbstractJdbcSource; import io.airbyte.integrations.source.jdbc.AbstractJdbcSource.SslMode; import java.net.URI; import java.nio.file.Path; @@ -23,7 +21,9 @@ import org.slf4j.LoggerFactory; public class PostgresCdcProperties { + private static final Logger LOGGER = LoggerFactory.getLogger(PostgresCdcProperties.class); + static Properties getDebeziumDefaultProperties(final JdbcDatabase database) { final JsonNode sourceConfig = database.getSourceConfig(); final Properties props = commonProperties(database); diff --git a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java index 5ff208e90c96..0facfaadf8f8 100644 --- a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java +++ b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java @@ -66,7 +66,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -127,7 +126,8 @@ public JsonNode toDatabaseConfig(final JsonNode config) { final Map sslParameters = parseSSLConfig(config); if (config.has(PARAM_SSL_MODE) && config.get(PARAM_SSL_MODE).has(PARAM_CA_CERTIFICATE)) { - sslParameters.put(CA_CERTIFICATE_PATH, JdbcSSLConnectionUtils.fileFromCertPem(config.get(PARAM_SSL_MODE).get(PARAM_CA_CERTIFICATE).asText()).toString()); + sslParameters.put(CA_CERTIFICATE_PATH, + JdbcSSLConnectionUtils.fileFromCertPem(config.get(PARAM_SSL_MODE).get(PARAM_CA_CERTIFICATE).asText()).toString()); LOGGER.debug("root ssl ca crt file: {}", sslParameters.get(CA_CERTIFICATE_PATH)); } @@ -301,8 +301,8 @@ public List> getCheckOperations(final J @Override public AutoCloseableIterator read(final JsonNode config, - final ConfiguredAirbyteCatalog catalog, - final JsonNode state) + final ConfiguredAirbyteCatalog catalog, + final JsonNode state) throws Exception { // this check is used to ensure that have the pgoutput slot available so Debezium won't attempt to // create it. @@ -317,10 +317,10 @@ public AutoCloseableIterator read(final JsonNode config, @Override public List> getIncrementalIterators(final JdbcDatabase database, - final ConfiguredAirbyteCatalog catalog, - final Map>> tableNameToTable, - final StateManager stateManager, - final Instant emittedAt) { + final ConfiguredAirbyteCatalog catalog, + final Map>> tableNameToTable, + final StateManager stateManager, + final Instant emittedAt) { final JsonNode sourceConfig = database.getSourceConfig(); if (PostgresUtils.isCdc(sourceConfig) && shouldUseCDC(catalog)) { final Duration firstRecordWaitTime = PostgresUtils.getFirstRecordWaitTime(sourceConfig); @@ -372,7 +372,7 @@ public List> getIncrementalIterators(final @Override public Set getPrivilegesTableForCurrentUser(final JdbcDatabase database, - final String schema) + final String schema) throws SQLException { final CheckedFunction statementCreator = connection -> { final PreparedStatement ps = connection.prepareStatement( diff --git a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java index 0b71a560f08f..8bc58ee3dbd4 100644 --- a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java +++ b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSourceStrictEncrypt.java @@ -12,7 +12,6 @@ import io.airbyte.integrations.base.Source; import io.airbyte.integrations.base.spec_modification.SpecModifyingSource; import io.airbyte.protocol.models.ConnectorSpecification; -import java.util.Arrays; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractCdcPostgresSourceSslAcceptanceTest.java b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractCdcPostgresSourceSslAcceptanceTest.java index 51967d535426..577e27f2dc1b 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractCdcPostgresSourceSslAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractCdcPostgresSourceSslAcceptanceTest.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ + package io.airbyte.integrations.io.airbyte.integration_tests.sources; import static io.airbyte.db.PostgresUtils.getCertificate; @@ -22,6 +23,7 @@ import org.testcontainers.utility.DockerImageName; public abstract class AbstractCdcPostgresSourceSslAcceptanceTest extends CdcPostgresSourceAcceptanceTest { + protected static final String PASSWORD = "Passw0rd"; protected static PostgresUtils.Certificate certs; @@ -29,7 +31,7 @@ public abstract class AbstractCdcPostgresSourceSslAcceptanceTest extends CdcPost protected void setupEnvironment(final TestDestinationEnv environment) throws Exception { container = new PostgreSQLContainer<>(DockerImageName.parse("postgres:bullseye") .asCompatibleSubstituteFor("postgres")) - .withCommand("postgres -c wal_level=logical"); + .withCommand("postgres -c wal_level=logical"); container.start(); certs = getCertificate(container); @@ -85,4 +87,5 @@ protected void setupEnvironment(final TestDestinationEnv environment) throws Exc } public abstract ImmutableMap getCertificateConfiguration(); + } diff --git a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java index 776aa8414f9f..a8d28976f59e 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractPostgresSourceDatatypeTest.java @@ -195,7 +195,6 @@ protected void initTests() { .addExpectedValues((String) null) .build()); - for (final String type : Set.of("double precision", "float", "float8")) { addDataTypeTestData( TestDataHolder.builder() diff --git a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceCaCertificateSslAcceptanceTest.java b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceCaCertificateSslAcceptanceTest.java index 7be0a5c3d7a1..b128560142e9 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceCaCertificateSslAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceCaCertificateSslAcceptanceTest.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ + package io.airbyte.integrations.io.airbyte.integration_tests.sources; import com.google.common.collect.ImmutableMap; @@ -14,4 +15,5 @@ public ImmutableMap getCertificateConfiguration() { .put("client_key_password", PASSWORD) .build(); } + } diff --git a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceFullCertificateSslAcceptanceTest.java b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceFullCertificateSslAcceptanceTest.java index 14b77beeb525..ef4a3c9fb6c7 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceFullCertificateSslAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/CDCPostgresSourceFullCertificateSslAcceptanceTest.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ + package io.airbyte.integrations.io.airbyte.integration_tests.sources; import com.google.common.collect.ImmutableMap; diff --git a/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceTest.java b/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceTest.java index fec850808032..1101eaeb7fb0 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceTest.java @@ -514,7 +514,6 @@ void testGetUsername() { assertEquals(username, PostgresSource.getUsername(azureConfig)); } - @Test public void tableWithInvalidCursorShouldThrowException() throws Exception { try (final PostgreSQLContainer db = new PostgreSQLContainer<>("postgres:13-alpine")) { @@ -523,7 +522,8 @@ public void tableWithInvalidCursorShouldThrowException() throws Exception { try (final DSLContext dslContext = getDslContext(config)) { final Database database = new Database(dslContext); final ConfiguredAirbyteStream tableWithInvalidCursorType = createTableWithInvalidCursorType(database); - final ConfiguredAirbyteCatalog configuredAirbyteCatalog = new ConfiguredAirbyteCatalog().withStreams(Collections.singletonList(tableWithInvalidCursorType)); + final ConfiguredAirbyteCatalog configuredAirbyteCatalog = + new ConfiguredAirbyteCatalog().withStreams(Collections.singletonList(tableWithInvalidCursorType)); final Throwable throwable = catchThrowable(() -> MoreIterators.toSet(new PostgresSource().read(config, configuredAirbyteCatalog, null))); assertThat(throwable).isInstanceOf(InvalidCursorException.class) @@ -547,9 +547,9 @@ private ConfiguredAirbyteStream createTableWithInvalidCursorType(final Database .withDestinationSyncMode(DestinationSyncMode.APPEND) .withSyncMode(SyncMode.INCREMENTAL) .withStream(CatalogHelpers.createAirbyteStream( - "test_table", - "public", - Field.of("id", JsonSchemaType.STRING)) + "test_table", + "public", + Field.of("id", JsonSchemaType.STRING)) .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH, SyncMode.INCREMENTAL)) .withSourceDefinedPrimaryKey(List.of(List.of("id")))); diff --git a/airbyte-integrations/connectors/source-primetric/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-primetric/integration_tests/configured_catalog.json index 9392ff6c0d16..d0c860c3a68e 100644 --- a/airbyte-integrations/connectors/source-primetric/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-primetric/integration_tests/configured_catalog.json @@ -1,1495 +1,983 @@ { - "streams" : [ + "streams": [ { - "stream" : { - "name" : "assignments", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "created_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "starts_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "ends_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "end_local_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "start_local_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "updated_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "status" : { - "type" : "integer", - "enum" : [ - 0, - 1, - 2 - ] - }, - "employee_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "label" : { - "type" : [ - "null", - "string" - ] - }, - "note" : { - "type" : [ - "null", - "string" - ] - }, - "scheduling_mode" : { - "type" : [ - "null", - "integer" - ] - }, - "affects_capacity" : { - "type" : [ - "null", - "boolean" - ] - }, - "billable" : { - "type" : [ - "null", - "boolean" - ] - }, - "project_role_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "hash_tag_ids" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" + "stream": { + "name": "assignments", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "starts_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "ends_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "end_local_date": { + "type": ["null", "string"], + "format": "date" + }, + "start_local_date": { + "type": ["null", "string"], + "format": "date" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "status": { + "type": "integer", + "enum": [0, 1, 2] + }, + "employee_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "label": { + "type": ["null", "string"] + }, + "note": { + "type": ["null", "string"] + }, + "scheduling_mode": { + "type": ["null", "integer"] + }, + "affects_capacity": { + "type": ["null", "boolean"] + }, + "billable": { + "type": ["null", "boolean"] + }, + "project_role_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "hash_tag_ids": { + "type": "array", + "items": { + "type": ["null", "string"], + "format": "uuid" } }, - "color" : { - "type" : [ - "null", - "string" - ] - }, - "project_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "project_phase_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "total_tracked" : { - "type" : [ - "null", - "number" - ] - }, - "total_scheduled" : { - "type" : [ - "null", - "number" - ] - }, - "is_settled" : { - "type" : [ - "null", - "boolean" - ] - }, - "daily_data" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "start" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "color": { + "type": ["null", "string"] + }, + "project_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "project_phase_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "total_tracked": { + "type": ["null", "number"] + }, + "total_scheduled": { + "type": ["null", "number"] + }, + "is_settled": { + "type": ["null", "boolean"] + }, + "daily_data": { + "type": ["null", "object"], + "properties": { + "start": { + "type": ["null", "string"], + "format": "date" }, - "end" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "end": { + "type": ["null", "string"], + "format": "date" }, - "tracked" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "number" - ] + "tracked": { + "type": "array", + "items": { + "type": ["null", "number"] } }, - "scheduled" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "number" - ] + "scheduled": { + "type": "array", + "items": { + "type": ["null", "number"] } } } }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" - }, - "background" : { - "type" : [ - "null", - "boolean" - ] - }, - "volatile_work_settings" : { - "type" : [ - "null", - "boolean" - ] - }, - "only_billable_work" : { - "type" : [ - "null", - "boolean" - ] - }, - "scheduling_time_frame" : { - "type" : [ - "null", - "integer" - ] - }, - "scheduled_work_per_time_frame" : { - "type" : [ - "null", - "integer" - ] - }, - "adjust_scheduling_to_time_off" : { - "type" : [ - "null", - "boolean" - ] - }, - "reduce_utilization_by_time_off" : { - "type" : [ - "null", - "boolean" - ] - }, - "adjust_scheduling_to_public_holidays" : { - "type" : [ - "null", - "boolean" - ] - }, - "reduce_utilization_by_public_holidays" : { - "type" : [ - "null", - "boolean" - ] - }, - "capacity_based_load" : { - "type" : [ - "null", - "integer" - ] - }, - "use_billable_capacity" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_by_capacity_per_monday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_tuesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_wednesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_thursday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_friday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_saturday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_sunday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_monday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_tuesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_wednesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_thursday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_friday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_saturday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_sunday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_on_monday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_tuesday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_wednesday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_thursday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_friday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_saturday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_sunday" : { - "type" : [ - "null", - "boolean" - ] - }, - "financial_budget_mode" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_client_currency" : { - "type" : [ - "null", - "string" - ] - }, - "financial_client_currency_exchange_rate" : { - "type" : [ - "null", - "number" - ] - }, - "financial_total_scheduled_income" : { - "$ref" : "money_object.json" - }, - "financial_total_scheduled_cost" : { - "$ref" : "money_object.json" - }, - "financial_total_tracked_cost" : { - "$ref" : "money_object.json" - }, - "financial_total_tracked_income" : { - "$ref" : "money_object.json" - }, - "financial_settled_income" : { - "type" : [ - "null", - "object" - ] - }, - "financial_settled_cost" : { - "type" : [ - "null", - "object" - ] - }, - "financial_total_work_for_cost" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_contractor_hour_cost" : { - "type" : [ - "null", - "object" - ] - }, - "financial_hour_rate" : { - "$ref" : "money_object.json" - }, - "financial_employee_default_hour_cost" : { - "$ref" : "money_object.json" - }, - "financial_use_default_hour_cost" : { - "type" : [ - "null", - "boolean" - ] - }, - "financial_default_hour_rate_source" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_total_work_for_income" : { - "type" : [ - "null", - "integer" - ] + "custom_attributes": { + "$ref": "custom_attributes.json" + }, + "background": { + "type": ["null", "boolean"] + }, + "volatile_work_settings": { + "type": ["null", "boolean"] + }, + "only_billable_work": { + "type": ["null", "boolean"] + }, + "scheduling_time_frame": { + "type": ["null", "integer"] + }, + "scheduled_work_per_time_frame": { + "type": ["null", "integer"] + }, + "adjust_scheduling_to_time_off": { + "type": ["null", "boolean"] + }, + "reduce_utilization_by_time_off": { + "type": ["null", "boolean"] + }, + "adjust_scheduling_to_public_holidays": { + "type": ["null", "boolean"] + }, + "reduce_utilization_by_public_holidays": { + "type": ["null", "boolean"] + }, + "capacity_based_load": { + "type": ["null", "integer"] + }, + "use_billable_capacity": { + "type": ["null", "boolean"] + }, + "work_by_capacity_per_monday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_tuesday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_wednesday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_thursday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_friday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_saturday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_sunday": { + "type": ["null", "integer"] + }, + "work_per_monday": { + "type": ["null", "integer"] + }, + "work_per_tuesday": { + "type": ["null", "integer"] + }, + "work_per_wednesday": { + "type": ["null", "integer"] + }, + "work_per_thursday": { + "type": ["null", "integer"] + }, + "work_per_friday": { + "type": ["null", "integer"] + }, + "work_per_saturday": { + "type": ["null", "integer"] + }, + "work_per_sunday": { + "type": ["null", "integer"] + }, + "work_on_monday": { + "type": ["null", "boolean"] + }, + "work_on_tuesday": { + "type": ["null", "boolean"] + }, + "work_on_wednesday": { + "type": ["null", "boolean"] + }, + "work_on_thursday": { + "type": ["null", "boolean"] + }, + "work_on_friday": { + "type": ["null", "boolean"] + }, + "work_on_saturday": { + "type": ["null", "boolean"] + }, + "work_on_sunday": { + "type": ["null", "boolean"] + }, + "financial_budget_mode": { + "type": ["null", "integer"] + }, + "financial_client_currency": { + "type": ["null", "string"] + }, + "financial_client_currency_exchange_rate": { + "type": ["null", "number"] + }, + "financial_total_scheduled_income": { + "$ref": "money_object.json" + }, + "financial_total_scheduled_cost": { + "$ref": "money_object.json" + }, + "financial_total_tracked_cost": { + "$ref": "money_object.json" + }, + "financial_total_tracked_income": { + "$ref": "money_object.json" + }, + "financial_settled_income": { + "type": ["null", "object"] + }, + "financial_settled_cost": { + "type": ["null", "object"] + }, + "financial_total_work_for_cost": { + "type": ["null", "integer"] + }, + "financial_contractor_hour_cost": { + "type": ["null", "object"] + }, + "financial_hour_rate": { + "$ref": "money_object.json" + }, + "financial_employee_default_hour_cost": { + "$ref": "money_object.json" + }, + "financial_use_default_hour_cost": { + "type": ["null", "boolean"] + }, + "financial_default_hour_rate_source": { + "type": ["null", "integer"] + }, + "financial_total_work_for_income": { + "type": ["null", "integer"] } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : true, - "default_cursor_field" : [ - "created_at" - ], - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["created_at"], + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "employees", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "nick" : { - "type" : [ - "null", - "string" - ] - }, - "name" : { - "type" : [ - "null", - "string" - ] - }, - "email" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_manager_id" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_finance_manager_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "stream": { + "name": "employees", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "nick": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "assigned_manager_id": { + "type": ["null", "string"] + }, + "assigned_finance_manager_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "summary" : { - "type" : [ - "null", - "string" - ] - }, - "seniority_id" : { - "type" : [ - "null", - "string" - ] - }, - "team_id" : { - "type" : [ - "null", - "string" - ] - }, - "department_id" : { - "type" : [ - "null", - "string" - ] - }, - "position_id" : { - "type" : [ - "null", - "string" - ] - }, - "hash_tag_ids" : { - "type" : "array", - "items" : { - "type" : "string" + "summary": { + "type": ["null", "string"] + }, + "seniority_id": { + "type": ["null", "string"] + }, + "team_id": { + "type": ["null", "string"] + }, + "department_id": { + "type": ["null", "string"] + }, + "position_id": { + "type": ["null", "string"] + }, + "hash_tag_ids": { + "type": "array", + "items": { + "type": "string" } }, - "nationality" : { - "type" : [ - "null", - "string" - ] + "nationality": { + "type": ["null", "string"] }, - "note" : { - "type" : [ - "null", - "string" - ] + "note": { + "type": ["null", "string"] }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "hashtags", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "hashtags", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_clients", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_clients", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_company_groups", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_company_groups", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_departments", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_departments", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_identity_providers", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "connector" : { - "type" : "string" - }, - "status" : { - "type" : "string" - }, - "name" : { - "type" : "string" + "stream": { + "name": "organization_identity_providers", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "connector": { + "type": "string" + }, + "status": { + "type": "string" + }, + "name": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_positions", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_positions", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_rag_scopes", - "json_schema" : { - "type" : "object", - "properties" : { - "text" : { - "type" : "string" - }, - "rag_type" : { - "type" : "integer", - "enum" : [ - 1, - 2 - ] - }, - "default_choice" : { - "type" : "integer", - "enum" : [ - 1, - 2 - ] - }, - "allow_undefined" : { - "type" : "boolean" - }, - "is_financial" : { - "type" : "boolean" + "stream": { + "name": "organization_rag_scopes", + "json_schema": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "rag_type": { + "type": "integer", + "enum": [1, 2] + }, + "default_choice": { + "type": "integer", + "enum": [1, 2] + }, + "allow_undefined": { + "type": "boolean" + }, + "is_financial": { + "type": "boolean" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "text" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["text"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_roles", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" - }, - "default_hour_rate" : { + "stream": { + "name": "organization_roles", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" + }, + "default_hour_rate": { "type": "string", "airbyte_type": "big_number" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_seniorities", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" - }, - "level" : { - "type" : "integer" + "stream": { + "name": "organization_seniorities", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" + }, + "level": { + "type": "integer" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_tags", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_tags", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_teams", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_teams", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_timeoff_types", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_timeoff_types", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "people", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "name" : { - "type" : "string" - }, - "mail" : { - "type" : "string" - }, - "archived" : { - "type" : "boolean" - }, - "roles" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "manager_id" : { - "type" : [ - "null", - "string" - ] + "stream": { + "name": "people", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "archived": { + "type": "boolean" + }, + "roles": { + "type": ["null", "object"], + "properties": { + "manager_id": { + "type": ["null", "string"] }, - "employee_id" : { - "type" : [ - "null", - "string" - ] + "employee_id": { + "type": ["null", "string"] }, - "administrator_id" : { - "type" : [ - "null", - "string" - ] + "administrator_id": { + "type": ["null", "string"] } } } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "projects", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "title" : { - "type" : [ - "null", - "string" - ] - }, - "hash_tag_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "stream": { + "name": "projects", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": ["null", "string"] + }, + "hash_tag_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "last_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "tentative" : { - "type" : [ - "null", - "boolean" - ] - }, - "likelihood" : { - "type" : [ - "null", - "integer" - ] - }, - "billing_model" : { - "type" : [ - "null", - "integer" - ] - }, - "hour_rate_source" : { - "type" : [ - "null", - "integer" - ] - }, - "customer_id" : { - "type" : [ - "null", - "string" - ] - }, - "currency" : { - "type" : [ - "null", - "string" - ] - }, - "currency_rate" : { - "type" : [ - "null", - "string" - ] - }, - "project_group_id" : { - "type" : [ - "null", - "string" - ] - }, - "status" : { - "type" : [ - "null", - "integer" - ] - }, - "color" : { - "type" : [ - "null", - "string" - ] - }, - "description" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_manager_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "last_date": { + "type": ["null", "string"], + "format": "date" + }, + "tentative": { + "type": ["null", "boolean"] + }, + "likelihood": { + "type": ["null", "integer"] + }, + "billing_model": { + "type": ["null", "integer"] + }, + "hour_rate_source": { + "type": ["null", "integer"] + }, + "customer_id": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "currency_rate": { + "type": ["null", "string"] + }, + "project_group_id": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "integer"] + }, + "color": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "assigned_manager_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "assigned_manager_readonly_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "assigned_manager_readonly_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "is_public" : { - "type" : [ - "null", - "boolean" - ] - }, - "integrations" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "is_public": { + "type": ["null", "boolean"] + }, + "integrations": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "projects_vacancies", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "project_id" : { - "type" : "string" - }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "seniority_id" : { - "type" : [ - "null", - "string" - ] - }, - "position_id" : { - "type" : [ - "null", - "string" - ] - }, - "note" : { - "type" : [ - "null", - "string" - ] - }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "stream": { + "name": "projects_vacancies", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "project_id": { + "type": "string" + }, + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "seniority_id": { + "type": ["null", "string"] + }, + "position_id": { + "type": ["null", "string"] + }, + "note": { + "type": ["null", "string"] + }, + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "rag_ratings", - "json_schema" : { - "type" : "object", - "properties" : { - "project_url" : { - "type" : "string" - }, - "project_id" : { - "type" : "string" - }, - "rag_ratings" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ] + "stream": { + "name": "rag_ratings", + "json_schema": { + "type": "object", + "properties": { + "project_url": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "rag_ratings": { + "type": "array", + "items": { + "type": ["null", "string"] } } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "project_id" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["project_id"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "skills", - "json_schema" : { - "type" : "object", - "properties" : { - "name" : { - "type" : "string" - }, - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "level" : { - "type" : "integer" - }, - "desc" : { - "type" : "string" - }, - "abstract" : { - "type" : "boolean" - }, - "path" : { - "type" : "string" - }, - "ancestors" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ] + "stream": { + "name": "skills", + "json_schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "level": { + "type": "integer" + }, + "desc": { + "type": "string" + }, + "abstract": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "ancestors": { + "type": "array", + "items": { + "type": ["null", "string"] } }, - "has_children" : { - "type" : [ - "null", - "boolean" - ] + "has_children": { + "type": ["null", "boolean"] } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "timeoffs", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "employee_id" : { - "type" : "string" - }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "approved" : { - "type" : "boolean" - }, - "approved_by" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "stream": { + "name": "timeoffs", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "employee_id": { + "type": "string" + }, + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "approved": { + "type": "boolean" + }, + "approved_by": { + "type": ["null", "object"], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "name" : { - "type" : "string" + "name": { + "type": "string" } } }, - "approved_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" + "approved_at": { + "type": ["null", "string"], + "format": "date-time" }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "worklogs", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "assignment_id" : { - "type" : [ - "null", - "string" - ] - }, - "project_id" : { - "type" : [ - "null", - "string" - ] - }, - "starts_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "created_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "work" : { - "type" : [ - "null", - "integer" - ] - }, - "desc" : { - "type" : [ - "null", - "string" - ] - }, - "in_progress" : { - "type" : [ - "null", - "boolean" - ] - }, - "billable" : { - "type" : [ - "null", - "boolean" - ] - }, - "developer_id" : { - "type" : [ - "null", - "string" - ] - }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "stream": { + "name": "worklogs", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "assignment_id": { + "type": ["null", "string"] + }, + "project_id": { + "type": ["null", "string"] + }, + "starts_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "work": { + "type": ["null", "integer"] + }, + "desc": { + "type": ["null", "string"] + }, + "in_progress": { + "type": ["null", "boolean"] + }, + "billable": { + "type": ["null", "boolean"] + }, + "developer_id": { + "type": ["null", "string"] + }, + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : true, - "default_cursor_field" : [ - "created_at" - ], - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["created_at"], + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" } ] } diff --git a/airbyte-integrations/connectors/source-primetric/sample_files/configured_catalog.json b/airbyte-integrations/connectors/source-primetric/sample_files/configured_catalog.json index 9392ff6c0d16..d0c860c3a68e 100644 --- a/airbyte-integrations/connectors/source-primetric/sample_files/configured_catalog.json +++ b/airbyte-integrations/connectors/source-primetric/sample_files/configured_catalog.json @@ -1,1495 +1,983 @@ { - "streams" : [ + "streams": [ { - "stream" : { - "name" : "assignments", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "created_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "starts_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "ends_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "end_local_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "start_local_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "updated_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "status" : { - "type" : "integer", - "enum" : [ - 0, - 1, - 2 - ] - }, - "employee_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "label" : { - "type" : [ - "null", - "string" - ] - }, - "note" : { - "type" : [ - "null", - "string" - ] - }, - "scheduling_mode" : { - "type" : [ - "null", - "integer" - ] - }, - "affects_capacity" : { - "type" : [ - "null", - "boolean" - ] - }, - "billable" : { - "type" : [ - "null", - "boolean" - ] - }, - "project_role_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "hash_tag_ids" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" + "stream": { + "name": "assignments", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "starts_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "ends_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "end_local_date": { + "type": ["null", "string"], + "format": "date" + }, + "start_local_date": { + "type": ["null", "string"], + "format": "date" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "status": { + "type": "integer", + "enum": [0, 1, 2] + }, + "employee_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "label": { + "type": ["null", "string"] + }, + "note": { + "type": ["null", "string"] + }, + "scheduling_mode": { + "type": ["null", "integer"] + }, + "affects_capacity": { + "type": ["null", "boolean"] + }, + "billable": { + "type": ["null", "boolean"] + }, + "project_role_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "hash_tag_ids": { + "type": "array", + "items": { + "type": ["null", "string"], + "format": "uuid" } }, - "color" : { - "type" : [ - "null", - "string" - ] - }, - "project_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "project_phase_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "total_tracked" : { - "type" : [ - "null", - "number" - ] - }, - "total_scheduled" : { - "type" : [ - "null", - "number" - ] - }, - "is_settled" : { - "type" : [ - "null", - "boolean" - ] - }, - "daily_data" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "start" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "color": { + "type": ["null", "string"] + }, + "project_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "project_phase_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "total_tracked": { + "type": ["null", "number"] + }, + "total_scheduled": { + "type": ["null", "number"] + }, + "is_settled": { + "type": ["null", "boolean"] + }, + "daily_data": { + "type": ["null", "object"], + "properties": { + "start": { + "type": ["null", "string"], + "format": "date" }, - "end" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "end": { + "type": ["null", "string"], + "format": "date" }, - "tracked" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "number" - ] + "tracked": { + "type": "array", + "items": { + "type": ["null", "number"] } }, - "scheduled" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "number" - ] + "scheduled": { + "type": "array", + "items": { + "type": ["null", "number"] } } } }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" - }, - "background" : { - "type" : [ - "null", - "boolean" - ] - }, - "volatile_work_settings" : { - "type" : [ - "null", - "boolean" - ] - }, - "only_billable_work" : { - "type" : [ - "null", - "boolean" - ] - }, - "scheduling_time_frame" : { - "type" : [ - "null", - "integer" - ] - }, - "scheduled_work_per_time_frame" : { - "type" : [ - "null", - "integer" - ] - }, - "adjust_scheduling_to_time_off" : { - "type" : [ - "null", - "boolean" - ] - }, - "reduce_utilization_by_time_off" : { - "type" : [ - "null", - "boolean" - ] - }, - "adjust_scheduling_to_public_holidays" : { - "type" : [ - "null", - "boolean" - ] - }, - "reduce_utilization_by_public_holidays" : { - "type" : [ - "null", - "boolean" - ] - }, - "capacity_based_load" : { - "type" : [ - "null", - "integer" - ] - }, - "use_billable_capacity" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_by_capacity_per_monday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_tuesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_wednesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_thursday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_friday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_saturday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_sunday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_monday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_tuesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_wednesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_thursday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_friday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_saturday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_sunday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_on_monday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_tuesday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_wednesday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_thursday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_friday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_saturday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_sunday" : { - "type" : [ - "null", - "boolean" - ] - }, - "financial_budget_mode" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_client_currency" : { - "type" : [ - "null", - "string" - ] - }, - "financial_client_currency_exchange_rate" : { - "type" : [ - "null", - "number" - ] - }, - "financial_total_scheduled_income" : { - "$ref" : "money_object.json" - }, - "financial_total_scheduled_cost" : { - "$ref" : "money_object.json" - }, - "financial_total_tracked_cost" : { - "$ref" : "money_object.json" - }, - "financial_total_tracked_income" : { - "$ref" : "money_object.json" - }, - "financial_settled_income" : { - "type" : [ - "null", - "object" - ] - }, - "financial_settled_cost" : { - "type" : [ - "null", - "object" - ] - }, - "financial_total_work_for_cost" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_contractor_hour_cost" : { - "type" : [ - "null", - "object" - ] - }, - "financial_hour_rate" : { - "$ref" : "money_object.json" - }, - "financial_employee_default_hour_cost" : { - "$ref" : "money_object.json" - }, - "financial_use_default_hour_cost" : { - "type" : [ - "null", - "boolean" - ] - }, - "financial_default_hour_rate_source" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_total_work_for_income" : { - "type" : [ - "null", - "integer" - ] + "custom_attributes": { + "$ref": "custom_attributes.json" + }, + "background": { + "type": ["null", "boolean"] + }, + "volatile_work_settings": { + "type": ["null", "boolean"] + }, + "only_billable_work": { + "type": ["null", "boolean"] + }, + "scheduling_time_frame": { + "type": ["null", "integer"] + }, + "scheduled_work_per_time_frame": { + "type": ["null", "integer"] + }, + "adjust_scheduling_to_time_off": { + "type": ["null", "boolean"] + }, + "reduce_utilization_by_time_off": { + "type": ["null", "boolean"] + }, + "adjust_scheduling_to_public_holidays": { + "type": ["null", "boolean"] + }, + "reduce_utilization_by_public_holidays": { + "type": ["null", "boolean"] + }, + "capacity_based_load": { + "type": ["null", "integer"] + }, + "use_billable_capacity": { + "type": ["null", "boolean"] + }, + "work_by_capacity_per_monday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_tuesday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_wednesday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_thursday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_friday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_saturday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_sunday": { + "type": ["null", "integer"] + }, + "work_per_monday": { + "type": ["null", "integer"] + }, + "work_per_tuesday": { + "type": ["null", "integer"] + }, + "work_per_wednesday": { + "type": ["null", "integer"] + }, + "work_per_thursday": { + "type": ["null", "integer"] + }, + "work_per_friday": { + "type": ["null", "integer"] + }, + "work_per_saturday": { + "type": ["null", "integer"] + }, + "work_per_sunday": { + "type": ["null", "integer"] + }, + "work_on_monday": { + "type": ["null", "boolean"] + }, + "work_on_tuesday": { + "type": ["null", "boolean"] + }, + "work_on_wednesday": { + "type": ["null", "boolean"] + }, + "work_on_thursday": { + "type": ["null", "boolean"] + }, + "work_on_friday": { + "type": ["null", "boolean"] + }, + "work_on_saturday": { + "type": ["null", "boolean"] + }, + "work_on_sunday": { + "type": ["null", "boolean"] + }, + "financial_budget_mode": { + "type": ["null", "integer"] + }, + "financial_client_currency": { + "type": ["null", "string"] + }, + "financial_client_currency_exchange_rate": { + "type": ["null", "number"] + }, + "financial_total_scheduled_income": { + "$ref": "money_object.json" + }, + "financial_total_scheduled_cost": { + "$ref": "money_object.json" + }, + "financial_total_tracked_cost": { + "$ref": "money_object.json" + }, + "financial_total_tracked_income": { + "$ref": "money_object.json" + }, + "financial_settled_income": { + "type": ["null", "object"] + }, + "financial_settled_cost": { + "type": ["null", "object"] + }, + "financial_total_work_for_cost": { + "type": ["null", "integer"] + }, + "financial_contractor_hour_cost": { + "type": ["null", "object"] + }, + "financial_hour_rate": { + "$ref": "money_object.json" + }, + "financial_employee_default_hour_cost": { + "$ref": "money_object.json" + }, + "financial_use_default_hour_cost": { + "type": ["null", "boolean"] + }, + "financial_default_hour_rate_source": { + "type": ["null", "integer"] + }, + "financial_total_work_for_income": { + "type": ["null", "integer"] } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : true, - "default_cursor_field" : [ - "created_at" - ], - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["created_at"], + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "employees", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "nick" : { - "type" : [ - "null", - "string" - ] - }, - "name" : { - "type" : [ - "null", - "string" - ] - }, - "email" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_manager_id" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_finance_manager_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "stream": { + "name": "employees", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "nick": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "email": { + "type": ["null", "string"] + }, + "assigned_manager_id": { + "type": ["null", "string"] + }, + "assigned_finance_manager_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "summary" : { - "type" : [ - "null", - "string" - ] - }, - "seniority_id" : { - "type" : [ - "null", - "string" - ] - }, - "team_id" : { - "type" : [ - "null", - "string" - ] - }, - "department_id" : { - "type" : [ - "null", - "string" - ] - }, - "position_id" : { - "type" : [ - "null", - "string" - ] - }, - "hash_tag_ids" : { - "type" : "array", - "items" : { - "type" : "string" + "summary": { + "type": ["null", "string"] + }, + "seniority_id": { + "type": ["null", "string"] + }, + "team_id": { + "type": ["null", "string"] + }, + "department_id": { + "type": ["null", "string"] + }, + "position_id": { + "type": ["null", "string"] + }, + "hash_tag_ids": { + "type": "array", + "items": { + "type": "string" } }, - "nationality" : { - "type" : [ - "null", - "string" - ] + "nationality": { + "type": ["null", "string"] }, - "note" : { - "type" : [ - "null", - "string" - ] + "note": { + "type": ["null", "string"] }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "hashtags", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "hashtags", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_clients", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_clients", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_company_groups", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_company_groups", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_departments", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_departments", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_identity_providers", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "connector" : { - "type" : "string" - }, - "status" : { - "type" : "string" - }, - "name" : { - "type" : "string" + "stream": { + "name": "organization_identity_providers", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "connector": { + "type": "string" + }, + "status": { + "type": "string" + }, + "name": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_positions", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_positions", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_rag_scopes", - "json_schema" : { - "type" : "object", - "properties" : { - "text" : { - "type" : "string" - }, - "rag_type" : { - "type" : "integer", - "enum" : [ - 1, - 2 - ] - }, - "default_choice" : { - "type" : "integer", - "enum" : [ - 1, - 2 - ] - }, - "allow_undefined" : { - "type" : "boolean" - }, - "is_financial" : { - "type" : "boolean" + "stream": { + "name": "organization_rag_scopes", + "json_schema": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "rag_type": { + "type": "integer", + "enum": [1, 2] + }, + "default_choice": { + "type": "integer", + "enum": [1, 2] + }, + "allow_undefined": { + "type": "boolean" + }, + "is_financial": { + "type": "boolean" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "text" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["text"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_roles", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" - }, - "default_hour_rate" : { + "stream": { + "name": "organization_roles", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" + }, + "default_hour_rate": { "type": "string", "airbyte_type": "big_number" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_seniorities", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" - }, - "level" : { - "type" : "integer" + "stream": { + "name": "organization_seniorities", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" + }, + "level": { + "type": "integer" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_tags", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_tags", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_teams", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_teams", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "organization_timeoff_types", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "text" : { - "type" : "string" + "stream": { + "name": "organization_timeoff_types", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "text": { + "type": "string" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "people", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "name" : { - "type" : "string" - }, - "mail" : { - "type" : "string" - }, - "archived" : { - "type" : "boolean" - }, - "roles" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "manager_id" : { - "type" : [ - "null", - "string" - ] + "stream": { + "name": "people", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "archived": { + "type": "boolean" + }, + "roles": { + "type": ["null", "object"], + "properties": { + "manager_id": { + "type": ["null", "string"] }, - "employee_id" : { - "type" : [ - "null", - "string" - ] + "employee_id": { + "type": ["null", "string"] }, - "administrator_id" : { - "type" : [ - "null", - "string" - ] + "administrator_id": { + "type": ["null", "string"] } } } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "projects", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "title" : { - "type" : [ - "null", - "string" - ] - }, - "hash_tag_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "stream": { + "name": "projects", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": ["null", "string"] + }, + "hash_tag_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "last_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "tentative" : { - "type" : [ - "null", - "boolean" - ] - }, - "likelihood" : { - "type" : [ - "null", - "integer" - ] - }, - "billing_model" : { - "type" : [ - "null", - "integer" - ] - }, - "hour_rate_source" : { - "type" : [ - "null", - "integer" - ] - }, - "customer_id" : { - "type" : [ - "null", - "string" - ] - }, - "currency" : { - "type" : [ - "null", - "string" - ] - }, - "currency_rate" : { - "type" : [ - "null", - "string" - ] - }, - "project_group_id" : { - "type" : [ - "null", - "string" - ] - }, - "status" : { - "type" : [ - "null", - "integer" - ] - }, - "color" : { - "type" : [ - "null", - "string" - ] - }, - "description" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_manager_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "last_date": { + "type": ["null", "string"], + "format": "date" + }, + "tentative": { + "type": ["null", "boolean"] + }, + "likelihood": { + "type": ["null", "integer"] + }, + "billing_model": { + "type": ["null", "integer"] + }, + "hour_rate_source": { + "type": ["null", "integer"] + }, + "customer_id": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "currency_rate": { + "type": ["null", "string"] + }, + "project_group_id": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "integer"] + }, + "color": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "assigned_manager_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "assigned_manager_readonly_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "assigned_manager_readonly_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "is_public" : { - "type" : [ - "null", - "boolean" - ] - }, - "integrations" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "is_public": { + "type": ["null", "boolean"] + }, + "integrations": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "projects_vacancies", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "project_id" : { - "type" : "string" - }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "seniority_id" : { - "type" : [ - "null", - "string" - ] - }, - "position_id" : { - "type" : [ - "null", - "string" - ] - }, - "note" : { - "type" : [ - "null", - "string" - ] - }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "stream": { + "name": "projects_vacancies", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "project_id": { + "type": "string" + }, + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "seniority_id": { + "type": ["null", "string"] + }, + "position_id": { + "type": ["null", "string"] + }, + "note": { + "type": ["null", "string"] + }, + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "rag_ratings", - "json_schema" : { - "type" : "object", - "properties" : { - "project_url" : { - "type" : "string" - }, - "project_id" : { - "type" : "string" - }, - "rag_ratings" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ] + "stream": { + "name": "rag_ratings", + "json_schema": { + "type": "object", + "properties": { + "project_url": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "rag_ratings": { + "type": "array", + "items": { + "type": ["null", "string"] } } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "project_id" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["project_id"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "skills", - "json_schema" : { - "type" : "object", - "properties" : { - "name" : { - "type" : "string" - }, - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "level" : { - "type" : "integer" - }, - "desc" : { - "type" : "string" - }, - "abstract" : { - "type" : "boolean" - }, - "path" : { - "type" : "string" - }, - "ancestors" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ] + "stream": { + "name": "skills", + "json_schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "uuid": { + "type": "string", + "format": "uuid" + }, + "level": { + "type": "integer" + }, + "desc": { + "type": "string" + }, + "abstract": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "ancestors": { + "type": "array", + "items": { + "type": ["null", "string"] } }, - "has_children" : { - "type" : [ - "null", - "boolean" - ] + "has_children": { + "type": ["null", "boolean"] } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "timeoffs", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "employee_id" : { - "type" : "string" - }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "approved" : { - "type" : "boolean" - }, - "approved_by" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "stream": { + "name": "timeoffs", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "employee_id": { + "type": "string" + }, + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "approved": { + "type": "boolean" + }, + "approved_by": { + "type": ["null", "object"], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "name" : { - "type" : "string" + "name": { + "type": "string" } } }, - "approved_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" + "approved_at": { + "type": ["null", "string"], + "format": "date-time" }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : false, - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": false, + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" }, { - "stream" : { - "name" : "worklogs", - "json_schema" : { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "assignment_id" : { - "type" : [ - "null", - "string" - ] - }, - "project_id" : { - "type" : [ - "null", - "string" - ] - }, - "starts_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "created_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "work" : { - "type" : [ - "null", - "integer" - ] - }, - "desc" : { - "type" : [ - "null", - "string" - ] - }, - "in_progress" : { - "type" : [ - "null", - "boolean" - ] - }, - "billable" : { - "type" : [ - "null", - "boolean" - ] - }, - "developer_id" : { - "type" : [ - "null", - "string" - ] - }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "stream": { + "name": "worklogs", + "json_schema": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "assignment_id": { + "type": ["null", "string"] + }, + "project_id": { + "type": ["null", "string"] + }, + "starts_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "work": { + "type": ["null", "integer"] + }, + "desc": { + "type": ["null", "string"] + }, + "in_progress": { + "type": ["null", "boolean"] + }, + "billable": { + "type": ["null", "boolean"] + }, + "developer_id": { + "type": ["null", "string"] + }, + "custom_attributes": { + "$ref": "custom_attributes.json" } }, - "supported_sync_modes" : [ - "full_refresh" - ], - "source_defined_cursor" : true, - "default_cursor_field" : [ - "created_at" - ], - "source_defined_primary_key" : [ - [ - "uuid" - ] - ] + "supported_sync_modes": ["full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["created_at"], + "source_defined_primary_key": [["uuid"]] } }, - "sync_mode" : "full_refresh", - "destination_sync_mode" : "overwrite" + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" } ] } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/assignments.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/assignments.json index 282a5ddac45e..696d09e65123 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/assignments.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/assignments.json @@ -1,464 +1,259 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "created_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "starts_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "ends_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "end_local_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "start_local_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "updated_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" - }, - "status" : { - "type" : "integer", - "enum" : [ - 0, - 1, - 2 - ] - }, - "employee_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "label" : { - "type" : [ - "null", - "string" - ] - }, - "note" : { - "type" : [ - "null", - "string" - ] - }, - "scheduling_mode" : { - "type" : [ - "null", - "integer" - ] - }, - "affects_capacity" : { - "type" : [ - "null", - "boolean" - ] - }, - "billable" : { - "type" : [ - "null", - "boolean" - ] - }, - "project_role_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "hash_tag_ids" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "starts_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "ends_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "end_local_date": { + "type": ["null", "string"], + "format": "date" + }, + "start_local_date": { + "type": ["null", "string"], + "format": "date" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "status": { + "type": "integer", + "enum": [0, 1, 2] + }, + "employee_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "label": { + "type": ["null", "string"] + }, + "note": { + "type": ["null", "string"] + }, + "scheduling_mode": { + "type": ["null", "integer"] + }, + "affects_capacity": { + "type": ["null", "boolean"] + }, + "billable": { + "type": ["null", "boolean"] + }, + "project_role_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "hash_tag_ids": { + "type": "array", + "items": { + "type": ["null", "string"], + "format": "uuid" } }, - "color" : { - "type" : [ - "null", - "string" - ] - }, - "project_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "project_phase_id" : { - "type" : [ - "null", - "string" - ], - "format" : "uuid" - }, - "total_tracked" : { - "type" : [ - "null", - "number" - ] - }, - "total_scheduled" : { - "type" : [ - "null", - "number" - ] - }, - "is_settled" : { - "type" : [ - "null", - "boolean" - ] - }, - "daily_data" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "start" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "color": { + "type": ["null", "string"] + }, + "project_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "project_phase_id": { + "type": ["null", "string"], + "format": "uuid" + }, + "total_tracked": { + "type": ["null", "number"] + }, + "total_scheduled": { + "type": ["null", "number"] + }, + "is_settled": { + "type": ["null", "boolean"] + }, + "daily_data": { + "type": ["null", "object"], + "properties": { + "start": { + "type": ["null", "string"], + "format": "date" }, - "end" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "end": { + "type": ["null", "string"], + "format": "date" }, - "tracked" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "number" - ] + "tracked": { + "type": "array", + "items": { + "type": ["null", "number"] } }, - "scheduled" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "number" - ] + "scheduled": { + "type": "array", + "items": { + "type": ["null", "number"] } } } }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" - }, - "background" : { - "type" : [ - "null", - "boolean" - ] - }, - "volatile_work_settings" : { - "type" : [ - "null", - "boolean" - ] - }, - "only_billable_work" : { - "type" : [ - "null", - "boolean" - ] - }, - "scheduling_time_frame" : { - "type" : [ - "null", - "integer" - ] - }, - "scheduled_work_per_time_frame" : { - "type" : [ - "null", - "integer" - ] - }, - "adjust_scheduling_to_time_off" : { - "type" : [ - "null", - "boolean" - ] - }, - "reduce_utilization_by_time_off" : { - "type" : [ - "null", - "boolean" - ] - }, - "adjust_scheduling_to_public_holidays" : { - "type" : [ - "null", - "boolean" - ] - }, - "reduce_utilization_by_public_holidays" : { - "type" : [ - "null", - "boolean" - ] - }, - "capacity_based_load" : { - "type" : [ - "null", - "integer" - ] - }, - "use_billable_capacity" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_by_capacity_per_monday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_tuesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_wednesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_thursday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_friday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_saturday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_by_capacity_per_sunday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_monday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_tuesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_wednesday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_thursday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_friday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_saturday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_per_sunday" : { - "type" : [ - "null", - "integer" - ] - }, - "work_on_monday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_tuesday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_wednesday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_thursday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_friday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_saturday" : { - "type" : [ - "null", - "boolean" - ] - }, - "work_on_sunday" : { - "type" : [ - "null", - "boolean" - ] - }, - "financial_budget_mode" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_client_currency" : { - "type" : [ - "null", - "string" - ] - }, - "financial_client_currency_exchange_rate" : { - "type" : [ - "null", - "number" - ] - }, - "financial_total_scheduled_income" : { - "$ref" : "money_object.json" - }, - "financial_total_scheduled_cost" : { - "$ref" : "money_object.json" - }, - "financial_total_tracked_cost" : { - "$ref" : "money_object.json" - }, - "financial_total_tracked_income" : { - "$ref" : "money_object.json" - }, - "financial_settled_income" : { - "type" : [ - "null", - "object" - ] - }, - "financial_settled_cost" : { - "type" : [ - "null", - "object" - ] - }, - "financial_total_work_for_cost" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_contractor_hour_cost" : { - "type" : [ - "null", - "object" - ] - }, - "financial_hour_rate" : { - "$ref" : "money_object.json" - }, - "financial_employee_default_hour_cost" : { - "$ref" : "money_object.json" - }, - "financial_use_default_hour_cost" : { - "type" : [ - "null", - "boolean" - ] - }, - "financial_default_hour_rate_source" : { - "type" : [ - "null", - "integer" - ] - }, - "financial_total_work_for_income" : { - "type" : [ - "null", - "integer" - ] + "custom_attributes": { + "$ref": "custom_attributes.json" + }, + "background": { + "type": ["null", "boolean"] + }, + "volatile_work_settings": { + "type": ["null", "boolean"] + }, + "only_billable_work": { + "type": ["null", "boolean"] + }, + "scheduling_time_frame": { + "type": ["null", "integer"] + }, + "scheduled_work_per_time_frame": { + "type": ["null", "integer"] + }, + "adjust_scheduling_to_time_off": { + "type": ["null", "boolean"] + }, + "reduce_utilization_by_time_off": { + "type": ["null", "boolean"] + }, + "adjust_scheduling_to_public_holidays": { + "type": ["null", "boolean"] + }, + "reduce_utilization_by_public_holidays": { + "type": ["null", "boolean"] + }, + "capacity_based_load": { + "type": ["null", "integer"] + }, + "use_billable_capacity": { + "type": ["null", "boolean"] + }, + "work_by_capacity_per_monday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_tuesday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_wednesday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_thursday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_friday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_saturday": { + "type": ["null", "integer"] + }, + "work_by_capacity_per_sunday": { + "type": ["null", "integer"] + }, + "work_per_monday": { + "type": ["null", "integer"] + }, + "work_per_tuesday": { + "type": ["null", "integer"] + }, + "work_per_wednesday": { + "type": ["null", "integer"] + }, + "work_per_thursday": { + "type": ["null", "integer"] + }, + "work_per_friday": { + "type": ["null", "integer"] + }, + "work_per_saturday": { + "type": ["null", "integer"] + }, + "work_per_sunday": { + "type": ["null", "integer"] + }, + "work_on_monday": { + "type": ["null", "boolean"] + }, + "work_on_tuesday": { + "type": ["null", "boolean"] + }, + "work_on_wednesday": { + "type": ["null", "boolean"] + }, + "work_on_thursday": { + "type": ["null", "boolean"] + }, + "work_on_friday": { + "type": ["null", "boolean"] + }, + "work_on_saturday": { + "type": ["null", "boolean"] + }, + "work_on_sunday": { + "type": ["null", "boolean"] + }, + "financial_budget_mode": { + "type": ["null", "integer"] + }, + "financial_client_currency": { + "type": ["null", "string"] + }, + "financial_client_currency_exchange_rate": { + "type": ["null", "number"] + }, + "financial_total_scheduled_income": { + "$ref": "money_object.json" + }, + "financial_total_scheduled_cost": { + "$ref": "money_object.json" + }, + "financial_total_tracked_cost": { + "$ref": "money_object.json" + }, + "financial_total_tracked_income": { + "$ref": "money_object.json" + }, + "financial_settled_income": { + "type": ["null", "object"] + }, + "financial_settled_cost": { + "type": ["null", "object"] + }, + "financial_total_work_for_cost": { + "type": ["null", "integer"] + }, + "financial_contractor_hour_cost": { + "type": ["null", "object"] + }, + "financial_hour_rate": { + "$ref": "money_object.json" + }, + "financial_employee_default_hour_cost": { + "$ref": "money_object.json" + }, + "financial_use_default_hour_cost": { + "type": ["null", "boolean"] + }, + "financial_default_hour_rate_source": { + "type": ["null", "integer"] + }, + "financial_total_work_for_income": { + "type": ["null", "integer"] } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/employees.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/employees.json index 11846e4c1a5f..182d6ee7030f 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/employees.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/employees.json @@ -1,93 +1,57 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "nick" : { - "type" : [ - "null", - "string" - ] + "nick": { + "type": ["null", "string"] }, - "name" : { - "type" : [ - "null", - "string" - ] + "name": { + "type": ["null", "string"] }, - "email" : { - "type" : [ - "null", - "string" - ] + "email": { + "type": ["null", "string"] }, - "assigned_manager_id" : { - "type" : [ - "null", - "string" - ] + "assigned_manager_id": { + "type": ["null", "string"] }, - "assigned_finance_manager_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "assigned_finance_manager_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "summary" : { - "type" : [ - "null", - "string" - ] + "summary": { + "type": ["null", "string"] }, - "seniority_id" : { - "type" : [ - "null", - "string" - ] + "seniority_id": { + "type": ["null", "string"] }, - "team_id" : { - "type" : [ - "null", - "string" - ] + "team_id": { + "type": ["null", "string"] }, - "department_id" : { - "type" : [ - "null", - "string" - ] + "department_id": { + "type": ["null", "string"] }, - "position_id" : { - "type" : [ - "null", - "string" - ] + "position_id": { + "type": ["null", "string"] }, - "hash_tag_ids" : { - "type" : "array", - "items" : { - "type" : "string" + "hash_tag_ids": { + "type": "array", + "items": { + "type": "string" } }, - "nationality" : { - "type" : [ - "null", - "string" - ] + "nationality": { + "type": ["null", "string"] }, - "note" : { - "type" : [ - "null", - "string" - ] + "note": { + "type": ["null", "string"] }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/hashtags.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/hashtags.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/hashtags.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/hashtags.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_clients.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_clients.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_clients.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_clients.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_company_groups.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_company_groups.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_company_groups.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_company_groups.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_departments.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_departments.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_departments.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_departments.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_identity_providers.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_identity_providers.json index 9205c71d1650..d613bc06a325 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_identity_providers.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_identity_providers.json @@ -1,18 +1,18 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "connector" : { - "type" : "string" + "connector": { + "type": "string" }, - "status" : { - "type" : "string" + "status": { + "type": "string" }, - "name" : { - "type" : "string" + "name": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_positions.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_positions.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_positions.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_positions.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_rag_scopes.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_rag_scopes.json index a42dc2c9179d..b2c4ca9d6bfc 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_rag_scopes.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_rag_scopes.json @@ -1,28 +1,22 @@ { - "type" : "object", - "properties" : { - "text" : { - "type" : "string" + "type": "object", + "properties": { + "text": { + "type": "string" }, - "rag_type" : { - "type" : "integer", - "enum" : [ - 1, - 2 - ] + "rag_type": { + "type": "integer", + "enum": [1, 2] }, - "default_choice" : { - "type" : "integer", - "enum" : [ - 1, - 2 - ] + "default_choice": { + "type": "integer", + "enum": [1, 2] }, - "allow_undefined" : { - "type" : "boolean" + "allow_undefined": { + "type": "boolean" }, - "is_financial" : { - "type" : "boolean" + "is_financial": { + "type": "boolean" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_roles.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_roles.json index a86ce982f47a..0dbaa57e1640 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_roles.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_roles.json @@ -1,14 +1,14 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" }, - "default_hour_rate" : { + "default_hour_rate": { "type": "string", "airbyte_type": "big_number" } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_seniorities.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_seniorities.json index 1970be7692c8..356af63eedf8 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_seniorities.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_seniorities.json @@ -1,15 +1,15 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" }, - "level" : { - "type" : "integer" + "level": { + "type": "integer" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_tags.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_tags.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_tags.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_tags.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_teams.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_teams.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_teams.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_teams.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_timeoff_types.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_timeoff_types.json index 790bee20fac4..2a209bed22d7 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_timeoff_types.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/organization_timeoff_types.json @@ -1,12 +1,12 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "text" : { - "type" : "string" + "text": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/people.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/people.json index eecd74ff4919..1f32bf69601c 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/people.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/people.json @@ -1,42 +1,30 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "name" : { - "type" : "string" + "name": { + "type": "string" }, - "mail" : { - "type" : "string" + "mail": { + "type": "string" }, - "archived" : { - "type" : "boolean" + "archived": { + "type": "boolean" }, - "roles" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "manager_id" : { - "type" : [ - "null", - "string" - ] + "roles": { + "type": ["null", "object"], + "properties": { + "manager_id": { + "type": ["null", "string"] }, - "employee_id" : { - "type" : [ - "null", - "string" - ] + "employee_id": { + "type": ["null", "string"] }, - "administrator_id" : { - "type" : [ - "null", - "string" - ] + "administrator_id": { + "type": ["null", "string"] } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects.json index cf02a55d08ee..d142de15ecf2 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects.json @@ -1,147 +1,87 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" - }, - "title" : { - "type" : [ - "null", - "string" - ] - }, - "hash_tag_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "title": { + "type": ["null", "string"] + }, + "hash_tag_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "last_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" - }, - "tentative" : { - "type" : [ - "null", - "boolean" - ] - }, - "likelihood" : { - "type" : [ - "null", - "integer" - ] - }, - "billing_model" : { - "type" : [ - "null", - "integer" - ] - }, - "hour_rate_source" : { - "type" : [ - "null", - "integer" - ] - }, - "customer_id" : { - "type" : [ - "null", - "string" - ] - }, - "currency" : { - "type" : [ - "null", - "string" - ] - }, - "currency_rate" : { - "type" : [ - "null", - "string" - ] - }, - "project_group_id" : { - "type" : [ - "null", - "string" - ] - }, - "status" : { - "type" : [ - "null", - "integer" - ] - }, - "color" : { - "type" : [ - "null", - "string" - ] - }, - "description" : { - "type" : [ - "null", - "string" - ] - }, - "assigned_manager_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "start_date": { + "type": ["null", "string"], + "format": "date" + }, + "end_date": { + "type": ["null", "string"], + "format": "date" + }, + "last_date": { + "type": ["null", "string"], + "format": "date" + }, + "tentative": { + "type": ["null", "boolean"] + }, + "likelihood": { + "type": ["null", "integer"] + }, + "billing_model": { + "type": ["null", "integer"] + }, + "hour_rate_source": { + "type": ["null", "integer"] + }, + "customer_id": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "currency_rate": { + "type": ["null", "string"] + }, + "project_group_id": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "integer"] + }, + "color": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "assigned_manager_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "assigned_manager_readonly_ids" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "assigned_manager_readonly_ids": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "is_public" : { - "type" : [ - "null", - "boolean" - ] - }, - "integrations" : { - "type" : [ - "null", - "array" - ], - "items" : { - "type" : "string" + "is_public": { + "type": ["null", "boolean"] + }, + "integrations": { + "type": ["null", "array"], + "items": { + "type": "string" } }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects_vacancies.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects_vacancies.json index 6f28e3bb697e..aea840f69a2a 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects_vacancies.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/projects_vacancies.json @@ -1,47 +1,32 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "project_id" : { - "type" : "string" + "project_id": { + "type": "string" }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "start_date": { + "type": ["null", "string"], + "format": "date" }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "end_date": { + "type": ["null", "string"], + "format": "date" }, - "seniority_id" : { - "type" : [ - "null", - "string" - ] + "seniority_id": { + "type": ["null", "string"] }, - "position_id" : { - "type" : [ - "null", - "string" - ] + "position_id": { + "type": ["null", "string"] }, - "note" : { - "type" : [ - "null", - "string" - ] + "note": { + "type": ["null", "string"] }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/rag_ratings.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/rag_ratings.json index 007774e85dc2..e09c64c2bcb8 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/rag_ratings.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/rag_ratings.json @@ -1,19 +1,16 @@ { - "type" : "object", - "properties" : { - "project_url" : { - "type" : "string" + "type": "object", + "properties": { + "project_url": { + "type": "string" }, - "project_id" : { - "type" : "string" + "project_id": { + "type": "string" }, - "rag_ratings" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ] + "rag_ratings": { + "type": "array", + "items": { + "type": ["null", "string"] } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/custom_attributes.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/custom_attributes.json index 076ad2e2b6f7..6e119f44b984 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/custom_attributes.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/custom_attributes.json @@ -1,18 +1,15 @@ { - "type" : "array", - "items" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "slug" : { - "type" : "string" + "type": "array", + "items": { + "type": ["null", "object"], + "properties": { + "slug": { + "type": "string" }, - "value" : { - "type" : "array", - "items" : { - "type" : "string" + "value": { + "type": "array", + "items": { + "type": "string" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/money_object.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/money_object.json index 0f3ca2bfda79..c8e4590a1520 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/money_object.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/shared/money_object.json @@ -1,26 +1,14 @@ { - "type" : [ - "null", - "object" - ], - "properties" : { - "amount" : { - "type" : [ - "null", - "number" - ] + "type": ["null", "object"], + "properties": { + "amount": { + "type": ["null", "number"] }, - "currency" : { - "type" : [ - "null", - "string" - ] + "currency": { + "type": ["null", "string"] }, - "exchange_rate" : { - "type" : [ - "null", - "number" - ] + "exchange_rate": { + "type": ["null", "number"] } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/skills.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/skills.json index 522dc35a1ef8..946edc686acb 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/skills.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/skills.json @@ -1,39 +1,33 @@ { - "type" : "object", - "properties" : { - "name" : { - "type" : "string" + "type": "object", + "properties": { + "name": { + "type": "string" }, - "uuid" : { - "type" : "string", - "format" : "uuid" + "uuid": { + "type": "string", + "format": "uuid" }, - "level" : { - "type" : "integer" + "level": { + "type": "integer" }, - "desc" : { - "type" : "string" + "desc": { + "type": "string" }, - "abstract" : { - "type" : "boolean" + "abstract": { + "type": "boolean" }, - "path" : { - "type" : "string" + "path": { + "type": "string" }, - "ancestors" : { - "type" : "array", - "items" : { - "type" : [ - "null", - "string" - ] + "ancestors": { + "type": "array", + "items": { + "type": ["null", "string"] } }, - "has_children" : { - "type" : [ - "null", - "boolean" - ] + "has_children": { + "type": ["null", "boolean"] } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/timeoffs.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/timeoffs.json index 80a9a8c8b95d..e93508259efb 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/timeoffs.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/timeoffs.json @@ -1,55 +1,43 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "employee_id" : { - "type" : "string", - "format" : "uuid" + "employee_id": { + "type": "string", + "format": "uuid" }, - "start_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "start_date": { + "type": ["null", "string"], + "format": "date" }, - "end_date" : { - "type" : [ - "null", - "string" - ], - "format" : "date" + "end_date": { + "type": ["null", "string"], + "format": "date" }, - "approved" : { - "type" : "boolean" + "approved": { + "type": "boolean" }, - "approved_by" : { - "type" : [ - "null", - "object" - ], - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "approved_by": { + "type": ["null", "object"], + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "name" : { - "type" : "string" + "name": { + "type": "string" } } }, - "approved_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" + "approved_at": { + "type": ["null", "string"], + "format": "date-time" }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/worklogs.json b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/worklogs.json index f20d3cd01a95..351e8f7818e5 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/worklogs.json +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/schemas/worklogs.json @@ -1,68 +1,41 @@ { - "type" : "object", - "properties" : { - "uuid" : { - "type" : "string", - "format" : "uuid" + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" }, - "assignment_id" : { - "type" : [ - "null", - "string" - ] + "assignment_id": { + "type": ["null", "string"] }, - "project_id" : { - "type" : [ - "null", - "string" - ] + "project_id": { + "type": ["null", "string"] }, - "starts_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" + "starts_at": { + "type": ["null", "string"], + "format": "date-time" }, - "created_at" : { - "type" : [ - "null", - "string" - ], - "format" : "date-time" + "created_at": { + "type": ["null", "string"], + "format": "date-time" }, - "work" : { - "type" : [ - "null", - "integer" - ] + "work": { + "type": ["null", "integer"] }, - "desc" : { - "type" : [ - "null", - "string" - ] + "desc": { + "type": ["null", "string"] }, - "in_progress" : { - "type" : [ - "null", - "boolean" - ] + "in_progress": { + "type": ["null", "boolean"] }, - "billable" : { - "type" : [ - "null", - "boolean" - ] + "billable": { + "type": ["null", "boolean"] }, - "developer_id" : { - "type" : [ - "null", - "string" - ] + "developer_id": { + "type": ["null", "string"] }, - "custom_attributes" : { - "$ref" : "custom_attributes.json" + "custom_attributes": { + "$ref": "custom_attributes.json" } } } diff --git a/airbyte-integrations/connectors/source-primetric/source_primetric/spec.yaml b/airbyte-integrations/connectors/source-primetric/source_primetric/spec.yaml index a402597a7b19..6bee958b2e5a 100644 --- a/airbyte-integrations/connectors/source-primetric/source_primetric/spec.yaml +++ b/airbyte-integrations/connectors/source-primetric/source_primetric/spec.yaml @@ -13,7 +13,7 @@ connectionSpecification: description: The Client ID of your Primetric developer application. The Client ID is visible here. pattern: ^[a-zA-Z0-9]+$ airbyte_secret: true - examples: [ "1234aBcD5678EFGh9045Neq79sdDlA15082VMYcj" ] + examples: ["1234aBcD5678EFGh9045Neq79sdDlA15082VMYcj"] order: 0 client_secret: type: string diff --git a/airbyte-integrations/connectors/source-redshift/src/test/resources/config-test.json b/airbyte-integrations/connectors/source-redshift/src/test/resources/config-test.json index 20b0d6cdc4a2..e91a33bade3d 100644 --- a/airbyte-integrations/connectors/source-redshift/src/test/resources/config-test.json +++ b/airbyte-integrations/connectors/source-redshift/src/test/resources/config-test.json @@ -2,10 +2,8 @@ "host": "localhost", "port": 1521, "database": "db", - "schemas": [ - "public" - ], + "schemas": ["public"], "username": "redshift", "password": "password", "jdbc_url_params": "property1=pValue1&property2=pValue2" -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java index b76a2d62aca6..da1eb7c241fb 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java @@ -167,7 +167,8 @@ public AutoCloseableIterator read(final JsonNode config, }); } - private void validateCursorFieldForIncrementalTables(final Map>> tableNameToTable, final ConfiguredAirbyteCatalog catalog) { + private void validateCursorFieldForIncrementalTables(final Map>> tableNameToTable, + final ConfiguredAirbyteCatalog catalog) { final List tablesWithInvalidCursor = new ArrayList<>(); for (final ConfiguredAirbyteStream airbyteStream : catalog.getStreams()) { final AirbyteStream stream = airbyteStream.getStream(); diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/InvalidCursorException.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/InvalidCursorException.java index 97cf102cab35..70237f4763bc 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/InvalidCursorException.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/InvalidCursorException.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.source.relationaldb; import java.util.List; @@ -6,8 +10,9 @@ public class InvalidCursorException extends RuntimeException { public InvalidCursorException(final List tablesWithInvalidCursor) { - super("The following tables have invalid columns selected as cursor, please select a column with a well-defined ordering as a cursor. " + tablesWithInvalidCursor.stream().map(InvalidCursorInfo::toString) - .collect(Collectors.joining(","))); + super("The following tables have invalid columns selected as cursor, please select a column with a well-defined ordering as a cursor. " + + tablesWithInvalidCursor.stream().map(InvalidCursorInfo::toString) + .collect(Collectors.joining(","))); } public record InvalidCursorInfo(String tableName, String cursorColumnName, String cursorSqlType) { @@ -20,7 +25,7 @@ public String toString() { ", cursorSqlType=" + cursorSqlType + '}'; } - } + } } diff --git a/airbyte-integrations/connectors/source-sendgrid/integration_tests/invalid_time.json b/airbyte-integrations/connectors/source-sendgrid/integration_tests/invalid_time.json index 083a4663d1c7..7f45d297544f 100644 --- a/airbyte-integrations/connectors/source-sendgrid/integration_tests/invalid_time.json +++ b/airbyte-integrations/connectors/source-sendgrid/integration_tests/invalid_time.json @@ -1,4 +1,4 @@ { "apikey": "apikey", "start_time": "some erroneous input" -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json b/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json index a1404d7c356e..c253b6f9fe87 100644 --- a/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json +++ b/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json @@ -17,7 +17,12 @@ "title": "Start time", "type": ["integer", "string"], "description": "Start time in ISO8601 format. Any data before this time point will not be replicated.", - "examples": ["2021-12-12", "2021-02-01 13:30:00", "2020-07-18T13:30:00.000Z", "2020-07-18 13:30:00+02:00"], + "examples": [ + "2021-12-12", + "2021-02-01 13:30:00", + "2020-07-18T13:30:00.000Z", + "2020-07-18 13:30:00+02:00" + ], "order": 1 } } diff --git a/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java index fe46f9d425f7..60c2406032b2 100644 --- a/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java @@ -23,6 +23,12 @@ import io.airbyte.protocol.models.AirbyteCatalog; import io.airbyte.protocol.models.AirbyteConnectionStatus; import io.airbyte.protocol.models.AirbyteConnectionStatus.Status; +import io.airbyte.protocol.models.AirbyteMessage; +import io.airbyte.protocol.models.AirbyteRecordMessage; +import io.airbyte.protocol.models.CatalogHelpers; +import io.airbyte.protocol.models.Field; +import io.airbyte.protocol.models.JsonSchemaType; +import io.airbyte.protocol.models.SyncMode; import java.math.BigDecimal; import java.nio.file.Path; import java.sql.JDBCType; @@ -30,13 +36,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; - -import io.airbyte.protocol.models.AirbyteMessage; -import io.airbyte.protocol.models.AirbyteRecordMessage; -import io.airbyte.protocol.models.CatalogHelpers; -import io.airbyte.protocol.models.Field; -import io.airbyte.protocol.models.JsonSchemaType; -import io.airbyte.protocol.models.SyncMode; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json b/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json index daaf6e9fdd0b..311650ac1f45 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json +++ b/airbyte-integrations/connectors/source-tiktok-marketing/source_tiktok_marketing/spec.json @@ -1,186 +1,151 @@ { - "documentationUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", - "changelogUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", - "connectionSpecification": { - "title": "TikTok Marketing Source Spec", + "documentationUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", + "changelogUrl": "https://docs.airbyte.com/integrations/sources/tiktok-marketing", + "connectionSpecification": { + "title": "TikTok Marketing Source Spec", + "type": "object", + "properties": { + "credentials": { + "title": "Authentication Method", + "description": "Authentication method", + "default": {}, + "order": 0, "type": "object", - "properties": { - "credentials": { - "title": "Authentication Method", - "description": "Authentication method", - "default": {}, + "oneOf": [ + { + "title": "OAuth2.0", + "type": "object", + "properties": { + "auth_type": { + "title": "Auth Type", + "const": "oauth2.0", "order": 0, - "type": "object", - "oneOf": [ - { - "title": "OAuth2.0", - "type": "object", - "properties": { - "auth_type": { - "title": "Auth Type", - "const": "oauth2.0", - "order": 0, - "type": "string" - }, - "app_id": { - "title": "App ID", - "description": "The Developer Application App ID.", - "airbyte_secret": true, - "type": "string" - }, - "secret": { - "title": "Secret", - "description": "The Developer Application Secret.", - "airbyte_secret": true, - "type": "string" - }, - "access_token": { - "title": "Access Token", - "description": "Long-term Authorized Access Token.", - "airbyte_secret": true, - "type": "string" - } - }, - "required": [ - "app_id", - "secret", - "access_token" - ] - }, - { - "title": "Sandbox Access Token", - "type": "object", - "properties": { - "auth_type": { - "title": "Auth Type", - "const": "sandbox_access_token", - "order": 0, - "type": "string" - }, - "advertiser_id": { - "title": "Advertiser ID", - "description": "The Advertiser ID which generated for the developer's Sandbox application.", - "type": "string" - }, - "access_token": { - "title": "Access Token", - "description": "The long-term authorized access token.", - "airbyte_secret": true, - "type": "string" - } - }, - "required": [ - "advertiser_id", - "access_token" - ] - } - ] - }, - "start_date": { - "title": "Replication Start Date", - "description": "The Start Date in format: YYYY-MM-DD. Any data before this date will not be replicated. If this parameter is not set, all data will be replicated.", - "default": "2016-09-01", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", - "order": 1, "type": "string" - }, - "end_date": { - "title": "End Date", - "description": "The date until which you'd like to replicate data for all incremental streams, in the format YYYY-MM-DD. All data generated between start_date and this date will be replicated. Not setting this option will result in always syncing the data till the current date.", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", - "order": 2, + }, + "app_id": { + "title": "App ID", + "description": "The Developer Application App ID.", + "airbyte_secret": true, "type": "string" - }, - "report_granularity": { - "title": "Report Aggregation Granularity", - "description": "The granularity used for aggregating performance data in reports. See
    the docs.", - "enum": [ - "LIFETIME", - "DAY", - "HOUR" - ], - "order": 3, - "airbyte_hidden": true, + }, + "secret": { + "title": "Secret", + "description": "The Developer Application Secret.", + "airbyte_secret": true, "type": "string" - } - } - }, - "supportsIncremental": true, - "supported_destination_sync_modes": [ - "overwrite", - "append", - "append_dedup" - ], - "advanced_auth": { - "auth_flow_type": "oauth2.0", - "predicate_key": [ - "credentials", - "auth_type" - ], - "predicate_value": "oauth2.0", - "oauth_config_specification": { - "complete_oauth_output_specification": { - "title": "CompleteOauthOutputSpecification", - "type": "object", - "properties": { - "access_token": { - "title": "Access Token", - "path_in_connector_config": [ - "credentials", - "access_token" - ], - "type": "string" - } - }, - "required": [ - "access_token" - ] + }, + "access_token": { + "title": "Access Token", + "description": "Long-term Authorized Access Token.", + "airbyte_secret": true, + "type": "string" + } }, - "complete_oauth_server_input_specification": { - "title": "CompleteOauthServerInputSpecification", - "type": "object", - "properties": { - "app_id": { - "title": "App Id", - "type": "string" - }, - "secret": { - "title": "Secret", - "type": "string" - } - }, - "required": [ - "app_id", - "secret" - ] + "required": ["app_id", "secret", "access_token"] + }, + { + "title": "Sandbox Access Token", + "type": "object", + "properties": { + "auth_type": { + "title": "Auth Type", + "const": "sandbox_access_token", + "order": 0, + "type": "string" + }, + "advertiser_id": { + "title": "Advertiser ID", + "description": "The Advertiser ID which generated for the developer's Sandbox application.", + "type": "string" + }, + "access_token": { + "title": "Access Token", + "description": "The long-term authorized access token.", + "airbyte_secret": true, + "type": "string" + } }, - "complete_oauth_server_output_specification": { - "title": "CompleteOauthServerOutputSpecification", - "type": "object", - "properties": { - "app_id": { - "title": "App Id", - "path_in_connector_config": [ - "credentials", - "app_id" - ], - "type": "string" - }, - "secret": { - "title": "Secret", - "path_in_connector_config": [ - "credentials", - "secret" - ], - "type": "string" - } - }, - "required": [ - "app_id", - "secret" - ] - } - } - }, - "additionalProperties": true + "required": ["advertiser_id", "access_token"] + } + ] + }, + "start_date": { + "title": "Replication Start Date", + "description": "The Start Date in format: YYYY-MM-DD. Any data before this date will not be replicated. If this parameter is not set, all data will be replicated.", + "default": "2016-09-01", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", + "order": 1, + "type": "string" + }, + "end_date": { + "title": "End Date", + "description": "The date until which you'd like to replicate data for all incremental streams, in the format YYYY-MM-DD. All data generated between start_date and this date will be replicated. Not setting this option will result in always syncing the data till the current date.", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", + "order": 2, + "type": "string" + }, + "report_granularity": { + "title": "Report Aggregation Granularity", + "description": "The granularity used for aggregating performance data in reports. See the docs.", + "enum": ["LIFETIME", "DAY", "HOUR"], + "order": 3, + "airbyte_hidden": true, + "type": "string" + } + } + }, + "supportsIncremental": true, + "supported_destination_sync_modes": ["overwrite", "append", "append_dedup"], + "advanced_auth": { + "auth_flow_type": "oauth2.0", + "predicate_key": ["credentials", "auth_type"], + "predicate_value": "oauth2.0", + "oauth_config_specification": { + "complete_oauth_output_specification": { + "title": "CompleteOauthOutputSpecification", + "type": "object", + "properties": { + "access_token": { + "title": "Access Token", + "path_in_connector_config": ["credentials", "access_token"], + "type": "string" + } + }, + "required": ["access_token"] + }, + "complete_oauth_server_input_specification": { + "title": "CompleteOauthServerInputSpecification", + "type": "object", + "properties": { + "app_id": { + "title": "App Id", + "type": "string" + }, + "secret": { + "title": "Secret", + "type": "string" + } + }, + "required": ["app_id", "secret"] + }, + "complete_oauth_server_output_specification": { + "title": "CompleteOauthServerOutputSpecification", + "type": "object", + "properties": { + "app_id": { + "title": "App Id", + "path_in_connector_config": ["credentials", "app_id"], + "type": "string" + }, + "secret": { + "title": "Secret", + "path_in_connector_config": ["credentials", "secret"], + "type": "string" + } + }, + "required": ["app_id", "secret"] + } + } + }, + "additionalProperties": true } diff --git a/airbyte-integrations/connectors/source-zendesk-talk/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-zendesk-talk/integration_tests/invalid_config.json index 0bae92b8eca8..d623d80fc17b 100644 --- a/airbyte-integrations/connectors/source-zendesk-talk/integration_tests/invalid_config.json +++ b/airbyte-integrations/connectors/source-zendesk-talk/integration_tests/invalid_config.json @@ -5,4 +5,4 @@ }, "subdomain": "d3v-airbyte", "start_date": "2221-04-01T00:00:00Z" -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-zenefits/integration_tests/catalog.json b/airbyte-integrations/connectors/source-zenefits/integration_tests/catalog.json index 7cd5a20bafb5..9713ec0bb457 100644 --- a/airbyte-integrations/connectors/source-zenefits/integration_tests/catalog.json +++ b/airbyte-integrations/connectors/source-zenefits/integration_tests/catalog.json @@ -6,9 +6,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -19,184 +19,184 @@ "destination_sync_mode": "append" }, { - "stream": { - "name": "employments", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + "stream": { + "name": "employments", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "vacation_requests", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "vacation_requests", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "vacation_types", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "vacation_types", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "time_durations", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "time_durations", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "departments", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "departments", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "locations", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "locations", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "labor_group_types", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "labor_group_types", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "custom_fields", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "custom_fields", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "custom_field_values", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "custom_field_values", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + "sync_mode": "full_refresh", + "destination_sync_mode": "append" }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - }, - { - "stream": { - "name": "labor_groups", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" + { + "stream": { + "name": "labor_groups", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" + } } - } + }, + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" - } + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + } ] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-zenefits/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-zenefits/integration_tests/configured_catalog.json index 76c940bf00c0..9713ec0bb457 100644 --- a/airbyte-integrations/connectors/source-zenefits/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-zenefits/integration_tests/configured_catalog.json @@ -1,32 +1,32 @@ { - "streams": [ - { - "stream": { - "name": "people", - "json_schema": { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties":{ - "token":{ - "type":"string" - } + "streams": [ + { + "stream": { + "name": "people", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "token": { + "type": "string" } - }, - "supported_sync_modes": ["full_refresh"], - "supported_destination_sync_modes": ["overwrite", "append"] + } }, - "sync_mode": "full_refresh", - "destination_sync_mode": "append" + "supported_sync_modes": ["full_refresh"], + "supported_destination_sync_modes": ["overwrite", "append"] }, - { + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + }, + { "stream": { "name": "employments", "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -42,9 +42,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -60,9 +60,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -78,9 +78,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -96,9 +96,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -114,9 +114,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -132,9 +132,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -150,9 +150,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -168,9 +168,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -186,9 +186,9 @@ "json_schema": { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "properties":{ - "token":{ - "type":"string" + "properties": { + "token": { + "type": "string" } } }, @@ -198,5 +198,5 @@ "sync_mode": "full_refresh", "destination_sync_mode": "append" } - ] - } \ No newline at end of file + ] +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_field_values.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_field_values.json index 2cadf64dd492..3a299ce490d2 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_field_values.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_field_values.json @@ -1,46 +1,46 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id":{ - "type":["string","null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "value": { + "type": ["null", "string", "boolean"] + }, + "custom_field": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "url":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "object":{ - "type":["string","null"] - }, - "value":{ - "type":["null","string","boolean"] + "url": { + "type": ["string", "null"] + } + } + }, + "person": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "custom_field": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "object": { + "type": ["string", "null"] }, - "person": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "url": { + "type": ["string", "null"] } + } } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_fields.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_fields.json index ea57a75b129f..0e128e3d199d 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_fields.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/custom_fields.json @@ -1,85 +1,85 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id":{ - "type":["string","null"] - }, - "can_manager_view_field":{ - "type":["boolean","null"] - }, - "custom_field_values": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } - }, - "person_during_onboarding":{ - "type":["boolean","null"] - }, - "name":{ - "type":["string","null"] - }, - "is_field_completer_person":{ - "type":["boolean","null"] - }, - "url":{ - "type":["string","null"] - }, - "is_sensitive":{ - "type":["boolean","null"] - }, - "is_field_required":{ - "type":["boolean","null"] - }, - "object":{ - "type":["string","null"] - }, - "can_person_view_field":{ - "type":["boolean","null"] - }, - "can_person_edit_field":{ - "type":["boolean","null"] - }, - "company": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } - }, - "company_during_hiring":{ - "type":["boolean","null"] - }, - "custom_field_type":{ - "type":["string","null"] - }, - "help_url":{ - "type":["null","string"] - }, - "help_text":{ - "type":["string","null"] - }, - "help_url_media":{ - "type":["null","string"] - }, - "media_file_type":{ - "type":["null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "can_manager_view_field": { + "type": ["boolean", "null"] + }, + "custom_field_values": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + } + } + }, + "person_during_onboarding": { + "type": ["boolean", "null"] + }, + "name": { + "type": ["string", "null"] + }, + "is_field_completer_person": { + "type": ["boolean", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "is_sensitive": { + "type": ["boolean", "null"] + }, + "is_field_required": { + "type": ["boolean", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "can_person_view_field": { + "type": ["boolean", "null"] + }, + "can_person_edit_field": { + "type": ["boolean", "null"] + }, + "company": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] } + } + }, + "company_during_hiring": { + "type": ["boolean", "null"] + }, + "custom_field_type": { + "type": ["string", "null"] + }, + "help_url": { + "type": ["null", "string"] + }, + "help_text": { + "type": ["string", "null"] + }, + "help_url_media": { + "type": ["null", "string"] + }, + "media_file_type": { + "type": ["null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/departments.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/departments.json index 8cc659d2506a..e4030ae9bf9c 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/departments.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/departments.json @@ -1,60 +1,60 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id":{ - "type":["string","null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "name": { + "type": ["string", "null"] + }, + "labor_group": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "name":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "labor_group": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "url": { + "type": ["string", "null"] + } + } + }, + "people": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "people": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "object": { + "type": ["string", "null"] }, - "company": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "url": { + "type": ["string", "null"] + } + } + }, + "company": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "url":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "object":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] } + } + }, + "url": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/employments.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/employments.json index f76a82609309..0ae59f207732 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/employments.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/employments.json @@ -1,95 +1,53 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "person": { - "type": "object", - "properties": { - "url": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "ref_object": { - "type": [ - "string", - "null" - ] - } - } - }, - "hire_date": { - "type": [ - "string", - "null" - ] - }, - "employment_type": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "is_active": { - "type": [ - "boolean", - "null" - ] - }, - "is_flsa_exempt": { - "type": [ - "string", - "null" - ] - }, - "termination_type": { - "type": [ - "string", - "null" - ] - }, - "termination_date": { - "type": [ - "string", - "null" - ] - }, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "person": { + "type": "object", + "properties": { "url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, - "id": { - "type": [ - "string", - "null" - ] - }, - "working_hours_per_week": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "employment_sub_type": { - "type": [ - "string", - "null" - ] + "ref_object": { + "type": ["string", "null"] } + } + }, + "hire_date": { + "type": ["string", "null"] + }, + "employment_type": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "is_active": { + "type": ["boolean", "null"] + }, + "is_flsa_exempt": { + "type": ["string", "null"] + }, + "termination_type": { + "type": ["string", "null"] + }, + "termination_date": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "id": { + "type": ["string", "null"] + }, + "working_hours_per_week": { + "type": ["string", "null"] + }, + "employment_sub_type": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_group_types.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_group_types.json index e94fb7f84eb2..53ad93ff7d42 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_group_types.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_group_types.json @@ -1,46 +1,46 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id":{ - "type":["string","null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "name": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "company": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "name":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "url":{ - "type":["string","null"] - }, - "company":{ - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "url": { + "type": ["string", "null"] + } + } + }, + "labor_groups": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "labor_groups":{ - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "object": { + "type": ["string", "null"] }, - "object":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] } + } + }, + "object": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_groups.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_groups.json index 680a1a5caedf..ed43d40ebb68 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_groups.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/labor_groups.json @@ -1,69 +1,69 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id":{ - "type":["string","null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "code": { + "type": ["string", "null"] + }, + "name": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "labor_group_type": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "code":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "name":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] + } + } + }, + "assigned_members": { + "type": "object", + "properties": { + "url": { + "type": ["string", "null"] }, - "url":{ - "type":["string","null"] + "next_url": { + "type": ["string", "null"] }, - "object":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "labor_group_type": { - "type": "object", + "data": { + "type": ["array", "null"], + "items": { + "type": ["object", "null"], "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } + "url": { + "type": ["string", "null"] + }, + "is_primary": { + "type": ["boolean", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "ref_object": { + "type": ["string", "null"] + } } - }, - "assigned_members": { - "type": "object", - "properties": { - "url": { - "type":["string","null"] - }, - "next_url": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "data":{ - "type": [ "array","null"], - "items": { - "type": ["object","null"], - "properties": { - "url":{ - "type":["string","null"] - }, - "is_primary":{ - "type":["boolean","null"] - }, - "object":{ - "type":["string","null"] - }, - "ref_object":{ - "type":["string","null"] - } - } - } - } - } + } } + } } + } } diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/locations.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/locations.json index dd35d4a62db9..820443269c77 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/locations.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/locations.json @@ -1,81 +1,81 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id":{ - "type":["string","null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["string", "null"] + }, + "name": { + "type": ["string", "null"] + }, + "city": { + "type": ["string", "null"] + }, + "labor_group": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "name":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "city":{ - "type":["string","null"] - }, - "labor_group": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } - }, - "zip":{ - "type":["string","null"] - }, - "people": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } - }, - "url":{ - "type":["string","null"] - }, - "street1":{ - "type":["string","null"] - }, - "street2":{ - "type":["string","null"] - }, - "object":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] + } + } + }, + "zip": { + "type": ["string", "null"] + }, + "people": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "phone":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "state":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] + } + } + }, + "url": { + "type": ["string", "null"] + }, + "street1": { + "type": ["string", "null"] + }, + "street2": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "phone": { + "type": ["string", "null"] + }, + "state": { + "type": ["string", "null"] + }, + "country": { + "type": ["string", "null"] + }, + "company": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "country":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "company": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } + "url": { + "type": ["string", "null"] } + } } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/people.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/people.json index c6fe26dc6610..610ecefb31a5 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/people.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/people.json @@ -1,310 +1,178 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "employee_number": { - "type": [ - "string", - "null" - ] - }, - "country": { - "type": [ - "string", - "null" - ] - }, - "labor_groups": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } - }, - "is_admin": { - "type": [ - "boolean", - "null" - ] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "employee_number": { + "type": ["string", "null"] + }, + "country": { + "type": ["string", "null"] + }, + "labor_groups": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, "object": { - "type": [ - "string", - "null" - ] - }, - "street1": { - "type": [ - "string", - "null" - ] - }, - "company": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } - }, - "department": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } - }, - "subordinates": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } - }, - "location": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } + "type": ["string", "null"] }, - "work_phone": { - "type": [ - "string", - "null" - ] - }, - "middle_name": { - "type": [ - "string", - "null" - ] - }, - "custom_field_values": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } - }, - "postal_code": { - "type": [ - "string", - "null" - ] + "url": { + "type": ["string", "null"] + } + } + }, + "is_admin": { + "type": ["boolean", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "street1": { + "type": ["string", "null"] + }, + "company": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "employments": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } + "object": { + "type": ["string", "null"] }, - "manager": { - "type": "object", - "properties": { - "ref_object": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "url": { - "type": [ - "string", - "null" - ] - } - } + "url": { + "type": ["string", "null"] + } + } + }, + "department": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "status": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "last_name": { - "type": [ - "string", - "null" - ] + "url": { + "type": ["string", "null"] + } + } + }, + "subordinates": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "first_name": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "state": { - "type": [ - "string", - "null" - ] + "url": { + "type": ["string", "null"] + } + } + }, + "location": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "title": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, "url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] + } + } + }, + "work_phone": { + "type": ["string", "null"] + }, + "middle_name": { + "type": ["string", "null"] + }, + "custom_field_values": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "street2": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "work_email": { - "type": [ - "string", - "null" - ] + "url": { + "type": ["string", "null"] + } + } + }, + "postal_code": { + "type": ["string", "null"] + }, + "employments": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "preferred_name": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "id": { - "type": [ - "string", - "null" - ] + "url": { + "type": ["string", "null"] + } + } + }, + "manager": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "type": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "city": { - "type": [ - "string", - "null" - ] + "url": { + "type": ["string", "null"] } + } + }, + "status": { + "type": ["string", "null"] + }, + "last_name": { + "type": ["string", "null"] + }, + "first_name": { + "type": ["string", "null"] + }, + "state": { + "type": ["string", "null"] + }, + "title": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "street2": { + "type": ["string", "null"] + }, + "work_email": { + "type": ["string", "null"] + }, + "preferred_name": { + "type": ["string", "null"] + }, + "id": { + "type": ["string", "null"] + }, + "type": { + "type": ["string", "null"] + }, + "city": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/time_durations.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/time_durations.json index 09d6e9c85674..94c5fd9f5e4d 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/time_durations.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/time_durations.json @@ -1,77 +1,77 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "is_overnight":{ - "type":["boolean","null"] + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "is_overnight": { + "type": ["boolean", "null"] + }, + "is_approved": { + "type": ["boolean", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "end": { + "type": ["string", "null"], + "format": "date-time" + }, + "person": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "is_approved":{ - "type":["boolean","null"] + "object": { + "type": ["string", "null"] }, - "object":{ - "type":["string","null"] - }, - "end":{ - "type":["string","null"], - "format": "date-time" - }, - "person": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } - }, - "url":{ - "type":["string","null"] - }, - "approver": { - "type": "object", - "properties": { - "ref_object": { - "type":["string","null"] - }, - "object": { - "type":["string","null"] - }, - "url": { - "type":["string","null"] - } - } - }, - "labor_group_ids":{ - "type":["string","null"] - }, - "hours":{ - "type":["string","null"] - }, - "start":{ - "type":["string","null"] - }, - "state":{ - "type":["string","null"] - }, - "approved_datetime":{ - "type":["string","null"] - }, - "valid_status":{ - "type":["string","null"] - }, - "date":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] + } + } + }, + "url": { + "type": ["string", "null"] + }, + "approver": { + "type": "object", + "properties": { + "ref_object": { + "type": ["string", "null"] }, - "activity":{ - "type":["string","null"] + "object": { + "type": ["string", "null"] }, - "id":{ - "type":["string","null"] + "url": { + "type": ["string", "null"] } + } + }, + "labor_group_ids": { + "type": ["string", "null"] + }, + "hours": { + "type": ["string", "null"] + }, + "start": { + "type": ["string", "null"] + }, + "state": { + "type": ["string", "null"] + }, + "approved_datetime": { + "type": ["string", "null"] + }, + "valid_status": { + "type": ["string", "null"] + }, + "date": { + "type": ["string", "null"] + }, + "activity": { + "type": ["string", "null"] + }, + "id": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_requests.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_requests.json index 8576786dd103..dbeb0bd1e486 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_requests.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_requests.json @@ -1,141 +1,81 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "status": { - "type": [ - "string", - "null" - ] - }, - "vacation_type": { - "type": "object", - "properties": { - "url": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "ref_object": { - "type": [ - "string", - "null" - ] - } - } - }, - "end_date": { - "type": [ - "string", - "null" - ] - }, - "creator": { - "type": "object", - "properties": { - "url": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "ref_object": { - "type": [ - "string", - "null" - ] - } - } - }, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "status": { + "type": ["string", "null"] + }, + "vacation_type": { + "type": "object", + "properties": { "url": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, "object": { - "type": [ - "string", - "null" - ] - }, - "start_date": { - "type": [ - "string", - "null" - ] - }, - "hours": { - "type": [ - "string", - "null" - ] - }, - "approved_date": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, - "reason": { - "type": [ - "string", - "null" - ] + "ref_object": { + "type": ["string", "null"] + } + } + }, + "end_date": { + "type": ["string", "null"] + }, + "creator": { + "type": "object", + "properties": { + "url": { + "type": ["string", "null"] }, - "person": { - "type": "object", - "properties": { - "url": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "ref_object": { - "type": [ - "string", - "null" - ] - } - } + "object": { + "type": ["string", "null"] }, - "created_date": { - "type": [ - "string", - "null" - ] + "ref_object": { + "type": ["string", "null"] + } + } + }, + "url": { + "type": ["string", "null"] + }, + "object": { + "type": ["string", "null"] + }, + "start_date": { + "type": ["string", "null"] + }, + "hours": { + "type": ["string", "null"] + }, + "approved_date": { + "type": ["string", "null"] + }, + "reason": { + "type": ["string", "null"] + }, + "person": { + "type": "object", + "properties": { + "url": { + "type": ["string", "null"] }, - "deny_reason": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "id": { - "type": [ - "string", - "null" - ] + "ref_object": { + "type": ["string", "null"] } + } + }, + "created_date": { + "type": ["string", "null"] + }, + "deny_reason": { + "type": ["string", "null"] + }, + "id": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_types.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_types.json index 08bde21cdc53..d7ca01218948 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_types.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/schemas/vacation_types.json @@ -1,88 +1,52 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "status": { - "type": [ - "string", - "null" - ] - }, - "name": { - "type": [ - "string", - "null" - ] - }, + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "status": { + "type": ["string", "null"] + }, + "name": { + "type": ["string", "null"] + }, + "url": { + "type": ["string", "null"] + }, + "company": { + "type": "object", + "properties": { "url": { - "type": [ - "string", - "null" - ] - }, - "company": { - "type": "object", - "properties": { - "url": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "ref_object": { - "type": [ - "string", - "null" - ] - } - } + "type": ["string", "null"] }, "object": { - "type": [ - "string", - "null" - ] + "type": ["string", "null"] }, - "vacation_requests": { - "type": "object", - "properties": { - "url": { - "type": [ - "string", - "null" - ] - }, - "object": { - "type": [ - "string", - "null" - ] - }, - "ref_object": { - "type": [ - "string", - "null" - ] - } - } + "ref_object": { + "type": ["string", "null"] + } + } + }, + "object": { + "type": ["string", "null"] + }, + "vacation_requests": { + "type": "object", + "properties": { + "url": { + "type": ["string", "null"] }, - "count_as": { - "type": [ - "string", - "null" - ] + "object": { + "type": ["string", "null"] }, - "id": { - "type": [ - "string", - "null" - ] + "ref_object": { + "type": ["string", "null"] } + } + }, + "count_as": { + "type": ["string", "null"] + }, + "id": { + "type": ["string", "null"] } -} \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/source.py b/airbyte-integrations/connectors/source-zenefits/source_zenefits/source.py index 6d0ed8f17532..9ced63df72af 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/source.py +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/source.py @@ -33,7 +33,6 @@ def request_params( def request_headers(self, **kwargs) -> Mapping[str, Any]: return {"Authorization": f"Bearer {self.token}", "Content-Type": "application/json", "Accept": "application/json"} - def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: response_json = response.json().get("data") @@ -44,7 +43,7 @@ def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, return next_params return None - + def parse_response( self, response: requests.Response, @@ -66,7 +65,6 @@ def path(self, **kwargs) -> str: return "core/people" - # Employee Employment details class Employments(ZenefitsStream): @@ -85,7 +83,6 @@ def path(self, **kwargs) -> str: return "core/departments" - # locations class Locations(ZenefitsStream): @@ -104,8 +101,6 @@ def path(self, **kwargs) -> str: return "core/labor_groups" - - # labor_groups_types class Labor_group_types(ZenefitsStream): @@ -115,8 +110,6 @@ def path(self, **kwargs) -> str: return "core/labor_group_types" - - # custom_fields class Custom_fields(ZenefitsStream): @@ -126,7 +119,6 @@ def path(self, **kwargs) -> str: return "core/custom_fields" - # custom_field_values class Custom_field_values(ZenefitsStream): @@ -152,7 +144,6 @@ def path(self, **kwargs) -> str: return "time_off/vacation_types" - # Time Durations class Time_durations(ZenefitsStream): primary_key = None @@ -161,7 +152,6 @@ def path(self, **kwargs) -> str: return "time_attendance/time_durations" - class SourceZenefits(AbstractSource): def check_connection(self, logger, config) -> Tuple[bool, any]: token = config["token"] diff --git a/airbyte-integrations/connectors/source-zenefits/source_zenefits/spec.json b/airbyte-integrations/connectors/source-zenefits/source_zenefits/spec.json index 759e17e43860..c30e4056ffd2 100644 --- a/airbyte-integrations/connectors/source-zenefits/source_zenefits/spec.json +++ b/airbyte-integrations/connectors/source-zenefits/source_zenefits/spec.json @@ -7,8 +7,8 @@ "required": ["token"], "additionalProperties": false, "properties": { - "token":{ - "title":"token", + "token": { + "title": "token", "type": "string", "description": "Use Sync with Zenefits button on the link given on the readme file, and get the token to access the api" } From c04d317e45c520f9a393fe6409ad4bf94308792a Mon Sep 17 00:00:00 2001 From: Baz Date: Wed, 12 Oct 2022 15:16:09 +0300 Subject: [PATCH 057/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Shopify:=20fix?= =?UTF-8?q?=20`404`=20for=20configured=20streams,=20fix=20missing=20cursor?= =?UTF-8?q?=20error=20for=20old=20records=20(#17777)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 40 +++++---- .../connectors/source-shopify/Dockerfile | 2 +- .../integration_tests/state.json | 90 +++++++++---------- .../source-shopify/source_shopify/source.py | 36 +++++++- .../source-shopify/source_shopify/spec.json | 48 +++++----- .../source-shopify/unit_tests/unit_test.py | 30 ++++++- docs/integrations/sources/shopify.md | 1 + 8 files changed, 156 insertions(+), 93 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 0cefad4524aa..b67cc2a76ca7 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -955,7 +955,7 @@ - name: Shopify sourceDefinitionId: 9da77001-af33-4bcd-be46-6252bf9342b9 dockerRepository: airbyte/source-shopify - dockerImageTag: 0.1.37 + dockerImageTag: 0.1.38 documentationUrl: https://docs.airbyte.com/integrations/sources/shopify icon: shopify.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 666bbc8c2f78..8287a6feea10 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -10010,7 +10010,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-shopify:0.1.37" +- dockerImage: "airbyte/source-shopify:0.1.38" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/shopify" connectionSpecification: @@ -10034,6 +10034,24 @@ type: "object" order: 2 oneOf: + - title: "API Password" + description: "API Password Auth" + type: "object" + required: + - "auth_method" + - "api_password" + properties: + auth_method: + type: "string" + const: "api_password" + order: 0 + api_password: + type: "string" + title: "API Password" + description: "The API Password for your private application in the\ + \ `Shopify` store." + airbyte_secret: true + order: 1 - type: "object" title: "OAuth2.0" description: "OAuth2.0" @@ -10049,33 +10067,19 @@ title: "Client ID" description: "The Client ID of the Shopify developer application." airbyte_secret: true + order: 1 client_secret: type: "string" title: "Client Secret" description: "The Client Secret of the Shopify developer application." airbyte_secret: true + order: 2 access_token: type: "string" title: "Access Token" description: "The Access Token for making authenticated requests." airbyte_secret: true - - title: "API Password" - description: "API Password Auth" - type: "object" - required: - - "auth_method" - - "api_password" - properties: - auth_method: - type: "string" - const: "api_password" - order: 0 - api_password: - type: "string" - title: "API Password" - description: "The API Password for your private application in the\ - \ `Shopify` store." - airbyte_secret: true + order: 3 start_date: type: "string" title: "Replication Start Date" diff --git a/airbyte-integrations/connectors/source-shopify/Dockerfile b/airbyte-integrations/connectors/source-shopify/Dockerfile index edb7ac03c3b0..1e7a6898795c 100644 --- a/airbyte-integrations/connectors/source-shopify/Dockerfile +++ b/airbyte-integrations/connectors/source-shopify/Dockerfile @@ -28,5 +28,5 @@ COPY source_shopify ./source_shopify ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.37 +LABEL io.airbyte.version=0.1.38 LABEL io.airbyte.name=airbyte/source-shopify diff --git a/airbyte-integrations/connectors/source-shopify/integration_tests/state.json b/airbyte-integrations/connectors/source-shopify/integration_tests/state.json index 00d1d3e75daa..c362f65b2358 100644 --- a/airbyte-integrations/connectors/source-shopify/integration_tests/state.json +++ b/airbyte-integrations/connectors/source-shopify/integration_tests/state.json @@ -1,84 +1,80 @@ { "customers": { - "updated_at": "2022-03-02T03:23:16-08:00" + "updated_at": "2022-06-22T03:50:13-07:00" }, "orders": { - "updated_at": "2022-03-03T03:47:46-08:00" + "updated_at": "2022-10-10T06:21:53-07:00" }, "draft_orders": { - "updated_at": "2022-02-22T04:29:57-08:00" + "updated_at": "2022-10-08T05:07:29-07:00" }, "products": { - "updated_at": "2022-03-03T03:47:51-08:00" - }, - "abandoned_checkouts": { - "updated_at": "2022-03-02T03:23:22-08:00" + "updated_at": "2022-10-10T06:21:56-07:00" }, + "abandoned_checkouts": {}, "metafields": { - "updated_at": "2021-07-08T03:38:45-07:00" + "updated_at": "2022-05-30T23:42:02-07:00" }, "collects": { - "id": 29427031703741 + "id": 29427031703740 }, "custom_collections": { - "updated_at": "2021-08-18T02:39:34-07:00" + "updated_at": "2022-10-08T04:44:51-07:00" }, "order_refunds": { - "created_at": "2022-03-03T03:47:46-08:00", - "orders": { - "updated_at": "2022-03-03T03:47:46-08:00" - } + "created_at": "2022-10-10T06:21:53-07:00", + "orders": { + "updated_at": "2022-10-10T06:21:53-07:00" + } }, "order_risks": { - "id": 6446736474301, - "orders": { - "updated_at": "2022-02-22T00:37:28-08:00" - } + "id": 6446736474301, + "orders": { + "updated_at": "2022-03-07T02:09:04-08:00" + } }, "transactions": { - "created_at": "2022-03-03T03:47:45-08:00", - "orders": { - "updated_at": "2022-03-03T03:47:46-08:00" - } + "created_at": "2022-10-10T06:21:52-07:00", + "orders": { + "updated_at": "2022-10-10T06:21:53-07:00" + } }, "tender_transactions": { - "processed_at": "2022-03-03T03:47:45-08:00" + "processed_at": "2022-10-10T06:21:52-07:00" }, "pages": { - "updated_at": "2021-07-08T05:24:10-07:00" + "updated_at": "2022-10-08T08:07:00-07:00" }, "price_rules": { - "updated_at": "2021-09-10T06:48:10-07:00" + "updated_at": "2021-09-10T06:48:10-07:00" }, "discount_codes": { - "updated_at": "2021-09-10T06:48:10-07:00", - "price_rules": { + "price_rules": { + "updated_at": "2021-09-10T06:48:10-07:00" + }, "updated_at": "2021-09-10T06:48:10-07:00" - } }, "inventory_items": { - "updated_at": "2022-02-22T00:40:26-08:00", - "products": { - "updated_at": "2022-02-18T02:39:48-07:00" - } + "products": { + "updated_at": "2022-03-17T03:10:35-07:00" + }, + "updated_at": "2022-03-06T14:12:20-08:00" }, "inventory_levels": { - "updated_at": "2022-03-03T03:47:51-08:00", - "locations": {} + "locations": {}, + "updated_at": "2022-10-10T06:21:56-07:00" }, "fulfillment_orders": { - "id": 5424260808893, - "orders": { - "updated_at": "2022-03-03T03:47:46-08:00" - } + "id": 5567724486845, + "orders": { + "updated_at": "2022-10-10T06:05:29-07:00" + } }, "fulfillments": { - "updated_at": "2022-02-27T23:49:13-08:00", - "orders": { - "updated_at": "2022-03-03T03:47:46-08:00" - } - }, - "balance_transactions": { - "id": 29427031703741 - } -} + "updated_at": "2022-06-22T03:50:14-07:00", + "orders": { + "updated_at": "2022-10-10T06:05:29-07:00" + } + }, + "balance_transactions": {} +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/source.py b/airbyte-integrations/connectors/source-shopify/source_shopify/source.py index f01589c17361..2e8dbe03958c 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/source.py +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/source.py @@ -30,6 +30,8 @@ class ShopifyStream(HttpStream, ABC): order_field = "updated_at" filter_field = "updated_at_min" + raise_on_http_errors = True + def __init__(self, config: Dict): super().__init__(authenticator=config["authenticator"]) self._transformer = DataTypeEnforcer(self.get_json_schema()) @@ -82,6 +84,12 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp record["shop_url"] = self.config["shop"] yield self._transformer.transform(record) + def should_retry(self, response: requests.Response) -> bool: + if response.status_code == 404: + self.logger.warn(f"Stream `{self.name}` is not available, skipping.") + setattr(self, "raise_on_http_errors", False) + return super().should_retry(response) + @property @abstractmethod def data_field(self) -> str: @@ -125,11 +133,18 @@ def request_params(self, stream_state: Mapping[str, Any] = None, next_page_token # Parse the `stream_slice` with respect to `stream_state` for `Incremental refresh` # cases where we slice the stream, the endpoints for those classes don't accept any other filtering, # but they provide us with the updated_at field in most cases, so we used that as incremental filtering during the order slicing. - def filter_records_newer_than_state(self, stream_state: Mapping[str, Any] = None, records_slice: Mapping[str, Any] = None) -> Iterable: + def filter_records_newer_than_state(self, stream_state: Mapping[str, Any] = None, records_slice: Iterable[Mapping] = None) -> Iterable: # Getting records >= state if stream_state: for record in records_slice: - if record.get(self.cursor_field, "") >= stream_state.get(self.cursor_field): + if self.cursor_field in record: + if record.get(self.cursor_field, self.default_state_comparison_value) >= stream_state.get(self.cursor_field): + yield record + else: + # old entities could miss the cursor field + self.logger.warn( + f"Stream `{self.name}`, Record ID: `{record.get(self.primary_key)}` missing cursor: {self.cursor_field}" + ) yield record else: yield from records_slice @@ -204,9 +219,11 @@ def stream_slices(self, stream_state: Mapping[str, Any] = None, **kwargs) -> Ite {slice_key: 999 ] """ + sorted_substream_slices = [] # reading parent nested stream_state from child stream state parent_stream_state = stream_state.get(self.parent_stream.name) if stream_state else {} + # reading the parent stream for record in self.parent_stream.read_records(stream_state=parent_stream_state, **kwargs): # updating the `stream_state` with the state of it's parent stream @@ -217,10 +234,23 @@ def stream_slices(self, stream_state: Mapping[str, Any] = None, **kwargs) -> Ite # and corresponds to the data of child_substream we need. if self.nested_substream: if record.get(self.nested_substream): - yield {self.slice_key: record[self.nested_record]} + sorted_substream_slices.append( + { + self.slice_key: record[self.nested_record], + self.cursor_field: record[self.nested_substream][0].get(self.cursor_field, self.default_state_comparison_value), + } + ) else: yield {self.slice_key: record[self.nested_record]} + # output slice from sorted list to avoid filtering older records + if self.nested_substream: + if len(sorted_substream_slices) > 0: + # sort by cursor_field + sorted_substream_slices.sort(key=lambda x: x.get(self.cursor_field)) + for sorted_slice in sorted_substream_slices: + yield {self.slice_key: sorted_slice[self.slice_key]} + def read_records( self, stream_state: Mapping[str, Any] = None, diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json b/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json index 73ef20e21374..eb65adb54c3f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/spec.json @@ -19,6 +19,26 @@ "type": "object", "order": 2, "oneOf": [ + { + "title": "API Password", + "description": "API Password Auth", + "type": "object", + "required": ["auth_method", "api_password"], + "properties": { + "auth_method": { + "type": "string", + "const": "api_password", + "order": 0 + }, + "api_password": { + "type": "string", + "title": "API Password", + "description": "The API Password for your private application in the `Shopify` store.", + "airbyte_secret": true, + "order": 1 + } + } + }, { "type": "object", "title": "OAuth2.0", @@ -34,38 +54,22 @@ "type": "string", "title": "Client ID", "description": "The Client ID of the Shopify developer application.", - "airbyte_secret": true + "airbyte_secret": true, + "order": 1 }, "client_secret": { "type": "string", "title": "Client Secret", "description": "The Client Secret of the Shopify developer application.", - "airbyte_secret": true + "airbyte_secret": true, + "order": 2 }, "access_token": { "type": "string", "title": "Access Token", "description": "The Access Token for making authenticated requests.", - "airbyte_secret": true - } - } - }, - { - "title": "API Password", - "description": "API Password Auth", - "type": "object", - "required": ["auth_method", "api_password"], - "properties": { - "auth_method": { - "type": "string", - "const": "api_password", - "order": 0 - }, - "api_password": { - "type": "string", - "title": "API Password", - "description": "The API Password for your private application in the `Shopify` store.", - "airbyte_secret": true + "airbyte_secret": true, + "order": 3 } } } diff --git a/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py index e4f0c47ca4c7..84684c28a032 100644 --- a/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py @@ -3,7 +3,7 @@ # import requests -from source_shopify.source import ShopifyStream, SourceShopify +from source_shopify.source import BalanceTransactions, DiscountCodes, ShopifyStream, SourceShopify def test_get_next_page_token(requests_mock): @@ -44,3 +44,31 @@ def test_privileges_validation(requests_mock, basic_config): ] assert [stream.name for stream in source.streams(basic_config)] == expected + + +def test_unavailable_stream(requests_mock, basic_config): + config = basic_config + config["authenticator"] = None + stream = BalanceTransactions(config) + url = stream.url_base + stream.path() + params = {"limit": 250, "order": stream.cursor_field + "+acs", "since_id": 0} + requests_mock.get(url=url, json={"errors": "Not Found"}, status_code=404) + response = requests.get(url, params) + assert stream.should_retry(response) is False + + +def test_filter_records_newer_than_state(basic_config): + config = basic_config + config["authenticator"] = None + stream = DiscountCodes(config) + records_slice = [ + # present cursor older than state - record should be omitted + {"id": 1, "updated_at": "2022-01-01T01:01:01-07:00"}, + # missing cursor, record should be present + {"id": 2}, + ] + state = {"updated_at": "2022-02-01T00:00:00-07:00"} + + expected = [{"id": 2}] + result = list(stream.filter_records_newer_than_state(state, records_slice)) + assert result == expected diff --git a/docs/integrations/sources/shopify.md b/docs/integrations/sources/shopify.md index 2c1e299f32f8..a54b834d66a1 100644 --- a/docs/integrations/sources/shopify.md +++ b/docs/integrations/sources/shopify.md @@ -139,6 +139,7 @@ This is expected when the connector hits the 429 - Rate Limit Exceeded HTTP Erro | Version | Date | Pull Request | Subject | |:--------| :--- | :--- | :--- | +| 0.1.38 | 2022-10-10 | [17777](https://github.com/airbytehq/airbyte/pull/17777) | Fixed `404` for configured streams, fix missing `cursor` error for old records | 0.1.37 | 2022-04-30 | [12500](https://github.com/airbytehq/airbyte/pull/12500) | Improve input configuration copy | | 0.1.36 | 2022-03-22 | [9850](https://github.com/airbytehq/airbyte/pull/9850) | Added `BalanceTransactions` stream | | 0.1.35 | 2022-03-07 | [10915](https://github.com/airbytehq/airbyte/pull/10915) | Fix a bug which caused `full-refresh` syncs of child REST entities configured for `incremental` | From 246172bd6c7c85cae5cc0d734859c1b344b6e9c0 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Wed, 12 Oct 2022 08:59:02 -0400 Subject: [PATCH 058/498] Use the Airbyte Base Java Image for all services (#17843) * Use the Amazon Corretto based base image for all services * Add comment * Use airbyte-base-java-image --- airbyte-bootloader/Dockerfile | 3 +-- airbyte-container-orchestrator/Dockerfile | 3 +-- airbyte-cron/Dockerfile | 3 +-- airbyte-metrics/reporter/Dockerfile | 3 +-- airbyte-server/Dockerfile | 3 +-- airbyte-workers/Dockerfile | 3 +-- build.gradle | 7 ++++--- 7 files changed, 10 insertions(+), 15 deletions(-) diff --git a/airbyte-bootloader/Dockerfile b/airbyte-bootloader/Dockerfile index fbb3cf5cb7d9..a8847e1d90cb 100644 --- a/airbyte-bootloader/Dockerfile +++ b/airbyte-bootloader/Dockerfile @@ -1,5 +1,4 @@ -ARG JDK_VERSION=17.0.4 -ARG JDK_IMAGE=amazoncorretto:${JDK_VERSION} +ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} ARG VERSION=0.40.14 diff --git a/airbyte-container-orchestrator/Dockerfile b/airbyte-container-orchestrator/Dockerfile index 5e0d7e699be6..4cdf5b13e0e4 100644 --- a/airbyte-container-orchestrator/Dockerfile +++ b/airbyte-container-orchestrator/Dockerfile @@ -1,5 +1,4 @@ -ARG JDK_VERSION=17.0.4 -ARG JDK_IMAGE=amazoncorretto:${JDK_VERSION} +ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS orchestrator ARG DOCKER_BUILD_ARCH=amd64 diff --git a/airbyte-cron/Dockerfile b/airbyte-cron/Dockerfile index 42b6d48432fb..bef9569442d5 100644 --- a/airbyte-cron/Dockerfile +++ b/airbyte-cron/Dockerfile @@ -1,5 +1,4 @@ -ARG JDK_VERSION=17.0.4 -ARG JDK_IMAGE=amazoncorretto:${JDK_VERSION} +ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS cron ARG VERSION=0.40.14 diff --git a/airbyte-metrics/reporter/Dockerfile b/airbyte-metrics/reporter/Dockerfile index 43c9fd86a501..ebf860125bf5 100644 --- a/airbyte-metrics/reporter/Dockerfile +++ b/airbyte-metrics/reporter/Dockerfile @@ -1,5 +1,4 @@ -ARG JDK_VERSION=17.0.4 -ARG JDK_IMAGE=amazoncorretto:${JDK_VERSION} +ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS metrics-reporter ARG VERSION=0.40.14 diff --git a/airbyte-server/Dockerfile b/airbyte-server/Dockerfile index 3f6120d17191..c26a1a991db1 100644 --- a/airbyte-server/Dockerfile +++ b/airbyte-server/Dockerfile @@ -1,5 +1,4 @@ -ARG JDK_VERSION=17.0.4 -ARG JDK_IMAGE=amazoncorretto:${JDK_VERSION} +ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS server EXPOSE 8000 diff --git a/airbyte-workers/Dockerfile b/airbyte-workers/Dockerfile index caa93e6b8594..7c594da87481 100644 --- a/airbyte-workers/Dockerfile +++ b/airbyte-workers/Dockerfile @@ -1,5 +1,4 @@ -ARG JDK_VERSION=17.0.4 -ARG JDK_IMAGE=amazoncorretto:${JDK_VERSION} +ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS worker ARG DOCKER_BUILD_ARCH=amd64 diff --git a/build.gradle b/build.gradle index 18a285126ad4..4ec3532b4cec 100644 --- a/build.gradle +++ b/build.gradle @@ -239,12 +239,13 @@ subprojects { subproj -> def arch = System.getenv('BUILD_ARCH') ?: System.getProperty("os.arch").toLowerCase() def isArm64 = arch == "aarch64" || arch == "arm64" - + def buildArch = System.getenv('DOCKER_BUILD_ARCH') ?: isArm64 ? 'arm64' : 'amd64' def buildPlatform = System.getenv('DOCKER_BUILD_PLATFORM') ?: isArm64 ? 'linux/arm64' : 'linux/amd64' def alpineImage = System.getenv('ALPINE_IMAGE') ?: isArm64 ? 'arm64v8/alpine:3.14' : 'amd64/alpine:3.14' def nginxImage = System.getenv('NGINX_IMAGE') ?: isArm64 ? 'arm64v8/nginx:alpine' : 'amd64/nginx:alpine' - def openjdkImage = System.getenv('JDK_IMAGE') ?: isArm64 ? "arm64v8/amazoncorretto:17.0.4" : "amd64/amazoncorretto:17.0.4" - def buildArch = System.getenv('DOCKER_BUILD_ARCH') ?: isArm64 ? 'arm64' : 'amd64' + + // Used by the platform -- Must be an Amazon Corretto-based image for build to work without modification to Dockerfile instructions + def openjdkImage = System.getenv('JDK_IMAGE') ?: "airbyte/airbyte-base-java-image:1.0" platform = buildPlatform images.add("airbyte/$subproj.dockerImageName:$rootProject.ext.image_tag") From 5df66cd572d7fcc527a5256556b34d37110e22ab Mon Sep 17 00:00:00 2001 From: Serhii Lazebnyi <53845333+lazebnyi@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:07:22 +0200 Subject: [PATCH 059/498] Source S3: Connector does not enforce SSL/TLS for non-S3 endpoints (#17800) * Deleted ssl/tsl flag from config * Updated PR number * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 14 +------------- .../connectors/source-s3/Dockerfile | 2 +- .../source-s3/integration_tests/config_minio.json | 2 +- .../source-s3/integration_tests/spec.json | 12 ------------ .../connectors/source-s3/source_s3/s3_utils.py | 4 ++-- .../connectors/source-s3/source_s3/source.py | 13 ------------- docs/integrations/sources/s3.md | 3 ++- 8 files changed, 8 insertions(+), 44 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index b67cc2a76ca7..3b6bbef558c0 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -915,7 +915,7 @@ - name: S3 sourceDefinitionId: 69589781-7828-43c5-9f63-8925b1c1ccc2 dockerRepository: airbyte/source-s3 - dockerImageTag: 0.1.22 + dockerImageTag: 0.1.23 documentationUrl: https://docs.airbyte.com/integrations/sources/s3 icon: s3.svg sourceType: file diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 8287a6feea10..03caa92a3b9e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9429,7 +9429,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-s3:0.1.22" +- dockerImage: "airbyte/source-s3:0.1.23" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/s3" changelogUrl: "https://docs.airbyte.com/integrations/sources/s3" @@ -9707,18 +9707,6 @@ default: "" order: 4 type: "string" - use_ssl: - title: "Use TLS" - description: "Whether the remote server is using a secure SSL/TLS connection.\ - \ Only relevant if using an S3-compatible, non-AWS server" - order: 5 - type: "boolean" - verify_ssl_cert: - title: "Verify TLS Certificates" - description: "Set this to false to allow self signed certificates. Only\ - \ relevant if using an S3-compatible, non-AWS server" - order: 6 - type: "boolean" required: - "bucket" order: 11 diff --git a/airbyte-integrations/connectors/source-s3/Dockerfile b/airbyte-integrations/connectors/source-s3/Dockerfile index 9459dc5752d5..cb908e0cd51d 100644 --- a/airbyte-integrations/connectors/source-s3/Dockerfile +++ b/airbyte-integrations/connectors/source-s3/Dockerfile @@ -17,5 +17,5 @@ COPY source_s3 ./source_s3 ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.22 +LABEL io.airbyte.version=0.1.23 LABEL io.airbyte.name=airbyte/source-s3 diff --git a/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json b/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json index c1ecebad5ae7..726aef0143d0 100644 --- a/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json +++ b/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json @@ -6,7 +6,7 @@ "aws_access_key_id": "123456", "aws_secret_access_key": "123456key", "path_prefix": "", - "endpoint": "http://10.0.154.238:9000" + "endpoint": "http://10.0.167.14:9000" }, "format": { "filetype": "csv" diff --git a/airbyte-integrations/connectors/source-s3/integration_tests/spec.json b/airbyte-integrations/connectors/source-s3/integration_tests/spec.json index b6c53fae4ce3..417c48058501 100644 --- a/airbyte-integrations/connectors/source-s3/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-s3/integration_tests/spec.json @@ -251,18 +251,6 @@ "default": "", "order": 4, "type": "string" - }, - "use_ssl": { - "title": "Use TLS", - "description": "Whether the remote server is using a secure SSL/TLS connection. Only relevant if using an S3-compatible, non-AWS server", - "order": 5, - "type": "boolean" - }, - "verify_ssl_cert": { - "title": "Verify TLS Certificates", - "description": "Set this to false to allow self signed certificates. Only relevant if using an S3-compatible, non-AWS server", - "order": 6, - "type": "boolean" } }, "required": ["bucket"], diff --git a/airbyte-integrations/connectors/source-s3/source_s3/s3_utils.py b/airbyte-integrations/connectors/source-s3/source_s3/s3_utils.py index 213d0d216720..607495951fa9 100644 --- a/airbyte-integrations/connectors/source-s3/source_s3/s3_utils.py +++ b/airbyte-integrations/connectors/source-s3/source_s3/s3_utils.py @@ -46,8 +46,8 @@ def _get_s3_client_args(provider: dict, config: Config) -> dict: # endpoint could be None or empty string, set to default Amazon endpoint in # this case. client_kv_args["endpoint_url"] = endpoint - client_kv_args["use_ssl"] = provider.get("use_ssl") - client_kv_args["verify"] = provider.get("verify_ssl_cert") + client_kv_args["use_ssl"] = provider.get("use_ssl", True) + client_kv_args["verify"] = provider.get("verify_ssl_cert", True) return client_kv_args diff --git a/airbyte-integrations/connectors/source-s3/source_s3/source.py b/airbyte-integrations/connectors/source-s3/source_s3/source.py index 8317f8d89710..f7b54c45f673 100644 --- a/airbyte-integrations/connectors/source-s3/source_s3/source.py +++ b/airbyte-integrations/connectors/source-s3/source_s3/source.py @@ -48,19 +48,6 @@ class Config: ) endpoint: str = Field("", description="Endpoint to an S3 compatible service. Leave empty to use AWS.", order=4) - use_ssl: bool = Field( - default=None, - title="Use TLS", - description="Whether the remote server is using a secure SSL/TLS connection. Only relevant if using an S3-compatible, " - "non-AWS server", - order=5, - ) - verify_ssl_cert: bool = Field( - default=None, - title="Verify TLS Certificates", - description="Set this to false to allow self signed certificates. Only relevant if using an S3-compatible, non-AWS server", - order=6, - ) provider: S3Provider diff --git a/docs/integrations/sources/s3.md b/docs/integrations/sources/s3.md index 77c244fd0ef4..9ab0d44064fe 100644 --- a/docs/integrations/sources/s3.md +++ b/docs/integrations/sources/s3.md @@ -205,7 +205,8 @@ The Jsonl parser uses pyarrow hence,only the line-delimited JSON format is suppo | Version | Date | Pull Request | Subject | | :------ | :--------- | :-------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | -| 0.1.22 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | +| 0.1.23 | 2022-10-10 | [17800](https://github.com/airbytehq/airbyte/pull/17800) | Deleted `use_ssl` and `verify_ssl_cert` flags and hardcoded to `True` | +| 0.1.22 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state | | 0.1.21 | 2022-09-20 | [16921](https://github.com/airbytehq/airbyte/pull/16921) | Upgrade pyarrow | | 0.1.20 | 2022-09-12 | [16607](https://github.com/airbytehq/airbyte/pull/16607) | Fix for reading jsonl files containing nested structures | | 0.1.19 | 2022-09-13 | [16631](https://github.com/airbytehq/airbyte/pull/16631) | Adjust column type to a broadest one when merging two or more json schemas | From f21f34559cb554cf6552a87536368aadd1123f94 Mon Sep 17 00:00:00 2001 From: Serhii Lazebnyi <53845333+lazebnyi@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:07:40 +0200 Subject: [PATCH 060/498] Source Amplitude: Add empty series validation (#17854) * Add seriaes validation * Updated PR number * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-amplitude/Dockerfile | 2 +- .../source-amplitude/source_amplitude/api.py | 4 ++-- .../source-amplitude/unit_tests/test_api.py | 13 ++++++++++++- docs/integrations/sources/amplitude.md | 1 + 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 3b6bbef558c0..f8d0ebadfe89 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -56,7 +56,7 @@ - name: Amplitude sourceDefinitionId: fa9f58c6-2d03-4237-aaa4-07d75e0c1396 dockerRepository: airbyte/source-amplitude - dockerImageTag: 0.1.15 + dockerImageTag: 0.1.16 documentationUrl: https://docs.airbyte.com/integrations/sources/amplitude icon: amplitude.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 03caa92a3b9e..9bafdacffd9e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -1105,7 +1105,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-amplitude:0.1.15" +- dockerImage: "airbyte/source-amplitude:0.1.16" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/amplitude" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-amplitude/Dockerfile b/airbyte-integrations/connectors/source-amplitude/Dockerfile index efe89edf1650..1c18929ba320 100644 --- a/airbyte-integrations/connectors/source-amplitude/Dockerfile +++ b/airbyte-integrations/connectors/source-amplitude/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.15 +LABEL io.airbyte.version=0.1.16 LABEL io.airbyte.name=airbyte/source-amplitude diff --git a/airbyte-integrations/connectors/source-amplitude/source_amplitude/api.py b/airbyte-integrations/connectors/source-amplitude/source_amplitude/api.py index 544c7d6b0d94..82e152eddc96 100644 --- a/airbyte-integrations/connectors/source-amplitude/source_amplitude/api.py +++ b/airbyte-integrations/connectors/source-amplitude/source_amplitude/api.py @@ -101,7 +101,7 @@ def get_updated_state(self, current_stream_state: MutableMapping[str, Any], late if self.compare_date_template: latest_state = pendulum.parse(latest_record[self.cursor_field]).strftime(self.compare_date_template) else: - latest_state = latest_record[self.cursor_field] + latest_state = latest_record.get(self.cursor_field, "") return {self.cursor_field: max(latest_state, current_stream_state.get(self.cursor_field, ""))} def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: @@ -234,7 +234,7 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp if response_data: series = list(map(list, zip(*response_data["series"]))) for i, date in enumerate(response_data["xValues"]): - yield {"date": date, "statistics": dict(zip(response_data["seriesLabels"], series[i]))} + yield from [{"date": date, "statistics": dict(zip(response_data["seriesLabels"], series[i]))}] if series else [] def path(self, **kwargs) -> str: return f"{self.api_version}/users" diff --git a/airbyte-integrations/connectors/source-amplitude/unit_tests/test_api.py b/airbyte-integrations/connectors/source-amplitude/unit_tests/test_api.py index 7fb88cf99603..1cbca0d37e13 100644 --- a/airbyte-integrations/connectors/source-amplitude/unit_tests/test_api.py +++ b/airbyte-integrations/connectors/source-amplitude/unit_tests/test_api.py @@ -74,6 +74,17 @@ class TestIncrementalStreams: }, [{"date": "2021-01-01", "statistics": {0: 1}}, {"date": "2021-01-02", "statistics": {0: 5}}], ), + ( + ActiveUsers, + { + "xValues": ["2021-01-01", "2021-01-02"], + "series": [], + "seriesCollapsed": [[0]], + "seriesLabels": [0], + "seriesMeta": [{"segmentIndex": 0}], + }, + [], + ), ( AverageSessionLength, { @@ -86,7 +97,7 @@ class TestIncrementalStreams: [{"date": "2019-05-23", "length": 2}, {"date": "2019-05-24", "length": 6}], ), ], - ids=["ActiveUsers", "AverageSessionLength"], + ids=["ActiveUsers", "EmptyActiveUsers", "AverageSessionLength"], ) def test_parse_response(self, requests_mock, stream_cls, data, expected): stream = stream_cls("2021-01-01T00:00:00Z") diff --git a/docs/integrations/sources/amplitude.md b/docs/integrations/sources/amplitude.md index 3be515e1a1e0..f1060334a469 100644 --- a/docs/integrations/sources/amplitude.md +++ b/docs/integrations/sources/amplitude.md @@ -43,6 +43,7 @@ The Amplitude connector ideally should gracefully handle Amplitude API limitatio | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:------------------------------------------------------------------------------------------------| +| 0.1.16 | 2022-10-11 | [17854](https://github.com/airbytehq/airbyte/pull/17854) | Add empty `series` validation | | 0.1.15 | 2022-10-03 | [17320](https://github.com/airbytehq/airbyte/pull/17320) | Add validation `start_date` filed if it's in the future | | 0.1.14 | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states. | | 0.1.13 | 2022-08-31 | [16185](https://github.com/airbytehq/airbyte/pull/16185) | Re-release on new `airbyte_cdk==0.1.81` | From 490f0ca241d427bf081d82fbbf043fddb94e5dbd Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Wed, 12 Oct 2022 16:07:57 +0200 Subject: [PATCH 061/498] Introduce secrets management into workspace persistence (#17851) * no-op refactor: rename getStandardWorkspace to prepare to introduce secrets * no-op refactor: rename writeStandardWorkspace to prepare to introduce secrets * introduce secrets management into workspace persistence --- .../analytics/TrackingClientSingleton.java | 2 +- .../TrackingClientSingletonTest.java | 10 +++--- .../io/airbyte/bootloader/BootloaderApp.java | 4 ++- .../airbyte/bootloader/BootloaderAppTest.java | 2 +- .../config/persistence/ConfigRepository.java | 17 +++++++--- .../persistence/SecretsRepositoryReader.java | 8 +++++ .../persistence/SecretsRepositoryWriter.java | 7 ++++ .../ConfigRepositoryE2EReadWriteTest.java | 2 +- .../persistence/ConfigRepositoryTest.java | 8 ++--- .../persistence/StatePersistenceTest.java | 2 +- .../airbyte/persistence/job/JobNotifier.java | 4 +-- .../job/errorreporter/JobErrorReporter.java | 6 ++-- .../persistence/job/tracker/JobTracker.java | 2 +- .../persistence/job/JobNotifierTest.java | 2 +- .../errorreporter/JobErrorReporterTest.java | 6 ++-- .../job/tracker/JobTrackerTest.java | 12 +++---- .../airbyte/server/apis/ConfigurationApi.java | 7 +++- .../server/handlers/ConnectionsHandler.java | 2 +- .../server/handlers/WorkspacesHandler.java | 33 ++++++++++++------- .../handlers/ConnectionsHandlerTest.java | 4 +-- .../handlers/WorkspacesHandlerTest.java | 26 ++++++++------- 21 files changed, 105 insertions(+), 61 deletions(-) diff --git a/airbyte-analytics/src/main/java/io/airbyte/analytics/TrackingClientSingleton.java b/airbyte-analytics/src/main/java/io/airbyte/analytics/TrackingClientSingleton.java index d64e6c4536ce..d9eae6e4af72 100644 --- a/airbyte-analytics/src/main/java/io/airbyte/analytics/TrackingClientSingleton.java +++ b/airbyte-analytics/src/main/java/io/airbyte/analytics/TrackingClientSingleton.java @@ -61,7 +61,7 @@ private static void initialize() { @VisibleForTesting static TrackingIdentity getTrackingIdentity(final ConfigRepository configRepository, final AirbyteVersion airbyteVersion, final UUID workspaceId) { try { - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, true); String email = null; if (workspace.getEmail() != null && workspace.getAnonymousDataCollection() != null && !workspace.getAnonymousDataCollection()) { email = workspace.getEmail(); diff --git a/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java b/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java index 782936f09a5a..ff058716f55e 100644 --- a/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java +++ b/airbyte-analytics/src/test/java/io/airbyte/analytics/TrackingClientSingletonTest.java @@ -78,8 +78,8 @@ void testGetTrackingIdentityRespectsWorkspaceId() throws JsonValidationException final StandardWorkspace workspace1 = new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withCustomerId(UUID.randomUUID()); final StandardWorkspace workspace2 = new StandardWorkspace().withWorkspaceId(UUID.randomUUID()).withCustomerId(UUID.randomUUID()); - when(configRepository.getStandardWorkspace(workspace1.getWorkspaceId(), true)).thenReturn(workspace1); - when(configRepository.getStandardWorkspace(workspace2.getWorkspaceId(), true)).thenReturn(workspace2); + when(configRepository.getStandardWorkspaceNoSecrets(workspace1.getWorkspaceId(), true)).thenReturn(workspace1); + when(configRepository.getStandardWorkspaceNoSecrets(workspace2.getWorkspaceId(), true)).thenReturn(workspace2); final TrackingIdentity workspace1Actual = TrackingClientSingleton.getTrackingIdentity(configRepository, AIRBYTE_VERSION, workspace1.getWorkspaceId()); @@ -96,7 +96,7 @@ void testGetTrackingIdentityRespectsWorkspaceId() throws JsonValidationException void testGetTrackingIdentityInitialSetupNotComplete() throws JsonValidationException, IOException, ConfigNotFoundException { final StandardWorkspace workspace = new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withCustomerId(UUID.randomUUID()); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(workspace); final TrackingIdentity actual = TrackingClientSingleton.getTrackingIdentity(configRepository, AIRBYTE_VERSION, WORKSPACE_ID); final TrackingIdentity expected = new TrackingIdentity(AIRBYTE_VERSION, workspace.getCustomerId(), null, null, null, null); @@ -115,7 +115,7 @@ void testGetTrackingIdentityNonAnonymous() throws JsonValidationException, IOExc .withSecurityUpdates(true) .withDefaultGeography(Geography.AUTO); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(workspace); final TrackingIdentity actual = TrackingClientSingleton.getTrackingIdentity(configRepository, AIRBYTE_VERSION, WORKSPACE_ID); final TrackingIdentity expected = new TrackingIdentity(AIRBYTE_VERSION, workspace.getCustomerId(), workspace.getEmail(), false, true, true); @@ -134,7 +134,7 @@ void testGetTrackingIdentityAnonymous() throws JsonValidationException, IOExcept .withSecurityUpdates(true) .withDefaultGeography(Geography.AUTO); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(workspace); final TrackingIdentity actual = TrackingClientSingleton.getTrackingIdentity(configRepository, AIRBYTE_VERSION, WORKSPACE_ID); final TrackingIdentity expected = new TrackingIdentity(AIRBYTE_VERSION, workspace.getCustomerId(), null, true, true, true); diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java index 59402fea95b8..1b875c358e68 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java @@ -289,7 +289,9 @@ private static void createWorkspaceIfNoneExists(final ConfigRepository configRep .withDisplaySetupWizard(true) .withTombstone(false) .withDefaultGeography(Geography.AUTO); - configRepository.writeStandardWorkspace(workspace); + // NOTE: it's safe to use the NoSecrets version since we know that the user hasn't supplied any + // secrets yet. + configRepository.writeStandardWorkspaceNoSecrets(workspace); } private static void assertNonBreakingMigration(final JobPersistence jobPersistence, final AirbyteVersion airbyteVersion) diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index d05940ba056f..976e3d518531 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -217,7 +217,7 @@ void testBootloaderAppRunSecretMigration() throws Exception { final ObjectMapper mapper = new ObjectMapper(); final UUID workspaceId = UUID.randomUUID(); - configRepository.writeStandardWorkspace(new StandardWorkspace() + configRepository.writeStandardWorkspaceNoSecrets(new StandardWorkspace() .withWorkspaceId(workspaceId) .withName("wName") .withSlug("wSlug") diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index c44d04de025a..270aaf4710f2 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -119,7 +119,7 @@ public boolean healthCheck() { return true; } - public StandardWorkspace getStandardWorkspace(final UUID workspaceId, final boolean includeTombstone) + public StandardWorkspace getStandardWorkspaceNoSecrets(final UUID workspaceId, final boolean includeTombstone) throws JsonValidationException, IOException, ConfigNotFoundException { final StandardWorkspace workspace = persistence.getConfig(ConfigSchema.STANDARD_WORKSPACE, workspaceId.toString(), StandardWorkspace.class); @@ -166,12 +166,21 @@ public List listStandardWorkspaces(final boolean includeTombs return workspaces; } - public void writeStandardWorkspace(final StandardWorkspace workspace) throws JsonValidationException, IOException { + /** + * MUST NOT ACCEPT SECRETS - Should only be called from { @link SecretsRepositoryWriter } + * + * Write a StandardWorkspace to the database. + * + * @param workspace - The configuration of the workspace + * @throws JsonValidationException - throws is the workspace is invalid + * @throws IOException - you never know when you IO + */ + public void writeStandardWorkspaceNoSecrets(final StandardWorkspace workspace) throws JsonValidationException, IOException { persistence.writeConfig(ConfigSchema.STANDARD_WORKSPACE, workspace.getWorkspaceId().toString(), workspace); } public void setFeedback(final UUID workflowId) throws JsonValidationException, ConfigNotFoundException, IOException { - final StandardWorkspace workspace = getStandardWorkspace(workflowId, false); + final StandardWorkspace workspace = getStandardWorkspaceNoSecrets(workflowId, false); workspace.setFeedbackDone(true); @@ -209,7 +218,7 @@ public StandardWorkspace getStandardWorkspaceFromConnection(final UUID connectio try { final StandardSync sync = getStandardSync(connectionId); final SourceConnection source = getSourceConnection(sync.getSourceId()); - return getStandardWorkspace(source.getWorkspaceId(), isTombstone); + return getStandardWorkspaceNoSecrets(source.getWorkspaceId(), isTombstone); } catch (final Exception e) { throw new RuntimeException(e); } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java index e1190eba6c7f..898ec634a663 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java @@ -10,6 +10,7 @@ import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; +import io.airbyte.config.StandardWorkspace; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import io.airbyte.validation.json.JsonValidationException; @@ -107,4 +108,11 @@ public WorkspaceServiceAccount getWorkspaceServiceAccountWithSecrets(final UUID return Jsons.clone(workspaceServiceAccount).withJsonCredential(jsonCredential).withHmacKey(hmacKey); } + public StandardWorkspace getWorkspaceWithSecrets(final UUID workspaceId, final boolean includeTombstone) + throws JsonValidationException, ConfigNotFoundException, IOException { + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, includeTombstone); + // TODO: hydrate any secrets once they're introduced. + return workspace; + } + } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java index d0d0fa4f5ee9..b6f8a19423b7 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java @@ -13,6 +13,7 @@ import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; +import io.airbyte.config.StandardWorkspace; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.config.persistence.split_secrets.SecretCoordinateToPayload; import io.airbyte.config.persistence.split_secrets.SecretPersistence; @@ -321,4 +322,10 @@ public Optional getOptionalWorkspaceServiceAccount(fina } } + public void writeWorkspace(final StandardWorkspace workspace) + throws JsonValidationException, IOException { + // TODO(msiega): split secrets once they're introduced. + configRepository.writeStandardWorkspaceNoSecrets(workspace); + } + } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index c1fe7f1aebaf..3b3a626ed4e2 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -102,7 +102,7 @@ void setup() throws IOException, JsonValidationException, SQLException, Database final DevDatabaseMigrator devDatabaseMigrator = new DevDatabaseMigrator(configsDatabaseMigrator); MigrationDevHelper.runLastMigration(devDatabaseMigrator); for (final StandardWorkspace workspace : MockData.standardWorkspaces()) { - configRepository.writeStandardWorkspace(workspace); + configRepository.writeStandardWorkspaceNoSecrets(workspace); } for (final StandardSourceDefinition sourceDefinition : MockData.standardSourceDefinitions()) { configRepository.writeStandardSourceDefinition(sourceDefinition); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java index b2fa1ea2ca37..d4507005049c 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java @@ -88,7 +88,7 @@ void testWorkspaceWithTrueTombstone() throws ConfigNotFoundException, IOExceptio void assertReturnsWorkspace(final StandardWorkspace workspace) throws ConfigNotFoundException, IOException, JsonValidationException { when(configPersistence.getConfig(ConfigSchema.STANDARD_WORKSPACE, WORKSPACE_ID.toString(), StandardWorkspace.class)).thenReturn(workspace); - assertEquals(workspace, configRepository.getStandardWorkspace(WORKSPACE_ID, true)); + assertEquals(workspace, configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)); } @ParameterizedTest @@ -111,11 +111,11 @@ void testWorkspaceByConnectionId(final boolean isTombstone) throws ConfigNotFoun .getSourceConnection(sourceId); doReturn(mWorkflow) .when(configRepository) - .getStandardWorkspace(WORKSPACE_ID, isTombstone); + .getStandardWorkspaceNoSecrets(WORKSPACE_ID, isTombstone); configRepository.getStandardWorkspaceFromConnection(connectionId, isTombstone); - verify(configRepository).getStandardWorkspace(WORKSPACE_ID, isTombstone); + verify(configRepository).getStandardWorkspaceNoSecrets(WORKSPACE_ID, isTombstone); } @Test @@ -452,7 +452,7 @@ void testUpdateFeedback() throws JsonValidationException, ConfigNotFoundExceptio final StandardWorkspace workspace = new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withTombstone(false); doReturn(workspace) .when(configRepository) - .getStandardWorkspace(WORKSPACE_ID, false); + .getStandardWorkspaceNoSecrets(WORKSPACE_ID, false); configRepository.setFeedback(WORKSPACE_ID); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java index 925573317a97..3901488e1454 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java @@ -558,7 +558,7 @@ private void setupTestData() throws JsonValidationException, IOException { final DestinationConnection destinationConnection = MockData.destinationConnections().get(0); final StandardSync sync = MockData.standardSyncs().get(0); - configRepository.writeStandardWorkspace(workspace); + configRepository.writeStandardWorkspaceNoSecrets(workspace); configRepository.writeStandardSourceDefinition(sourceDefinition); configRepository.writeSourceConnectionNoSecrets(sourceConnection); configRepository.writeStandardDestinationDefinition(destinationDefinition); diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobNotifier.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobNotifier.java index 9ef323fb55fb..b2b9a3eae1ef 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobNotifier.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobNotifier.java @@ -58,7 +58,7 @@ public JobNotifier(final WebUrlHelper webUrlHelper, private void notifyJob(final String reason, final String action, final Job job) { try { final UUID workspaceId = workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(job.getId()); - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, true); notifyJob(reason, action, job, workspaceId, workspace, workspace.getNotifications()); } catch (final Exception e) { LOGGER.error("Unable to read configuration:", e); @@ -146,7 +146,7 @@ public void notifyJobByEmail(final String reason, final String action, final Job emailNotification.setNotificationType(NotificationType.CUSTOMERIO); try { final UUID workspaceId = workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(job.getId()); - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, true); notifyJob(reason, action, job, workspaceId, workspace, Collections.singletonList(emailNotification)); } catch (final Exception e) { LOGGER.error("Unable to read configuration:", e); diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java index 553e8d9d597c..e5e551e1fdbe 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java @@ -137,7 +137,7 @@ public void reportSourceCheckJobFailure(final UUID sourceDefinitionId, final FailureReason failureReason, final ConnectorJobReportingContext jobContext) throws JsonValidationException, ConfigNotFoundException, IOException { - final StandardWorkspace workspace = workspaceId != null ? configRepository.getStandardWorkspace(workspaceId, true) : null; + final StandardWorkspace workspace = workspaceId != null ? configRepository.getStandardWorkspaceNoSecrets(workspaceId, true) : null; final StandardSourceDefinition sourceDefinition = configRepository.getStandardSourceDefinition(sourceDefinitionId); final Map metadata = MoreMaps.merge( getSourceMetadata(sourceDefinition), @@ -158,7 +158,7 @@ public void reportDestinationCheckJobFailure(final UUID destinationDefinitionId, final FailureReason failureReason, final ConnectorJobReportingContext jobContext) throws JsonValidationException, ConfigNotFoundException, IOException { - final StandardWorkspace workspace = workspaceId != null ? configRepository.getStandardWorkspace(workspaceId, true) : null; + final StandardWorkspace workspace = workspaceId != null ? configRepository.getStandardWorkspaceNoSecrets(workspaceId, true) : null; final StandardDestinationDefinition destinationDefinition = configRepository.getStandardDestinationDefinition(destinationDefinitionId); final Map metadata = MoreMaps.merge( getDestinationMetadata(destinationDefinition), @@ -178,7 +178,7 @@ public void reportDiscoverJobFailure(final UUID sourceDefinitionId, final FailureReason failureReason, final ConnectorJobReportingContext jobContext) throws JsonValidationException, ConfigNotFoundException, IOException { - final StandardWorkspace workspace = workspaceId != null ? configRepository.getStandardWorkspace(workspaceId, true) : null; + final StandardWorkspace workspace = workspaceId != null ? configRepository.getStandardWorkspaceNoSecrets(workspaceId, true) : null; final StandardSourceDefinition sourceDefinition = configRepository.getStandardSourceDefinition(sourceDefinitionId); final Map metadata = MoreMaps.merge( getSourceMetadata(sourceDefinition), diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/JobTracker.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/JobTracker.java index adcc95c75e29..86c0b154891c 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/JobTracker.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/JobTracker.java @@ -397,7 +397,7 @@ private void track(final UUID workspaceId, final Map metadata) // unfortunate but in the case of jobs that cannot be linked to a workspace there not a sensible way // track it. if (workspaceId != null) { - final StandardWorkspace standardWorkspace = configRepository.getStandardWorkspace(workspaceId, true); + final StandardWorkspace standardWorkspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, true); if (standardWorkspace != null && standardWorkspace.getName() != null) { final Map standardTrackingMetadata = Map.of( "workspace_id", workspaceId, diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/JobNotifierTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/JobNotifierTest.java index 8bd3c41249cc..792535404a02 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/JobNotifierTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/JobNotifierTest.java @@ -82,7 +82,7 @@ void testFailJob() throws IOException, InterruptedException, JsonValidationExcep when(configRepository.getDestinationDefinitionFromConnection(any())).thenReturn(destinationDefinition); when(configRepository.getStandardSourceDefinition(any())).thenReturn(sourceDefinition); when(configRepository.getStandardDestinationDefinition(any())).thenReturn(destinationDefinition); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(getWorkspace()); + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(getWorkspace()); when(workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(job.getId())).thenReturn(WORKSPACE_ID); when(notificationClient.notifyJobFailure(anyString(), anyString(), anyString(), anyString(), anyLong())).thenReturn(true); diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java index 642ab95d04e5..332c2b4bde75 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java @@ -259,7 +259,7 @@ void testReportSourceCheckJobFailure() throws JsonValidationException, ConfigNot final StandardWorkspace mWorkspace = Mockito.mock(StandardWorkspace.class); Mockito.when(mWorkspace.getWorkspaceId()).thenReturn(WORKSPACE_ID); - Mockito.when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(mWorkspace); + Mockito.when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(mWorkspace); jobErrorReporter.reportSourceCheckJobFailure(SOURCE_DEFINITION_ID, WORKSPACE_ID, failureReason, jobContext); @@ -337,7 +337,7 @@ void testReportDestinationCheckJobFailure() throws JsonValidationException, Conf final StandardWorkspace mWorkspace = Mockito.mock(StandardWorkspace.class); Mockito.when(mWorkspace.getWorkspaceId()).thenReturn(WORKSPACE_ID); - Mockito.when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(mWorkspace); + Mockito.when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(mWorkspace); jobErrorReporter.reportDestinationCheckJobFailure(DESTINATION_DEFINITION_ID, WORKSPACE_ID, failureReason, jobContext); @@ -415,7 +415,7 @@ void testReportDiscoverJobFailure() throws JsonValidationException, ConfigNotFou final StandardWorkspace mWorkspace = Mockito.mock(StandardWorkspace.class); Mockito.when(mWorkspace.getWorkspaceId()).thenReturn(WORKSPACE_ID); - Mockito.when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)).thenReturn(mWorkspace); + Mockito.when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)).thenReturn(mWorkspace); jobErrorReporter.reportDiscoverJobFailure(SOURCE_DEFINITION_ID, WORKSPACE_ID, failureReason, jobContext); diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java index f10a0ab5c065..fa6eb014a707 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java @@ -204,7 +204,7 @@ void testTrackCheckConnectionSource() throws ConfigNotFoundException, IOExceptio .withName(SOURCE_DEF_NAME) .withDockerRepository(CONNECTOR_REPOSITORY) .withDockerImageTag(CONNECTOR_VERSION)); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)) + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)) .thenReturn(new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withName(WORKSPACE_NAME)); assertCheckConnCorrectMessageForEachState( (jobState, output) -> jobTracker.trackCheckConnectionSource(JOB_ID, UUID1, WORKSPACE_ID, jobState, output), @@ -234,7 +234,7 @@ void testTrackCheckConnectionDestination() throws ConfigNotFoundException, IOExc .withName(DESTINATION_DEF_NAME) .withDockerRepository(CONNECTOR_REPOSITORY) .withDockerImageTag(CONNECTOR_VERSION)); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)) + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)) .thenReturn(new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withName(WORKSPACE_NAME)); assertCheckConnCorrectMessageForEachState( (jobState, output) -> jobTracker.trackCheckConnectionDestination(JOB_ID, UUID2, WORKSPACE_ID, jobState, output), @@ -264,7 +264,7 @@ void testTrackDiscover() throws ConfigNotFoundException, IOException, JsonValida .withName(SOURCE_DEF_NAME) .withDockerRepository(CONNECTOR_REPOSITORY) .withDockerImageTag(CONNECTOR_VERSION)); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)) + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)) .thenReturn(new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withName(WORKSPACE_NAME)); assertCorrectMessageForEachState((jobState) -> jobTracker.trackDiscover(JOB_ID, UUID1, WORKSPACE_ID, jobState), metadata); assertCorrectMessageForEachState((jobState) -> jobTracker.trackDiscover(JOB_ID, UUID1, null, jobState), metadata); @@ -285,7 +285,7 @@ void testTrackSyncForInternalFailure() throws JsonValidationException, ConfigNot when(workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(jobId)).thenReturn(WORKSPACE_ID); when(configRepository.getStandardSync(CONNECTION_ID)) .thenReturn(new StandardSync().withConnectionId(CONNECTION_ID).withManual(true).withCatalog(CATALOG)); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)) + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)) .thenReturn(new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withName(WORKSPACE_NAME)); when(configRepository.getStandardSync(CONNECTION_ID)) .thenReturn(new StandardSync().withConnectionId(CONNECTION_ID).withManual(false).withCatalog(CATALOG) @@ -369,7 +369,7 @@ void testAsynchronous(final ConfigType configType, final Map add when(configRepository.getStandardSync(CONNECTION_ID)) .thenReturn(new StandardSync().withConnectionId(CONNECTION_ID).withManual(true).withCatalog(CATALOG)); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)) + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)) .thenReturn(new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withName(WORKSPACE_NAME)); final Map manualMetadata = MoreMaps.merge( metadata, @@ -489,7 +489,7 @@ void testAsynchronousAttempt(final ConfigType configType, final Job job, final M when(configRepository.getStandardSync(CONNECTION_ID)) .thenReturn(new StandardSync().withConnectionId(CONNECTION_ID).withManual(true).withCatalog(CATALOG)); when(workspaceHelper.getWorkspaceForJobIdIgnoreExceptions(LONG_JOB_ID)).thenReturn(WORKSPACE_ID); - when(configRepository.getStandardWorkspace(WORKSPACE_ID, true)) + when(configRepository.getStandardWorkspaceNoSecrets(WORKSPACE_ID, true)) .thenReturn(new StandardWorkspace().withWorkspaceId(WORKSPACE_ID).withName(WORKSPACE_NAME)); final Map manualMetadata = MoreMaps.merge( ATTEMPT_METADATA, diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index 92236e6bc751..ae575aaa2572 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -225,7 +225,12 @@ public ConfigurationApi(final ConfigRepository configRepository, schemaValidator, connectionsHandler); destinationDefinitionsHandler = new DestinationDefinitionsHandler(configRepository, synchronousSchedulerClient, destinationHandler); - workspacesHandler = new WorkspacesHandler(configRepository, connectionsHandler, destinationHandler, sourceHandler); + workspacesHandler = new WorkspacesHandler( + configRepository, + secretsRepositoryWriter, + connectionsHandler, + destinationHandler, + sourceHandler); jobHistoryHandler = new JobHistoryHandler(jobPersistence, workerEnvironment, logConfigs, connectionsHandler, sourceHandler, sourceDefinitionsHandler, destinationHandler, destinationDefinitionsHandler, airbyteVersion); oAuthHandler = new OAuthHandler(configRepository, httpClient, trackingClient); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java index 6b7e21c724ec..6472de841091 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java @@ -189,7 +189,7 @@ private Geography getGeographyFromConnectionCreateOrWorkspace(final ConnectionCr // connectionCreate didn't specify a geography, so use the workspace default geography if one exists final UUID workspaceId = workspaceHelper.getWorkspaceForSourceId(connectionCreate.getSourceId()); - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, true); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, true); if (workspace.getDefaultGeography() != null) { return workspace.getDefaultGeography(); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java index 47605c5200e5..71ece0d59250 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java @@ -27,6 +27,7 @@ import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.notification.NotificationClient; import io.airbyte.server.converters.NotificationConverter; import io.airbyte.server.errors.IdNotFoundKnownException; @@ -46,6 +47,7 @@ public class WorkspacesHandler { private static final Logger LOGGER = LoggerFactory.getLogger(WorkspacesHandler.class); private final ConfigRepository configRepository; + private final SecretsRepositoryWriter secretsRepositoryWriter; private final ConnectionsHandler connectionsHandler; private final DestinationHandler destinationHandler; private final SourceHandler sourceHandler; @@ -53,18 +55,21 @@ public class WorkspacesHandler { private final Slugify slugify; public WorkspacesHandler(final ConfigRepository configRepository, + final SecretsRepositoryWriter secretsRepositoryWriter, final ConnectionsHandler connectionsHandler, final DestinationHandler destinationHandler, final SourceHandler sourceHandler) { - this(configRepository, connectionsHandler, destinationHandler, sourceHandler, UUID::randomUUID); + this(configRepository, secretsRepositoryWriter, connectionsHandler, destinationHandler, sourceHandler, UUID::randomUUID); } public WorkspacesHandler(final ConfigRepository configRepository, + final SecretsRepositoryWriter secretsRepositoryWriter, final ConnectionsHandler connectionsHandler, final DestinationHandler destinationHandler, final SourceHandler sourceHandler, final Supplier uuidSupplier) { this.configRepository = configRepository; + this.secretsRepositoryWriter = secretsRepositoryWriter; this.connectionsHandler = connectionsHandler; this.destinationHandler = destinationHandler; this.sourceHandler = sourceHandler; @@ -104,15 +109,13 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate) workspace.withEmail(email); } - configRepository.writeStandardWorkspace(workspace); - - return buildWorkspaceRead(workspace); + return persistStandardWorkspace(workspace); } public void deleteWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) throws JsonValidationException, IOException, ConfigNotFoundException { // get existing implementation - final StandardWorkspace persistedWorkspace = configRepository.getStandardWorkspace(workspaceIdRequestBody.getWorkspaceId(), false); + final StandardWorkspace persistedWorkspace = configRepository.getStandardWorkspaceNoSecrets(workspaceIdRequestBody.getWorkspaceId(), false); // disable all connections associated with this workspace for (final ConnectionRead connectionRead : connectionsHandler.listConnectionsForWorkspace(workspaceIdRequestBody).getConnections()) { @@ -130,7 +133,7 @@ public void deleteWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) } persistedWorkspace.withTombstone(true); - configRepository.writeStandardWorkspace(persistedWorkspace); + persistStandardWorkspace(persistedWorkspace); } public WorkspaceReadList listWorkspaces() throws JsonValidationException, IOException { @@ -143,7 +146,7 @@ public WorkspaceReadList listWorkspaces() throws JsonValidationException, IOExce public WorkspaceRead getWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) throws JsonValidationException, IOException, ConfigNotFoundException { final UUID workspaceId = workspaceIdRequestBody.getWorkspaceId(); - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, false); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, false); return buildWorkspaceRead(workspace); } @@ -161,7 +164,7 @@ public WorkspaceRead updateWorkspace(final WorkspaceUpdate workspacePatch) throw LOGGER.debug("Starting updateWorkspace for workspaceId {}...", workspaceId); LOGGER.debug("Incoming workspacePatch: {}", workspacePatch); - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, false); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, false); LOGGER.debug("Initial workspace: {}", workspace); validateWorkspacePatch(workspace, workspacePatch); @@ -171,7 +174,8 @@ public WorkspaceRead updateWorkspace(final WorkspaceUpdate workspacePatch) throw applyPatchToStandardWorkspace(workspace, workspacePatch); LOGGER.debug("Patched Workspace before persisting: {}", workspace); - configRepository.writeStandardWorkspace(workspace); + + persistStandardWorkspace(workspace); // after updating email or tracking info, we need to re-identify the instance. TrackingClientSingleton.get().identify(workspaceId); @@ -183,13 +187,13 @@ public WorkspaceRead updateWorkspaceName(final WorkspaceUpdateName workspaceUpda throws JsonValidationException, ConfigNotFoundException, IOException { final UUID workspaceId = workspaceUpdateName.getWorkspaceId(); - final StandardWorkspace persistedWorkspace = configRepository.getStandardWorkspace(workspaceId, false); + final StandardWorkspace persistedWorkspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, false); persistedWorkspace .withName(workspaceUpdateName.getName()) .withSlug(generateUniqueSlug(workspaceUpdateName.getName())); - configRepository.writeStandardWorkspace(persistedWorkspace); + persistStandardWorkspace(persistedWorkspace); return buildWorkspaceReadFromId(workspaceId); } @@ -217,7 +221,7 @@ public void setFeedbackDone(final WorkspaceGiveFeedback workspaceGiveFeedback) } private WorkspaceRead buildWorkspaceReadFromId(final UUID workspaceId) throws ConfigNotFoundException, IOException, JsonValidationException { - final StandardWorkspace workspace = configRepository.getStandardWorkspace(workspaceId, false); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, false); return buildWorkspaceRead(workspace); } @@ -293,4 +297,9 @@ private void applyPatchToStandardWorkspace(final StandardWorkspace workspace, fi } } + private WorkspaceRead persistStandardWorkspace(final StandardWorkspace workspace) throws JsonValidationException, IOException { + secretsRepositoryWriter.writeWorkspace(workspace); + return buildWorkspaceRead(workspace); + } + } diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java index 5bd3d2648caf..b3bb02c8b986 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java @@ -223,7 +223,7 @@ void testCreateConnection() throws JsonValidationException, ConfigNotFoundExcept final StandardWorkspace workspace = new StandardWorkspace() .withWorkspaceId(workspaceId) .withDefaultGeography(Geography.EU); - when(configRepository.getStandardWorkspace(workspaceId, true)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(workspaceId, true)).thenReturn(workspace); final ConnectionCreate connectionCreate = new ConnectionCreate() .sourceId(standardSync.getSourceId()) @@ -290,7 +290,7 @@ void testCreateConnectionUsesDefaultGeographyFromWorkspace() throws JsonValidati final StandardWorkspace workspace = new StandardWorkspace() .withWorkspaceId(workspaceId) .withDefaultGeography(Geography.EU); - when(configRepository.getStandardWorkspace(workspaceId, true)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(workspaceId, true)).thenReturn(workspace); // the expected read and verified write is generated from the standardSync, so set this to EU as // well diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java index 31879030da0d..d8a431823e50 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java @@ -37,6 +37,7 @@ import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.server.converters.NotificationConverter; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; @@ -54,6 +55,7 @@ class WorkspacesHandlerTest { public static final String FAILURE_NOTIFICATION_WEBHOOK = "http://airbyte.notifications/failure"; private ConfigRepository configRepository; + private SecretsRepositoryWriter secretsRepositoryWriter; private ConnectionsHandler connectionsHandler; private DestinationHandler destinationHandler; private SourceHandler sourceHandler; @@ -74,12 +76,14 @@ class WorkspacesHandlerTest { @BeforeEach void setUp() { configRepository = mock(ConfigRepository.class); + secretsRepositoryWriter = new SecretsRepositoryWriter(configRepository, Optional.empty(), Optional.empty()); connectionsHandler = mock(ConnectionsHandler.class); destinationHandler = mock(DestinationHandler.class); sourceHandler = mock(SourceHandler.class); uuidSupplier = mock(Supplier.class); workspace = generateWorkspace(); - workspacesHandler = new WorkspacesHandler(configRepository, connectionsHandler, destinationHandler, sourceHandler, uuidSupplier); + workspacesHandler = new WorkspacesHandler(configRepository, secretsRepositoryWriter, connectionsHandler, + destinationHandler, sourceHandler, uuidSupplier); } private StandardWorkspace generateWorkspace() { @@ -120,7 +124,7 @@ void testCreateWorkspace() throws JsonValidationException, IOException { final UUID uuid = UUID.randomUUID(); when(uuidSupplier.get()).thenReturn(uuid); - configRepository.writeStandardWorkspace(workspace); + configRepository.writeStandardWorkspaceNoSecrets(workspace); final WorkspaceCreate workspaceCreate = new WorkspaceCreate() .name("new workspace") @@ -159,7 +163,7 @@ void testCreateWorkspaceDuplicateSlug() throws JsonValidationException, IOExcept final UUID uuid = UUID.randomUUID(); when(uuidSupplier.get()).thenReturn(uuid); - configRepository.writeStandardWorkspace(workspace); + configRepository.writeStandardWorkspaceNoSecrets(workspace); final WorkspaceCreate workspaceCreate = new WorkspaceCreate() .name(workspace.getName()) @@ -204,7 +208,7 @@ void testDeleteWorkspace() throws JsonValidationException, ConfigNotFoundExcepti final DestinationRead destination = new DestinationRead(); final SourceRead source = new SourceRead(); - when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)).thenReturn(workspace); when(configRepository.listStandardWorkspaces(false)).thenReturn(Collections.singletonList(workspace)); @@ -266,7 +270,7 @@ void testListWorkspaces() throws JsonValidationException, IOException { @Test void testGetWorkspace() throws JsonValidationException, ConfigNotFoundException, IOException { - when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)).thenReturn(workspace); + when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)).thenReturn(workspace); final WorkspaceIdRequestBody workspaceIdRequestBody = new WorkspaceIdRequestBody().workspaceId(workspace.getWorkspaceId()); @@ -340,7 +344,7 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .withNotifications(List.of(expectedNotification)) .withDefaultGeography(Geography.US); - when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)) + when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)) .thenReturn(workspace) .thenReturn(expectedWorkspace); @@ -362,7 +366,7 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .notifications(List.of(expectedNotificationRead)) .defaultGeography(GEOGRAPHY_US); - verify(configRepository).writeStandardWorkspace(expectedWorkspace); + verify(configRepository).writeStandardWorkspaceNoSecrets(expectedWorkspace); assertEquals(expectedWorkspaceRead, actualWorkspaceRead); } @@ -389,7 +393,7 @@ void testUpdateWorkspaceNoNameUpdate() throws JsonValidationException, ConfigNot .withNotifications(workspace.getNotifications()) .withDefaultGeography(Geography.AUTO); - when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)) + when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)) .thenReturn(workspace) .thenReturn(expectedWorkspace); @@ -409,7 +413,7 @@ void testUpdateWorkspaceNoNameUpdate() throws JsonValidationException, ConfigNot .notifications(List.of(generateApiNotification())) .defaultGeography(GEOGRAPHY_AUTO); - verify(configRepository).writeStandardWorkspace(expectedWorkspace); + verify(configRepository).writeStandardWorkspaceNoSecrets(expectedWorkspace); assertEquals(expectedWorkspaceRead, actualWorkspaceRead); } @@ -424,7 +428,7 @@ void testWorkspacePatchUpdate() throws JsonValidationException, ConfigNotFoundEx .email(EXPECTED_NEW_EMAIL); final StandardWorkspace expectedWorkspace = Jsons.clone(workspace).withEmail(EXPECTED_NEW_EMAIL).withAnonymousDataCollection(true); - when(configRepository.getStandardWorkspace(workspace.getWorkspaceId(), false)) + when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)) .thenReturn(workspace) .thenReturn(expectedWorkspace); // The same as the original workspace, with only the email and data collection flags changed. @@ -443,7 +447,7 @@ void testWorkspacePatchUpdate() throws JsonValidationException, ConfigNotFoundEx .defaultGeography(GEOGRAPHY_AUTO); final WorkspaceRead actualWorkspaceRead = workspacesHandler.updateWorkspace(workspaceUpdate); - verify(configRepository).writeStandardWorkspace(expectedWorkspace); + verify(configRepository).writeStandardWorkspaceNoSecrets(expectedWorkspace); assertEquals(expectedWorkspaceRead, actualWorkspaceRead); } From 6a8b053fb5873f8f9b3bf3cac48af8bd306793d6 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Wed, 12 Oct 2022 17:46:24 +0300 Subject: [PATCH 062/498] Use locally-installed qemu rather than docker-installed version (2) (#17889) Signed-off-by: Sergey Chvalyuk --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-faker/Dockerfile | 2 +- docs/integrations/sources/faker.md | 1 + tools/integrations/manage.sh | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index f8d0ebadfe89..c875770b9b59 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -295,7 +295,7 @@ - name: Faker sourceDefinitionId: dfd88b22-b603-4c3d-aad7-3701784586b1 dockerRepository: airbyte/source-faker - dockerImageTag: 0.1.7 + dockerImageTag: 0.1.8 documentationUrl: https://docs.airbyte.com/integrations/sources/faker sourceType: api releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 9bafdacffd9e..742ade1acb11 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2948,7 +2948,7 @@ oauthFlowInitParameters: [] oauthFlowOutputParameters: - - "access_token" -- dockerImage: "airbyte/source-faker:0.1.7" +- dockerImage: "airbyte/source-faker:0.1.8" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/faker" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-faker/Dockerfile b/airbyte-integrations/connectors/source-faker/Dockerfile index 05fea6d1438c..8691ac121aa0 100644 --- a/airbyte-integrations/connectors/source-faker/Dockerfile +++ b/airbyte-integrations/connectors/source-faker/Dockerfile @@ -34,5 +34,5 @@ COPY source_faker ./source_faker ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.7 +LABEL io.airbyte.version=0.1.8 LABEL io.airbyte.name=airbyte/source-faker diff --git a/docs/integrations/sources/faker.md b/docs/integrations/sources/faker.md index 5afaaf181154..020ca88deeac 100644 --- a/docs/integrations/sources/faker.md +++ b/docs/integrations/sources/faker.md @@ -41,6 +41,7 @@ N/A | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :-------------------------------------------------------- | +| 0.1.8 | 2022-10-12 | [17889](https://github.com/airbytehq/airbyte/pull/17889) | Bump to test publish command (2) | | 0.1.7 | 2022-10-11 | [17848](https://github.com/airbytehq/airbyte/pull/17848) | Bump to test publish command | | 0.1.6 | 2022-09-07 | [16418](https://github.com/airbytehq/airbyte/pull/16418) | Log start of each stream | | 0.1.5 | 2022-06-10 | [13695](https://github.com/airbytehq/airbyte/pull/13695) | Emit timestamps in the proper ISO format | diff --git a/tools/integrations/manage.sh b/tools/integrations/manage.sh index bd74a75edf38..2215daede757 100755 --- a/tools/integrations/manage.sh +++ b/tools/integrations/manage.sh @@ -220,7 +220,7 @@ cmd_publish() { # Install docker emulators # TODO: Don't run this command on M1 macs locally (it won't work and isn't needed) - apt-get install -y qemu-user-static + apt-get update && apt-get install -y qemu-user-static # log into docker if test -z "${DOCKER_HUB_USERNAME}"; then From 1ddd4d7d7d77bfe5ade819622d700bb6b9f52f3a Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:33:19 +0200 Subject: [PATCH 063/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=8E=89=20Add=20opt?= =?UTF-8?q?ional=20invite=20user=20hint=20to=20connector=20create=20pages?= =?UTF-8?q?=20(#15799)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add InviteUsersModalService and migrate UserSettingsView to use it * Add InviteUsersHint component and show in create source/destination pages * Update InviteUsersHint to use Text component and spacing variables * Add experiments for showing invite user hint Move cloud settings paths to its own file * Update Invite hint to lazy load with suspense * Fix invite user modal experiment names and add unit test * Rename CloudInviteUsersHint component file * Add notification when users are invited * Fix copy and remove plural form of invite success dialog * Show invite users hint in connector create form page * Fix stylelint issue in InviteUsersHint * Fix access management path in InviteUsersHint * Fix button text in UserSettingsView * Fix linkToUsersPage type in experiments iface * Cleanup code * Cleanup scss path in InviteUsersHint.module * update InviteUsersHint layout to be consistent with or without button --- .../CloudInviteUsersHint.tsx | 15 ++++ .../components/CloudInviteUsersHint/index.ts | 1 + .../hooks/services/Experiment/experiments.ts | 2 + .../src/hooks/services/Notification/index.tsx | 1 + .../src/hooks/services/Notification/types.ts | 6 +- .../src/packages/cloud/locales/en.json | 7 +- .../users/InviteUsersModalService.tsx | 39 ++++++++++ .../views/settings/CloudSettingsPage.tsx | 13 +--- .../cloud/views/settings/routePaths.ts | 12 +++ .../InviteUsersHint.module.scss | 14 ++++ .../InviteUsersHint/InviteUsersHint.test.tsx | 77 +++++++++++++++++++ .../users/InviteUsersHint/InviteUsersHint.tsx | 67 ++++++++++++++++ .../views/users/InviteUsersHint/index.ts | 1 + .../views/users/InviteUsersHint/types.ts | 3 + .../InviteUsersModal/InviteUsersModal.tsx | 10 ++- .../UsersSettingsView/UsersSettingsView.tsx | 65 +++++++++------- .../CreationFormPage/CreationFormPage.tsx | 3 + .../CreateDestinationPage.tsx | 2 + .../CreateSourcePage/CreateSourcePage.tsx | 2 + 19 files changed, 298 insertions(+), 42 deletions(-) create mode 100644 airbyte-webapp/src/components/CloudInviteUsersHint/CloudInviteUsersHint.tsx create mode 100644 airbyte-webapp/src/components/CloudInviteUsersHint/index.ts create mode 100644 airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx create mode 100644 airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts create mode 100644 airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.module.scss create mode 100644 airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.test.tsx create mode 100644 airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.tsx create mode 100644 airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/index.ts create mode 100644 airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/types.ts diff --git a/airbyte-webapp/src/components/CloudInviteUsersHint/CloudInviteUsersHint.tsx b/airbyte-webapp/src/components/CloudInviteUsersHint/CloudInviteUsersHint.tsx new file mode 100644 index 000000000000..9f5b5b885c4a --- /dev/null +++ b/airbyte-webapp/src/components/CloudInviteUsersHint/CloudInviteUsersHint.tsx @@ -0,0 +1,15 @@ +import { lazy, Suspense } from "react"; + +import { InviteUsersHintProps } from "packages/cloud/views/users/InviteUsersHint/types"; +import { isCloudApp } from "utils/app"; + +const LazyInviteUsersHint = lazy(() => + import("packages/cloud/views/users/InviteUsersHint").then(({ InviteUsersHint }) => ({ default: InviteUsersHint })) +); + +export const CloudInviteUsersHint: React.VFC = (props) => + isCloudApp() ? ( + + + + ) : null; diff --git a/airbyte-webapp/src/components/CloudInviteUsersHint/index.ts b/airbyte-webapp/src/components/CloudInviteUsersHint/index.ts new file mode 100644 index 000000000000..bfe0be13e8d1 --- /dev/null +++ b/airbyte-webapp/src/components/CloudInviteUsersHint/index.ts @@ -0,0 +1 @@ +export * from "./CloudInviteUsersHint"; diff --git a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts index 1323d1a874f7..a06231626681 100644 --- a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts +++ b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts @@ -5,6 +5,8 @@ export interface Experiments { "onboarding.hideOnboarding": boolean; + "connector.inviteUsersHint.visible": boolean; + "connector.inviteUsersHint.linkToUsersPage": boolean; "connector.orderOverwrite": Record; "connector.frequentlyUsedDestinationIds": string[]; "connector.startWithDestinationId": string; diff --git a/airbyte-webapp/src/hooks/services/Notification/index.tsx b/airbyte-webapp/src/hooks/services/Notification/index.tsx index 07b31c844d5f..94de118c512a 100644 --- a/airbyte-webapp/src/hooks/services/Notification/index.tsx +++ b/airbyte-webapp/src/hooks/services/Notification/index.tsx @@ -1,4 +1,5 @@ import NotificationService, { useNotificationService } from "./NotificationService"; +export * from "./types"; export default NotificationService; export { NotificationService, useNotificationService }; diff --git a/airbyte-webapp/src/hooks/services/Notification/types.ts b/airbyte-webapp/src/hooks/services/Notification/types.ts index 23419bef17c0..5a136fd49df5 100644 --- a/airbyte-webapp/src/hooks/services/Notification/types.ts +++ b/airbyte-webapp/src/hooks/services/Notification/types.ts @@ -1,7 +1,9 @@ +import React from "react"; + export interface Notification { id: string | number; - title: string; - text?: string; + title: React.ReactNode; + text?: React.ReactNode; isError?: boolean; nonClosable?: boolean; onClose?: () => void; diff --git a/airbyte-webapp/src/packages/cloud/locales/en.json b/airbyte-webapp/src/packages/cloud/locales/en.json index e2703274dcde..2e762fb5820e 100644 --- a/airbyte-webapp/src/packages/cloud/locales/en.json +++ b/airbyte-webapp/src/packages/cloud/locales/en.json @@ -118,6 +118,8 @@ "settings.accessManagement.roleEditors": "Editors can edit connections", "settings.accessManagement.roleAdmin": "Admin can also manage users", + "addUsers.success.title": "Invitations sent!", + "credits.date": "Date", "credits.amount": "Credits", @@ -149,5 +151,8 @@ "verifyEmail.notification": "You successfully verified your email. Thank you.", - "webapp.cannotReachServer": "Cannot reach server." + "webapp.cannotReachServer": "Cannot reach server.", + + "inviteUsersHint.message": "Need help from a teammate to set up the {connector}?", + "inviteUsersHint.cta": "Invite users" } diff --git a/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx b/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx new file mode 100644 index 000000000000..46f2e3e3f7b2 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx @@ -0,0 +1,39 @@ +import { createContext, useContext, useMemo } from "react"; +import { useToggle } from "react-use"; + +import { InviteUsersModal } from "packages/cloud/views/users/InviteUsersModal"; + +interface InviteUsersModalServiceContext { + isInviteUsersModalOpen: boolean; + toggleInviteUsersModalOpen: (open?: boolean) => void; +} + +const inviteUsersModalServiceContext = createContext(null); +const { Provider } = inviteUsersModalServiceContext; + +export const useInviteUsersModalService = () => { + const ctx = useContext(inviteUsersModalServiceContext); + if (!ctx) { + throw new Error("useInviteUsersModalService should be use within InviteUsersModalServiceProvider"); + } + return ctx; +}; + +export const InviteUsersModalServiceProvider: React.FC = ({ children }) => { + const [isOpen, toggleIsOpen] = useToggle(false); + + const contextValue = useMemo( + () => ({ + isInviteUsersModalOpen: isOpen, + toggleInviteUsersModalOpen: toggleIsOpen, + }), + [isOpen, toggleIsOpen] + ); + + return ( + + {children} + {isOpen && } + + ); +}; diff --git a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx index 2ec80029d6f9..86b89add3811 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx @@ -12,19 +12,10 @@ import { } from "pages/SettingsPage/pages/ConnectorsPage"; // import ConfigurationsPage from "pages/SettingsPage/pages/ConfigurationsPage"; import NotificationPage from "pages/SettingsPage/pages/NotificationPage"; -import { PageConfig, SettingsRoute } from "pages/SettingsPage/SettingsPage"; +import { PageConfig } from "pages/SettingsPage/SettingsPage"; import { isOsanoActive, showOsanoDrawer } from "utils/dataPrivacy"; -const CloudSettingsRoutes = { - Configuration: SettingsRoute.Configuration, - Notifications: SettingsRoute.Notifications, - Account: SettingsRoute.Account, - Source: SettingsRoute.Source, - Destination: SettingsRoute.Destination, - - Workspace: "workspaces", - AccessManagement: "access-management", -} as const; +import { CloudSettingsRoutes } from "./routePaths"; export const CloudSettingsPage: React.FC = () => { // TODO: uncomment when supported in cloud diff --git a/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts b/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts new file mode 100644 index 000000000000..c82011c13398 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts @@ -0,0 +1,12 @@ +import { SettingsRoute } from "pages/SettingsPage/SettingsPage"; + +export const CloudSettingsRoutes = { + Configuration: SettingsRoute.Configuration, + Notifications: SettingsRoute.Notifications, + Account: SettingsRoute.Account, + Source: SettingsRoute.Source, + Destination: SettingsRoute.Destination, + + Workspace: "workspaces", + AccessManagement: "access-management", +} as const; diff --git a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.module.scss b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.module.scss new file mode 100644 index 000000000000..314275886b69 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.module.scss @@ -0,0 +1,14 @@ +@use "scss/variables"; + +.container { + text-align: center; + margin-top: variables.$spacing-lg; + + &.withLink { + padding: variables.$spacing-md; + } +} + +.ctaButton { + margin-left: variables.$spacing-sm; +} diff --git a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.test.tsx b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.test.tsx new file mode 100644 index 000000000000..1cef3cba4429 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.test.tsx @@ -0,0 +1,77 @@ +import { fireEvent, render } from "@testing-library/react"; +import React from "react"; +import { TestWrapper } from "test-utils/testutils"; + +import { Experiments } from "hooks/services/Experiment/experiments"; +import * as ExperimentService from "hooks/services/Experiment/ExperimentService"; +import { RoutePaths } from "pages/routePaths"; + +import { CloudSettingsRoutes } from "../../settings/routePaths"; + +const mockToggleInviteUsersModalOpen = jest.fn(); +jest.doMock("packages/cloud/services/users/InviteUsersModalService", () => ({ + InviteUsersModalServiceProvider: ({ children }: { children: React.ReactNode }): JSX.Element => <>{children}, + useInviteUsersModalService: () => ({ + toggleInviteUsersModalOpen: mockToggleInviteUsersModalOpen, + }), +})); + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { InviteUsersHint } = require("./InviteUsersHint"); + +const createUseExperimentMock = + (options: { visible?: boolean; linkToUsersPage?: boolean }) => (key: keyof Experiments) => { + switch (key) { + case "connector.inviteUsersHint.visible": + return options.visible ?? false; + case "connector.inviteUsersHint.linkToUsersPage": + return options.linkToUsersPage ?? false; + default: + throw new Error(`${key} is not mocked`); + } + }; + +describe("InviteUsersHint", () => { + beforeEach(() => { + mockToggleInviteUsersModalOpen.mockReset(); + }); + + it("does not render by default", () => { + const { queryByTestId } = render(, { wrapper: TestWrapper }); + expect(queryByTestId("inviteUsersHint")).not.toBeInTheDocument(); + }); + + it("renders when `connector.inviteUserHint.visible` is set to `true`", () => { + jest.spyOn(ExperimentService, "useExperiment").mockImplementation(createUseExperimentMock({ visible: true })); + + const { getByTestId } = render(, { wrapper: TestWrapper }); + const element = getByTestId("inviteUsersHint"); + expect(element).toBeInTheDocument(); + }); + + it("opens modal when clicking on CTA by default", () => { + jest.spyOn(ExperimentService, "useExperiment").mockImplementation(createUseExperimentMock({ visible: true })); + + const { getByTestId } = render(, { wrapper: TestWrapper }); + const element = getByTestId("inviteUsersHint-cta"); + + expect(element).not.toHaveAttribute("href"); + + fireEvent.click(element); + expect(mockToggleInviteUsersModalOpen).toHaveBeenCalledTimes(1); + }); + + it("opens link to access-management settings when clicking on CTA and `connector.inviteUsersHint.linkToUsersPage` is `true`", () => { + jest + .spyOn(ExperimentService, "useExperiment") + .mockImplementation(createUseExperimentMock({ visible: true, linkToUsersPage: true })); + + const { getByTestId } = render(, { wrapper: TestWrapper }); + const element = getByTestId("inviteUsersHint-cta"); + + expect(element).toHaveAttribute("href", `../${RoutePaths.Settings}/${CloudSettingsRoutes.AccessManagement}`); + + fireEvent.click(element); + expect(mockToggleInviteUsersModalOpen).not.toHaveBeenCalled(); + }); +}); diff --git a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.tsx b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.tsx new file mode 100644 index 000000000000..606f45481dc3 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/InviteUsersHint.tsx @@ -0,0 +1,67 @@ +import classNames from "classnames"; +import { FormattedMessage, useIntl } from "react-intl"; + +import { Button } from "components/ui/Button"; +import { Text } from "components/ui/Text"; + +import { useExperiment } from "hooks/services/Experiment"; +import { + InviteUsersModalServiceProvider, + useInviteUsersModalService, +} from "packages/cloud/services/users/InviteUsersModalService"; +import { CloudSettingsRoutes } from "packages/cloud/views/settings/routePaths"; +import { RoutePaths } from "pages/routePaths"; + +import styles from "./InviteUsersHint.module.scss"; +import { InviteUsersHintProps } from "./types"; + +const ACCESS_MANAGEMENT_PATH = `../${RoutePaths.Settings}/${CloudSettingsRoutes.AccessManagement}`; + +const InviteUsersHintContent: React.VFC = ({ connectorType }) => { + const { formatMessage } = useIntl(); + const { toggleInviteUsersModalOpen } = useInviteUsersModalService(); + const linkToUsersPage = useExperiment("connector.inviteUsersHint.linkToUsersPage", false); + + const inviteUsersCta = linkToUsersPage ? ( + + + + ) : ( + + ); + + return ( + + {" "} + {inviteUsersCta} + + ); +}; + +export const InviteUsersHint: React.VFC = (props) => { + const isVisible = useExperiment("connector.inviteUsersHint.visible", false); + + return isVisible ? ( + + + + ) : null; +}; diff --git a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/index.ts b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/index.ts new file mode 100644 index 000000000000..6bffca09a935 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/index.ts @@ -0,0 +1 @@ +export * from "./InviteUsersHint"; diff --git a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/types.ts b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/types.ts new file mode 100644 index 000000000000..2a80eac9d00c --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersHint/types.ts @@ -0,0 +1,3 @@ +export interface InviteUsersHintProps { + connectorType: "source" | "destination"; +} diff --git a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersModal/InviteUsersModal.tsx b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersModal/InviteUsersModal.tsx index a958a032b432..eb78eef6c241 100644 --- a/airbyte-webapp/src/packages/cloud/views/users/InviteUsersModal/InviteUsersModal.tsx +++ b/airbyte-webapp/src/packages/cloud/views/users/InviteUsersModal/InviteUsersModal.tsx @@ -13,6 +13,7 @@ import { DropDown } from "components/ui/DropDown"; import { Input } from "components/ui/Input"; import { Modal } from "components/ui/Modal"; +import { useNotificationService } from "hooks/services/Notification"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; import { useUserHook } from "packages/cloud/services/users/UseUserHook"; @@ -59,6 +60,7 @@ export const InviteUsersModal: React.FC<{ const { formatMessage } = useIntl(); const { workspaceId } = useCurrentWorkspace(); const { inviteUserLogic } = useUserHook(); + const { registerNotification } = useNotificationService(); const { mutateAsync: invite } = inviteUserLogic; const isRoleVisible = false; // Temporarily hiding roles because there's only 'Admin' in cloud. @@ -81,7 +83,13 @@ export const InviteUsersModal: React.FC<{ await invite( { users: values.users, workspaceId }, { - onSuccess: () => props.onClose(), + onSuccess: () => { + registerNotification({ + title: formatMessage({ id: "addUsers.success.title" }), + id: "invite-users-success", + }); + props.onClose(); + }, } ); }} diff --git a/airbyte-webapp/src/packages/cloud/views/users/UsersSettingsView/UsersSettingsView.tsx b/airbyte-webapp/src/packages/cloud/views/users/UsersSettingsView/UsersSettingsView.tsx index 15f9ab29027d..c36f889e682c 100644 --- a/airbyte-webapp/src/packages/cloud/views/users/UsersSettingsView/UsersSettingsView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/users/UsersSettingsView/UsersSettingsView.tsx @@ -1,25 +1,27 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import React from "react"; +import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { CellProps } from "react-table"; -import { useToggle } from "react-use"; -import { H5 } from "components/base/Titles"; import { Button } from "components/ui/Button"; import { Table } from "components/ui/Table"; +import { Text } from "components/ui/Text"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; import { useConfirmationModalService } from "hooks/services/ConfirmationModal"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; import { User } from "packages/cloud/lib/domain/users"; import { useAuthService } from "packages/cloud/services/auth/AuthService"; +import { + InviteUsersModalServiceProvider, + useInviteUsersModalService, +} from "packages/cloud/services/users/InviteUsersModalService"; import { useListUsers, useUserHook } from "packages/cloud/services/users/UseUserHook"; -import { InviteUsersModal } from "packages/cloud/views/users/InviteUsersModal"; import styles from "./UsersSettingsView.module.scss"; -const RemoveUserSection: React.FC<{ workspaceId: string; email: string }> = ({ workspaceId, email }) => { +const RemoveUserSection: React.VFC<{ workspaceId: string; email: string }> = ({ workspaceId, email }) => { const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService(); const { removeUserLogic } = useUserHook(); const { isLoading, mutate: removeUser } = removeUserLogic; @@ -44,17 +46,32 @@ const RemoveUserSection: React.FC<{ workspaceId: string; email: string }> = ({ w ); }; -export const UsersSettingsView: React.FC = () => { - useTrackPage(PageTrackingCodes.SETTINGS_ACCESS_MANAGEMENT); +const Header: React.VFC = () => { + const { toggleInviteUsersModalOpen } = useInviteUsersModalService(); + return ( +
    + + + + +
    + ); +}; - const [modalIsOpen, toggleModal] = useToggle(false); +export const UsersTable: React.FC = () => { const { workspaceId } = useCurrentWorkspace(); - const users = useListUsers(); - const { user } = useAuthService(); - const columns = React.useMemo( + const columns = useMemo( () => [ { Header: , @@ -96,22 +113,16 @@ export const UsersSettingsView: React.FC = () => { [workspaceId, user] ); + return ; +}; + +export const UsersSettingsView: React.VFC = () => { + useTrackPage(PageTrackingCodes.SETTINGS_ACCESS_MANAGEMENT); + return ( - <> -
    -
    - -
    - -
    -
    - {modalIsOpen && } - + +
    + + ); }; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx index 3bce72196493..e97420b17c60 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx @@ -3,6 +3,7 @@ import { FormattedMessage } from "react-intl"; import { useLocation, useNavigate } from "react-router-dom"; import { LoadingPage } from "components"; +import { CloudInviteUsersHint } from "components/CloudInviteUsersHint"; import ConnectionBlock from "components/ConnectionBlock"; import { FormPageContent } from "components/ConnectorBlocks"; import { CreateConnectionForm } from "components/CreateConnection/CreateConnectionForm"; @@ -144,6 +145,7 @@ export const CreationFormPage: React.FC = () => { } }} /> + ); } else if (currentEntityStep === EntityStepsTypes.DESTINATION) { @@ -158,6 +160,7 @@ export const CreationFormPage: React.FC = () => { setCurrentStep(StepsTypes.CREATE_CONNECTION); }} /> + ); } diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx index e21148398bc8..f0e800a652d6 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { CloudInviteUsersHint } from "components/CloudInviteUsersHint"; import { FormPageContent } from "components/ConnectorBlocks"; import HeadTitle from "components/HeadTitle"; import { PageHeader } from "components/ui/PageHeader"; @@ -50,6 +51,7 @@ export const CreateDestinationPage: React.FC = () => { destinationDefinitions={destinationDefinitions} hasSuccess={successRequest} /> + diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx index 3f6c6e9e7a79..a5fc4ddf9947 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { CloudInviteUsersHint } from "components/CloudInviteUsersHint"; import { FormPageContent } from "components/ConnectorBlocks"; import HeadTitle from "components/HeadTitle"; import { PageHeader } from "components/ui/PageHeader"; @@ -47,6 +48,7 @@ const CreateSourcePage: React.FC = () => { } /> + From 7c919e00519359a17be108b8e57b011e12b4064c Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:35:20 +0200 Subject: [PATCH 064/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=94=A7=20Add=20Dat?= =?UTF-8?q?adog=20support=20to=20webapp,=20cleanup=20sentry=20init=20(#178?= =?UTF-8?q?21)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for Datadog Real User Monitoring (RUM) * Move sentry init to its own util * loadDatadogRum -> initDatadogRum * Move comment back to app index * Lazy load Sentry and Datadog * Update version value for Datadog to match Sentry * Switch process.env with window for sentry and datadog init * Remove import optimizations from Sentry and Datadog --- airbyte-webapp/package-lock.json | 53 +++++++++++++++++++++++++++++ airbyte-webapp/package.json | 5 ++- airbyte-webapp/src/config/types.ts | 4 +++ airbyte-webapp/src/index.tsx | 14 +++----- airbyte-webapp/src/utils/datadog.ts | 28 +++++++++++++++ airbyte-webapp/src/utils/sentry.ts | 19 +++++++++++ 6 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 airbyte-webapp/src/utils/datadog.ts create mode 100644 airbyte-webapp/src/utils/sentry.ts diff --git a/airbyte-webapp/package-lock.json b/airbyte-webapp/package-lock.json index 3e520af63247..12dbb3e0b9ac 100644 --- a/airbyte-webapp/package-lock.json +++ b/airbyte-webapp/package-lock.json @@ -8,6 +8,7 @@ "name": "airbyte-webapp", "version": "0.40.14", "dependencies": { + "@datadog/browser-rum": "^4.21.2", "@floating-ui/react-dom": "^1.0.0", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-brands-svg-icons": "^6.1.1", @@ -2498,6 +2499,36 @@ "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==", "dev": true }, + "node_modules/@datadog/browser-core": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.21.2.tgz", + "integrity": "sha512-o3UvCPBF0OdCInCbiC9j79K0F7/wThARZFq8+wnAOitZu64VT5XNpHFQqFP+9c+zzcxmwlTIINHmWLdkpKEECg==" + }, + "node_modules/@datadog/browser-rum": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-4.21.2.tgz", + "integrity": "sha512-qvC7sRrZ5yy7siCHeGPnBsM6sKoU+jc1YGy/5WgRSs24WUt9trgBoRcVR1KwU/aK8xn6hUOKRdEIxkrss5JaiA==", + "dependencies": { + "@datadog/browser-core": "4.21.2", + "@datadog/browser-rum-core": "4.21.2" + }, + "peerDependencies": { + "@datadog/browser-logs": "4.21.2" + }, + "peerDependenciesMeta": { + "@datadog/browser-logs": { + "optional": true + } + } + }, + "node_modules/@datadog/browser-rum-core": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.21.2.tgz", + "integrity": "sha512-8hNiNygHY8Jt2APtm4nvciGyRKIEniaupe7Uj5Bq6OFZIFNgf6qj88bRXwOdPsP9ksBNNK18Hol1oI4EdxdkkQ==", + "dependencies": { + "@datadog/browser-core": "4.21.2" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -49338,6 +49369,28 @@ "integrity": "sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==", "dev": true }, + "@datadog/browser-core": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@datadog/browser-core/-/browser-core-4.21.2.tgz", + "integrity": "sha512-o3UvCPBF0OdCInCbiC9j79K0F7/wThARZFq8+wnAOitZu64VT5XNpHFQqFP+9c+zzcxmwlTIINHmWLdkpKEECg==" + }, + "@datadog/browser-rum": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum/-/browser-rum-4.21.2.tgz", + "integrity": "sha512-qvC7sRrZ5yy7siCHeGPnBsM6sKoU+jc1YGy/5WgRSs24WUt9trgBoRcVR1KwU/aK8xn6hUOKRdEIxkrss5JaiA==", + "requires": { + "@datadog/browser-core": "4.21.2", + "@datadog/browser-rum-core": "4.21.2" + } + }, + "@datadog/browser-rum-core": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@datadog/browser-rum-core/-/browser-rum-core-4.21.2.tgz", + "integrity": "sha512-8hNiNygHY8Jt2APtm4nvciGyRKIEniaupe7Uj5Bq6OFZIFNgf6qj88bRXwOdPsP9ksBNNK18Hol1oI4EdxdkkQ==", + "requires": { + "@datadog/browser-core": "4.21.2" + } + }, "@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index 7354e7bc034e..a6fa2a1a9db4 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -24,6 +24,7 @@ "validate-links": "ts-node --skip-project ./scripts/validate-links.ts" }, "dependencies": { + "@datadog/browser-rum": "^4.21.2", "@floating-ui/react-dom": "^1.0.0", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-brands-svg-icons": "^6.1.1", @@ -162,7 +163,9 @@ }, "jest": { "transformIgnorePatterns": [], - "snapshotSerializers": ["./scripts/classname-serializer.js"], + "snapshotSerializers": [ + "./scripts/classname-serializer.js" + ], "coveragePathIgnorePatterns": [ ".stories.tsx" ] diff --git a/airbyte-webapp/src/config/types.ts b/airbyte-webapp/src/config/types.ts index 92265730edb8..ad818fd46446 100644 --- a/airbyte-webapp/src/config/types.ts +++ b/airbyte-webapp/src/config/types.ts @@ -4,6 +4,10 @@ declare global { AIRBYTE_VERSION?: string; API_URL?: string; CLOUD?: string; + REACT_APP_DATADOG_APPLICATION_ID: string; + REACT_APP_DATADOG_CLIENT_TOKEN: string; + REACT_APP_DATADOG_SITE: string; + REACT_APP_DATADOG_SERVICE: string; REACT_APP_SENTRY_DSN?: string; REACT_APP_WEBAPP_TAG?: string; REACT_APP_INTERCOM_APP_ID?: string; diff --git a/airbyte-webapp/src/index.tsx b/airbyte-webapp/src/index.tsx index 84e3678e5f54..751cd8a448c4 100644 --- a/airbyte-webapp/src/index.tsx +++ b/airbyte-webapp/src/index.tsx @@ -1,21 +1,17 @@ -import * as Sentry from "@sentry/react"; -import { Integrations } from "@sentry/tracing"; import { lazy, Suspense } from "react"; import ReactDOM from "react-dom"; import "react-reflex/styles.css"; import { isCloudApp } from "utils/app"; +import { loadDatadog } from "utils/datadog"; import { loadOsano } from "utils/dataPrivacy"; +import { loadSentry } from "utils/sentry"; import "./globals"; -// We do not follow default config approach since we want to init sentry asap -Sentry.init({ - dsn: process.env.REACT_APP_SENTRY_DSN || window.REACT_APP_SENTRY_DSN, - release: process.env.REACT_APP_WEBAPP_TAG || window.REACT_APP_WEBAPP_TAG || "dev", - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: 1.0, // may need to adjust this in the future -}); +// We do not follow default config approach since we want to init sentry/datadog asap +loadSentry(); +loadDatadog(); // In Cloud load the Osano script (GDPR consent tool before anything else) if (isCloudApp()) { diff --git a/airbyte-webapp/src/utils/datadog.ts b/airbyte-webapp/src/utils/datadog.ts new file mode 100644 index 000000000000..4fa03ed73500 --- /dev/null +++ b/airbyte-webapp/src/utils/datadog.ts @@ -0,0 +1,28 @@ +import { datadogRum } from "@datadog/browser-rum"; +export const loadDatadog = (): void => { + const applicationId = window.REACT_APP_DATADOG_APPLICATION_ID ?? process.env.REACT_APP_DATADOG_APPLICATION_ID; + if (!applicationId) { + return; + } + + const clientToken = window.REACT_APP_DATADOG_CLIENT_TOKEN ?? process.env.REACT_APP_DATADOG_CLIENT_TOKEN; + const site = window.REACT_APP_DATADOG_SITE ?? process.env.REACT_APP_DATADOG_SITE; + const service = window.REACT_APP_DATADOG_SERVICE ?? process.env.REACT_APP_DATADOG_SERVICE; + const version = window.REACT_APP_WEBAPP_TAG ?? process.env.REACT_APP_WEBAPP_TAG ?? "dev"; + + datadogRum.init({ + applicationId, + clientToken, + site, + service, + version, + sampleRate: 100, + sessionReplaySampleRate: 0, + trackInteractions: false, + trackResources: true, + trackLongTasks: true, + defaultPrivacyLevel: "mask-user-input", + }); + + datadogRum.startSessionReplayRecording(); +}; diff --git a/airbyte-webapp/src/utils/sentry.ts b/airbyte-webapp/src/utils/sentry.ts new file mode 100644 index 000000000000..d8f63339f019 --- /dev/null +++ b/airbyte-webapp/src/utils/sentry.ts @@ -0,0 +1,19 @@ +import * as Sentry from "@sentry/react"; +import { Integrations } from "@sentry/tracing"; + +export const loadSentry = (): void => { + const dsn = window.REACT_APP_SENTRY_DSN ?? process.env.REACT_APP_SENTRY_DSN; + if (!dsn) { + return; + } + + const release = window.REACT_APP_WEBAPP_TAG ?? process.env.REACT_APP_WEBAPP_TAG ?? "dev"; + const integrations = [new Integrations.BrowserTracing()]; + + Sentry.init({ + dsn, + release, + integrations, + tracesSampleRate: 1.0, + }); +}; From e0db09bc920f199e791e617ed23a67d24beddaa1 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Wed, 12 Oct 2022 09:10:05 -0700 Subject: [PATCH 065/498] improve query performance (#17862) * replace WITH query with more performant query * replace multiple ORs with an IN --- .../metrics/reporter/MetricRepository.java | 33 +++++-------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java index b36f6ad15b30..8dda0ba29869 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java @@ -144,7 +144,7 @@ Map overallJobRuntimeForTerminalJobsInLastHour() { final var query = """ SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs WHERE updated_at >= NOW() - INTERVAL '1 HOUR' - AND (jobs.status = 'failed' OR jobs.status = 'succeeded' OR jobs.status = 'cancelled'); + AND jobs.status IN ('failed', 'succeeded', 'cancelled'); """; final var queryResults = ctx.fetch(query); final var statuses = queryResults.getValues("status", JobStatus.class); @@ -159,32 +159,17 @@ SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs } private long oldestJobAgeSecs(final JobStatus status) { - final var readableTimeField = "run_duration"; - final var durationSecField = "run_duration_secs"; - final var query = String.format(""" - WITH - oldest_job AS ( - SELECT id, - age(current_timestamp, created_at) AS %s - FROM jobs - WHERE status = '%s' - ORDER BY run_duration DESC - LIMIT 1) - SELECT id, - run_duration, - extract(epoch from run_duration) as %s - FROM oldest_job""", readableTimeField, status.getLiteral(), durationSecField); - final var res = ctx.fetch(query); - // unfortunately there are no good Jooq methods for retrieving a single record of a single column - // forcing the List cast. - final var duration = res.getValues(durationSecField, Double.class); - - if (duration.size() == 0) { + final var query = """ + SELECT id, EXTRACT(EPOCH FROM (current_timestamp - created_at)) AS run_duration_seconds + FROM jobs WHERE status = ?::job_status + ORDER BY created_at ASC limit 1; + """; + final var result = ctx.fetchOne(query, status.getLiteral()); + if (result == null) { return 0L; } - // as double can have rounding errors, round down to remove noise. - return duration.get(0).longValue(); + return result.getValue("run_duration_seconds", Double.class).longValue(); } } From de8895b8eff10484e5a4c5d56bd6634e086f4e00 Mon Sep 17 00:00:00 2001 From: Tyler B <104733644+TBernstein4@users.noreply.github.com> Date: Wed, 12 Oct 2022 12:51:25 -0400 Subject: [PATCH 066/498] Changes Checkout Streams to Checkout Sessions (#17796) I noticed a discrepancy in title of the "Checkout Streams" and "Checkout Streams Line Items" where the actual name of the streams are "Checkout Sessions" and "Checkout Sessions Line Items" --- docs/integrations/sources/stripe.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/sources/stripe.md b/docs/integrations/sources/stripe.md index 4b0191de0c62..c3aa3bb0526b 100644 --- a/docs/integrations/sources/stripe.md +++ b/docs/integrations/sources/stripe.md @@ -44,8 +44,8 @@ The Stripe source connector supports the following streams: - [Bank accounts](https://stripe.com/docs/api/customer_bank_accounts/list) - [Charges](https://stripe.com/docs/api/charges/list) \(Incremental\) - The `amount` column defaults to the smallest currency unit. (See [charge object](https://stripe.com/docs/api/charges/object) for more details) -- [Checkout Streams](https://stripe.com/docs/api/checkout/sessions/list) -- [Checkout Streams Line Items](https://stripe.com/docs/api/checkout/sessions/line_items) +- [Checkout Sessions](https://stripe.com/docs/api/checkout/sessions/list) +- [Checkout Sessions Line Items](https://stripe.com/docs/api/checkout/sessions/line_items) - [Coupons](https://stripe.com/docs/api/coupons/list) \(Incremental\) - [Customer Balance Transactions](https://stripe.com/docs/api/customer_balance_transactions/list) - [Customers](https://stripe.com/docs/api/customers/list) \(Incremental\) From e1701c97ebcc67f876db3500f1e63e42a89c2645 Mon Sep 17 00:00:00 2001 From: Akash Kulkarni <113392464+akashkulk@users.noreply.github.com> Date: Wed, 12 Oct 2022 09:53:25 -0700 Subject: [PATCH 067/498] =?UTF-8?q?=F0=9F=8E=89=20=20MySQL=20Source=20:=20?= =?UTF-8?q?Expose=20serverTimezone=20debezium=20option=20via=20MySQL=20Sou?= =?UTF-8?q?rce=20spec=20for=20CDC=20(#17815)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expose serverTimezone debezium option via MySQL Source spec for CDC * Fix tests * Bump dockerfile version * Update Dockerfile * Update mysql.md * Update mysql.md * Documentation * Code Review Comments * Addressing comments * Addressing comments * Addressing comments * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 11 ++++++++-- .../source-mysql-strict-encrypt/Dockerfile | 2 +- .../src/test/resources/expected_spec.json | 10 +++++++-- .../connectors/source-mysql/Dockerfile | 2 +- .../source/mysql/MySqlCdcProperties.java | 11 +++++++++- .../source/mysql/MySqlSource.java | 1 + .../mysql/helpers/CdcConfigurationHelper.java | 22 +++++++++++++++++++ .../source-mysql/src/main/resources/spec.json | 10 +++++++-- .../mysql/CdcConfigurationHelperTest.java | 14 ++++++++++++ .../source/mysql/CdcMysqlSourceTest.java | 1 + docs/integrations/sources/mysql.md | 12 ++++++++++ 12 files changed, 88 insertions(+), 10 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index c875770b9b59..f5b6b96539fa 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -678,7 +678,7 @@ - name: MySQL sourceDefinitionId: 435bb9a5-7887-4809-aa58-28c27df0d7ad dockerRepository: airbyte/source-mysql - dockerImageTag: 1.0.3 + dockerImageTag: 1.0.4 documentationUrl: https://docs.airbyte.com/integrations/sources/mysql icon: mysql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 742ade1acb11..a67eff29f083 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -6964,7 +6964,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mysql:1.0.3" +- dockerImage: "airbyte/source-mysql:1.0.4" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/mysql" connectionSpecification: @@ -7194,9 +7194,16 @@ \ initial waiting time." default: 300 - order: 4 min: 120 max: 1200 + order: 1 + server_time_zone: + type: "string" + title: "Configured server timezone for the MySQL source (Advanced)" + description: "Enter the configured MySQL server timezone. This should\ + \ only be done if the configured timezone in your MySQL instance\ + \ does not conform to IANNA standard." + order: 2 tunnel_method: type: "object" title: "SSH Tunnel Method" diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile index a185d075b585..70596dc34839 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile @@ -17,6 +17,6 @@ ENV APPLICATION source-mysql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.3 +LABEL io.airbyte.version=1.0.4 LABEL io.airbyte.name=airbyte/source-mysql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json index 2336c8b7f893..870f0a66f303 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/resources/expected_spec.json @@ -210,9 +210,15 @@ "title": "Initial Waiting Time in Seconds (Advanced)", "description": "The amount of time the connector will wait when it launches to determine if there is new data to sync or not. Defaults to 300 seconds. Valid range: 120 seconds to 1200 seconds. Read about initial waiting time.", "default": 300, - "order": 4, "min": 120, - "max": 1200 + "max": 1200, + "order": 1 + }, + "server_time_zone": { + "type": "string", + "title": "Configured server timezone for the MySQL source (Advanced)", + "description": "Enter the configured MySQL server timezone. This should only be done if the configured timezone in your MySQL instance does not conform to IANNA standard.", + "order": 2 } } } diff --git a/airbyte-integrations/connectors/source-mysql/Dockerfile b/airbyte-integrations/connectors/source-mysql/Dockerfile index 1a2ee2230018..0d6a6b405691 100644 --- a/airbyte-integrations/connectors/source-mysql/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.3 +LABEL io.airbyte.version=1.0.4 LABEL io.airbyte.name=airbyte/source-mysql diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java index edb7b839ccbf..82bca2a517d9 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java @@ -32,6 +32,7 @@ static Properties getDebeziumProperties(final JdbcDatabase database) { // https://debezium.io/documentation/reference/1.9/connectors/mysql.html#mysql-property-snapshot-mode props.setProperty("snapshot.mode", "when_needed"); } + return props; } @@ -52,6 +53,15 @@ private static Properties commonProperties(final JdbcDatabase database) { props.setProperty("boolean.type", "io.debezium.connector.mysql.converters.TinyIntOneToBooleanConverter"); props.setProperty("datetime.type", "io.airbyte.integrations.debezium.internals.MySQLDateTimeConverter"); + // For CDC mode, the user cannot provide timezone arguments as JDBC parameters - they are specifically defined in the replication_method + // config. + if (sourceConfig.get("replication_method").has("server_time_zone")) { + final String serverTimeZone = sourceConfig.get("replication_method").get("server_time_zone").asText(); + if (!serverTimeZone.isEmpty()) { + props.setProperty("database.serverTimezone", serverTimeZone); + } + } + // Check params for SSL connection in config and add properties for CDC SSL connection // https://debezium.io/documentation/reference/stable/connectors/mysql.html#mysql-property-database-ssl-mode if (!sourceConfig.has(JdbcUtils.SSL_KEY) || sourceConfig.get(JdbcUtils.SSL_KEY).asBoolean()) { @@ -111,7 +121,6 @@ private static Properties commonProperties(final JdbcDatabase database) { props.setProperty("database.include.list", sourceConfig.get("database").asText()); return props; - } static Properties getSnapshotProperties(final JdbcDatabase database) { diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java index b1268f1c8630..6cd1cb72432f 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlSource.java @@ -125,6 +125,7 @@ public List> getCheckOperations(final J checkOperations.add(database -> { CdcConfigurationHelper.checkFirstRecordWaitTime(config); + CdcConfigurationHelper.checkServerTimeZoneConfig(config); }); } return checkOperations; diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java index 555544107260..303efaa43242 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java @@ -5,9 +5,11 @@ package io.airbyte.integrations.source.mysql.helpers; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.annotations.VisibleForTesting; import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.db.jdbc.JdbcDatabase; import java.time.Duration; +import java.time.ZoneId; import java.util.List; import java.util.Optional; import org.slf4j.Logger; @@ -74,6 +76,26 @@ public static Optional getFirstRecordWaitSeconds(final JsonNode config) return Optional.empty(); } + private static Optional getCdcServerTimezone(final JsonNode config) { + final JsonNode replicationMethod = config.get("replication_method"); + if (replicationMethod != null && replicationMethod.has("server_time_zone")) { + final String serverTimeZone = config.get("replication_method").get("server_time_zone").asText(); + return Optional.of(serverTimeZone); + } + return Optional.empty(); + } + + public static void checkServerTimeZoneConfig(final JsonNode config) { + final Optional serverTimeZone = getCdcServerTimezone(config); + if (serverTimeZone.isPresent()) { + final String timeZone = serverTimeZone.get(); + if (!timeZone.isEmpty() && !ZoneId.getAvailableZoneIds().contains((timeZone))) { + throw new IllegalArgumentException(String.format("Given timezone %s is not valid. The given timezone must conform to the IANNA standard. " + + "See https://www.iana.org/time-zones for more details", serverTimeZone.get())); + } + } + } + public static void checkFirstRecordWaitTime(final JsonNode config) { // we need to skip the check because in tests, we set initial_waiting_seconds // to 5 seconds for performance reasons, which is shorter than the minimum diff --git a/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json b/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json index 456f50136b42..c1a6f48b4e87 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/source-mysql/src/main/resources/spec.json @@ -217,9 +217,15 @@ "title": "Initial Waiting Time in Seconds (Advanced)", "description": "The amount of time the connector will wait when it launches to determine if there is new data to sync or not. Defaults to 300 seconds. Valid range: 120 seconds to 1200 seconds. Read about initial waiting time.", "default": 300, - "order": 4, "min": 120, - "max": 1200 + "max": 1200, + "order": 1 + }, + "server_time_zone": { + "type": "string", + "title": "Configured server timezone for the MySQL source (Advanced)", + "description": "Enter the configured MySQL server timezone. This should only be done if the configured timezone in your MySQL instance does not conform to IANNA standard.", + "order": 2 } } } diff --git a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java index 594252bdd6c1..2c15cfd2b456 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcConfigurationHelperTest.java @@ -49,4 +49,18 @@ void testGetFirstRecordWaitTime() { assertEquals(MAX_FIRST_RECORD_WAIT_TIME, CdcConfigurationHelper.getFirstRecordWaitTime(tooLongConfig)); } + @Test + void testServerTimeConfig() { + final JsonNode emptyConfig = Jsons.jsonNode(Collections.emptyMap()); + assertDoesNotThrow(() -> CdcConfigurationHelper.checkServerTimeZoneConfig(emptyConfig)); + + final JsonNode normalConfig = Jsons.jsonNode(Map.of("replication_method", + Map.of("method", "CDC", "server_time_zone", "America/Los_Angeles"))); + assertDoesNotThrow(() -> CdcConfigurationHelper.checkServerTimeZoneConfig(normalConfig)); + + final JsonNode invalidConfig = Jsons.jsonNode(Map.of("replication_method", + Map.of("method", "CDC", "server_time_zone", "CEST"))); + assertThrows(IllegalArgumentException.class, () -> CdcConfigurationHelper.checkServerTimeZoneConfig(invalidConfig)); + } + } diff --git a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcMysqlSourceTest.java b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcMysqlSourceTest.java index db4adfdc2905..128ef25def1d 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcMysqlSourceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/CdcMysqlSourceTest.java @@ -82,6 +82,7 @@ private void init() { final JsonNode replicationMethod = Jsons.jsonNode(ImmutableMap.builder() .put("method", "CDC") .put("initial_waiting_seconds", INITIAL_WAITING_SECONDS) + .put("time_zone", "America/Los_Angeles") .build()); config = Jsons.jsonNode(ImmutableMap.builder() diff --git a/docs/integrations/sources/mysql.md b/docs/integrations/sources/mysql.md index 70d1314b1daa..3d84b175170b 100644 --- a/docs/integrations/sources/mysql.md +++ b/docs/integrations/sources/mysql.md @@ -124,6 +124,17 @@ The connector waits for the default initial wait time of 5 minutes (300 seconds) If you know there are database changes to be synced, but the connector cannot read those changes, the root cause may be insufficient waiting time. In that case, you can increase the waiting time (example: set to 600 seconds) to test if it is indeed the root cause. On the other hand, if you know there are no database changes, you can decrease the wait time to speed up the zero record syncs. +**4. Set up server timezone\(Optional\)** + +:::warning +This is an advanced feature. Use it if absolutely necessary. +::: + +In CDC mode, the MySQl connector may need a timezone configured if the existing MySQL database been set up with a system timezone that is not recognized by the [IANA Timezone Database](https://www.iana.org/time-zones). + +In this case, you can configure the server timezone to the equivalent IANA timezone compliant timezone. (e.g. CEST -> Europe/Berlin). + + **Note** When a sync runs for the first time using CDC, Airbyte performs an initial consistent snapshot of your database. Airbyte doesn't acquire any table locks \(for tables defined with MyISAM engine, the tables would still be locked\) while creating the snapshot to allow writes by other database clients. But in order for the sync to work without any error/unexpected behaviour, it is assumed that no schema changes are happening while the snapshot is running. @@ -240,6 +251,7 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura ## Changelog | Version | Date | Pull Request | Subject | |:--------|:-----------|:-----------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | | 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | | 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | | 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | From 67d146ae41ff709784801e996713363ae90799b1 Mon Sep 17 00:00:00 2001 From: Yowan Ramchoreeter <26179814+YowanR@users.noreply.github.com> Date: Wed, 12 Oct 2022 13:08:19 -0400 Subject: [PATCH 068/498] Minor nit: Removing extra Note (#17572) --- docs/integrations/sources/paypal-transaction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/sources/paypal-transaction.md b/docs/integrations/sources/paypal-transaction.md index 46297dc290f6..bbeeb02ee92f 100644 --- a/docs/integrations/sources/paypal-transaction.md +++ b/docs/integrations/sources/paypal-transaction.md @@ -13,7 +13,7 @@ In order to get an `Client ID` and `Secret` please go to [this](https://develope :::note -Note: Our Paypal Transactions Source Connector does not support OAuth at this time due to limitations outside of our control. If OAuth for Paypal Transactions is critical to your business, [please reach out to us](mailto:product@airbyte.io) to discuss how we may be able to partner on this effort. +Our Paypal Transactions Source Connector does not support OAuth at this time due to limitations outside of our control. If OAuth for Paypal Transactions is critical to your business, [please reach out to us](mailto:product@airbyte.io) to discuss how we may be able to partner on this effort. ::: From 07e5e358557cb4039aba3438582235b762bed791 Mon Sep 17 00:00:00 2001 From: Krisjan O Date: Wed, 12 Oct 2022 19:20:05 +0200 Subject: [PATCH 069/498] Google Analytics v4: Added support for segments and filters (#16920) * Google Analytics v4: Added support for segments and filters * Bumped version to 0.1.26 and updated docs * Fix connection test * Fix merge conflict (connector version) --- .../source-google-analytics-v4/Dockerfile | 4 ++-- .../configured_catalog_segment_filters.json | 15 +++++++++++++++ .../source_google_analytics_v4/source.py | 10 +++++++++- docs/integrations/sources/google-analytics-v4.md | 12 +++++++++++- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 airbyte-integrations/connectors/source-google-analytics-v4/integration_tests/configured_catalog_segment_filters.json diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile b/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile index 2dc5d9e6fc2f..95ba3c2b6f5e 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile +++ b/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile @@ -12,5 +12,5 @@ COPY main.py ./ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.27 -LABEL io.airbyte.name=airbyte/source-google-analytics-v4 +LABEL io.airbyte.version=0.1.28 +LABEL io.airbyte.name=airbyte/source-google-analytics-v4 \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/integration_tests/configured_catalog_segment_filters.json b/airbyte-integrations/connectors/source-google-analytics-v4/integration_tests/configured_catalog_segment_filters.json new file mode 100644 index 000000000000..2c34edf6530d --- /dev/null +++ b/airbyte-integrations/connectors/source-google-analytics-v4/integration_tests/configured_catalog_segment_filters.json @@ -0,0 +1,15 @@ +{ + "streams": [ + { + "stream": { + "name": "new_users_per_day", + "json_schema": {}, + "supported_sync_modes": ["incremental"], + "source_defined_cursor": true + }, + "sync_mode": "incremental", + "cursor_field": ["ga_date"], + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py b/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py index 6c5b8266acfe..16db57232c28 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py +++ b/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py @@ -103,6 +103,8 @@ def __init__(self, config: MutableMapping): self.view_id = config["view_id"] self.metrics = config["metrics"] self.dimensions = config["dimensions"] + self.segments = config.get("segments", list()) + self.filtersExpression = config.get("filter", "") self._config = config self.dimensions_ref, self.metrics_ref = GoogleAnalyticsV4TypesList().read_records(sync_mode=None) @@ -167,6 +169,8 @@ def request_body_json( metrics = [{"expression": metric} for metric in self.metrics] dimensions = [{"name": dimension} for dimension in self.dimensions] + segments = [{"segmentId": segment} for segment in self.segments] + filtersExpression = self.filtersExpression request_body = { "reportRequests": [ @@ -176,6 +180,8 @@ def request_body_json( "pageSize": self.page_size, "metrics": metrics, "dimensions": dimensions, + "segments": segments, + "filtersExpression": filtersExpression, } ] } @@ -268,7 +274,7 @@ def lookup_data_type(self, field_type: str, attribute: str) -> str: """ try: if field_type == "dimension": - if attribute.startswith(("ga:dimension", "ga:customVarName", "ga:customVarValue")): + if attribute.startswith(("ga:dimension", "ga:customVarName", "ga:customVarValue", "ga:segment")): # Custom Google Analytics Dimensions that are not part of self.dimensions_ref. They are always # strings return "string" @@ -602,6 +608,8 @@ def streams(self, config: MutableMapping[str, Any]) -> List[Stream]: for stream in config["ga_streams"]: config["metrics"] = stream["metrics"] config["dimensions"] = stream["dimensions"] + config["segments"] = stream.get("segments", list()) + config["filter"] = stream.get("filter", "") # construct GAReadStreams sub-class for each stream stream_name = stream["name"] diff --git a/docs/integrations/sources/google-analytics-v4.md b/docs/integrations/sources/google-analytics-v4.md index 80d0aac1d572..6ab4ac083f03 100644 --- a/docs/integrations/sources/google-analytics-v4.md +++ b/docs/integrations/sources/google-analytics-v4.md @@ -9,10 +9,19 @@ This connector supports GA4 properties through the [Analytics Data API v1](https * JSON credentials for the service account that has access to Google Analytics. For more details check [instructions](https://support.google.com/analytics/answer/1009702#zippy=%2Cin-this-article) * OAuth 2.0 credentials for the service account that has access to Google Analytics * Property ID -* Custom reports in format `{"name": "", "dimensions": ["", ...], "metrics": ["metric-name", ...]}` * Date Range Start Date * Data request time increment in days (Optional) +## Custom reports + +* Support for multiple custom reports +* Custom reports in format `[{"name": "", "dimensions": ["", ...], "metrics": ["", ...]}]` +* Custom report format when using segments and / or filters `[{"name": "", "dimensions": ["", ...], "metrics": ["", ...], "segments": [""}]` +* When using segments, make sure you add the `ga:segment` dimension. +* Custom reports: [Dimensions and metrics explorer](https://ga-dev-tools.web.app/dimensions-metrics-explorer/) +* Custom reports: [Segment](https://developers.google.com/analytics/devguides/reporting/core/v3/reference#segment) and [filter](https://developers.google.com/analytics/devguides/reporting/core/v3/reference#filters) definition guide (v3 format) +* Example: `[{"name": "sessions_example", "dimensions": ["ga:date", "ga:segment", "ga:deviceCategory"], "metrics": ["ga:pageviews","ga:sessions","ga:users","ga:transactions"], "segments": ["sessions::condition::ga:browser==Chrome"], "filter": "ga:deviceCategory==desktop"}]` + ## Step 1: Set up Source ### Create a Service Account @@ -63,6 +72,7 @@ added by default to any report. There are 8 default reports. To add more reports | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------| +| 0.0.4 | 2022-09-24 | [16920](https://github.com/airbytehq/airbyte/pull/16920) | Added segments and filters to custom reports | | 0.0.3 | 2022-08-15 | [15229](https://github.com/airbytehq/airbyte/pull/15229) | Source Google Analytics Data Api: code refactoring | | 0.0.2 | 2022-07-27 | [15087](https://github.com/airbytehq/airbyte/pull/15087) | fix documentationUrl | | 0.0.1 | 2022-05-09 | [12701](https://github.com/airbytehq/airbyte/pull/12701) | Introduce Google Analytics Data API source | From 0cbd4deeb4d5f22fb35b76e3e59adc39734e1d14 Mon Sep 17 00:00:00 2001 From: Augustin Date: Wed, 12 Oct 2022 19:32:07 +0200 Subject: [PATCH 070/498] SAT: unit tests `test_state_with_abnormally_large_values` + add missing `future_state_path` (#17791) --- .../bases/source-acceptance-test/CHANGELOG.md | 4 +- .../bases/source-acceptance-test/Dockerfile | 2 +- .../unit_tests/test_incremental.py | 68 ++++++++++++++ .../acceptance-test-config.yml | 2 +- .../integration_tests/abnormal_state.json | 88 ++++++++++++++---- .../integration_tests/sample_state.json | 88 ++++++++++++++---- .../acceptance-test-config.yml | 3 +- .../integration_tests/abnormal_state.json | 90 +++++++++++++++++++ .../integration_tests/sample_state.json | 68 +++++++++++--- .../acceptance-test-config.yml | 1 + .../acceptance-test-config.yml | 10 +-- .../integration_tests/sample_state.json | 60 ++++++++++++- 12 files changed, 425 insertions(+), 59 deletions(-) create mode 100644 airbyte-integrations/connectors/source-paystack/integration_tests/abnormal_state.json diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index a462bf91612c..5894fff70338 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,9 +1,11 @@ # Changelog +## 0.2.5 +Unit test `test_state_with_abnormally_large_values` to check state emission testing is working. + ## 0.2.4 Make incremental tests compatible with per stream states.[#16686](https://github.com/airbytehq/airbyte/pull/16686/) - ## 0.2.3 Backward compatibility tests: improve `check_if_type_of_type_field_changed` to make it less radical when validating specs and allow `'str' -> ['str', '']` type changes.[#16429](https://github.com/airbytehq/airbyte/pull/16429/) diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index 532ccd74f05d..049e3ebe2e57 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.4 +LABEL io.airbyte.version=0.2.5 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_incremental.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_incremental.py index 3bf2feeda18c..fe061812b409 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_incremental.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_incremental.py @@ -613,3 +613,71 @@ def test_config_skip_test(): # This is guaranteed to fail when the test gets executed docker_runner_mock.call_read.assert_not_called() + + +@pytest.mark.parametrize( + "read_output, expectation", + [ + pytest.param([], pytest.raises(AssertionError), id="Error because incremental stream should always emit state messages"), + pytest.param( + [ + AirbyteMessage( + type=Type.RECORD, record=AirbyteRecordMessage(stream="test_stream", data={"date": "2022-10-04"}, emitted_at=111) + ), + AirbyteMessage( + type=Type.STATE, + state=AirbyteStateMessage( + type=AirbyteStateType.STREAM, + stream=AirbyteStreamState( + stream_descriptor=StreamDescriptor(name="test_stream"), + stream_state=AirbyteStateBlob.parse_obj({"date": "2022-10-04"}), + ), + data={"date": "2022-10-04"}, + ), + ), + ], + pytest.raises(AssertionError), + id="Error because incremental sync with abnormally large state value should not produce record.", + ), + pytest.param( + [ + AirbyteMessage( + type=Type.STATE, + state=AirbyteStateMessage( + type=AirbyteStateType.STREAM, + stream=AirbyteStreamState( + stream_descriptor=StreamDescriptor(name="test_stream"), + stream_state=AirbyteStateBlob.parse_obj({"date": "2022-10-04"}), + ), + data={"date": "2022-10-04"}, + ), + ) + ], + does_not_raise(), + ), + ], +) +def test_state_with_abnormally_large_values(mocker, read_output, expectation): + docker_runner_mock = mocker.MagicMock() + docker_runner_mock.call_read_with_state.return_value = read_output + t = _TestIncremental() + with expectation: + t.test_state_with_abnormally_large_values( + connector_config=mocker.MagicMock(), + configured_catalog=ConfiguredAirbyteCatalog( + streams=[ + ConfiguredAirbyteStream( + stream=AirbyteStream( + name="test_stream", + json_schema={"type": "object", "properties": {"date": {"type": "date"}}}, + supported_sync_modes=[SyncMode.full_refresh, SyncMode.incremental], + ), + sync_mode=SyncMode.incremental, + destination_sync_mode=DestinationSyncMode.overwrite, + cursor_field=["date"], + ) + ] + ), + future_state=mocker.MagicMock(), + docker_runner=docker_runner_mock, + ) diff --git a/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml b/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml index 3f9d1efe2ee2..df5768d18531 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-bigcommerce/acceptance-test-config.yml @@ -18,7 +18,7 @@ tests: incremental: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" - # future_state_path: "integration_tests/abnormal_state.json" + future_state_path: "integration_tests/abnormal_state.json" full_refresh: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/abnormal_state.json index ac43461345a0..7dffb12b07f6 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/abnormal_state.json +++ b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/abnormal_state.json @@ -1,23 +1,79 @@ -{ - "customers": { - "date_modified": "2080-07-30T22:16:46" +[ + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2080-07-30T22:16:46" + }, + "stream_descriptor": { + "name": "customers" + } + } }, - "orders": { - "date_modified": "2080-07-30T22:16:46+00:00" + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2080-07-30T22:16:46+00:00" + }, + "stream_descriptor": { + "name": "orders" + } + } }, - "transactions": { - "id": 5783048848838997 + { + "type": "STREAM", + "stream": { + "stream_state": { + "id": 5783048848838997 + }, + "stream_descriptor": { + "name": "transactions" + } + } }, - "pages": { - "id": 9000002039398998 + { + "type": "STREAM", + "stream": { + "stream_state": { + "id": 9000002039398998 + }, + "stream_descriptor": { + "name": "pages" + } + } }, - "products": { - "date_modified": "2080-01-10T00:17:08+00:00" + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2080-01-10T00:17:08+00:00" + }, + "stream_descriptor": { + "name": "products" + } + } }, - "channels": { - "date_modified": "2080-01-10T00:17:08+00:00" + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2080-01-10T00:17:08+00:00" + }, + "stream_descriptor": { + "name": "channels" + } + } }, - "store": { - "store_id": 99199991999999199 + { + "type": "STREAM", + "stream": { + "stream_state": { + "store_id": 99199991999999200 + }, + "stream_descriptor": { + "name": "store" + } + } } -} +] diff --git a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/sample_state.json index 908c17c29d8d..b9495cc6af9f 100644 --- a/airbyte-integrations/connectors/source-bigcommerce/integration_tests/sample_state.json +++ b/airbyte-integrations/connectors/source-bigcommerce/integration_tests/sample_state.json @@ -1,23 +1,79 @@ -{ - "customers": { - "date_modified": "2021-08-11T15:18:55Z" +[ + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2021-08-11T15:18:55Z" + }, + "stream_descriptor": { + "name": "customers" + } + } }, - "orders": { - "date_modified": "2021-08-16T21:09:10+00:00" + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2021-08-16T21:09:10+00:00" + }, + "stream_descriptor": { + "name": "orders" + } + } }, - "transactions": { - "id": 0 + { + "type": "STREAM", + "stream": { + "stream_state": { + "id": 0 + }, + "stream_descriptor": { + "name": "transactions" + } + } }, - "pages": { - "id": 4 + { + "type": "STREAM", + "stream": { + "stream_state": { + "id": 4 + }, + "stream_descriptor": { + "name": "pages" + } + } }, - "products": { - "date_modified": "2022-01-10T00:17:08+00:00" + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2022-01-10T00:17:08+00:00" + }, + "stream_descriptor": { + "name": "products" + } + } }, - "channels": { - "date_modified": "2022-01-10T00:17:08+00:00" + { + "type": "STREAM", + "stream": { + "stream_state": { + "date_modified": "2022-01-10T00:17:08+00:00" + }, + "stream_descriptor": { + "name": "channels" + } + } }, - "store": { - "store_id": 0 + { + "type": "STREAM", + "stream": { + "stream_state": { + "store_id": 0 + }, + "stream_descriptor": { + "name": "store" + } + } } -} +] diff --git a/airbyte-integrations/connectors/source-paystack/acceptance-test-config.yml b/airbyte-integrations/connectors/source-paystack/acceptance-test-config.yml index 3c59848ec0aa..78a5be12a22c 100644 --- a/airbyte-integrations/connectors/source-paystack/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-paystack/acceptance-test-config.yml @@ -12,10 +12,11 @@ tests: basic_read: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" - empty_streams: ['disputes', 'transfers', 'settlements', 'invoices'] + empty_streams: ["disputes", "transfers", "settlements", "invoices"] incremental: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" + future_state_path: "integration_tests/abnormal_state.json" full_refresh: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-paystack/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-paystack/integration_tests/abnormal_state.json new file mode 100644 index 000000000000..62a1e078bdfc --- /dev/null +++ b/airbyte-integrations/connectors/source-paystack/integration_tests/abnormal_state.json @@ -0,0 +1,90 @@ +[ + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "customers" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "disputes" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "created_at": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "invoices" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "refunds" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "settlements" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "subscriptions" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "transactions" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "createdAt": "2099-08-01T00:00:00Z" + }, + "stream_descriptor": { + "name": "transfers" + } + } + } +] diff --git a/airbyte-integrations/connectors/source-paystack/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-paystack/integration_tests/sample_state.json index 2b7a6496036c..8eebf5980873 100644 --- a/airbyte-integrations/connectors/source-paystack/integration_tests/sample_state.json +++ b/airbyte-integrations/connectors/source-paystack/integration_tests/sample_state.json @@ -1,10 +1,58 @@ -{ - "customers": { "createdAt": "2019-08-01T00:00:00Z" }, - "disputes": { "createdAt": "2019-08-01T00:00:00Z" }, - "invoices": { "created_at": "2019-08-01T00:00:00Z" }, - "refunds": { "createdAt": "2019-08-01T00:00:00Z" }, - "settlements": { "createdAt": "2019-08-01T00:00:00Z" }, - "subscriptions": { "createdAt": "2019-08-01T00:00:00Z" }, - "transactions": { "createdAt": "2019-08-01T00:00:00Z" }, - "transfers": { "createdAt": "2019-08-01T00:00:00Z" } -} +[ + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "customers" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "disputes" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "created_at": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "invoices" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "refunds" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "settlements" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "subscriptions" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "transactions" } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { "createdAt": "2019-08-01T00:00:00Z" }, + "stream_descriptor": { "name": "transfers" } + } + } +] diff --git a/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml b/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml index fd2f549bb517..0feabb8df976 100644 --- a/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml @@ -15,6 +15,7 @@ tests: incremental: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" + future_state_path: "integration_tests/abnormal_state.json" full_refresh: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml b/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml index c6c27a98f3f5..6cc635226775 100644 --- a/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-talkdesk-explore/acceptance-test-config.yml @@ -15,15 +15,7 @@ tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" empty_streams: [] - # TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file - # expect_records: - # path: "integration_tests/expected_records.txt" - # extra_fields: no - # exact_order: no - # extra_records: yes incremental: # TODO if your connector does not implement incremental sync, remove this block - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" - # full_refresh: - # - config_path: "secrets/config.json" - # configured_catalog_path: "integration_tests/configured_catalog.json" + future_state_path: "integration_tests/abnormal_state.json" diff --git a/airbyte-integrations/connectors/source-talkdesk-explore/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-talkdesk-explore/integration_tests/sample_state.json index 3587e579822d..2489355851a2 100644 --- a/airbyte-integrations/connectors/source-talkdesk-explore/integration_tests/sample_state.json +++ b/airbyte-integrations/connectors/source-talkdesk-explore/integration_tests/sample_state.json @@ -1,5 +1,57 @@ -{ - "todo-stream-name": { - "todo-field-name": "value" +[ + { + "type": "STREAM", + "stream": { + "stream_state": { + "end_at": "2099-01-01T00:00:00" + }, + "stream_descriptor": { + "name": "calls" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "status_end_at": "2099-01-01T00:00:00" + }, + "stream_descriptor": { + "name": "user_status" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "studio_flow_executions_aggregated.flow_execution_finished_time": "2099-01-01T00:00:00" + }, + "stream_descriptor": { + "name": "studio_flow_execution" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "finished_at": "2099-01-01T00:00:00" + }, + "stream_descriptor": { + "name": "contacts" + } + } + }, + { + "type": "STREAM", + "stream": { + "stream_state": { + "ring_finished_at_time": "2099-01-01T00:00:00" + }, + "stream_descriptor": { + "name": "ring_attempts" + } + } } -} +] From 9a30ae743f4eadf076e98a4a36ffb82b5ab9c280 Mon Sep 17 00:00:00 2001 From: Sajarin Date: Wed, 12 Oct 2022 14:02:56 -0400 Subject: [PATCH 071/498] Publishing Google Analytics connector (#17898) * Publishing Google Analytics connector * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index f5b6b96539fa..56987f690356 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -386,7 +386,7 @@ - name: Google Analytics (Universal Analytics) sourceDefinitionId: eff3616a-f9c3-11eb-9a03-0242ac130003 dockerRepository: airbyte/source-google-analytics-v4 - dockerImageTag: 0.1.27 + dockerImageTag: 0.1.28 documentationUrl: https://docs.airbyte.com/integrations/sources/google-analytics-universal-analytics icon: google-analytics.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index a67eff29f083..cc1181894792 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3865,7 +3865,7 @@ oauthFlowOutputParameters: - - "access_token" - - "refresh_token" -- dockerImage: "airbyte/source-google-analytics-v4:0.1.27" +- dockerImage: "airbyte/source-google-analytics-v4:0.1.28" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-analytics-universal-analytics" connectionSpecification: From 0644a844aa8d37c21ccc734d9e0a5c5c69eb00fd Mon Sep 17 00:00:00 2001 From: Augustin Date: Wed, 12 Oct 2022 20:21:29 +0200 Subject: [PATCH 072/498] SAT: backward compatiblity hypothesis testing: Disable `filter too much` health check (#17871) --- .../bases/source-acceptance-test/CHANGELOG.md | 5 ++++- .../bases/source-acceptance-test/Dockerfile | 2 +- .../source_acceptance_test/utils/backward_compatibility.py | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index 5894fff70338..dec343110fd6 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,7 +1,10 @@ # Changelog +## 0.2.6 +Backward compatibility hypothesis testing: disable "filtering too much" health check. [#17871](https://github.com/airbytehq/airbyte/pull/17871) + ## 0.2.5 -Unit test `test_state_with_abnormally_large_values` to check state emission testing is working. +Unit test `test_state_with_abnormally_large_values` to check state emission testing is working. [#17791](https://github.com/airbytehq/airbyte/pull/17791) ## 0.2.4 Make incremental tests compatible with per stream states.[#16686](https://github.com/airbytehq/airbyte/pull/16686/) diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index 049e3ebe2e57..cbf201bba88a 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.5 +LABEL io.airbyte.version=0.2.6 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/backward_compatibility.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/backward_compatibility.py index 461c2b500354..4ea64a86bd9f 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/backward_compatibility.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/backward_compatibility.py @@ -188,7 +188,11 @@ def validate_previous_configs( 2. Validate a fake previous config against the actual connector specification json schema.""" @given(from_schema(previous_connector_spec.dict()["connectionSpecification"])) - @settings(max_examples=number_of_configs_to_generate, verbosity=Verbosity.quiet, suppress_health_check=(HealthCheck.too_slow,)) + @settings( + max_examples=number_of_configs_to_generate, + verbosity=Verbosity.quiet, + suppress_health_check=(HealthCheck.too_slow, HealthCheck.filter_too_much), + ) def check_fake_previous_config_against_actual_spec(fake_previous_config): if isinstance(fake_previous_config, dict): # Looks like hypothesis-jsonschema not only generate dict objects... fake_previous_config = SecretDict(fake_previous_config) From ca198ecf3630c9e924128fef4e0f2001ec092d63 Mon Sep 17 00:00:00 2001 From: Ganpat Agarwal Date: Wed, 12 Oct 2022 23:57:44 +0530 Subject: [PATCH 073/498] update amazon-ads version to 0.1.23 (#17870) --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-amazon-ads/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 56987f690356..214e9aeb405c 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -33,7 +33,7 @@ - name: Amazon Ads sourceDefinitionId: c6b0a29e-1da9-4512-9002-7bfd0cba2246 dockerRepository: airbyte/source-amazon-ads - dockerImageTag: 0.1.22 + dockerImageTag: 0.1.23 documentationUrl: https://docs.airbyte.com/integrations/sources/amazon-ads icon: amazonads.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index cc1181894792..a1107711a5fd 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -647,7 +647,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-amazon-ads:0.1.22" +- dockerImage: "airbyte/source-amazon-ads:0.1.23" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/amazon-ads" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile index 21aba2f05dfa..69f76987802c 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.22 +LABEL io.airbyte.version=0.1.23 LABEL io.airbyte.name=airbyte/source-amazon-ads From eb6a2429ad46c94ca8c613f90ee9c9025f30604a Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Wed, 12 Oct 2022 22:53:38 +0300 Subject: [PATCH 074/498] Source acceptance test: bugfix (#17757) * #17506 SATs: bugfix * #17506 SATs: upd changelog * SATs: bump version --- .../bases/source-acceptance-test/CHANGELOG.md | 3 ++ .../bases/source-acceptance-test/Dockerfile | 2 +- .../tests/test_incremental.py | 34 ++++++------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index dec343110fd6..3c6429a31587 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.7 +Fix a bug when a state is evaluated once before used in a loop of `test_read_sequential_slices` [#17757](https://github.com/airbytehq/airbyte/pull/17757/) + ## 0.2.6 Backward compatibility hypothesis testing: disable "filtering too much" health check. [#17871](https://github.com/airbytehq/airbyte/pull/17871) diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index cbf201bba88a..778684b102f7 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.6 +LABEL io.airbyte.version=0.2.7 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py index 2173e92c8b4e..be31a19af224 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py @@ -176,13 +176,8 @@ def test_read_sequential_slices( ): """ Incremental test that makes calls the read method without a state checkpoint. Then we partition the results by stream and - slice checkpoints resulting in batches of messages that look like: - - - ... - - - Using these batches, we then make additional read method calls using the state message and verify the correctness of the + slice checkpoints. + Then we make additional read method calls using the state message and verify the correctness of the messages in the response. """ if inputs.skip_comprehensive_incremental_tests: @@ -212,15 +207,7 @@ def test_read_sequential_slices( record_value <= state_value ), f"First incremental sync should produce records younger or equal to cursor value from the state. Stream: {stream_name}" - # Create partitions made up of one state message followed by any records that come before the next state - filtered_messages = [message for message in output if message.type == Type.STATE or message.type == Type.RECORD] - right_index = len(filtered_messages) - checkpoint_messages = [] - for index, message in reversed(list(enumerate(filtered_messages))): - if message.type == Type.STATE: - message_group = (filtered_messages[index], filtered_messages[index + 1 : right_index]) - checkpoint_messages.insert(0, message_group) - right_index = index + checkpoint_messages = filter_output(output, type_=Type.STATE) # We sometimes have duplicate identical state messages in a stream which we can filter out to speed things up checkpoint_messages = [message for index, message in enumerate(checkpoint_messages) if message not in checkpoint_messages[:index]] @@ -229,10 +216,10 @@ def test_read_sequential_slices( min_batches_to_test = 10 sample_rate = len(checkpoint_messages) // min_batches_to_test stream_name_to_per_stream_state = dict() - for idx, message_batch in enumerate(checkpoint_messages): - assert len(message_batch) > 0 and message_batch[0].type == Type.STATE + for idx, state_message in enumerate(checkpoint_messages): + assert state_message.type == Type.STATE state_input, complete_state = self.get_next_state_input( - message_batch, latest_state, stream_name_to_per_stream_state, is_per_stream + state_message, stream_name_to_per_stream_state, is_per_stream ) if len(checkpoint_messages) >= min_batches_to_test and idx % sample_rate != 0: @@ -259,15 +246,14 @@ def test_state_with_abnormally_large_values(self, connector_config, configured_c def get_next_state_input( self, - message_batch: List[AirbyteStateMessage], - latest_state: MutableMapping, + state_message: AirbyteStateMessage, stream_name_to_per_stream_state: MutableMapping, is_per_stream, ) -> Tuple[Union[List[MutableMapping], MutableMapping], MutableMapping]: if is_per_stream: # Including all the latest state values from previous batches, update the combined stream state # with the current batch's stream state and then use it in the following read() request - current_state = message_batch[0].state + current_state = state_message.state if current_state and current_state.type == AirbyteStateType.STREAM: per_stream = current_state.stream if per_stream.stream_state: @@ -276,8 +262,8 @@ def get_next_state_input( ) state_input = [ {"type": "STREAM", "stream": {"stream_descriptor": {"name": stream_name}, "stream_state": stream_state}} - for stream_name, stream_state in latest_state.items() + for stream_name, stream_state in stream_name_to_per_stream_state.items() ] return state_input, stream_name_to_per_stream_state else: - return message_batch[0].state.data, message_batch[0].state.data + return state_message.state.data, state_message.state.data From 034b3fb1004f360ef42ca35a55bac9f82971035c Mon Sep 17 00:00:00 2001 From: Greg Solovyev Date: Wed, 12 Oct 2022 13:24:53 -0700 Subject: [PATCH 075/498] Remove unpulbished version of source-amazon-ads from seed file (#17904) --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-amazon-ads/Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 214e9aeb405c..56987f690356 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -33,7 +33,7 @@ - name: Amazon Ads sourceDefinitionId: c6b0a29e-1da9-4512-9002-7bfd0cba2246 dockerRepository: airbyte/source-amazon-ads - dockerImageTag: 0.1.23 + dockerImageTag: 0.1.22 documentationUrl: https://docs.airbyte.com/integrations/sources/amazon-ads icon: amazonads.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index a1107711a5fd..cc1181894792 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -647,7 +647,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-amazon-ads:0.1.23" +- dockerImage: "airbyte/source-amazon-ads:0.1.22" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/amazon-ads" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile index 69f76987802c..21aba2f05dfa 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.23 +LABEL io.airbyte.version=0.1.22 LABEL io.airbyte.name=airbyte/source-amazon-ads From bd0c2388df03efe6df7a722ad5d71f03b93ca8b7 Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Wed, 12 Oct 2022 13:51:55 -0700 Subject: [PATCH 076/498] Destination Clickhouse: Publish normalization after removing native port (#17896) * bump version for publish * add link for publish pr --- .../workers/normalization/NormalizationRunnerFactory.java | 2 +- airbyte-integrations/bases/base-normalization/Dockerfile | 2 +- docs/understanding-airbyte/basic-normalization.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java index 9a5c1b9e6373..cc1530d3fd49 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java @@ -13,7 +13,7 @@ public class NormalizationRunnerFactory { public static final String BASE_NORMALIZATION_IMAGE_NAME = "airbyte/normalization"; - public static final String NORMALIZATION_VERSION = "0.2.22"; + public static final String NORMALIZATION_VERSION = "0.2.23"; static final Map> NORMALIZATION_MAPPING = ImmutableMap.>builder() diff --git a/airbyte-integrations/bases/base-normalization/Dockerfile b/airbyte-integrations/bases/base-normalization/Dockerfile index 5b4de8302135..8e375a1b8de3 100644 --- a/airbyte-integrations/bases/base-normalization/Dockerfile +++ b/airbyte-integrations/bases/base-normalization/Dockerfile @@ -28,5 +28,5 @@ WORKDIR /airbyte ENV AIRBYTE_ENTRYPOINT "/airbyte/entrypoint.sh" ENTRYPOINT ["/airbyte/entrypoint.sh"] -LABEL io.airbyte.version=0.2.22 +LABEL io.airbyte.version=0.2.23 LABEL io.airbyte.name=airbyte/normalization diff --git a/docs/understanding-airbyte/basic-normalization.md b/docs/understanding-airbyte/basic-normalization.md index 26a095eed246..90a684809f6e 100644 --- a/docs/understanding-airbyte/basic-normalization.md +++ b/docs/understanding-airbyte/basic-normalization.md @@ -353,6 +353,7 @@ Therefore, in order to "upgrade" to the desired normalization version, you need | Airbyte Version | Normalization Version | Date | Pull Request | Subject | |:----------------|:----------------------|:-----------|:------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------| +| | 0.2.23 | 2022-10-12 | [\#17483](https://github.com/airbytehq/airbyte/pull/17483) (published in [\#17896](https://github.com/airbytehq/airbyte/pull/17896)) | Remove unnecessary `Native Port` config option | | | 0.2.22 | 2022-09-05 | [\#16339](https://github.com/airbytehq/airbyte/pull/16339) | Update Clickhouse DBT to 1.1.8 | | | 0.2.21 | 2022-09-09 | [\#15833](https://github.com/airbytehq/airbyte/pull/15833/) | SSH Tunnel: allow using OPENSSH key format (published in [\#16545](https://github.com/airbytehq/airbyte/pull/16545)) | | | 0.2.20 | 2022-08-30 | [\#15592](https://github.com/airbytehq/airbyte/pull/15592) | Add TiDB support | From 808af16023940c9d1f68984246590e88f63124ef Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Wed, 12 Oct 2022 14:08:46 -0700 Subject: [PATCH 077/498] Fix Docker image existence check (normalization) (#17908) * fix path to NormalizationRunnerFactory * check for file existence also --- tools/bin/check_images_exist.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/bin/check_images_exist.sh b/tools/bin/check_images_exist.sh index 39a582b2cd4d..efbab4272f04 100755 --- a/tools/bin/check_images_exist.sh +++ b/tools/bin/check_images_exist.sh @@ -29,7 +29,10 @@ checkPlatformImages() { checkNormalizationImages() { # the only way to know what version of normalization the platform is using is looking in NormalizationRunnerFactory. local image_version; - image_version=$(cat airbyte-workers/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java | grep 'NORMALIZATION_VERSION =' | cut -d"=" -f2 | sed 's:;::' | sed -e 's:"::g' | sed -e 's:[[:space:]]::g') + factory_path=airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java + # This check should fail if the file doesn't exist, so just try to ls the file + ls $factory_path > /dev/null + image_version=$(cat $factory_path | grep 'NORMALIZATION_VERSION =' | cut -d"=" -f2 | sed 's:;::' | sed -e 's:"::g' | sed -e 's:[[:space:]]::g') echo "Checking normalization images with version $image_version exist..." VERSION=$image_version docker-compose -f airbyte-integrations/bases/base-normalization/docker-compose.yaml pull || exit 1 echo "Success! All normalization images exist!" From 94038a1864adde3377dedc1acb7ef956a9af82cc Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Wed, 12 Oct 2022 15:05:56 -0700 Subject: [PATCH 078/498] fix formatting (#17913) --- .../source_acceptance_test/tests/test_incremental.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py index be31a19af224..15e2f7c38ae4 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_incremental.py @@ -218,9 +218,7 @@ def test_read_sequential_slices( stream_name_to_per_stream_state = dict() for idx, state_message in enumerate(checkpoint_messages): assert state_message.type == Type.STATE - state_input, complete_state = self.get_next_state_input( - state_message, stream_name_to_per_stream_state, is_per_stream - ) + state_input, complete_state = self.get_next_state_input(state_message, stream_name_to_per_stream_state, is_per_stream) if len(checkpoint_messages) >= min_batches_to_test and idx % sample_rate != 0: continue From a0e1fdf0c72692973779eabe87e456b3fa920c9b Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Wed, 12 Oct 2022 16:53:11 -0700 Subject: [PATCH 079/498] remove logging command line (#17925) --- airbyte-temporal/scripts/build-temporal.sh | 2 -- airbyte-temporal/scripts/update-and-start-temporal.sh | 2 -- 2 files changed, 4 deletions(-) diff --git a/airbyte-temporal/scripts/build-temporal.sh b/airbyte-temporal/scripts/build-temporal.sh index 6a026ac08553..3a9d5b7f873d 100755 --- a/airbyte-temporal/scripts/build-temporal.sh +++ b/airbyte-temporal/scripts/build-temporal.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -set -x - TEMPORAL_VERSION=1.13.0 curl -OL https://github.com/temporalio/temporal/archive/refs/tags/v"$TEMPORAL_VERSION".tar.gz diff --git a/airbyte-temporal/scripts/update-and-start-temporal.sh b/airbyte-temporal/scripts/update-and-start-temporal.sh index a77277f6abf7..64da337936c4 100755 --- a/airbyte-temporal/scripts/update-and-start-temporal.sh +++ b/airbyte-temporal/scripts/update-and-start-temporal.sh @@ -1,7 +1,5 @@ #!/usr/bin/env bash -set -x - DBNAME="${DBNAME:-temporal}" VISIBILITY_DBNAME="${VISIBILITY_DBNAME:-temporal_visibility}" DB_PORT="${DB_PORT:-3306}" From 3c147fbc3298dc81832d377928e5fcb4d094ed38 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Thu, 13 Oct 2022 02:07:25 +0200 Subject: [PATCH 080/498] Return source catalog id after discovery (#17923) * Return source catalog id after discovery * Add unit tests for catalog id in discover schema API response * Unit test fix with discover schema output --- .../java/io/airbyte/server/handlers/SchedulerHandler.java | 1 + .../io/airbyte/server/handlers/SchedulerHandlerTest.java | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java index 7892919d6213..9ab734d073c5 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java @@ -267,6 +267,7 @@ private SourceDiscoverSchemaRead retrieveDiscoveredSchema(final SynchronousRespo final AirbyteCatalog persistenceCatalog = Jsons.object(catalog.getCatalog(), io.airbyte.protocol.models.AirbyteCatalog.class); sourceDiscoverSchemaRead.catalog(CatalogConverter.toApi(persistenceCatalog)); + sourceDiscoverSchemaRead.catalogId(response.getOutput()); } return sourceDiscoverSchemaRead; diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java index d58958c4cfac..f9ad56a8bf5f 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java @@ -385,6 +385,7 @@ void testDiscoverSchemaForSourceFromSourceId() throws IOException, JsonValidatio final SourceDiscoverSchemaRead actual = schedulerHandler.discoverSchemaForSourceFromSourceId(request); assertNotNull(actual.getCatalog()); + assertEquals(actual.getCatalogId(), discoverResponse.getOutput()); assertNotNull(actual.getJobInfo()); assertTrue(actual.getJobInfo().getSucceeded()); verify(configRepository).getSourceConnection(source.getSourceId()); @@ -399,8 +400,9 @@ void testDiscoverSchemaForSourceFromSourceIdCachedCatalog() throws IOException, final SynchronousResponse discoverResponse = (SynchronousResponse) jobResponse; final SynchronousJobMetadata metadata = mock(SynchronousJobMetadata.class); + final UUID thisCatalogId = UUID.randomUUID(); when(discoverResponse.isSuccess()).thenReturn(true); - when(discoverResponse.getOutput()).thenReturn(UUID.randomUUID()); + when(discoverResponse.getOutput()).thenReturn(thisCatalogId); when(discoverResponse.getMetadata()).thenReturn(metadata); when(metadata.isSucceeded()).thenReturn(true); @@ -413,7 +415,7 @@ void testDiscoverSchemaForSourceFromSourceIdCachedCatalog() throws IOException, final ActorCatalog actorCatalog = new ActorCatalog() .withCatalog(Jsons.jsonNode(airbyteCatalog)) .withCatalogHash("") - .withId(UUID.randomUUID()); + .withId(thisCatalogId); when(configRepository.getActorCatalog(any(), any(), any())).thenReturn(Optional.of(actorCatalog)); when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) .thenReturn(discoverResponse); @@ -422,6 +424,7 @@ void testDiscoverSchemaForSourceFromSourceIdCachedCatalog() throws IOException, assertNotNull(actual.getCatalog()); assertNotNull(actual.getJobInfo()); + assertEquals(actual.getCatalogId(), discoverResponse.getOutput()); assertTrue(actual.getJobInfo().getSucceeded()); verify(configRepository).getSourceConnection(source.getSourceId()); verify(configRepository).getActorCatalog(eq(request.getSourceId()), any(), any()); @@ -528,6 +531,7 @@ void testDiscoverSchemaForSourceFromSourceCreate() throws JsonValidationExceptio assertNotNull(actual.getCatalog()); assertNotNull(actual.getJobInfo()); + assertEquals(actual.getCatalogId(), discoverResponse.getOutput()); assertTrue(actual.getJobInfo().getSucceeded()); verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); } From 473b5db43f0c1480b359d3f3792fdef0b1907f0f Mon Sep 17 00:00:00 2001 From: Malik Diarra Date: Wed, 12 Oct 2022 18:01:16 -0700 Subject: [PATCH 081/498] Revert metrics reporter migration to micronaut (#17927) * Revert "improve query performance (#17862)" This reverts commit e0db09bc920f199e791e617ed23a67d24beddaa1. * Revert "fix metric reporter not producing metrics (#17863)" This reverts commit 63e39e6142f8a4accd42994068315168789d41f4. * Revert "convert airbyte-metrics-reporter to micronaut (#17365)" This reverts commit d30de1bdd3059f5ed0f844d05370f6b6876b2a5a. --- .../io/airbyte/metrics/lib/MetricQueries.java | 177 ++++++ .../metrics/lib/OssMetricsRegistry.java | 6 +- .../metrics/lib/MetricsQueriesTest.java | 505 +++++++++++++++++ airbyte-metrics/reporter/build.gradle | 24 +- .../airbyte/metrics/reporter/Application.java | 23 - .../io/airbyte/metrics/reporter/Emitter.java | 193 ------- .../metrics/reporter/EventListeners.java | 56 -- .../metrics/reporter/MetricRepository.java | 175 ------ .../airbyte/metrics/reporter/ReporterApp.java | 68 +++ .../metrics/reporter/ReporterFactory.java | 24 - .../io/airbyte/metrics/reporter/ToEmit.java | 112 ++++ .../src/main/resources/application.yml | 38 -- .../src/main/resources/micronaut-banner.txt | 8 - .../airbyte/metrics/reporter/EmitterTest.java | 156 ------ .../reporter/MetricRepositoryTest.java | 506 ------------------ 15 files changed, 867 insertions(+), 1204 deletions(-) delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java delete mode 100644 airbyte-metrics/reporter/src/main/resources/application.yml delete mode 100644 airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt delete mode 100644 airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java delete mode 100644 airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java index bb4eeaaa6347..4a83550a02e8 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java @@ -6,11 +6,19 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; +import static org.jooq.impl.SQLDataType.VARCHAR; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; +import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.jooq.DSLContext; /** @@ -49,4 +57,173 @@ public static List srcIdAndDestIdToReleaseStages(final DSLContext .or(ACTOR.ID.eq(dstId)).fetch().getValues(ACTOR_DEFINITION.RELEASE_STAGE); } + public static int numberOfPendingJobs(final DSLContext ctx) { + return ctx.selectCount().from(JOBS).where(JOBS.STATUS.eq(JobStatus.pending)).fetchOne(0, int.class); + } + + public static int numberOfRunningJobs(final DSLContext ctx) { + return ctx.selectCount().from(JOBS).join(CONNECTION).on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) + .fetchOne(0, int.class); + } + + public static int numberOfOrphanRunningJobs(final DSLContext ctx) { + return ctx.selectCount().from(JOBS).join(CONNECTION).on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.ne(StatusType.active))) + .fetchOne(0, int.class); + } + + public static Long oldestPendingJobAgeSecs(final DSLContext ctx) { + return oldestJobAgeSecs(ctx, JobStatus.pending); + } + + public static Long oldestRunningJobAgeSecs(final DSLContext ctx) { + return oldestJobAgeSecs(ctx, JobStatus.running); + } + + private static Long oldestJobAgeSecs(final DSLContext ctx, final JobStatus status) { + final var readableTimeField = "run_duration"; + final var durationSecField = "run_duration_secs"; + final var query = String.format(""" + WITH + oldest_job AS ( + SELECT id, + age(current_timestamp, created_at) AS %s + FROM jobs + WHERE status = '%s' + ORDER BY run_duration DESC + LIMIT 1) + SELECT id, + run_duration, + extract(epoch from run_duration) as %s + FROM oldest_job""", readableTimeField, status.getLiteral(), durationSecField); + final var res = ctx.fetch(query); + // unfortunately there are no good Jooq methods for retrieving a single record of a single column + // forcing the List cast. + final var duration = res.getValues(durationSecField, Double.class); + + if (duration.size() == 0) { + return 0L; + } + // .get(0) works in the following code due to the query's SELECT 1. + final var id = res.getValues("id", String.class).get(0); + final var readableTime = res.getValues(readableTimeField, String.class).get(0); + log.info("oldest job information - id: {}, readable time: {}", id, readableTime); + + // as double can have rounding errors, round down to remove noise. + return duration.get(0).longValue(); + } + + public static List numberOfActiveConnPerWorkspace(final DSLContext ctx) { + final var countField = "num_conn"; + final var query = String.format(""" + SELECT workspace_id, count(c.id) as %s + FROM actor + INNER JOIN workspace ws ON actor.workspace_id = ws.id + INNER JOIN connection c ON actor.id = c.source_id + WHERE ws.tombstone = false + AND actor.tombstone = false AND actor.actor_type = 'source' + AND c.status = 'active' + GROUP BY workspace_id;""", countField); + return ctx.fetch(query).getValues(countField, long.class); + } + + public static List> overallJobRuntimeForTerminalJobsInLastHour(final DSLContext ctx) { + final var statusField = "status"; + final var timeField = "sec"; + final var query = + String.format(""" + SELECT %s, extract(epoch from age(updated_at, created_at)) AS %s FROM jobs + WHERE updated_at >= NOW() - INTERVAL '1 HOUR' + AND (jobs.status = 'failed' OR jobs.status = 'succeeded' OR jobs.status = 'cancelled');""", statusField, timeField); + final var statuses = ctx.fetch(query).getValues(statusField, JobStatus.class); + final var times = ctx.fetch(query).getValues(timeField, double.class); + + final var pairedRes = new ArrayList>(); + for (int i = 0; i < statuses.size(); i++) { + final var pair = new ImmutablePair<>(statuses.get(i), times.get(i)); + pairedRes.add(pair); + } + + return pairedRes; + } + + /* + * A connection that is not running on schedule is defined in last 24 hours if the number of runs + * are not matching with the number of expected runs according to the schedule settings. Refer to + * runbook for detailed discussion. + * + */ + public static Long numberOfJobsNotRunningOnScheduleInLastDay(final DSLContext ctx) { + final var countField = "cnt"; + // This query finds all sync jobs ran in last 24 hours and count how many times they have run. + // Comparing this to the expected number of runs (24 hours divide by configured cadence in hours), + // if it runs below that expected number it will be considered as abnormal instance. + // For example, if it's configured to run every 6 hours but in last 24 hours it only has 3 runs, + // it will be considered as 1 abnormal instance. + final var queryForAbnormalSyncInHoursInLastDay = + String.format(""" + select count(1) as %s + from + ( + select + c.id, + count(*) as cnt + from + connection c + left join Jobs j on + j.scope::uuid = c.id + where + c.schedule is not null + and c.schedule != 'null' + and j.created_at > now() - interval '24 hours 1 minutes' + and c.status = 'active' + and j.config_type = 'sync' + and c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) = '"hours"' + group by 1 + having count(*) < 24 / cast(c.schedule::jsonb->'units' as integer)) as abnormal_jobs + """, countField); + + // Similar to the query above, this finds if the connection cadence's timeUnit is minutes. + // thus we use 1440 (=24 hours x 60 minutes) to divide the configured cadence. + final var queryForAbnormalSyncInMinutesInLastDay = + String.format(""" + select count(1) as %s from ( + select + c.id, + count(*) as cnt + from + connection c + left join Jobs j on + j.scope::uuid = c.id + where + c.schedule is not null + and c.schedule != 'null' + and j.created_at > now() - interval '24 hours 1 minutes' + and c.status = 'active' + and j.config_type = 'sync' + and c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) = '"minutes"' + group by 1 + having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer)) as abnormal_jobs + """, countField); + return ctx.fetch(queryForAbnormalSyncInHoursInLastDay).getValues(countField, long.class).get(0) + + ctx.fetch(queryForAbnormalSyncInMinutesInLastDay).getValues(countField, long.class).get(0); + } + + public static Long numScheduledActiveConnectionsInLastDay(final DSLContext ctx) { + final var countField = "cnt"; + final var queryForTotalConnections = String.format(""" + select count(1) as %s + from connection c + where + c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) IN ('"hours"', '"minutes"') + and c.status = 'active' + """, countField); + + return ctx.fetch(queryForTotalConnections).getValues(countField, long.class).get(0); + } + } diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java index 75e30ebf335d..67c750bd49f1 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java @@ -9,15 +9,15 @@ /** * Enum source of truth of all Airbyte metrics. Each enum value represent a metric and is linked to * an application and contains a description to make it easier to understand. - *

    + * * Each object of the enum actually represent a metric, so the Registry name is misleading. The * reason 'Registry' is in the name is to emphasize this enum's purpose as a source of truth for all * metrics. This also helps code readability i.e. AirbyteMetricsRegistry.metricA. - *

    + * * Metric Name Convention (adapted from * https://docs.datadoghq.com/developers/guide/what-best-practices-are-recommended-for-naming-metrics-and-tags/): *

    - * - Use lowercase. Metric names are case-sensitive. + * - Use lowercase. Metric names are case sensitive. *

    * - Use underscore to delimit names with multiple words. *

    diff --git a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java index ae94b43f07d5..7184b892ab04 100644 --- a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java +++ b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java @@ -15,6 +15,7 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; @@ -22,13 +23,19 @@ import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; +import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; import io.airbyte.db.instance.test.TestDatabaseProviders; import io.airbyte.test.utils.DatabaseConnectionHelper; import java.io.IOException; import java.sql.SQLException; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; import javax.sql.DataSource; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.jooq.DSLContext; import org.jooq.JSONB; import org.jooq.SQLDialect; @@ -165,4 +172,502 @@ void shouldReturnNothingIfNotApplicable() throws SQLException { } + @Nested + class numJobs { + + @AfterEach + void tearDown() throws SQLException { + configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); + } + + @Test + void runningJobsShouldReturnCorrectCount() throws SQLException { + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) + .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) + .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) + .execute()); + final UUID activeConnectionId = UUID.randomUUID(); + final UUID inactiveConnectionId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, + CONNECTION.DESTINATION_ID, CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL) + .values(activeConnectionId, StatusType.active, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) + .values(inactiveConnectionId, StatusType.inactive, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) + .execute()); + + // non-pending jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, activeConnectionId.toString(), JobStatus.pending).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, activeConnectionId.toString(), JobStatus.failed).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, activeConnectionId.toString(), JobStatus.running).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, activeConnectionId.toString(), JobStatus.running).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(5L, inactiveConnectionId.toString(), JobStatus.running).execute()); + + assertEquals(2, configDb.query(MetricQueries::numberOfRunningJobs)); + assertEquals(1, configDb.query(MetricQueries::numberOfOrphanRunningJobs)); + } + + @Test + void runningJobsShouldReturnZero() throws SQLException { + // non-pending jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); + + final var res = configDb.query(MetricQueries::numberOfRunningJobs); + assertEquals(0, res); + } + + @Test + void pendingJobsShouldReturnCorrectCount() throws SQLException { + // non-pending jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.running).execute()); + + final var res = configDb.query(MetricQueries::numberOfPendingJobs); + assertEquals(2, res); + } + + @Test + void pendingJobsShouldReturnZero() throws SQLException { + // non-pending jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.running).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); + + final var res = configDb.query(MetricQueries::numberOfPendingJobs); + assertEquals(0, res); + } + + } + + @Nested + class oldestPendingJob { + + @AfterEach + void tearDown() throws SQLException { + configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); + } + + @Test + @DisplayName("should return only the pending job's age in seconds") + void shouldReturnOnlyPendingSeconds() throws SQLException { + final var expAgeSecs = 1000; + final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + // oldest pending job + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(1L, "", JobStatus.pending, oldestCreateAt) + .execute()); + // second oldest pending job + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(2L, "", JobStatus.pending, OffsetDateTime.now()) + .execute()); + // non-pending jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.running).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.failed).execute()); + + final var res = configDb.query(MetricQueries::oldestPendingJobAgeSecs); + // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(List.of(999L, 1000L, 1001L).contains(res)); + } + + @Test + @DisplayName(DISPLAY_NAME) + void shouldReturnNothingIfNotApplicable() throws SQLException { + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.succeeded).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.running).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.failed).execute()); + + final var res = configDb.query(MetricQueries::oldestPendingJobAgeSecs); + assertEquals(0L, res); + } + + } + + @Nested + class oldestRunningJob { + + @AfterEach + void tearDown() throws SQLException { + configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); + } + + @Test + @DisplayName("should return only the running job's age in seconds") + void shouldReturnOnlyRunningSeconds() throws SQLException { + final var expAgeSecs = 10000; + final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + // oldest pending job + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(1L, "", JobStatus.running, oldestCreateAt) + .execute()); + // second oldest pending job + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(2L, "", JobStatus.running, OffsetDateTime.now()) + .execute()); + // non-pending jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.failed).execute()); + + final var res = configDb.query(MetricQueries::oldestRunningJobAgeSecs); + // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); + } + + @Test + @DisplayName(DISPLAY_NAME) + void shouldReturnNothingIfNotApplicable() throws SQLException { + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.succeeded).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.pending).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.failed).execute()); + + final var res = configDb.query(MetricQueries::oldestRunningJobAgeSecs); + assertEquals(0L, res); + } + + } + + @Nested + class numActiveConnPerWorkspace { + + @AfterEach + void tearDown() throws SQLException { + configDb.transaction(ctx -> ctx.truncate(CONNECTION).cascade().execute()); + configDb.transaction(ctx -> ctx.truncate(ACTOR).cascade().execute()); + configDb.transaction(ctx -> ctx.truncate(WORKSPACE).cascade().execute()); + } + + @Test + @DisplayName("should return only connections per workspace") + void shouldReturnNumConnectionsBasic() throws SQLException { + final var workspaceId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", false) + .execute()); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx + .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute()); + + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .execute()); + + final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); + assertEquals(1, res.size()); + assertEquals(2, res.get(0)); + } + + @Test + @DisplayName("should ignore deleted connections") + void shouldIgnoreNonRunningConnections() throws SQLException { + final var workspaceId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", false) + .execute()); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx + .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute()); + + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.deprecated) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.inactive) + .execute()); + + final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); + assertEquals(1, res.size()); + assertEquals(2, res.get(0)); + } + + @Test + @DisplayName("should ignore deleted connections") + void shouldIgnoreDeletedWorkspaces() throws SQLException { + final var workspaceId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", true) + .execute()); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + configDb.transaction( + ctx -> ctx + .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute()); + + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .execute()); + + final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); + assertEquals(0, res.size()); + } + + @Test + @DisplayName(DISPLAY_NAME) + void shouldReturnNothingIfNotApplicable() throws SQLException { + final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); + assertEquals(0, res.size()); + } + + } + + @Nested + class overallJobRuntimeForTerminalJobsInLastHour { + + @AfterEach + void tearDown() throws SQLException { + configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); + } + + @Test + @DisplayName("should ignore non terminal jobs") + void shouldIgnoreNonTerminalJobs() throws SQLException { + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.running).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.incomplete).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); + + final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); + assertEquals(0, res.size()); + } + + @Test + @DisplayName("should ignore jobs older than 1 hour") + void shouldIgnoreJobsOlderThan1Hour() throws SQLException { + final var updateAt = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.UPDATED_AT).values(1L, "", JobStatus.succeeded, updateAt).execute()); + + final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); + assertEquals(0, res.size()); + } + + @Test + @DisplayName("should return correct duration for terminal jobs") + void shouldReturnTerminalJobs() throws SQLException { + final var updateAt = OffsetDateTime.now(); + final var expAgeSecs = 10000; + final var createAt = updateAt.minus(expAgeSecs, ChronoUnit.SECONDS); + + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(1L, "", JobStatus.succeeded, createAt, updateAt).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(2L, "", JobStatus.failed, createAt, updateAt).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(3L, "", JobStatus.cancelled, createAt, updateAt).execute()); + + final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); + assertEquals(3, res.size()); + + final var exp = List.of( + new ImmutablePair<>(JobStatus.succeeded, expAgeSecs * 1.0), + new ImmutablePair<>(JobStatus.cancelled, expAgeSecs * 1.0), + new ImmutablePair<>(JobStatus.failed, expAgeSecs * 1.0)); + assertTrue(res.containsAll(exp) && exp.containsAll(res)); + } + + @Test + @DisplayName("should return correct duration for jobs that terminated in the last hour") + void shouldReturnTerminalJobsComplex() throws SQLException { + final var updateAtNow = OffsetDateTime.now(); + final var expAgeSecs = 10000; + final var createAt = updateAtNow.minus(expAgeSecs, ChronoUnit.SECONDS); + + // terminal jobs in last hour + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(1L, "", JobStatus.succeeded, createAt, updateAtNow).execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(2L, "", JobStatus.failed, createAt, updateAtNow).execute()); + + // old terminal jobs + final var updateAtOld = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(3L, "", JobStatus.cancelled, createAt, updateAtOld).execute()); + + // non-terminal jobs + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + .values(4L, "", JobStatus.running, createAt).execute()); + + final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); + assertEquals(2, res.size()); + + final var exp = List.of( + new ImmutablePair<>(JobStatus.succeeded, expAgeSecs * 1.0), + new ImmutablePair<>(JobStatus.failed, expAgeSecs * 1.0)); + assertTrue(res.containsAll(exp) && exp.containsAll(res)); + } + + @Test + @DisplayName(DISPLAY_NAME) + void shouldReturnNothingIfNotApplicable() throws SQLException { + final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); + assertEquals(0, res.size()); + } + + } + + @Nested + class AbnormalJobsInLastDay { + + @AfterEach + void tearDown() throws SQLException { + configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); + configDb.transaction(ctx -> ctx.truncate(CONNECTION).cascade().execute()); + } + + @Test + @DisplayName("should return correct number for abnormal jobs") + void shouldCountInJobsWithMissingRun() throws SQLException { + final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); + final var connectionId = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(connectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 6, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) + .execute()); + + // Jobs running in prior day will not be counted + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), updateAt, syncConfigType) + .execute()); + + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, syncConfigType) + .execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, syncConfigType) + .execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(5, ChronoUnit.HOURS), updateAt, syncConfigType) + .execute()); + + final var totalConnectionResult = configDb.query(MetricQueries::numScheduledActiveConnectionsInLastDay); + assertEquals(1, totalConnectionResult); + + final var abnormalConnectionResult = configDb.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); + assertEquals(1, abnormalConnectionResult); + } + + @Test + @DisplayName("normal jobs should not be counted") + void shouldNotCountNormalJobsInAbnormalMetric() throws SQLException { + final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); + final var inactiveConnectionId = UUID.randomUUID(); + final var activeConnectionId = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(inactiveConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.inactive, updateAt, updateAt) + .execute()); + + configDb.transaction( + ctx -> ctx + .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(activeConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) + .execute()); + + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(1L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, + syncConfigType) + .execute()); + configDb.transaction( + ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(2L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, + syncConfigType) + .execute()); + + final var totalConnectionResult = configDb.query(MetricQueries::numScheduledActiveConnectionsInLastDay); + assertEquals(1, totalConnectionResult); + + final var abnormalConnectionResult = configDb.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); + assertEquals(0, abnormalConnectionResult); + } + + } + } diff --git a/airbyte-metrics/reporter/build.gradle b/airbyte-metrics/reporter/build.gradle index 22885a77de9f..806d48a52545 100644 --- a/airbyte-metrics/reporter/build.gradle +++ b/airbyte-metrics/reporter/build.gradle @@ -2,38 +2,18 @@ plugins { id 'application' } -configurations { - jdbc -} - dependencies { - annotationProcessor platform(libs.micronaut.bom) - annotationProcessor libs.bundles.micronaut.annotation.processor - - implementation platform(libs.micronaut.bom) - implementation libs.bundles.micronaut + implementation libs.flyway.core implementation project(':airbyte-config:config-models') implementation project(':airbyte-db:jooq') implementation project(':airbyte-db:db-lib') implementation project(':airbyte-metrics:metrics-lib') - - implementation(libs.jooq) { - force = true - } - - testAnnotationProcessor platform(libs.micronaut.bom) - testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor - - testImplementation project(':airbyte-test-utils') - testImplementation libs.bundles.micronaut.test - testImplementation libs.postgresql - testImplementation libs.platform.testcontainers.postgresql } application { applicationName = "airbyte-metrics-reporter" - mainClass = 'io.airbyte.metrics.reporter.Application' + mainClass = 'io.airbyte.metrics.reporter.ReporterApp' applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0'] } diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java deleted file mode 100644 index f8af2de13b23..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.metrics.lib.MetricClientFactory; -import io.airbyte.metrics.lib.MetricEmittingApps; -import io.micronaut.runtime.Micronaut; - -/** - * Metric Reporter application. - *

    - * Responsible for emitting metric information on a periodic basis. - */ -public class Application { - - public static void main(final String[] args) { - MetricClientFactory.initialize(MetricEmittingApps.METRICS_REPORTER); - Micronaut.run(Application.class, args); - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java deleted file mode 100644 index 5e21723ad3e4..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.metrics.lib.MetricAttribute; -import io.airbyte.metrics.lib.MetricClient; -import io.airbyte.metrics.lib.MetricTags; -import io.airbyte.metrics.lib.OssMetricsRegistry; -import jakarta.inject.Singleton; -import java.lang.invoke.MethodHandles; -import java.time.Duration; -import java.util.concurrent.Callable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Singleton -final class NumPendingJobs extends Emitter { - - public NumPendingJobs(final MetricClient client, final MetricRepository db) { - super(() -> { - final var pending = db.numberOfPendingJobs(); - client.gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pending); - return null; - }); - } - -} - -@Singleton -final class NumRunningJobs extends Emitter { - - public NumRunningJobs(final MetricClient client, final MetricRepository db) { - super(() -> { - final var running = db.numberOfRunningJobs(); - client.gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, running); - return null; - }); - } - -} - -@Singleton -final class NumOrphanRunningJobs extends Emitter { - - NumOrphanRunningJobs(final MetricClient client, final MetricRepository db) { - super(() -> { - final var orphaned = db.numberOfOrphanRunningJobs(); - client.gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, orphaned); - return null; - }); - } - -} - -@Singleton -final class OldestRunningJob extends Emitter { - - OldestRunningJob(final MetricClient client, final MetricRepository db) { - super(() -> { - final var age = db.oldestRunningJobAgeSecs(); - client.gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); - return null; - }); - } - -} - -@Singleton -final class OldestPendingJob extends Emitter { - - OldestPendingJob(final MetricClient client, final MetricRepository db) { - super(() -> { - final var age = db.oldestPendingJobAgeSecs(); - client.gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); - return null; - }); - } - -} - -@Singleton -final class NumActiveConnectionsPerWorkspace extends Emitter { - - NumActiveConnectionsPerWorkspace(final MetricClient client, final MetricRepository db) { - super(() -> { - final var workspaceConns = db.numberOfActiveConnPerWorkspace(); - for (final long numCons : workspaceConns) { - client.distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, numCons); - } - return null; - }); - } - -} - -@Singleton -final class NumAbnormalScheduledSyncs extends Emitter { - - NumAbnormalScheduledSyncs(final MetricClient client, final MetricRepository db) { - super(() -> { - final var count = db.numberOfJobsNotRunningOnScheduleInLastDay(); - client.gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); - return null; - }); - } - - @Override - public Duration getDuration() { - return Duration.ofHours(1); - } - -} - -@Singleton -final class TotalScheduledSyncs extends Emitter { - - TotalScheduledSyncs(final MetricClient client, final MetricRepository db) { - super(() -> { - final var count = db.numScheduledActiveConnectionsInLastDay(); - client.gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); - return null; - }); - } - - @Override - public Duration getDuration() { - return Duration.ofHours(1); - } - -} - -@Singleton -final class TotalJobRuntimeByTerminalState extends Emitter { - - public TotalJobRuntimeByTerminalState(final MetricClient client, final MetricRepository db) { - super(() -> { - db.overallJobRuntimeForTerminalJobsInLastHour() - .forEach((jobStatus, time) -> client.distribution( - OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, - time, - new MetricAttribute(MetricTags.JOB_STATUS, jobStatus.getLiteral()))); - return null; - }); - } - - @Override - public Duration getDuration() { - return Duration.ofHours(1); - } - -} - -/** - * Abstract base class for all emitted metrics. - *

    - * As this is a sealed class, all implementations of it are contained within this same file. - */ -sealed class Emitter { - - protected static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - protected final Callable callable; - - Emitter(final Callable callable) { - this.callable = callable; - } - - /** - * Emit the metrics by calling the callable. - *

    - * Any exception thrown by the callable will be logged. - * - * @TODO: replace log message with a published error-event of some kind. - */ - public void Emit() { - try { - callable.call(); - } catch (final Exception e) { - log.error("Exception querying database for metric: ", e); - } - } - - /** - * How often this metric should report, defaults to 15s if not overwritten. - * - * @return Duration of how often this metric should report. - */ - public Duration getDuration() { - return Duration.ofSeconds(15); - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java deleted file mode 100644 index 4bf30ef92cbc..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.micronaut.runtime.event.ApplicationShutdownEvent; -import io.micronaut.runtime.event.ApplicationStartupEvent; -import io.micronaut.runtime.event.annotation.EventListener; -import jakarta.inject.Singleton; -import java.lang.invoke.MethodHandles; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * EventListeners registers event listeners for the startup and shutdown events from Micronaut. - */ -@Singleton -class EventListeners { - - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - private final List emitters; - private final ScheduledExecutorService executor; - - EventListeners(final List emitters) { - this.emitters = emitters; - this.executor = Executors.newScheduledThreadPool(emitters.size()); - } - - /** - * Manually registers all the emitters to run on startup. - * - * @param event unused but required in order to listen to the startup event. - */ - @EventListener - public void startEmitters(final ApplicationStartupEvent event) { - emitters.forEach(emitter -> executor.scheduleAtFixedRate(emitter::Emit, 0, emitter.getDuration().getSeconds(), TimeUnit.SECONDS)); - log.info("registered {} emitters", emitters.size()); - } - - /** - * Attempts to cleanly shutdown the running emitters - * - * @param event unused but required in order to listen to the shutdown event. - */ - @EventListener - public void stopEmitters(final ApplicationShutdownEvent event) { - log.info("shutting down emitters"); - executor.shutdown(); - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java deleted file mode 100644 index 8dda0ba29869..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; -import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; -import static org.jooq.impl.SQLDataType.VARCHAR; - -import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import jakarta.inject.Singleton; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.jooq.DSLContext; - -@Singleton -class MetricRepository { - - private final DSLContext ctx; - - MetricRepository(final DSLContext ctx) { - this.ctx = ctx; - } - - int numberOfPendingJobs() { - return ctx.selectCount() - .from(JOBS) - .where(JOBS.STATUS.eq(JobStatus.pending)) - .fetchOne(0, int.class); - } - - int numberOfRunningJobs() { - return ctx.selectCount() - .from(JOBS) - .join(CONNECTION) - .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) - .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) - .fetchOne(0, int.class); - } - - int numberOfOrphanRunningJobs() { - return ctx.selectCount() - .from(JOBS) - .join(CONNECTION) - .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) - .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.ne(StatusType.active))) - .fetchOne(0, int.class); - } - - long oldestPendingJobAgeSecs() { - return oldestJobAgeSecs(JobStatus.pending); - } - - long oldestRunningJobAgeSecs() { - return oldestJobAgeSecs(JobStatus.running); - } - - List numberOfActiveConnPerWorkspace() { - final var query = """ - SELECT workspace_id, count(c.id) as num_conn - FROM actor - INNER JOIN workspace ws ON actor.workspace_id = ws.id - INNER JOIN connection c ON actor.id = c.source_id - WHERE ws.tombstone = false - AND actor.tombstone = false AND actor.actor_type = 'source' - AND c.status = 'active' - GROUP BY workspace_id; - """; - return ctx.fetch(query).getValues("num_conn", long.class); - } - - long numScheduledActiveConnectionsInLastDay() { - final var queryForTotalConnections = """ - select count(1) as connection_count - from connection c - where - c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) IN ('"hours"', '"minutes"') - and c.status = 'active' - """; - - return ctx.fetchOne(queryForTotalConnections).get("connection_count", long.class); - } - - long numberOfJobsNotRunningOnScheduleInLastDay() { - // This query finds all sync jobs ran in last 24 hours and count how many times they have run. - // Comparing this to the expected number of runs (24 hours divide by configured cadence in hours), - // if it runs below that expected number it will be considered as abnormal instance. - // For example, if it's configured to run every 6 hours but in last 24 hours it only has 3 runs, - // it will be considered as 1 abnormal instance. - final var queryForAbnormalSyncInHoursInLastDay = """ - select count(1) as cnt - from ( - select - c.id, - count(*) as cnt - from connection c - left join jobs j on j.scope::uuid = c.id - where - c.schedule is not null - and c.schedule != 'null' - and j.created_at > now() - interval '24 hours 1 minutes' - and c.status = 'active' - and j.config_type = 'sync' - and c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) = '"hours"' - group by 1 - having count(*) < 24 / cast(c.schedule::jsonb->'units' as integer) - ) as abnormal_jobs - """; - - // Similar to the query above, this finds if the connection cadence's timeUnit is minutes. - // thus we use 1440 (=24 hours x 60 minutes) to divide the configured cadence. - final var queryForAbnormalSyncInMinutesInLastDay = """ - select count(1) as cnt - from ( - select - c.id, - count(*) as cnt - from - connection c - left join Jobs j on - j.scope::uuid = c.id - where - c.schedule is not null - and c.schedule != 'null' - and j.created_at > now() - interval '24 hours 1 minutes' - and c.status = 'active' - and j.config_type = 'sync' - and c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) = '"minutes"' - group by 1 - having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer) - ) as abnormal_jobs - """; - return ctx.fetchOne(queryForAbnormalSyncInHoursInLastDay).get("cnt", long.class) - + ctx.fetchOne(queryForAbnormalSyncInMinutesInLastDay).get("cnt", long.class); - } - - Map overallJobRuntimeForTerminalJobsInLastHour() { - final var query = """ - SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs - WHERE updated_at >= NOW() - INTERVAL '1 HOUR' - AND jobs.status IN ('failed', 'succeeded', 'cancelled'); - """; - final var queryResults = ctx.fetch(query); - final var statuses = queryResults.getValues("status", JobStatus.class); - final var times = queryResults.getValues("sec", double.class); - - final var results = new HashMap(); - for (int i = 0; i < statuses.size(); i++) { - results.put(statuses.get(i), times.get(i)); - } - - return results; - } - - private long oldestJobAgeSecs(final JobStatus status) { - final var query = """ - SELECT id, EXTRACT(EPOCH FROM (current_timestamp - created_at)) AS run_duration_seconds - FROM jobs WHERE status = ?::job_status - ORDER BY created_at ASC limit 1; - """; - final var result = ctx.fetchOne(query, status.getLiteral()); - if (result == null) { - return 0L; - } - // as double can have rounding errors, round down to remove noise. - return result.getValue("run_duration_seconds", Double.class).longValue(); - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java new file mode 100644 index 000000000000..17d8331dcc7b --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.commons.lang.CloseableShutdownHook; +import io.airbyte.config.Configs; +import io.airbyte.config.EnvConfigs; +import io.airbyte.db.Database; +import io.airbyte.db.check.DatabaseCheckException; +import io.airbyte.db.factory.DSLContextFactory; +import io.airbyte.db.factory.DataSourceFactory; +import io.airbyte.db.factory.DatabaseCheckFactory; +import io.airbyte.db.factory.DatabaseDriver; +import io.airbyte.db.factory.FlywayFactory; +import io.airbyte.db.instance.configs.ConfigsDatabaseMigrator; +import io.airbyte.metrics.lib.MetricClientFactory; +import io.airbyte.metrics.lib.MetricEmittingApps; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import javax.sql.DataSource; +import lombok.extern.slf4j.Slf4j; +import org.flywaydb.core.Flyway; +import org.jooq.DSLContext; +import org.jooq.SQLDialect; + +@Slf4j +public class ReporterApp { + + public static Database configDatabase; + + public static void main(final String[] args) throws DatabaseCheckException { + final Configs configs = new EnvConfigs(); + + MetricClientFactory.initialize(MetricEmittingApps.METRICS_REPORTER); + + final DataSource dataSource = DataSourceFactory.create( + configs.getConfigDatabaseUser(), + configs.getConfigDatabasePassword(), + DatabaseDriver.POSTGRESQL.getDriverClassName(), + configs.getConfigDatabaseUrl()); + + try (final DSLContext dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES)) { + + final Flyway flyway = FlywayFactory.create(dataSource, ReporterApp.class.getSimpleName(), + ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); + + // Ensure that the database resources are closed on application shutdown + CloseableShutdownHook.registerRuntimeShutdownHook(dataSource, dslContext); + + // Ensure that the Configuration database is available + DatabaseCheckFactory.createConfigsDatabaseMigrationCheck(dslContext, flyway, configs.getConfigsDatabaseMinimumFlywayMigrationVersion(), + configs.getConfigsDatabaseInitializationTimeoutMs()).check(); + + configDatabase = new Database(dslContext); + + final var toEmits = ToEmit.values(); + final var pollers = Executors.newScheduledThreadPool(toEmits.length); + + log.info("Scheduling {} metrics for emission..", toEmits.length); + for (final ToEmit toEmit : toEmits) { + pollers.scheduleAtFixedRate(toEmit.emit, 0, toEmit.duration.getSeconds(), TimeUnit.SECONDS); + } + } + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java deleted file mode 100644 index 2e5683fb55b1..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.metrics.lib.MetricClient; -import io.airbyte.metrics.lib.MetricClientFactory; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - -/** - * Micronaut factory for creating the appropriate singletons utilized by the metric reporter - * service. - */ -@Factory -class ReporterFactory { - - @Singleton - public MetricClient metricClient() { - return MetricClientFactory.getMetricClient(); - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java new file mode 100644 index 000000000000..3313a73da4bc --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import io.airbyte.metrics.lib.MetricAttribute; +import io.airbyte.metrics.lib.MetricClientFactory; +import io.airbyte.metrics.lib.MetricQueries; +import io.airbyte.metrics.lib.MetricTags; +import io.airbyte.metrics.lib.OssMetricsRegistry; +import java.lang.invoke.MethodHandles; +import java.time.Duration; +import java.util.concurrent.Callable; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class contains all metrics emitted by the {@link ReporterApp}. + */ +public enum ToEmit { + + NUM_PENDING_JOBS(countMetricEmission(() -> { + final var pendingJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfPendingJobs); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pendingJobs); + return null; + })), + NUM_RUNNING_JOBS(countMetricEmission(() -> { + final var runningJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfRunningJobs); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, runningJobs); + return null; + })), + NUM_ORPHAN_RUNNING_JOB(countMetricEmission(() -> { + final var orphanRunningJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfOrphanRunningJobs); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, orphanRunningJobs); + return null; + })), + OLDEST_RUNNING_JOB_AGE_SECS(countMetricEmission(() -> { + final var age = ReporterApp.configDatabase.query(MetricQueries::oldestRunningJobAgeSecs); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); + return null; + })), + OLDEST_PENDING_JOB_AGE_SECS(countMetricEmission(() -> { + final var age = ReporterApp.configDatabase.query(MetricQueries::oldestPendingJobAgeSecs); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); + return null; + })), + NUM_ACTIVE_CONN_PER_WORKSPACE(countMetricEmission(() -> { + final var age = ReporterApp.configDatabase.query(MetricQueries::numberOfActiveConnPerWorkspace); + for (final long count : age) { + MetricClientFactory.getMetricClient().distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, count); + } + return null; + })), + NUM_ABNORMAL_SCHEDULED_SYNCS_LAST_DAY(Duration.ofHours(1), countMetricEmission(() -> { + final var count = ReporterApp.configDatabase.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); + return null; + })), + NUM_TOTAL_SCHEDULED_SYNCS_LAST_DAY(Duration.ofHours(1), countMetricEmission(() -> { + final var count = ReporterApp.configDatabase.query(MetricQueries::numScheduledActiveConnectionsInLastDay); + MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); + return null; + })), + OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS(Duration.ofHours(1), countMetricEmission(() -> { + final var times = ReporterApp.configDatabase.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); + for (final Pair pair : times) { + MetricClientFactory.getMetricClient().distribution( + OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, pair.getRight(), + new MetricAttribute(MetricTags.JOB_STATUS, MetricTags.getJobStatus(pair.getLeft()))); + } + return null; + })); + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + // default constructor + /** A runnable that emits a metric. */ + final public Runnable emit; + /** How often this metric would emit data. */ + final public Duration duration; + + ToEmit(final Runnable emit) { + this(Duration.ofSeconds(15), emit); + } + + ToEmit(final Duration duration, final Runnable emit) { + this.duration = duration; + this.emit = emit; + } + + /** + * Wrapper callable to handle 1) query exception logging and 2) counting metric emissions so + * reporter app can be monitored too. + * + * @param metricQuery + * @return + */ + private static Runnable countMetricEmission(final Callable metricQuery) { + return () -> { + try { + metricQuery.call(); + MetricClientFactory.getMetricClient().count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } catch (final Exception e) { + log.error("Exception querying database for metric: ", e); + } + }; + } + +} diff --git a/airbyte-metrics/reporter/src/main/resources/application.yml b/airbyte-metrics/reporter/src/main/resources/application.yml deleted file mode 100644 index 49dd8cb8d7ae..000000000000 --- a/airbyte-metrics/reporter/src/main/resources/application.yml +++ /dev/null @@ -1,38 +0,0 @@ -micronaut: - application: - name: airbyte-metrics-reporter - security: - intercept-url-map: - - pattern: /** - httpMethod: GET - access: - - isAnonymous() - server: - port: 9000 - -datasources: - config: - connection-test-query: SELECT 1 - connection-timeout: 30000 - idle-timeout: 600000 - maximum-pool-size: 10 - url: ${DATABASE_URL} - driverClassName: org.postgresql.Driver - username: ${DATABASE_USER} - password: ${DATABASE_PASSWORD} - -jooq: - datasources: - config: - jackson-converter-enabled: true - sql-dialect: POSTGRES - -endpoints: - all: - enabled: true - -logger: - levels: - io.airbyte.bootloader: DEBUG -# Uncomment to help resolve issues with conditional beans -# io.micronaut.context.condition: DEBUG diff --git a/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt b/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt deleted file mode 100644 index 633f73326c1a..000000000000 --- a/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt +++ /dev/null @@ -1,8 +0,0 @@ - - ___ _ __ __ - / | (_)____/ /_ __ __/ /____ - / /| | / / ___/ __ \/ / / / __/ _ \ - / ___ |/ / / / /_/ / /_/ / /_/ __/ -/_/ |_/_/_/ /_.___/\__, /\__/\___/ - /____/ - : airbyte-metrics-reporter : \ No newline at end of file diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java deleted file mode 100644 index d509ee577668..000000000000 --- a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import io.airbyte.metrics.lib.MetricAttribute; -import io.airbyte.metrics.lib.MetricClient; -import io.airbyte.metrics.lib.MetricTags; -import io.airbyte.metrics.lib.OssMetricsRegistry; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class EmitterTest { - - private MetricClient client; - private MetricRepository repo; - - @BeforeEach - void setUp() { - client = mock(MetricClient.class); - repo = mock(MetricRepository.class); - } - - @Test - void TestNumPendingJobs() { - final var value = 101; - when(repo.numberOfPendingJobs()).thenReturn(value); - - final var emitter = new NumPendingJobs(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).numberOfPendingJobs(); - verify(client).gauge(OssMetricsRegistry.NUM_PENDING_JOBS, value); - } - - @Test - void TestNumRunningJobs() { - final var value = 101; - when(repo.numberOfRunningJobs()).thenReturn(value); - - final var emitter = new NumRunningJobs(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).numberOfRunningJobs(); - verify(client).gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, value); - } - - @Test - void TestNumOrphanRunningJobs() { - final var value = 101; - when(repo.numberOfOrphanRunningJobs()).thenReturn(value); - - final var emitter = new NumOrphanRunningJobs(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).numberOfOrphanRunningJobs(); - verify(client).gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, value); - } - - @Test - void TestOldestRunningJob() { - final var value = 101; - when(repo.oldestRunningJobAgeSecs()).thenReturn((long) value); - - final var emitter = new OldestRunningJob(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).oldestRunningJobAgeSecs(); - verify(client).gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, value); - } - - @Test - void TestOldestPendingJob() { - final var value = 101; - when(repo.oldestPendingJobAgeSecs()).thenReturn((long) value); - - final var emitter = new OldestPendingJob(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).oldestPendingJobAgeSecs(); - verify(client).gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, value); - } - - @Test - void TestNumActiveConnectionsPerWorkspace() { - final var values = List.of(101L, 202L); - when(repo.numberOfActiveConnPerWorkspace()).thenReturn(values); - - final var emitter = new NumActiveConnectionsPerWorkspace(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).numberOfActiveConnPerWorkspace(); - for (final var value : values) { - verify(client).distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, value); - } - } - - @Test - void TestNumAbnormalScheduledSyncs() { - final var value = 101; - when(repo.numberOfJobsNotRunningOnScheduleInLastDay()).thenReturn((long) value); - - final var emitter = new NumAbnormalScheduledSyncs(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofHours(1), emitter.getDuration()); - verify(repo).numberOfJobsNotRunningOnScheduleInLastDay(); - verify(client).gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, value); - } - - @Test - void TestTotalScheduledSyncs() { - final var value = 101; - when(repo.numScheduledActiveConnectionsInLastDay()).thenReturn((long) value); - - final var emitter = new TotalScheduledSyncs(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofHours(1), emitter.getDuration()); - verify(repo).numScheduledActiveConnectionsInLastDay(); - verify(client).gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, value); - } - - @Test - void TestTotalJobRuntimeByTerminalState() { - final var values = Map.of(JobStatus.cancelled, 101.0, JobStatus.succeeded, 202.0, JobStatus.failed, 303.0); - when(repo.overallJobRuntimeForTerminalJobsInLastHour()).thenReturn(values); - - final var emitter = new TotalJobRuntimeByTerminalState(client, repo); - emitter.Emit(); - - assertEquals(Duration.ofHours(1), emitter.getDuration()); - verify(repo).overallJobRuntimeForTerminalJobsInLastHour(); - values.forEach((jobStatus, time) -> { - verify(client).distribution(OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, time, - new MetricAttribute(MetricTags.JOB_STATUS, jobStatus.getLiteral())); - }); - } - -} diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java deleted file mode 100644 index 8ff5f2e8cd69..000000000000 --- a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java +++ /dev/null @@ -1,506 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import static io.airbyte.db.instance.configs.jooq.generated.Keys.ACTOR_CATALOG_FETCH_EVENT__ACTOR_CATALOG_FETCH_EVENT_ACTOR_ID_FKEY; -import static io.airbyte.db.instance.configs.jooq.generated.Keys.ACTOR__ACTOR_WORKSPACE_ID_FKEY; -import static io.airbyte.db.instance.configs.jooq.generated.Keys.CONNECTION__CONNECTION_DESTINATION_ID_FKEY; -import static io.airbyte.db.instance.configs.jooq.generated.Keys.CONNECTION__CONNECTION_SOURCE_ID_FKEY; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_CATALOG_FETCH_EVENT; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; -import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.airbyte.db.factory.DSLContextFactory; -import io.airbyte.db.init.DatabaseInitializationException; -import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; -import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; -import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; -import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import io.airbyte.db.instance.test.TestDatabaseProviders; -import io.airbyte.test.utils.DatabaseConnectionHelper; -import java.io.IOException; -import java.sql.SQLException; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.jooq.DSLContext; -import org.jooq.JSONB; -import org.jooq.SQLDialect; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.PostgreSQLContainer; - -class MetricRepositoryTest { - - private static final String SRC = "src"; - private static final String DEST = "dst"; - private static final String CONN = "conn"; - private static final UUID SRC_DEF_ID = UUID.randomUUID(); - private static final UUID DST_DEF_ID = UUID.randomUUID(); - private static MetricRepository db; - private static DSLContext ctx; - - @BeforeAll - public static void setUpAll() throws DatabaseInitializationException, IOException { - final var psqlContainer = new PostgreSQLContainer<>("postgres:13-alpine") - .withUsername("user") - .withPassword("hunter2"); - psqlContainer.start(); - - final var dataSource = DatabaseConnectionHelper.createDataSource(psqlContainer); - ctx = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - final var dbProviders = new TestDatabaseProviders(dataSource, ctx); - dbProviders.createNewConfigsDatabase(); - dbProviders.createNewJobsDatabase(); - - ctx.insertInto(ACTOR_DEFINITION, ACTOR_DEFINITION.ID, ACTOR_DEFINITION.NAME, ACTOR_DEFINITION.DOCKER_REPOSITORY, - ACTOR_DEFINITION.DOCKER_IMAGE_TAG, ACTOR_DEFINITION.SPEC, ACTOR_DEFINITION.ACTOR_TYPE, ACTOR_DEFINITION.RELEASE_STAGE) - .values(SRC_DEF_ID, "srcDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.source, ReleaseStage.beta) - .values(DST_DEF_ID, "dstDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.destination, ReleaseStage.generally_available) - .values(UUID.randomUUID(), "dstDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.destination, ReleaseStage.alpha) - .execute(); - - // drop constraints to simplify test set up - ctx.alterTable(ACTOR).dropForeignKey(ACTOR__ACTOR_WORKSPACE_ID_FKEY.constraint()).execute(); - ctx.alterTable(CONNECTION).dropForeignKey(CONNECTION__CONNECTION_DESTINATION_ID_FKEY.constraint()).execute(); - ctx.alterTable(CONNECTION).dropForeignKey(CONNECTION__CONNECTION_SOURCE_ID_FKEY.constraint()).execute(); - ctx.alterTable(ACTOR_CATALOG_FETCH_EVENT) - .dropForeignKey(ACTOR_CATALOG_FETCH_EVENT__ACTOR_CATALOG_FETCH_EVENT_ACTOR_ID_FKEY.constraint()).execute(); - ctx.alterTable(WORKSPACE).alter(WORKSPACE.SLUG).dropNotNull().execute(); - ctx.alterTable(WORKSPACE).alter(WORKSPACE.INITIAL_SETUP_COMPLETE).dropNotNull().execute(); - - db = new MetricRepository(ctx); - } - - @BeforeEach - void setUp() { - ctx.truncate(ACTOR).execute(); - ctx.truncate(CONNECTION).cascade().execute(); - ctx.truncate(JOBS).cascade().execute(); - ctx.truncate(WORKSPACE).cascade().execute(); - } - - @AfterEach - void tearDown() { - - } - - @Nested - class NumJobs { - - @Test - void shouldReturnReleaseStages() { - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - - ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) - .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) - .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) - .execute(); - - final var activeConnectionId = UUID.randomUUID(); - final var inactiveConnectionId = UUID.randomUUID(); - ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, - CONNECTION.DESTINATION_ID, CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL) - .values(activeConnectionId, StatusType.active, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) - .values(inactiveConnectionId, StatusType.inactive, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) - .execute(); - - // non-pending jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, activeConnectionId.toString(), JobStatus.pending) - .execute(); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(2L, activeConnectionId.toString(), JobStatus.failed) - .values(3L, activeConnectionId.toString(), JobStatus.running) - .values(4L, activeConnectionId.toString(), JobStatus.running) - .values(5L, inactiveConnectionId.toString(), JobStatus.running) - .execute(); - - assertEquals(2, db.numberOfRunningJobs()); - assertEquals(1, db.numberOfOrphanRunningJobs()); - } - - @Test - void runningJobsShouldReturnZero() throws SQLException { - // non-pending jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute(); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute(); - - final var res = db.numberOfRunningJobs(); - assertEquals(0, res); - } - - @Test - void pendingJobsShouldReturnCorrectCount() throws SQLException { - // non-pending jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.pending) - .values(2L, "", JobStatus.failed) - .values(3L, "", JobStatus.pending) - .values(4L, "", JobStatus.running) - .execute(); - - final var res = db.numberOfPendingJobs(); - assertEquals(2, res); - } - - @Test - void pendingJobsShouldReturnZero() throws SQLException { - // non-pending jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.running) - .values(2L, "", JobStatus.failed) - .execute(); - - final var res = db.numberOfPendingJobs(); - assertEquals(0, res); - } - - } - - @Nested - class OldestPendingJob { - - @Test - void shouldReturnOnlyPendingSeconds() throws SQLException { - final var expAgeSecs = 1000; - final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) - // oldest pending job - .values(1L, "", JobStatus.pending, oldestCreateAt) - // second-oldest pending job - .values(2L, "", JobStatus.pending, OffsetDateTime.now()) - .execute(); - // non-pending jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(3L, "", JobStatus.running) - .values(4L, "", JobStatus.failed) - .execute(); - - final var res = db.oldestPendingJobAgeSecs(); - // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(999L, 1000L, 1001L).contains(res)); - } - - @Test - void shouldReturnNothingIfNotApplicable() { - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.succeeded) - .values(2L, "", JobStatus.running) - .values(3L, "", JobStatus.failed).execute(); - - final var res = db.oldestPendingJobAgeSecs(); - assertEquals(0L, res); - } - - } - - @Nested - class OldestRunningJob { - - @Test - void shouldReturnOnlyRunningSeconds() { - final var expAgeSecs = 10000; - final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) - // oldest pending job - .values(1L, "", JobStatus.running, oldestCreateAt) - // second-oldest pending job - .values(2L, "", JobStatus.running, OffsetDateTime.now()) - .execute(); - - // non-pending jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(3L, "", JobStatus.pending) - .values(4L, "", JobStatus.failed) - .execute(); - - final var res = db.oldestRunningJobAgeSecs(); - // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); - } - - @Test - void shouldReturnNothingIfNotApplicable() { - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.succeeded) - .values(2L, "", JobStatus.pending) - .values(3L, "", JobStatus.failed) - .execute(); - - final var res = db.oldestRunningJobAgeSecs(); - assertEquals(0L, res); - } - - } - - @Nested - class NumActiveConnsPerWorkspace { - - @Test - void shouldReturnNumConnectionsBasic() { - final var workspaceId = UUID.randomUUID(); - ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) - .values(workspaceId, "test-0", false) - .execute(); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute(); - - ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .execute(); - - final var res = db.numberOfActiveConnPerWorkspace(); - assertEquals(1, res.size()); - assertEquals(2, res.get(0)); - } - - @Test - @DisplayName("should ignore deleted connections") - void shouldIgnoreNonRunningConnections() { - final var workspaceId = UUID.randomUUID(); - ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) - .values(workspaceId, "test-0", false) - .execute(); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute(); - - ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.deprecated) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.inactive) - .execute(); - - final var res = db.numberOfActiveConnPerWorkspace(); - assertEquals(1, res.size()); - assertEquals(2, res.get(0)); - } - - @Test - @DisplayName("should ignore deleted connections") - void shouldIgnoreDeletedWorkspaces() { - final var workspaceId = UUID.randomUUID(); - ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) - .values(workspaceId, "test-0", true) - .execute(); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute(); - - ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .execute(); - - final var res = db.numberOfActiveConnPerWorkspace(); - assertEquals(0, res.size()); - } - - @Test - void shouldReturnNothingIfNotApplicable() { - final var res = db.numberOfActiveConnPerWorkspace(); - assertEquals(0, res.size()); - } - - } - - @Nested - class OverallJobRuntimeForTerminalJobsInLastHour { - - @Test - void shouldIgnoreNonTerminalJobs() throws SQLException { - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.running) - .values(2L, "", JobStatus.incomplete) - .values(3L, "", JobStatus.pending) - .execute(); - - final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); - assertEquals(0, res.size()); - } - - @Test - void shouldIgnoreJobsOlderThan1Hour() { - final var updateAt = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.UPDATED_AT).values(1L, "", JobStatus.succeeded, updateAt).execute(); - - final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); - assertEquals(0, res.size()); - } - - @Test - @DisplayName("should return correct duration for terminal jobs") - void shouldReturnTerminalJobs() { - final var updateAt = OffsetDateTime.now(); - final var expAgeSecs = 10000; - final var createAt = updateAt.minus(expAgeSecs, ChronoUnit.SECONDS); - - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(1L, "", JobStatus.succeeded, createAt, updateAt) - .execute(); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(2L, "", JobStatus.failed, createAt, updateAt) - .execute(); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(3L, "", JobStatus.cancelled, createAt, updateAt) - .execute(); - - final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); - assertEquals(3, res.size()); - - final var exp = Map.of( - JobStatus.succeeded, expAgeSecs * 1.0, - JobStatus.cancelled, expAgeSecs * 1.0, - JobStatus.failed, expAgeSecs * 1.0); - assertEquals(exp, res); - } - - @Test - void shouldReturnTerminalJobsComplex() { - final var updateAtNow = OffsetDateTime.now(); - final var expAgeSecs = 10000; - final var createAt = updateAtNow.minus(expAgeSecs, ChronoUnit.SECONDS); - - // terminal jobs in last hour - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(1L, "", JobStatus.succeeded, createAt, updateAtNow) - .execute(); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(2L, "", JobStatus.failed, createAt, updateAtNow) - .execute(); - - // old terminal jobs - final var updateAtOld = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(3L, "", JobStatus.cancelled, createAt, updateAtOld) - .execute(); - - // non-terminal jobs - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) - .values(4L, "", JobStatus.running, createAt) - .execute(); - - final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); - assertEquals(2, res.size()); - - final var exp = Map.of( - JobStatus.succeeded, expAgeSecs * 1.0, - JobStatus.failed, expAgeSecs * 1.0); - assertEquals(exp, res); - } - - @Test - void shouldReturnNothingIfNotApplicable() { - final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); - assertEquals(0, res.size()); - } - - } - - @Nested - class AbnormalJobsInLastDay { - - @Test - void shouldCountInJobsWithMissingRun() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); - final var connectionId = UUID.randomUUID(); - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - final var syncConfigType = JobConfigType.sync; - - ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(connectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 6, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) - .execute(); - - // Jobs running in prior day will not be counted - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), updateAt, syncConfigType) - .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, syncConfigType) - .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, syncConfigType) - .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(5, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute(); - - final var totalConnectionResult = db.numScheduledActiveConnectionsInLastDay(); - assertEquals(1, totalConnectionResult); - - final var abnormalConnectionResult = db.numberOfJobsNotRunningOnScheduleInLastDay(); - assertEquals(1, abnormalConnectionResult); - } - - @Test - void shouldNotCountNormalJobsInAbnormalMetric() { - final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); - final var inactiveConnectionId = UUID.randomUUID(); - final var activeConnectionId = UUID.randomUUID(); - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - final var syncConfigType = JobConfigType.sync; - - ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(inactiveConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.inactive, updateAt, updateAt) - .values(activeConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) - .execute(); - - ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(1L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, - syncConfigType) - .values(2L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, - syncConfigType) - .execute(); - - final var totalConnectionResult = db.numScheduledActiveConnectionsInLastDay(); - assertEquals(1, totalConnectionResult); - - final var abnormalConnectionResult = db.numberOfJobsNotRunningOnScheduleInLastDay(); - assertEquals(0, abnormalConnectionResult); - } - - } - -} From 981190a00575c24fc0efb24658caa8d88cd3e28b Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Thu, 13 Oct 2022 09:05:19 +0300 Subject: [PATCH 082/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Salesforce:=20m?= =?UTF-8?q?ake=20paging=20work,=20if=20`cursor=5Ffield`=20is=20not=20chang?= =?UTF-8?q?ed=20inside=20one=20page=20(#17615)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sergey Chvalyuk --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-salesforce/Dockerfile | 3 +- .../source_salesforce/streams.py | 16 +++++- .../source-salesforce/unit_tests/api_test.py | 56 +++++++++++++++++++ .../source-salesforce/unit_tests/conftest.py | 6 ++ docs/integrations/sources/salesforce.md | 1 + 7 files changed, 79 insertions(+), 7 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 56987f690356..e9e4babc025e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -931,7 +931,7 @@ - name: Salesforce sourceDefinitionId: b117307c-14b6-41aa-9422-947e34922962 dockerRepository: airbyte/source-salesforce - dockerImageTag: 1.0.21 + dockerImageTag: 1.0.22 documentationUrl: https://docs.airbyte.com/integrations/sources/salesforce icon: salesforce.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index cc1181894792..68bee7e94514 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9769,7 +9769,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-salesforce:1.0.21" +- dockerImage: "airbyte/source-salesforce:1.0.22" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/salesforce" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-salesforce/Dockerfile b/airbyte-integrations/connectors/source-salesforce/Dockerfile index 9c1fb17676f3..72ddaf32b2d9 100644 --- a/airbyte-integrations/connectors/source-salesforce/Dockerfile +++ b/airbyte-integrations/connectors/source-salesforce/Dockerfile @@ -13,6 +13,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=1.0.21 - +LABEL io.airbyte.version=1.0.22 LABEL io.airbyte.name=airbyte/source-salesforce diff --git a/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py b/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py index c22a79e12f1f..89d24aba86a3 100644 --- a/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py +++ b/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py @@ -471,7 +471,11 @@ class BulkIncrementalSalesforceStream(BulkSalesforceStream, IncrementalSalesforc def next_page_token(self, last_record: Mapping[str, Any]) -> Optional[Mapping[str, Any]]: if self.name not in UNSUPPORTED_FILTERING_STREAMS: page_token: str = last_record[self.cursor_field] - return {"next_token": page_token} + res = {"next_token": page_token} + # use primary key as additional filtering param, if cursor_field is not increased from previous page + if self.primary_key and self.prev_start_date == page_token: + res["primary_key"] = last_record[self.primary_key] + return res return None def request_params( @@ -481,13 +485,19 @@ def request_params( stream_date = stream_state.get(self.cursor_field) next_token = (next_page_token or {}).get("next_token") + primary_key = (next_page_token or {}).get("primary_key") start_date = next_token or stream_date or self.start_date + self.prev_start_date = start_date query = f"SELECT {','.join(selected_properties.keys())} FROM {self.name} " if start_date: - query += f"WHERE {self.cursor_field} >= {start_date} " + if primary_key and self.name not in UNSUPPORTED_FILTERING_STREAMS: + query += f"WHERE ({self.cursor_field} = {start_date} AND {self.primary_key} > '{primary_key}') OR ({self.cursor_field} > {start_date}) " + else: + query += f"WHERE {self.cursor_field} >= {start_date} " if self.name not in UNSUPPORTED_FILTERING_STREAMS: - query += f"ORDER BY {self.cursor_field} ASC LIMIT {self.page_size}" + order_by_fields = [self.cursor_field, self.primary_key] if self.primary_key else [self.cursor_field] + query += f"ORDER BY {','.join(order_by_fields)} ASC LIMIT {self.page_size}" return {"q": query} diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py index 2334c4945ba0..aadaef4cae19 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py @@ -528,3 +528,59 @@ def test_convert_to_standard_instance(stream_config, stream_api): bulk_stream = generate_stream("Account", stream_config, stream_api) rest_stream = bulk_stream.get_standard_instance() assert isinstance(rest_stream, IncrementalSalesforceStream) + + +def test_bulk_stream_paging(stream_config, stream_api_pk): + last_modified_date1 = "2022-10-01T00:00:00Z" + last_modified_date2 = "2022-10-02T00:00:00Z" + assert last_modified_date1 < last_modified_date2 + + stream_config["start_date"] = last_modified_date1 + stream: BulkIncrementalSalesforceStream = generate_stream("Account", stream_config, stream_api_pk) + stream.page_size = 2 + + csv_header = "Field1,LastModifiedDate,Id" + pages = [ + [f"test,{last_modified_date1},1", f"test,{last_modified_date1},3"], + [f"test,{last_modified_date1},5", f"test,{last_modified_date2},2"], + [f"test,{last_modified_date2},2", f"test,{last_modified_date2},4"], + [f"test,{last_modified_date2},6"], + ] + + with requests_mock.Mocker() as mocked_requests: + + post_responses = [] + for job_id, page in enumerate(pages, 1): + post_responses.append({"json": {"id": f"{job_id}"}}) + mocked_requests.register_uri("GET", stream.path() + f"/{job_id}", json={"state": "JobComplete"}) + mocked_requests.register_uri("GET", stream.path() + f"/{job_id}/results", text="\n".join([csv_header] + page)) + mocked_requests.register_uri("DELETE", stream.path() + f"/{job_id}") + mocked_requests.register_uri("POST", stream.path(), post_responses) + + records = list(stream.read_records(sync_mode=SyncMode.full_refresh)) + + assert records == [ + {"Field1": "test", "Id": 1, "LastModifiedDate": last_modified_date1}, + {"Field1": "test", "Id": 3, "LastModifiedDate": last_modified_date1}, + {"Field1": "test", "Id": 5, "LastModifiedDate": last_modified_date1}, + {"Field1": "test", "Id": 2, "LastModifiedDate": last_modified_date2}, + {"Field1": "test", "Id": 2, "LastModifiedDate": last_modified_date2}, # duplicate record + {"Field1": "test", "Id": 4, "LastModifiedDate": last_modified_date2}, + {"Field1": "test", "Id": 6, "LastModifiedDate": last_modified_date2}, + ] + + def get_query(request_index): + return mocked_requests.request_history[request_index].json()["query"] + + SELECT = "SELECT LastModifiedDate,Id FROM Account" + ORDER_BY = "ORDER BY LastModifiedDate,Id ASC LIMIT 2" + + assert get_query(0) == f"{SELECT} WHERE LastModifiedDate >= {last_modified_date1} {ORDER_BY}" + + q = f"{SELECT} WHERE (LastModifiedDate = {last_modified_date1} AND Id > '3') OR (LastModifiedDate > {last_modified_date1}) {ORDER_BY}" + assert get_query(4) == q + + assert get_query(8) == f"{SELECT} WHERE LastModifiedDate >= {last_modified_date2} {ORDER_BY}" + + q = f"{SELECT} WHERE (LastModifiedDate = {last_modified_date2} AND Id > '4') OR (LastModifiedDate > {last_modified_date2}) {ORDER_BY}" + assert get_query(12) == q diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py index e0de6179f98f..4b14585d6669 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/conftest.py @@ -99,6 +99,12 @@ def stream_api_v2(stream_config): return _stream_api(stream_config, describe_response_data=describe_response_data) +@pytest.fixture(scope="module") +def stream_api_pk(stream_config): + describe_response_data = {"fields": [{"name": "LastModifiedDate", "type": "string"}, {"name": "Id", "type": "string"}]} + return _stream_api(stream_config, describe_response_data=describe_response_data) + + def generate_stream(stream_name, stream_config, stream_api): return SourceSalesforce.generate_streams(stream_config, {stream_name: None}, stream_api)[0] diff --git a/docs/integrations/sources/salesforce.md b/docs/integrations/sources/salesforce.md index 5c1a730337fe..900b17d4a174 100644 --- a/docs/integrations/sources/salesforce.md +++ b/docs/integrations/sources/salesforce.md @@ -121,6 +121,7 @@ Now that you have set up the Salesforce source connector, check out the followin | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | +| 1.0.22 | 2022-10-12 | [17615](https://github.com/airbytehq/airbyte/pull/17615) | Make paging work, if `cursor_field` is not changed inside one page | | 1.0.21 | 2022-10-10 | [17778](https://github.com/airbytehq/airbyte/pull/17778) | Add `EventWhoRelation` to the list of unsupported Bulk API objects. | | 1.0.20 | 2022-09-30 | [17453](https://github.com/airbytehq/airbyte/pull/17453) | Check objects that are not supported by the Bulk API (v52.0) | | 1.0.19 | 2022-09-29 | [17314](https://github.com/airbytehq/airbyte/pull/17314) | Fixed bug with decoding response | From c2415415bd7aadd80160275bd8f43f4fe20506b8 Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Thu, 13 Oct 2022 11:31:10 +0300 Subject: [PATCH 083/498] Source google-anlytics-v4: do not retry exceeded daily quota (#17905) * source google-anlytics-v4: do not retry exceeded daily quota * source google analytics: upd changelog * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 2 +- .../source-google-analytics-v4/Dockerfile | 2 +- .../source_google_analytics_v4/source.py | 6 +++++- .../unit_tests/unit_test.py | 14 ++++++++------ .../google-analytics-universal-analytics.md | 6 ++++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index e9e4babc025e..8df1d9a55dd9 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -386,7 +386,7 @@ - name: Google Analytics (Universal Analytics) sourceDefinitionId: eff3616a-f9c3-11eb-9a03-0242ac130003 dockerRepository: airbyte/source-google-analytics-v4 - dockerImageTag: 0.1.28 + dockerImageTag: 0.1.29 documentationUrl: https://docs.airbyte.com/integrations/sources/google-analytics-universal-analytics icon: google-analytics.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 68bee7e94514..e1c07d513171 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3865,7 +3865,7 @@ oauthFlowOutputParameters: - - "access_token" - - "refresh_token" -- dockerImage: "airbyte/source-google-analytics-v4:0.1.28" +- dockerImage: "airbyte/source-google-analytics-v4:0.1.29" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-analytics-universal-analytics" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile b/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile index 95ba3c2b6f5e..869a3f7f2d2f 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile +++ b/airbyte-integrations/connectors/source-google-analytics-v4/Dockerfile @@ -12,5 +12,5 @@ COPY main.py ./ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.28 +LABEL io.airbyte.version=0.1.29 LABEL io.airbyte.name=airbyte/source-google-analytics-v4 \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py b/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py index 16db57232c28..589f342cbaf0 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py +++ b/airbyte-integrations/connectors/source-google-analytics-v4/source_google_analytics_v4/source.py @@ -150,11 +150,13 @@ def should_retry(self, response: requests.Response) -> bool: if response.status_code == 400: self.logger.info(f"{response.json()['error']['message']}") self._raise_on_http_errors = False + return False elif response.status_code == 429 and "has exceeded the daily request limit" in response.json()["error"]["message"]: rate_limit_docs_url = "https://developers.google.com/analytics/devguides/reporting/core/v4/limits-quotas" self.logger.info(f"{response.json()['error']['message']}. More info: {rate_limit_docs_url}") self._raise_on_http_errors = False + return False result: bool = HttpStream.should_retry(self, response) return result @@ -395,7 +397,9 @@ def parse_response(self, response: requests.Response, **kwargs: Any) -> Iterable "ga_exitRate":6.523809523809524 } """ - json_response = response.json() + json_response = response.json() if response.status_code not in (400, 429) else None + if not json_response: + return [] reports = json_response.get(self.report_field, []) for report in reports: diff --git a/airbyte-integrations/connectors/source-google-analytics-v4/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-google-analytics-v4/unit_tests/unit_test.py index bde1bfff9977..ae620d4ac0bf 100644 --- a/airbyte-integrations/connectors/source-google-analytics-v4/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-google-analytics-v4/unit_tests/unit_test.py @@ -313,14 +313,16 @@ def test_check_connection_success_oauth( assert mock_api_returns_valid_records.called -def test_unknown_metrics_or_dimensions_error_validation(mock_metrics_dimensions_type_list_link, mock_unknown_metrics_or_dimensions_error): - records = GoogleAnalyticsV4Stream(MagicMock()).read_records(sync_mode=None) - assert records +def test_unknown_metrics_or_dimensions_error_validation( + mocker, test_config, mock_metrics_dimensions_type_list_link, mock_unknown_metrics_or_dimensions_error +): + records = GoogleAnalyticsV4Stream(test_config).read_records(sync_mode=None) + assert list(records) == [] -def test_daily_request_limit_error_validation(mock_metrics_dimensions_type_list_link, mock_daily_request_limit_error): - records = GoogleAnalyticsV4Stream(MagicMock()).read_records(sync_mode=None) - assert records +def test_daily_request_limit_error_validation(mocker, test_config, mock_metrics_dimensions_type_list_link, mock_daily_request_limit_error): + records = GoogleAnalyticsV4Stream(test_config).read_records(sync_mode=None) + assert list(records) == [] @freeze_time("2021-11-30") diff --git a/docs/integrations/sources/google-analytics-universal-analytics.md b/docs/integrations/sources/google-analytics-universal-analytics.md index 87e6ecaaeb17..127fcec645f0 100644 --- a/docs/integrations/sources/google-analytics-universal-analytics.md +++ b/docs/integrations/sources/google-analytics-universal-analytics.md @@ -154,8 +154,10 @@ Incremental sync is supported only if you add `ga:date` dimension to your custom ## Changelog | Version | Date | Pull Request | Subject | -| :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------- | -| 0.1.27 | 2022-10-07 | [17717](https://github.com/airbytehq/airbyte/pull/17717) | Improve CHECK by using `ga:hits` metric. | +|:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------| +| 0.1.29 | 2022-10-12 | [17905](https://github.com/airbytehq/airbyte/pull/17905) | Handle exceeded daily quota gracefully | +| 0.1.28 | 2022-09-24 | [16920](https://github.com/airbytehq/airbyte/pull/16920) | Added segments and filters to custom reports | +| 0.1.27 | 2022-10-07 | [17717](https://github.com/airbytehq/airbyte/pull/17717) | Improve CHECK by using `ga:hits` metric. | | 0.1.26 | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/15087) | Migrate to per-stream states. | | 0.1.25 | 2022-07-27 | [15087](https://github.com/airbytehq/airbyte/pull/15087) | Fix documentationUrl | | 0.1.24 | 2022-07-26 | [15042](https://github.com/airbytehq/airbyte/pull/15042) | Update `additionalProperties` field to true from schemas | From 2a4a5d3a1a326fe0725789b661e9984f0b8c35fc Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Thu, 13 Oct 2022 11:41:34 +0200 Subject: [PATCH 084/498] Handle className removal on SVG (#17935) --- airbyte-webapp/scripts/classname-serializer.js | 9 +++++++-- .../__snapshots__/CreateConnectionForm.test.tsx.snap | 12 ++++++------ .../GitBlock/__snapshots__/GitBlock.test.tsx.snap | 4 ++-- .../ConnectionReplicationTab.test.tsx.snap | 12 ++++++------ .../FrequentlyUsedDestinations.test.tsx.snap | 4 ++-- 5 files changed, 23 insertions(+), 18 deletions(-) diff --git a/airbyte-webapp/scripts/classname-serializer.js b/airbyte-webapp/scripts/classname-serializer.js index 40bb27a80cd6..a9f86a9f14a1 100644 --- a/airbyte-webapp/scripts/classname-serializer.js +++ b/airbyte-webapp/scripts/classname-serializer.js @@ -5,8 +5,13 @@ import { prettyDOM } from "@testing-library/react"; * the count of classnames instead, e.g. "<3 classnames>" */ const traverseAndRedactClasses = (node) => { - if (node.className && typeof node.className === "string") { - node.className = ``; + if ( + node.className && + (typeof node.className === "string" || (node.className instanceof SVGAnimatedString && node.className.baseVal)) + ) { + // We need to use setAttribute here, since on SVGElement we can't + // set `className` to a string for the `SVGAnimatedString` case. + node.setAttribute("class", ``); } node.childNodes.forEach(traverseAndRedactClasses); }; diff --git a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap index 4bd204d77b07..19aca020b0a1 100644 --- a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap +++ b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap @@ -160,7 +160,7 @@ exports[`CreateConnectionForm should render 1`] = ` > {item.icon} + {item.displayName} + + ); +}; + +interface ButtonMenuItemProps { + active: boolean; + item: MenuItemButton; +} + +const ButtonMenuItem: React.FC = ({ active, item }) => { + return ( + + ); +}; + export const SidebarDropdownMenu: React.FC<{ label: Label; - options?: Array; + options: Array; }> = ({ label, options }) => { - function menuItem(active: boolean, item: MenuItemLink | MenuItemButton): React.ReactNode { - switch (item.type) { - case SidebarDropdownMenuItemType.LINK: - return ( - - {item.icon} - {item.displayName} - - ); - case SidebarDropdownMenuItemType.BUTTON: - return ( - - ); - } - } - return (

    {({ open }) => ( @@ -69,8 +78,16 @@ export const SidebarDropdownMenu: React.FC<{ - {options?.map((item, index) => ( - {({ active }) => menuItem(active, item)} + {options.map((item, index) => ( + + {({ active }) => + item.type === SidebarDropdownMenuItemType.LINK ? ( + + ) : ( + + ) + } + ))} From 3268cfe1c3ec4f17e1f3d54396110085c6f63d4a Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Thu, 13 Oct 2022 16:16:04 +0200 Subject: [PATCH 091/498] Add React.PropsWithChildren where needed (#17946) --- .../CreateConnection/CreateConnectionForm.test.tsx | 3 ++- .../components/ui/TextInputContainer/TextInputContainer.tsx | 2 +- .../hooks/services/ConnectionEdit/ConnectionEditService.tsx | 5 ++++- .../hooks/services/ConnectionForm/ConnectionFormService.tsx | 5 ++++- .../cloud/services/users/InviteUsersModalService.tsx | 4 ++-- .../ConnectionItemPage/ConnectionReplicationTab.test.tsx | 4 ++-- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx index 7be178b5a9ff..a27137b0cc81 100644 --- a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx +++ b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.test.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { act, render as tlr } from "@testing-library/react"; +import React from "react"; import mockConnection from "test-utils/mock-data/mockConnection.json"; import mockDest from "test-utils/mock-data/mockDestinationDefinition.json"; import { TestWrapper } from "test-utils/testutils"; @@ -19,7 +20,7 @@ jest.mock("services/workspaces/WorkspacesService", () => ({ })); describe("CreateConnectionForm", () => { - const Wrapper: React.FC = ({ children }) => {children}; + const Wrapper: React.FC> = ({ children }) => {children}; const render = async () => { let renderResult: ReturnType; diff --git a/airbyte-webapp/src/components/ui/TextInputContainer/TextInputContainer.tsx b/airbyte-webapp/src/components/ui/TextInputContainer/TextInputContainer.tsx index 289836db1d1c..7108b2f8f85f 100644 --- a/airbyte-webapp/src/components/ui/TextInputContainer/TextInputContainer.tsx +++ b/airbyte-webapp/src/components/ui/TextInputContainer/TextInputContainer.tsx @@ -11,7 +11,7 @@ export interface TextInputContainerProps { onBlur?: React.FocusEventHandler; } -export const TextInputContainer: React.FC = ({ +export const TextInputContainer: React.FC> = ({ disabled, light, error, diff --git a/airbyte-webapp/src/hooks/services/ConnectionEdit/ConnectionEditService.tsx b/airbyte-webapp/src/hooks/services/ConnectionEdit/ConnectionEditService.tsx index 31a5cfa6ebb8..7ba926e10725 100644 --- a/airbyte-webapp/src/hooks/services/ConnectionEdit/ConnectionEditService.tsx +++ b/airbyte-webapp/src/hooks/services/ConnectionEdit/ConnectionEditService.tsx @@ -48,7 +48,10 @@ const ConnectionEditContext = createContext | null>(null); -export const ConnectionEditServiceProvider: React.FC = ({ children, ...props }) => { +export const ConnectionEditServiceProvider: React.FC> = ({ + children, + ...props +}) => { const { refreshSchema, schemaError, ...data } = useConnectionEdit(props); return ( diff --git a/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx b/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx index 670eecf66548..b1856cec5a06 100644 --- a/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx +++ b/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx @@ -82,7 +82,10 @@ const useConnectionForm = ({ connection, mode, schemaError, refreshSchema }: Con const ConnectionFormContext = createContext | null>(null); -export const ConnectionFormServiceProvider: React.FC = ({ children, ...props }) => { +export const ConnectionFormServiceProvider: React.FC> = ({ + children, + ...props +}) => { const data = useConnectionForm(props); return {children}; }; diff --git a/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx b/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx index 46f2e3e3f7b2..7b209f743572 100644 --- a/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx +++ b/airbyte-webapp/src/packages/cloud/services/users/InviteUsersModalService.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useMemo } from "react"; +import React, { createContext, useContext, useMemo } from "react"; import { useToggle } from "react-use"; import { InviteUsersModal } from "packages/cloud/views/users/InviteUsersModal"; @@ -19,7 +19,7 @@ export const useInviteUsersModalService = () => { return ctx; }; -export const InviteUsersModalServiceProvider: React.FC = ({ children }) => { +export const InviteUsersModalServiceProvider: React.FC> = ({ children }) => { const [isOpen, toggleIsOpen] = useToggle(false); const contextValue = useMemo( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx index a110635e8c6c..f23af65597d1 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { render as tlr, act } from "@testing-library/react"; -import { Suspense } from "react"; +import React, { Suspense } from "react"; import mockConnection from "test-utils/mock-data/mockConnection.json"; import mockDest from "test-utils/mock-data/mockDestinationDefinition.json"; import { TestWrapper } from "test-utils/testutils"; @@ -18,7 +18,7 @@ jest.mock("services/connector/DestinationDefinitionSpecificationService", () => })); describe("ConnectionReplicationTab", () => { - const Wrapper: React.FC = ({ children }) => ( + const Wrapper: React.FC> = ({ children }) => ( I should not show up in a snapshot}> From 908f3949d206ecba249a27faef582877dfc19a85 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Thu, 13 Oct 2022 10:28:40 -0400 Subject: [PATCH 092/498] remove usages of YamlSeedConfigPersistence (#17895) --- .../java/io/airbyte/bootloader/BootloaderApp.java | 3 +-- .../io/airbyte/bootloader/BootloaderAppTest.java | 9 ++++++--- airbyte-config/config-persistence/readme.md | 2 +- .../config/init/LocalDefinitionsProvider.java | 2 ++ .../java/io/airbyte/config/init/SpecFormatTest.java | 12 +++--------- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java index 1b875c358e68..8c508fe5b151 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java @@ -17,7 +17,6 @@ import io.airbyte.config.init.ApplyDefinitionsHelper; import io.airbyte.config.init.DefinitionsProvider; import io.airbyte.config.init.LocalDefinitionsProvider; -import io.airbyte.config.init.YamlSeedConfigPersistence; import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; @@ -206,7 +205,7 @@ private static ConfigPersistence getConfigPersistence(final Database configDatab } private static DefinitionsProvider getLocalDefinitionsProvider() throws IOException { - return new LocalDefinitionsProvider(YamlSeedConfigPersistence.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); + return new LocalDefinitionsProvider(LocalDefinitionsProvider.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); } private static Database getJobDatabase(final DSLContext dslContext) throws IOException { diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index 976e3d518531..cf2630467ca4 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -24,7 +24,9 @@ import io.airbyte.config.Geography; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardWorkspace; -import io.airbyte.config.init.YamlSeedConfigPersistence; +import io.airbyte.config.init.DefinitionProviderToConfigPersistenceAdapter; +import io.airbyte.config.init.DefinitionsProvider; +import io.airbyte.config.init.LocalDefinitionsProvider; import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; @@ -199,9 +201,10 @@ void testBootloaderAppRunSecretMigration() throws Exception { val initBootloader = new BootloaderApp(mockedConfigs, mockedFeatureFlags, null, configsDslContext, jobsDslContext, configsFlyway, jobsFlyway); initBootloader.load(); - final ConfigPersistence localSchema = new YamlSeedConfigPersistence(YamlSeedConfigPersistence.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); + final DefinitionsProvider localDefinitions = new LocalDefinitionsProvider(LocalDefinitionsProvider.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); final ConfigRepository configRepository = new ConfigRepository(configPersistence, configDatabase); - configRepository.loadDataNoSecrets(localSchema); + final ConfigPersistence localConfigPersistence = new DefinitionProviderToConfigPersistenceAdapter(localDefinitions); + configRepository.loadDataNoSecrets(localConfigPersistence); final String sourceSpecs = """ { diff --git a/airbyte-config/config-persistence/readme.md b/airbyte-config/config-persistence/readme.md index 0bfd8b20dcfd..b314dd62bf35 100644 --- a/airbyte-config/config-persistence/readme.md +++ b/airbyte-config/config-persistence/readme.md @@ -3,5 +3,5 @@ This module contains the logic for accessing the config database. This database is primarily used by the `airbyte-server` but is also accessed from `airbyte-workers`. It contains all configuration information for Airbyte. ## Key files -* `ConfigPersistence.java` is the interface over "low-level" access to the db. The most commonly used implementation of it is `DatabaseConfigPersistence.java` The only other one that is used is the `YamlSeedConfigPersistence.java` which is used for loading configs that ship with the app. +* `ConfigPersistence.java` is the interface over "low-level" access to the db. The most commonly used implementation of it is `DatabaseConfigPersistence.java`. * `ConfigRepository.java` is what is most used for accessing the databases. The `ConfigPersistence` iface was hard to work with. `ConfigRepository` builds on top of it and houses any databases queries to keep them from proliferating throughout the codebase. diff --git a/airbyte-config/init/src/main/java/io/airbyte/config/init/LocalDefinitionsProvider.java b/airbyte-config/init/src/main/java/io/airbyte/config/init/LocalDefinitionsProvider.java index cd0f841e7049..bc8207c55c54 100644 --- a/airbyte-config/init/src/main/java/io/airbyte/config/init/LocalDefinitionsProvider.java +++ b/airbyte-config/init/src/main/java/io/airbyte/config/init/LocalDefinitionsProvider.java @@ -33,6 +33,8 @@ */ final public class LocalDefinitionsProvider implements DefinitionsProvider { + public static final Class DEFAULT_SEED_DEFINITION_RESOURCE_CLASS = SeedType.class; + private final static String PROTOCOL_VERSION = "protocol_version"; private final static String SPEC = "spec"; diff --git a/airbyte-config/init/src/test/java/io/airbyte/config/init/SpecFormatTest.java b/airbyte-config/init/src/test/java/io/airbyte/config/init/SpecFormatTest.java index ef7d7c8af801..c8b3561c892a 100644 --- a/airbyte-config/init/src/test/java/io/airbyte/config/init/SpecFormatTest.java +++ b/airbyte-config/init/src/test/java/io/airbyte/config/init/SpecFormatTest.java @@ -6,10 +6,6 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.json.JsonSchemas; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.StandardDestinationDefinition; -import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; @@ -25,16 +21,14 @@ class SpecFormatTest { @Test void testOnAllExistingConfig() throws IOException, JsonValidationException { - final ConfigPersistence configPersistence = new YamlSeedConfigPersistence(YamlSeedConfigPersistence.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); + final DefinitionsProvider definitionsProvider = new LocalDefinitionsProvider(LocalDefinitionsProvider.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); - final List sourceSpecs = configPersistence.listConfigs( - ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class) + final List sourceSpecs = definitionsProvider.getSourceDefinitions() .stream() .map(standardSourceDefinition -> standardSourceDefinition.getSpec().getConnectionSpecification()) .toList(); - final List destinationSpecs = configPersistence.listConfigs( - ConfigSchema.STANDARD_DESTINATION_DEFINITION, StandardDestinationDefinition.class) + final List destinationSpecs = definitionsProvider.getDestinationDefinitions() .stream() .map(standardDestinationDefinition -> standardDestinationDefinition.getSpec().getConnectionSpecification()) .toList(); From d9d2261fdf6d7db01d0a55b17f43ec64258d85ef Mon Sep 17 00:00:00 2001 From: Amruta Ranade <11484018+Amruta-Ranade@users.noreply.github.com> Date: Thu, 13 Oct 2022 09:31:51 -0500 Subject: [PATCH 093/498] fixed links (#17949) --- docs/integrations/sources/timely.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/integrations/sources/timely.md b/docs/integrations/sources/timely.md index 09bb0d659ac9..1bff1c2ba1d7 100644 --- a/docs/integrations/sources/timely.md +++ b/docs/integrations/sources/timely.md @@ -4,12 +4,9 @@ This page contains the setup guide and reference information for the Timely sour ## Prerequisites -1. Please follow these [steps](https://dev.timelyapp.com/#authorization) to obtain `Bearer_token` for your account.
    -2. Login into your `https://app.timelyapp.com` portal, fetch the `account-id` present in the URL.
    - URL `https://app.timelyapp.com/12345/calendar`
    - account-id `12345`
    -3. Get a start-date to your events.
    - Dateformat `YYYY-MM-DD` +1. Please follow these [steps](https://dev.timelyapp.com/#authorization) to obtain `Bearer_token` for your account. +2. Login into your `https://app.timelyapp.com` portal, fetch the `account-id` present in the URL (example: URL `https://app.timelyapp.com/12345/calendar` and account-id `12345`). +3. Get a start-date to your events. Dateformat `YYYY-MM-DD`. ## Setup guide ## Step 1: Set up the Timely connector in Airbyte From f267746a18682eb93872d0e6b5b1e231e21b0d31 Mon Sep 17 00:00:00 2001 From: Jimmy Ma Date: Thu, 13 Oct 2022 08:11:31 -0700 Subject: [PATCH 094/498] Use MessageMigration for Source Connection Check. (#17656) * More AirbyteVersion references fix * Propagate protocol version from sourceDef to SchedulerClient * Propagate protocol version to LauncherConfig * Add VersionedMigratorFactory * Update VersionedAirbyteStreamFactory * Fix Version Json serialization/deserialization * Plug message migration in CheckConnection for Sources --- .../protocol/AirbyteMessageSerDeProvider.java | 6 ++-- ...irbyteMessageVersionedMigratorFactory.java | 26 +++++++++++++++ .../migrations/AirbyteMessageMigrationV0.java | 4 +-- .../serde/AirbyteMessageDeserializer.java | 4 +-- .../serde/AirbyteMessageSerializer.java | 4 +-- .../AirbyteMessageMigratorMicronautTest.java | 3 +- .../AirbyteMessageSerDeProviderTest.java | 32 +++++++++---------- .../internal/DefaultAirbyteStreamFactory.java | 2 +- .../VersionedAirbyteStreamFactory.java | 23 ++++++++----- .../IntegrationLauncherConfig.yaml | 3 ++ .../io/airbyte/commons/version/Version.java | 4 +++ .../commons/version/VersionDeserializer.java | 31 ++++++++++++++++++ .../commons/version/VersionSerializer.java | 29 +++++++++++++++++ .../airbyte/commons/version/VersionTest.java | 28 ++++++++++++++++ .../types/JobCheckConnectionConfig.yaml | 3 ++ .../server/handlers/SchedulerHandler.java | 8 +++-- .../DefaultSynchronousSchedulerClient.java | 8 +++-- .../scheduler/SynchronousSchedulerClient.java | 5 ++- .../server/handlers/SchedulerHandlerTest.java | 22 +++++++++---- ...DefaultSynchronousSchedulerClientTest.java | 8 +++-- .../workers/temporal/TemporalClient.java | 3 +- .../CheckConnectionActivityImpl.java | 18 +++++++++-- 22 files changed, 222 insertions(+), 52 deletions(-) create mode 100644 airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java create mode 100644 airbyte-commons/src/main/java/io/airbyte/commons/version/VersionDeserializer.java create mode 100644 airbyte-commons/src/main/java/io/airbyte/commons/version/VersionSerializer.java create mode 100644 airbyte-commons/src/test/java/io/airbyte/commons/version/VersionTest.java diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProvider.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProvider.java index 829b11b696f4..f38f290a5907 100644 --- a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProvider.java +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProvider.java @@ -7,7 +7,7 @@ import com.google.common.annotations.VisibleForTesting; import io.airbyte.commons.protocol.serde.AirbyteMessageDeserializer; import io.airbyte.commons.protocol.serde.AirbyteMessageSerializer; -import io.airbyte.commons.version.AirbyteVersion; +import io.airbyte.commons.version.Version; import jakarta.annotation.PostConstruct; import jakarta.inject.Singleton; import java.util.Collections; @@ -51,14 +51,14 @@ public void initialize() { /** * Returns the Deserializer for the version if known else empty */ - public Optional> getDeserializer(final AirbyteVersion version) { + public Optional> getDeserializer(final Version version) { return Optional.ofNullable(deserializers.get(version.getMajorVersion())); } /** * Returns the Serializer for the version if known else empty */ - public Optional> getSerializer(final AirbyteVersion version) { + public Optional> getSerializer(final Version version) { return Optional.ofNullable(serializers.get(version.getMajorVersion())); } diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java new file mode 100644 index 000000000000..afce6264089f --- /dev/null +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.protocol; + +import io.airbyte.commons.version.Version; +import jakarta.inject.Singleton; + +/** + * Factory to build AirbyteMessageVersionedMigrator + */ +@Singleton +public class AirbyteMessageVersionedMigratorFactory { + + private final AirbyteMessageMigrator migrator; + + public AirbyteMessageVersionedMigratorFactory(final AirbyteMessageMigrator migrator) { + this.migrator = migrator; + } + + public AirbyteMessageVersionedMigrator getVersionedMigrator(final Version version) { + return new AirbyteMessageVersionedMigrator<>(this.migrator, version); + } + +} diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/migrations/AirbyteMessageMigrationV0.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/migrations/AirbyteMessageMigrationV0.java index be6617374ddb..9306cf21e752 100644 --- a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/migrations/AirbyteMessageMigrationV0.java +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/migrations/AirbyteMessageMigrationV0.java @@ -7,13 +7,13 @@ import io.airbyte.commons.json.Jsons; import io.airbyte.commons.version.Version; import io.airbyte.protocol.models.AirbyteMessage; +import jakarta.inject.Singleton; /** * Demo migration to illustrate the template. This should be deleted once we added the v0 to v1 * migration. */ -// NOTE, to actually wire this migration, uncomment the annotation -// @Singleton +@Singleton public class AirbyteMessageMigrationV0 implements AirbyteMessageMigration { diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageDeserializer.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageDeserializer.java index 9e9337417d44..35f1dab4bb54 100644 --- a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageDeserializer.java +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageDeserializer.java @@ -5,12 +5,12 @@ package io.airbyte.commons.protocol.serde; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.version.AirbyteVersion; +import io.airbyte.commons.version.Version; public interface AirbyteMessageDeserializer { T deserialize(final JsonNode json); - AirbyteVersion getTargetVersion(); + Version getTargetVersion(); } diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageSerializer.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageSerializer.java index d3f42b27c689..e7fbb793813a 100644 --- a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageSerializer.java +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/serde/AirbyteMessageSerializer.java @@ -4,12 +4,12 @@ package io.airbyte.commons.protocol.serde; -import io.airbyte.commons.version.AirbyteVersion; +import io.airbyte.commons.version.Version; public interface AirbyteMessageSerializer { String serialize(final T message); - AirbyteVersion getTargetVersion(); + Version getTargetVersion(); } diff --git a/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageMigratorMicronautTest.java b/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageMigratorMicronautTest.java index d5a899fcbd10..a2d5556d99cd 100644 --- a/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageMigratorMicronautTest.java +++ b/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageMigratorMicronautTest.java @@ -9,6 +9,7 @@ import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import java.util.HashSet; +import java.util.List; import org.junit.jupiter.api.Test; @MicronautTest @@ -21,7 +22,7 @@ class AirbyteMessageMigratorMicronautTest { void testMigrationInjection() { // This should contain the list of all the supported majors of the airbyte protocol except the most // recent one since the migrations themselves are keyed on the lower version. - assertEquals(new HashSet<>(), messageMigrator.getMigrationKeys()); + assertEquals(new HashSet<>(List.of("0")), messageMigrator.getMigrationKeys()); } } diff --git a/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProviderTest.java b/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProviderTest.java index 20fd6339aae0..bb52c5047293 100644 --- a/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProviderTest.java +++ b/airbyte-commons-protocol/src/test/java/io/airbyte/commons/protocol/AirbyteMessageSerDeProviderTest.java @@ -11,7 +11,7 @@ import io.airbyte.commons.protocol.serde.AirbyteMessageDeserializer; import io.airbyte.commons.protocol.serde.AirbyteMessageSerializer; -import io.airbyte.commons.version.AirbyteVersion; +import io.airbyte.commons.version.Version; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,35 +29,35 @@ class AirbyteMessageSerDeProviderTest { void beforeEach() { serDeProvider = new AirbyteMessageSerDeProvider(); - deserV0 = buildDeserializer(new AirbyteVersion("0.1.0")); - deserV1 = buildDeserializer(new AirbyteVersion("1.1.0")); + deserV0 = buildDeserializer(new Version("0.1.0")); + deserV1 = buildDeserializer(new Version("1.1.0")); serDeProvider.registerDeserializer(deserV0); serDeProvider.registerDeserializer(deserV1); - serV0 = buildSerializer(new AirbyteVersion("0.2.0")); - serV1 = buildSerializer(new AirbyteVersion("1.0.0")); + serV0 = buildSerializer(new Version("0.2.0")); + serV1 = buildSerializer(new Version("1.0.0")); serDeProvider.registerSerializer(serV0); serDeProvider.registerSerializer(serV1); } @Test void testGetDeserializer() { - assertEquals(Optional.of(deserV0), serDeProvider.getDeserializer(new AirbyteVersion("0.1.0"))); - assertEquals(Optional.of(deserV0), serDeProvider.getDeserializer(new AirbyteVersion("0.2.0"))); - assertEquals(Optional.of(deserV1), serDeProvider.getDeserializer(new AirbyteVersion("1.1.0"))); - assertEquals(Optional.empty(), serDeProvider.getDeserializer(new AirbyteVersion("2.0.0"))); + assertEquals(Optional.of(deserV0), serDeProvider.getDeserializer(new Version("0.1.0"))); + assertEquals(Optional.of(deserV0), serDeProvider.getDeserializer(new Version("0.2.0"))); + assertEquals(Optional.of(deserV1), serDeProvider.getDeserializer(new Version("1.1.0"))); + assertEquals(Optional.empty(), serDeProvider.getDeserializer(new Version("2.0.0"))); } @Test void testGetSerializer() { - assertEquals(Optional.of(serV0), serDeProvider.getSerializer(new AirbyteVersion("0.1.0"))); - assertEquals(Optional.of(serV1), serDeProvider.getSerializer(new AirbyteVersion("1.0.0"))); - assertEquals(Optional.empty(), serDeProvider.getSerializer(new AirbyteVersion("3.2.0"))); + assertEquals(Optional.of(serV0), serDeProvider.getSerializer(new Version("0.1.0"))); + assertEquals(Optional.of(serV1), serDeProvider.getSerializer(new Version("1.0.0"))); + assertEquals(Optional.empty(), serDeProvider.getSerializer(new Version("3.2.0"))); } @Test void testRegisterDeserializerShouldFailOnVersionCollision() { - AirbyteMessageDeserializer deser = buildDeserializer(new AirbyteVersion("0.2.0")); + AirbyteMessageDeserializer deser = buildDeserializer(new Version("0.2.0")); assertThrows(RuntimeException.class, () -> { serDeProvider.registerDeserializer(deser); }); @@ -65,19 +65,19 @@ void testRegisterDeserializerShouldFailOnVersionCollision() { @Test void testRegisterSerializerShouldFailOnVersionCollision() { - AirbyteMessageSerializer ser = buildSerializer(new AirbyteVersion("0.5.0")); + AirbyteMessageSerializer ser = buildSerializer(new Version("0.5.0")); assertThrows(RuntimeException.class, () -> { serDeProvider.registerSerializer(ser); }); } - private AirbyteMessageDeserializer buildDeserializer(AirbyteVersion version) { + private AirbyteMessageDeserializer buildDeserializer(Version version) { final AirbyteMessageDeserializer deser = mock(AirbyteMessageDeserializer.class); when(deser.getTargetVersion()).thenReturn(version); return deser; } - private AirbyteMessageSerializer buildSerializer(AirbyteVersion version) { + private AirbyteMessageSerializer buildSerializer(Version version) { final AirbyteMessageSerializer ser = mock(AirbyteMessageSerializer.class); when(ser.getTargetVersion()).thenReturn(version); return ser; diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java index 6f57949b93a7..0fe70a947d40 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java @@ -32,7 +32,7 @@ public class DefaultAirbyteStreamFactory implements AirbyteStreamFactory { private final MdcScope.Builder containerLogMdcBuilder; private final AirbyteProtocolPredicate protocolValidator; - private final Logger logger; + protected final Logger logger; public DefaultAirbyteStreamFactory() { this(MdcScope.DEFAULT_BUILDER); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java index 98b01931a2d2..88410da0c55d 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java @@ -7,8 +7,11 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.logging.MdcScope; +import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; import io.airbyte.commons.protocol.AirbyteMessageVersionedMigrator; +import io.airbyte.commons.protocol.AirbyteMessageVersionedMigratorFactory; import io.airbyte.commons.protocol.serde.AirbyteMessageDeserializer; +import io.airbyte.commons.version.Version; import io.airbyte.protocol.models.AirbyteMessage; import java.util.stream.Stream; import org.slf4j.Logger; @@ -26,19 +29,23 @@ public class VersionedAirbyteStreamFactory extends DefaultAirbyteStreamFactor private final AirbyteMessageDeserializer deserializer; private final AirbyteMessageVersionedMigrator migrator; + private final Version protocolVersion; - public VersionedAirbyteStreamFactory(final AirbyteMessageDeserializer deserializer, - final AirbyteMessageVersionedMigrator migrator) { - this(deserializer, migrator, MdcScope.DEFAULT_BUILDER); + public VersionedAirbyteStreamFactory(final AirbyteMessageSerDeProvider serDeProvider, + final AirbyteMessageVersionedMigratorFactory migratorFactory, + final Version protocolVersion) { + this(serDeProvider, migratorFactory, protocolVersion, MdcScope.DEFAULT_BUILDER); } - public VersionedAirbyteStreamFactory(final AirbyteMessageDeserializer deserializer, - final AirbyteMessageVersionedMigrator migrator, + public VersionedAirbyteStreamFactory(final AirbyteMessageSerDeProvider serDeProvider, + final AirbyteMessageVersionedMigratorFactory migratorFactory, + final Version protocolVersion, final MdcScope.Builder containerLogMdcBuilder) { // TODO AirbyteProtocolPredicate needs to be updated to be protocol version aware super(new AirbyteProtocolPredicate(), LOGGER, containerLogMdcBuilder); - this.deserializer = deserializer; - this.migrator = migrator; + this.deserializer = (AirbyteMessageDeserializer) serDeProvider.getDeserializer(protocolVersion).orElseThrow(); + this.migrator = migratorFactory.getVersionedMigrator(protocolVersion); + this.protocolVersion = protocolVersion; } @Override @@ -47,7 +54,7 @@ protected Stream toAirbyteMessage(final JsonNode json) { final io.airbyte.protocol.models.v0.AirbyteMessage message = migrator.upgrade(deserializer.deserialize(json)); return Stream.of(convert(message)); } catch (RuntimeException e) { - LOGGER.warn("Failed to upgrade a message from version {}: {}", migrator.getVersion(), Jsons.serialize(json)); + logger.warn("Failed to upgrade a message from version {}: {}", protocolVersion, Jsons.serialize(json), e); return Stream.empty(); } } diff --git a/airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml b/airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml index afc098b734b5..a4c7b6fa63c6 100644 --- a/airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml +++ b/airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml @@ -16,3 +16,6 @@ properties: type: integer dockerImage: type: string + protocolVersion: + type: object + existingJavaType: io.airbyte.commons.version.Version diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/version/Version.java b/airbyte-commons/src/main/java/io/airbyte/commons/version/Version.java index 49fe82e05c55..308d7ececc1e 100644 --- a/airbyte-commons/src/main/java/io/airbyte/commons/version/Version.java +++ b/airbyte-commons/src/main/java/io/airbyte/commons/version/Version.java @@ -4,6 +4,8 @@ package io.airbyte.commons.version; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.base.Preconditions; import java.util.Objects; @@ -11,6 +13,8 @@ * A semVer Version class that allows "dev" as a version. */ @SuppressWarnings({"PMD.AvoidFieldNameMatchingTypeName", "PMD.ConstructorCallsOverridableMethod"}) +@JsonDeserialize(using = VersionDeserializer.class) +@JsonSerialize(using = VersionSerializer.class) public class Version { public static final String DEV_VERSION_PREFIX = "dev"; diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/version/VersionDeserializer.java b/airbyte-commons/src/main/java/io/airbyte/commons/version/VersionDeserializer.java new file mode 100644 index 000000000000..0965c2e71acb --- /dev/null +++ b/airbyte-commons/src/main/java/io/airbyte/commons/version/VersionDeserializer.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.version; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; + +public class VersionDeserializer extends StdDeserializer { + + public VersionDeserializer() { + this(null); + } + + public VersionDeserializer(Class vc) { + super(vc); + } + + @Override + public Version deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException { + final JsonNode node = p.getCodec().readTree(p); + final String v = node.get("version").asText(); + return new Version(v); + } + +} diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/version/VersionSerializer.java b/airbyte-commons/src/main/java/io/airbyte/commons/version/VersionSerializer.java new file mode 100644 index 000000000000..2e66802ce0d4 --- /dev/null +++ b/airbyte-commons/src/main/java/io/airbyte/commons/version/VersionSerializer.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.version; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; + +public class VersionSerializer extends StdSerializer { + + public VersionSerializer() { + this(null); + } + + public VersionSerializer(Class t) { + super(t); + } + + @Override + public void serialize(Version value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + gen.writeStringField("version", value.version); + gen.writeEndObject(); + } + +} diff --git a/airbyte-commons/src/test/java/io/airbyte/commons/version/VersionTest.java b/airbyte-commons/src/test/java/io/airbyte/commons/version/VersionTest.java new file mode 100644 index 000000000000..0219d6abdbc5 --- /dev/null +++ b/airbyte-commons/src/test/java/io/airbyte/commons/version/VersionTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.version; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.airbyte.commons.json.Jsons; +import org.junit.jupiter.api.Test; + +class VersionTest { + + @Test + void testJsonSerializationDeserialization() { + final String jsonString = """ + {"version": "1.2.3"} + """; + final Version expectedVersion = new Version("1.2.3"); + + final Version deserializedVersion = Jsons.deserialize(jsonString, Version.class); + assertEquals(expectedVersion, deserializedVersion); + + final Version deserializedVersionLoop = Jsons.deserialize(Jsons.serialize(deserializedVersion), Version.class); + assertEquals(expectedVersion, deserializedVersionLoop); + } + +} diff --git a/airbyte-config/config-models/src/main/resources/types/JobCheckConnectionConfig.yaml b/airbyte-config/config-models/src/main/resources/types/JobCheckConnectionConfig.yaml index 15592f0a9a29..7f239733d589 100644 --- a/airbyte-config/config-models/src/main/resources/types/JobCheckConnectionConfig.yaml +++ b/airbyte-config/config-models/src/main/resources/types/JobCheckConnectionConfig.yaml @@ -15,3 +15,6 @@ properties: existingJavaType: com.fasterxml.jackson.databind.JsonNode dockerImage: type: string + protocolVersion: + type: object + existingJavaType: io.airbyte.commons.version.Version diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java index 9ab734d073c5..cc88681305dd 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java @@ -35,6 +35,7 @@ import io.airbyte.commons.docker.DockerUtils; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.version.Version; import io.airbyte.config.ActorCatalog; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.DestinationConnection; @@ -131,8 +132,9 @@ public CheckConnectionRead checkSourceConnectionFromSourceId(final SourceIdReque final SourceConnection source = configRepository.getSourceConnection(sourceIdRequestBody.getSourceId()); final StandardSourceDefinition sourceDef = configRepository.getStandardSourceDefinition(source.getSourceDefinitionId()); final String imageName = DockerUtils.getTaggedImageName(sourceDef.getDockerRepository(), sourceDef.getDockerImageTag()); + final Version protocolVersion = new Version(sourceDef.getProtocolVersion()); - return reportConnectionStatus(synchronousSchedulerClient.createSourceCheckConnectionJob(source, imageName)); + return reportConnectionStatus(synchronousSchedulerClient.createSourceCheckConnectionJob(source, imageName, protocolVersion)); } public CheckConnectionRead checkSourceConnectionFromSourceCreate(final SourceCoreConfig sourceConfig) @@ -149,8 +151,10 @@ public CheckConnectionRead checkSourceConnectionFromSourceCreate(final SourceCor .withConfiguration(partialConfig) .withWorkspaceId(sourceConfig.getWorkspaceId()); + final Version protocolVersion = new Version(sourceDef.getProtocolVersion()); + final String imageName = DockerUtils.getTaggedImageName(sourceDef.getDockerRepository(), sourceDef.getDockerImageTag()); - return reportConnectionStatus(synchronousSchedulerClient.createSourceCheckConnectionJob(source, imageName)); + return reportConnectionStatus(synchronousSchedulerClient.createSourceCheckConnectionJob(source, imageName, protocolVersion)); } public CheckConnectionRead checkSourceConnectionFromSourceIdForUpdate(final SourceUpdate sourceUpdate) diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java index 749fa0b08bed..493fea4dc120 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java @@ -11,6 +11,7 @@ import com.google.common.hash.Hashing; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; +import io.airbyte.commons.version.Version; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.DestinationConnection; import io.airbyte.config.JobCheckConnectionConfig; @@ -59,7 +60,9 @@ public DefaultSynchronousSchedulerClient(final TemporalClient temporalClient, } @Override - public SynchronousResponse createSourceCheckConnectionJob(final SourceConnection source, final String dockerImage) + public SynchronousResponse createSourceCheckConnectionJob(final SourceConnection source, + final String dockerImage, + final Version protocolVersion) throws IOException { final JsonNode sourceConfiguration = oAuthConfigSupplier.injectSourceOAuthParameters( source.getSourceDefinitionId(), @@ -67,7 +70,8 @@ public SynchronousResponse createSourceCheckConne source.getConfiguration()); final JobCheckConnectionConfig jobCheckConnectionConfig = new JobCheckConnectionConfig() .withConnectionConfiguration(sourceConfiguration) - .withDockerImage(dockerImage); + .withDockerImage(dockerImage) + .withProtocolVersion(protocolVersion); final UUID jobId = UUID.randomUUID(); final ConnectorJobReportingContext jobReportingContext = new ConnectorJobReportingContext(jobId, dockerImage); diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java index 9bdb462280e8..719a6b196729 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java @@ -4,6 +4,7 @@ package io.airbyte.server.scheduler; +import io.airbyte.commons.version.Version; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardCheckConnectionOutput; @@ -17,7 +18,9 @@ */ public interface SynchronousSchedulerClient { - SynchronousResponse createSourceCheckConnectionJob(SourceConnection source, String dockerImage) + SynchronousResponse createSourceCheckConnectionJob(SourceConnection source, + String dockerImage, + Version protocolVersion) throws IOException; SynchronousResponse createDestinationCheckConnectionJob(DestinationConnection destination, String dockerImage) diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java index f9ad56a8bf5f..98bdebd68d3b 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java @@ -40,6 +40,7 @@ import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; +import io.airbyte.commons.version.Version; import io.airbyte.config.ActorCatalog; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.DestinationConnection; @@ -89,10 +90,12 @@ class SchedulerHandlerTest { private static final String SOURCE_DOCKER_REPO = "srcimage"; private static final String SOURCE_DOCKER_TAG = "tag"; private static final String SOURCE_DOCKER_IMAGE = DockerUtils.getTaggedImageName(SOURCE_DOCKER_REPO, SOURCE_DOCKER_TAG); + private static final String SOURCE_PROTOCOL_VERSION = "0.4.5"; private static final String DESTINATION_DOCKER_REPO = "dstimage"; private static final String DESTINATION_DOCKER_TAG = "tag"; private static final String DESTINATION_DOCKER_IMAGE = DockerUtils.getTaggedImageName(DESTINATION_DOCKER_REPO, DESTINATION_DOCKER_TAG); + private static final String DESTINATION_PROTOCOL_VERSION = "0.7.9"; private static final AirbyteCatalog airbyteCatalog = CatalogHelpers.createAirbyteCatalog("shoes", Field.of("sku", JsonSchemaType.STRING)); @@ -165,20 +168,22 @@ void setup() { void testCheckSourceConnectionFromSourceId() throws JsonValidationException, IOException, ConfigNotFoundException { final SourceConnection source = SourceHelpers.generateSource(UUID.randomUUID()); final SourceIdRequestBody request = new SourceIdRequestBody().sourceId(source.getSourceId()); + final Version protocolVersion = new Version(SOURCE_PROTOCOL_VERSION); when(configRepository.getStandardSourceDefinition(source.getSourceDefinitionId())) .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); when(configRepository.getSourceConnection(source.getSourceId())).thenReturn(source); - when(synchronousSchedulerClient.createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE)) + when(synchronousSchedulerClient.createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE, protocolVersion)) .thenReturn((SynchronousResponse) jobResponse); schedulerHandler.checkSourceConnectionFromSourceId(request); verify(configRepository).getSourceConnection(source.getSourceId()); - verify(synchronousSchedulerClient).createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE); + verify(synchronousSchedulerClient).createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE, protocolVersion); } @Test @@ -192,20 +197,23 @@ void testCheckSourceConnectionFromSourceCreate() throws JsonValidationException, .connectionConfiguration(source.getConfiguration()) .workspaceId(source.getWorkspaceId()); + final Version protocolVersion = new Version(SOURCE_PROTOCOL_VERSION); + when(configRepository.getStandardSourceDefinition(source.getSourceDefinitionId())) .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); when(secretsRepositoryWriter.statefulSplitEphemeralSecrets( eq(source.getConfiguration()), any())).thenReturn(source.getConfiguration()); - when(synchronousSchedulerClient.createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE)) + when(synchronousSchedulerClient.createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE, protocolVersion)) .thenReturn((SynchronousResponse) jobResponse); schedulerHandler.checkSourceConnectionFromSourceCreate(sourceCoreConfig); - verify(synchronousSchedulerClient).createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE); + verify(synchronousSchedulerClient).createSourceCheckConnectionJob(source, SOURCE_DOCKER_IMAGE, protocolVersion); } @Test @@ -218,8 +226,10 @@ void testCheckSourceConnectionFromUpdate() throws IOException, JsonValidationExc final StandardSourceDefinition sourceDefinition = new StandardSourceDefinition() .withDockerRepository(DESTINATION_DOCKER_REPO) .withDockerImageTag(DESTINATION_DOCKER_TAG) + .withProtocolVersion(DESTINATION_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId()) .withSpec(CONNECTOR_SPECIFICATION); + final Version protocolVersion = new Version(DESTINATION_PROTOCOL_VERSION); when(configRepository.getStandardSourceDefinition(source.getSourceDefinitionId())) .thenReturn(sourceDefinition); when(configRepository.getSourceConnection(source.getSourceId())).thenReturn(source); @@ -227,7 +237,7 @@ void testCheckSourceConnectionFromUpdate() throws IOException, JsonValidationExc final SourceConnection submittedSource = new SourceConnection() .withSourceDefinitionId(source.getSourceDefinitionId()) .withConfiguration(source.getConfiguration()); - when(synchronousSchedulerClient.createSourceCheckConnectionJob(submittedSource, DESTINATION_DOCKER_IMAGE)) + when(synchronousSchedulerClient.createSourceCheckConnectionJob(submittedSource, DESTINATION_DOCKER_IMAGE, protocolVersion)) .thenReturn((SynchronousResponse) jobResponse); when(secretsRepositoryWriter.statefulSplitEphemeralSecrets( eq(source.getConfiguration()), @@ -235,7 +245,7 @@ void testCheckSourceConnectionFromUpdate() throws IOException, JsonValidationExc schedulerHandler.checkSourceConnectionFromSourceIdForUpdate(sourceUpdate); verify(jsonSchemaValidator).ensure(CONNECTOR_SPECIFICATION.getConnectionSpecification(), source.getConfiguration()); - verify(synchronousSchedulerClient).createSourceCheckConnectionJob(submittedSource, DESTINATION_DOCKER_IMAGE); + verify(synchronousSchedulerClient).createSourceCheckConnectionJob(submittedSource, DESTINATION_DOCKER_IMAGE, protocolVersion); } @Test diff --git a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java index e8e920be670b..02c203a53318 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.version.Version; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.DestinationConnection; import io.airbyte.config.JobCheckConnectionConfig; @@ -54,8 +55,8 @@ class DefaultSynchronousSchedulerClientTest { private static final Path LOG_PATH = Path.of("/tmp"); private static final String DOCKER_IMAGE = "foo/bar"; - private static final String DOCKER_IMAGE_TAG = "baz/qux"; + private static final Version PROTOCOL_VERSION = new Version("0.2.3"); private static final UUID WORKSPACE_ID = UUID.randomUUID(); private static final UUID UUID1 = UUID.randomUUID(); private static final UUID UUID2 = UUID.randomUUID(); @@ -202,14 +203,15 @@ class TestJobCreation { void testCreateSourceCheckConnectionJob() throws IOException { final JobCheckConnectionConfig jobCheckConnectionConfig = new JobCheckConnectionConfig() .withConnectionConfiguration(SOURCE_CONNECTION.getConfiguration()) - .withDockerImage(DOCKER_IMAGE); + .withDockerImage(DOCKER_IMAGE) + .withProtocolVersion(PROTOCOL_VERSION); final StandardCheckConnectionOutput mockOutput = mock(StandardCheckConnectionOutput.class); final ConnectorJobOutput jobOutput = new ConnectorJobOutput().withCheckConnection(mockOutput); when(temporalClient.submitCheckConnection(any(UUID.class), eq(0), eq(jobCheckConnectionConfig))) .thenReturn(new TemporalResponse<>(jobOutput, createMetadata(true))); final SynchronousResponse response = - schedulerClient.createSourceCheckConnectionJob(SOURCE_CONNECTION, DOCKER_IMAGE); + schedulerClient.createSourceCheckConnectionJob(SOURCE_CONNECTION, DOCKER_IMAGE, PROTOCOL_VERSION); assertEquals(mockOutput, response.getOutput()); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java index bcb646db3ac4..b85a32f79ba8 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java @@ -125,7 +125,8 @@ public TemporalResponse submitCheckConnection(final UUID job final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() .withJobId(jobId.toString()) .withAttemptId((long) attempt) - .withDockerImage(config.getDockerImage()); + .withDockerImage(config.getDockerImage()) + .withProtocolVersion(config.getProtocolVersion()); final StandardCheckConnectionInput input = new StandardCheckConnectionInput().withConnectionConfiguration(config.getConnectionConfiguration()); return execute(jobRunConfig, diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java index b53e60211999..8448b1014aef 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; +import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; +import io.airbyte.commons.protocol.AirbyteMessageVersionedMigratorFactory; import io.airbyte.commons.temporal.CancellationHandler; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.ConnectorJobOutput; @@ -20,6 +22,9 @@ import io.airbyte.workers.WorkerConfigs; import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.general.DefaultCheckConnectionWorker; +import io.airbyte.workers.internal.AirbyteStreamFactory; +import io.airbyte.workers.internal.DefaultAirbyteStreamFactory; +import io.airbyte.workers.internal.VersionedAirbyteStreamFactory; import io.airbyte.workers.process.AirbyteIntegrationLauncher; import io.airbyte.workers.process.IntegrationLauncher; import io.airbyte.workers.process.ProcessFactory; @@ -44,6 +49,8 @@ public class CheckConnectionActivityImpl implements CheckConnectionActivity { private final LogConfigs logConfigs; private final AirbyteApiClient airbyteApiClient; private final String airbyteVersion; + private final AirbyteMessageSerDeProvider serDeProvider; + private final AirbyteMessageVersionedMigratorFactory migratorFactory; public CheckConnectionActivityImpl(@Named("checkWorkerConfigs") final WorkerConfigs workerConfigs, @Named("checkProcessFactory") final ProcessFactory processFactory, @@ -52,7 +59,9 @@ public CheckConnectionActivityImpl(@Named("checkWorkerConfigs") final WorkerConf final WorkerEnvironment workerEnvironment, final LogConfigs logConfigs, final AirbyteApiClient airbyteApiClient, - @Value("${airbyte.version}") final String airbyteVersion) { + @Value("${airbyte.version}") final String airbyteVersion, + final AirbyteMessageSerDeProvider serDeProvider, + final AirbyteMessageVersionedMigratorFactory migratorFactory) { this.workerConfigs = workerConfigs; this.processFactory = processFactory; this.workspaceRoot = workspaceRoot; @@ -61,6 +70,8 @@ public CheckConnectionActivityImpl(@Named("checkWorkerConfigs") final WorkerConf this.airbyteApiClient = airbyteApiClient; this.secretsHydrator = secretsHydrator; this.airbyteVersion = airbyteVersion; + this.serDeProvider = serDeProvider; + this.migratorFactory = migratorFactory; } @Override @@ -105,8 +116,11 @@ private CheckedSupplier launcherConfig.getDockerImage(), processFactory, workerConfigs.getResourceRequirements()); + final AirbyteStreamFactory streamFactory = launcherConfig.getProtocolVersion() != null + ? new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, launcherConfig.getProtocolVersion()) + : new DefaultAirbyteStreamFactory(); - return new DefaultCheckConnectionWorker(integrationLauncher); + return new DefaultCheckConnectionWorker(integrationLauncher, streamFactory); }; } From 2247038a520c687b35a9e28ad9a39fac35558d28 Mon Sep 17 00:00:00 2001 From: Subham Sahu Date: Thu, 13 Oct 2022 21:37:35 +0530 Subject: [PATCH 095/498] DocS: Update authentication.md (#17931) --- .../understanding-the-yaml-file/authentication.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md b/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md index 187752f2f200..bcb4c126d1c2 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/authentication.md @@ -46,7 +46,7 @@ Example: authenticator: type: "ApiKeyAuthenticator" header: "Authorization" - token: "Bearer hello" + api_token: "Bearer hello" ``` ### BearerAuthenticator @@ -74,7 +74,7 @@ Example: ```yaml authenticator: type: "BearerAuthenticator" - token: "hello" + api_token: "hello" ``` More information on bearer authentication can be found [here](https://swagger.io/docs/specification/authentication/bearer-authentication/). @@ -189,4 +189,4 @@ authenticator: ## More readings - [Requester](./requester.md) -- [Request options](./request-options.md) \ No newline at end of file +- [Request options](./request-options.md) From 6eb3893f14806818968b31f87a96824ac854a3e9 Mon Sep 17 00:00:00 2001 From: Delena Malan Date: Thu, 13 Oct 2022 18:07:56 +0200 Subject: [PATCH 096/498] Remove typo line from incremental reads docs (#17920) --- .../config-based/tutorial/5-incremental-reads.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/connector-development/config-based/tutorial/5-incremental-reads.md b/docs/connector-development/config-based/tutorial/5-incremental-reads.md index b75626a13e52..87f08c4c43fb 100644 --- a/docs/connector-development/config-based/tutorial/5-incremental-reads.md +++ b/docs/connector-development/config-based/tutorial/5-incremental-reads.md @@ -125,7 +125,6 @@ streams: ``` We'll also update the retriever to user the stream slicer: -> > > > > > > master ```yaml definitions: From 27e089eabc0d4b54525521892e7d48b74b10d3f0 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Thu, 13 Oct 2022 12:17:36 -0400 Subject: [PATCH 097/498] Ensure database initialization in test container (#17697) * Ensure database initialization in test container * Remove unused import * Update schema dump * Add schema dump Co-authored-by: terencecho --- .../development/MigrationDevCenter.java | 11 ++++++ .../configs_database/schema_dump.txt | 38 +++++++++++++++++++ .../resources/jobs_database/schema_dump.txt | 16 ++++++++ 3 files changed, 65 insertions(+) diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/development/MigrationDevCenter.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/development/MigrationDevCenter.java index 9b4d2d89af6b..b95580c9f98b 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/development/MigrationDevCenter.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/instance/development/MigrationDevCenter.java @@ -16,6 +16,8 @@ import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.ext.ScriptUtils; +import org.testcontainers.jdbc.JdbcDatabaseDelegate; /** * Helper class for migration development. See README for details. @@ -47,6 +49,9 @@ private static PostgreSQLContainer createContainer() { .withUsername("docker") .withPassword("docker"); container.start(); + + initializeDatabase(container); + return container; } @@ -106,6 +111,12 @@ private void dumpSchema() { } } + private static void initializeDatabase(final PostgreSQLContainer container) { + final var containerDelegate = new JdbcDatabaseDelegate(container, ""); + ScriptUtils.runInitScript(containerDelegate, "configs_database/schema.sql"); + ScriptUtils.runInitScript(containerDelegate, "jobs_database/schema.sql"); + } + public static void main(final String[] args) { final MigrationDevCenter devCenter; diff --git a/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt b/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt index 682bb706bd5f..caaa27c9bb93 100644 --- a/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt +++ b/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt @@ -86,6 +86,25 @@ create table "public"."airbyte_configs_migrations"( constraint "airbyte_configs_migrations_pk" primary key ("installed_rank") ); +create table "public"."airbyte_metadata"( + "key" varchar(255) not null, + "value" varchar(255) null, + constraint "airbyte_metadata_pkey" + primary key ("key") +); +create table "public"."attempts"( + "id" int8 generated by default as identity not null, + "job_id" int8 null, + "attempt_number" int4 null, + "log_path" varchar(255) null, + "output" jsonb null, + "status" any null, + "created_at" timestamptz(35) null, + "updated_at" timestamptz(35) null, + "ended_at" timestamptz(35) null, + constraint "attempts_pkey" + primary key ("id") +); create table "public"."connection"( "id" uuid not null, "namespace_definition" namespace_definition_type not null, @@ -124,6 +143,18 @@ create table "public"."connection_operation"( "operation_id" ) ); +create table "public"."jobs"( + "id" int8 generated by default as identity not null, + "config_type" any null, + "scope" varchar(255) null, + "config" jsonb null, + "status" any null, + "started_at" timestamptz(35) null, + "created_at" timestamptz(35) null, + "updated_at" timestamptz(35) null, + constraint "jobs_pkey" + primary key ("id") +); create table "public"."operation"( "id" uuid not null, "workspace_id" uuid not null, @@ -273,6 +304,12 @@ create index "actor_oauth_parameter_workspace_definition_idx" on "public"."actor ); create unique index "airbyte_configs_migrations_pk" on "public"."airbyte_configs_migrations"("installed_rank" asc); create index "airbyte_configs_migrations_s_idx" on "public"."airbyte_configs_migrations"("success" asc); +create unique index "airbyte_metadata_pkey" on "public"."airbyte_metadata"("key" asc); +create unique index "attempts_pkey" on "public"."attempts"("id" asc); +create unique index "job_attempt_idx" on "public"."attempts"( + "job_id" asc, + "attempt_number" asc +); create index "connection_destination_id_idx" on "public"."connection"("destination_id" asc); create unique index "connection_pkey" on "public"."connection"("id" asc); create index "connection_source_id_idx" on "public"."connection"("source_id" asc); @@ -282,6 +319,7 @@ create unique index "connection_operation_pkey" on "public"."connection_operatio "connection_id" asc, "operation_id" asc ); +create unique index "jobs_pkey" on "public"."jobs"("id" asc); create unique index "operation_pkey" on "public"."operation"("id" asc); create unique index "state__connection_id__stream_name__namespace__uq" on "public"."state"( "connection_id" asc, diff --git a/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt b/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt index 15cd985a9118..460b55b07c3e 100644 --- a/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt +++ b/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt @@ -2,6 +2,16 @@ // It is also not used by any piece of code to generate anything. // It doesn't contain the enums created in the database and the default values might also be buggy. +create table "public"."airbyte_configs"( + "id" int8 generated by default as identity not null, + "config_id" varchar(36) not null, + "config_type" varchar(60) not null, + "config_blob" jsonb not null, + "created_at" timestamptz(35) not null default null, + "updated_at" timestamptz(35) not null default null, + constraint "airbyte_configs_pkey" + primary key ("id") +); create table "public"."airbyte_jobs_migrations"( "installed_rank" int4 not null, "version" varchar(50) null, @@ -86,6 +96,12 @@ alter table "public"."sync_stats" add constraint "sync_stats_attempt_id_fkey" foreign key ("attempt_id") references "public"."attempts" ("id"); +create index "airbyte_configs_id_idx" on "public"."airbyte_configs"("config_id" asc); +create unique index "airbyte_configs_pkey" on "public"."airbyte_configs"("id" asc); +create unique index "airbyte_configs_type_id_idx" on "public"."airbyte_configs"( + "config_type" asc, + "config_id" asc +); create unique index "airbyte_jobs_migrations_pk" on "public"."airbyte_jobs_migrations"("installed_rank" asc); create index "airbyte_jobs_migrations_s_idx" on "public"."airbyte_jobs_migrations"("success" asc); create unique index "airbyte_metadata_pkey" on "public"."airbyte_metadata"("key" asc); From ace489cb15d4d36acc8ee53ae6ac547c62ff0d59 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Thu, 13 Oct 2022 09:45:35 -0700 Subject: [PATCH 098/498] Remove the bump in the value.yml (#17959) --- .bumpversion.cfg | 2 -- 1 file changed, 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 379b1ed34d28..f01a4920ec12 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -38,8 +38,6 @@ serialize = [bumpversion:file:charts/airbyte-bootloader/Chart.yaml] -[bumpversion:file:charts/airbyte/values.yaml] - [bumpversion:file:charts/airbyte/README.md] [bumpversion:file:docs/operator-guides/upgrading-airbyte.md] From 4ad7c97ac9f620c6e87191d8cafa4c3145b452c0 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Thu, 13 Oct 2022 12:55:30 -0400 Subject: [PATCH 099/498] Update schema dumps (#17960) --- .../configs_database/schema_dump.txt | 38 ------------------- .../resources/jobs_database/schema_dump.txt | 16 -------- 2 files changed, 54 deletions(-) diff --git a/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt b/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt index caaa27c9bb93..682bb706bd5f 100644 --- a/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt +++ b/airbyte-db/db-lib/src/main/resources/configs_database/schema_dump.txt @@ -86,25 +86,6 @@ create table "public"."airbyte_configs_migrations"( constraint "airbyte_configs_migrations_pk" primary key ("installed_rank") ); -create table "public"."airbyte_metadata"( - "key" varchar(255) not null, - "value" varchar(255) null, - constraint "airbyte_metadata_pkey" - primary key ("key") -); -create table "public"."attempts"( - "id" int8 generated by default as identity not null, - "job_id" int8 null, - "attempt_number" int4 null, - "log_path" varchar(255) null, - "output" jsonb null, - "status" any null, - "created_at" timestamptz(35) null, - "updated_at" timestamptz(35) null, - "ended_at" timestamptz(35) null, - constraint "attempts_pkey" - primary key ("id") -); create table "public"."connection"( "id" uuid not null, "namespace_definition" namespace_definition_type not null, @@ -143,18 +124,6 @@ create table "public"."connection_operation"( "operation_id" ) ); -create table "public"."jobs"( - "id" int8 generated by default as identity not null, - "config_type" any null, - "scope" varchar(255) null, - "config" jsonb null, - "status" any null, - "started_at" timestamptz(35) null, - "created_at" timestamptz(35) null, - "updated_at" timestamptz(35) null, - constraint "jobs_pkey" - primary key ("id") -); create table "public"."operation"( "id" uuid not null, "workspace_id" uuid not null, @@ -304,12 +273,6 @@ create index "actor_oauth_parameter_workspace_definition_idx" on "public"."actor ); create unique index "airbyte_configs_migrations_pk" on "public"."airbyte_configs_migrations"("installed_rank" asc); create index "airbyte_configs_migrations_s_idx" on "public"."airbyte_configs_migrations"("success" asc); -create unique index "airbyte_metadata_pkey" on "public"."airbyte_metadata"("key" asc); -create unique index "attempts_pkey" on "public"."attempts"("id" asc); -create unique index "job_attempt_idx" on "public"."attempts"( - "job_id" asc, - "attempt_number" asc -); create index "connection_destination_id_idx" on "public"."connection"("destination_id" asc); create unique index "connection_pkey" on "public"."connection"("id" asc); create index "connection_source_id_idx" on "public"."connection"("source_id" asc); @@ -319,7 +282,6 @@ create unique index "connection_operation_pkey" on "public"."connection_operatio "connection_id" asc, "operation_id" asc ); -create unique index "jobs_pkey" on "public"."jobs"("id" asc); create unique index "operation_pkey" on "public"."operation"("id" asc); create unique index "state__connection_id__stream_name__namespace__uq" on "public"."state"( "connection_id" asc, diff --git a/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt b/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt index 460b55b07c3e..15cd985a9118 100644 --- a/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt +++ b/airbyte-db/db-lib/src/main/resources/jobs_database/schema_dump.txt @@ -2,16 +2,6 @@ // It is also not used by any piece of code to generate anything. // It doesn't contain the enums created in the database and the default values might also be buggy. -create table "public"."airbyte_configs"( - "id" int8 generated by default as identity not null, - "config_id" varchar(36) not null, - "config_type" varchar(60) not null, - "config_blob" jsonb not null, - "created_at" timestamptz(35) not null default null, - "updated_at" timestamptz(35) not null default null, - constraint "airbyte_configs_pkey" - primary key ("id") -); create table "public"."airbyte_jobs_migrations"( "installed_rank" int4 not null, "version" varchar(50) null, @@ -96,12 +86,6 @@ alter table "public"."sync_stats" add constraint "sync_stats_attempt_id_fkey" foreign key ("attempt_id") references "public"."attempts" ("id"); -create index "airbyte_configs_id_idx" on "public"."airbyte_configs"("config_id" asc); -create unique index "airbyte_configs_pkey" on "public"."airbyte_configs"("id" asc); -create unique index "airbyte_configs_type_id_idx" on "public"."airbyte_configs"( - "config_type" asc, - "config_id" asc -); create unique index "airbyte_jobs_migrations_pk" on "public"."airbyte_jobs_migrations"("installed_rank" asc); create index "airbyte_jobs_migrations_s_idx" on "public"."airbyte_jobs_migrations"("success" asc); create unique index "airbyte_metadata_pkey" on "public"."airbyte_metadata"("key" asc); From 3bcf15ba995626121c708023235c54af6a8354a4 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Thu, 13 Oct 2022 10:41:52 -0700 Subject: [PATCH 100/498] fix `est_num_metrics_emitted_by_reporter` not being emitted (#17929) * Revert "Revert metrics reporter migration to micronaut (#17927)" This reverts commit 473b5db43f0c1480b359d3f3792fdef0b1907f0f. * ensure EST_NUM_METRICS_EMITTED_BY_REPORTER is called --- .../io/airbyte/metrics/lib/MetricQueries.java | 177 ------ .../metrics/lib/OssMetricsRegistry.java | 6 +- .../metrics/lib/MetricsQueriesTest.java | 505 ----------------- airbyte-metrics/reporter/build.gradle | 24 +- .../airbyte/metrics/reporter/Application.java | 23 + .../io/airbyte/metrics/reporter/Emitter.java | 196 +++++++ .../metrics/reporter/EventListeners.java | 56 ++ .../metrics/reporter/MetricRepository.java | 175 ++++++ .../airbyte/metrics/reporter/ReporterApp.java | 68 --- .../metrics/reporter/ReporterFactory.java | 24 + .../io/airbyte/metrics/reporter/ToEmit.java | 112 ---- .../src/main/resources/application.yml | 38 ++ .../src/main/resources/micronaut-banner.txt | 8 + .../airbyte/metrics/reporter/EmitterTest.java | 167 ++++++ .../reporter/MetricRepositoryTest.java | 506 ++++++++++++++++++ 15 files changed, 1218 insertions(+), 867 deletions(-) create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java create mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java delete mode 100644 airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java create mode 100644 airbyte-metrics/reporter/src/main/resources/application.yml create mode 100644 airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt create mode 100644 airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java create mode 100644 airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java index 4a83550a02e8..bb4eeaaa6347 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricQueries.java @@ -6,19 +6,11 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; -import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; -import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; -import static org.jooq.impl.SQLDataType.VARCHAR; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; -import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import java.util.ArrayList; import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; import org.jooq.DSLContext; /** @@ -57,173 +49,4 @@ public static List srcIdAndDestIdToReleaseStages(final DSLContext .or(ACTOR.ID.eq(dstId)).fetch().getValues(ACTOR_DEFINITION.RELEASE_STAGE); } - public static int numberOfPendingJobs(final DSLContext ctx) { - return ctx.selectCount().from(JOBS).where(JOBS.STATUS.eq(JobStatus.pending)).fetchOne(0, int.class); - } - - public static int numberOfRunningJobs(final DSLContext ctx) { - return ctx.selectCount().from(JOBS).join(CONNECTION).on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) - .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) - .fetchOne(0, int.class); - } - - public static int numberOfOrphanRunningJobs(final DSLContext ctx) { - return ctx.selectCount().from(JOBS).join(CONNECTION).on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) - .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.ne(StatusType.active))) - .fetchOne(0, int.class); - } - - public static Long oldestPendingJobAgeSecs(final DSLContext ctx) { - return oldestJobAgeSecs(ctx, JobStatus.pending); - } - - public static Long oldestRunningJobAgeSecs(final DSLContext ctx) { - return oldestJobAgeSecs(ctx, JobStatus.running); - } - - private static Long oldestJobAgeSecs(final DSLContext ctx, final JobStatus status) { - final var readableTimeField = "run_duration"; - final var durationSecField = "run_duration_secs"; - final var query = String.format(""" - WITH - oldest_job AS ( - SELECT id, - age(current_timestamp, created_at) AS %s - FROM jobs - WHERE status = '%s' - ORDER BY run_duration DESC - LIMIT 1) - SELECT id, - run_duration, - extract(epoch from run_duration) as %s - FROM oldest_job""", readableTimeField, status.getLiteral(), durationSecField); - final var res = ctx.fetch(query); - // unfortunately there are no good Jooq methods for retrieving a single record of a single column - // forcing the List cast. - final var duration = res.getValues(durationSecField, Double.class); - - if (duration.size() == 0) { - return 0L; - } - // .get(0) works in the following code due to the query's SELECT 1. - final var id = res.getValues("id", String.class).get(0); - final var readableTime = res.getValues(readableTimeField, String.class).get(0); - log.info("oldest job information - id: {}, readable time: {}", id, readableTime); - - // as double can have rounding errors, round down to remove noise. - return duration.get(0).longValue(); - } - - public static List numberOfActiveConnPerWorkspace(final DSLContext ctx) { - final var countField = "num_conn"; - final var query = String.format(""" - SELECT workspace_id, count(c.id) as %s - FROM actor - INNER JOIN workspace ws ON actor.workspace_id = ws.id - INNER JOIN connection c ON actor.id = c.source_id - WHERE ws.tombstone = false - AND actor.tombstone = false AND actor.actor_type = 'source' - AND c.status = 'active' - GROUP BY workspace_id;""", countField); - return ctx.fetch(query).getValues(countField, long.class); - } - - public static List> overallJobRuntimeForTerminalJobsInLastHour(final DSLContext ctx) { - final var statusField = "status"; - final var timeField = "sec"; - final var query = - String.format(""" - SELECT %s, extract(epoch from age(updated_at, created_at)) AS %s FROM jobs - WHERE updated_at >= NOW() - INTERVAL '1 HOUR' - AND (jobs.status = 'failed' OR jobs.status = 'succeeded' OR jobs.status = 'cancelled');""", statusField, timeField); - final var statuses = ctx.fetch(query).getValues(statusField, JobStatus.class); - final var times = ctx.fetch(query).getValues(timeField, double.class); - - final var pairedRes = new ArrayList>(); - for (int i = 0; i < statuses.size(); i++) { - final var pair = new ImmutablePair<>(statuses.get(i), times.get(i)); - pairedRes.add(pair); - } - - return pairedRes; - } - - /* - * A connection that is not running on schedule is defined in last 24 hours if the number of runs - * are not matching with the number of expected runs according to the schedule settings. Refer to - * runbook for detailed discussion. - * - */ - public static Long numberOfJobsNotRunningOnScheduleInLastDay(final DSLContext ctx) { - final var countField = "cnt"; - // This query finds all sync jobs ran in last 24 hours and count how many times they have run. - // Comparing this to the expected number of runs (24 hours divide by configured cadence in hours), - // if it runs below that expected number it will be considered as abnormal instance. - // For example, if it's configured to run every 6 hours but in last 24 hours it only has 3 runs, - // it will be considered as 1 abnormal instance. - final var queryForAbnormalSyncInHoursInLastDay = - String.format(""" - select count(1) as %s - from - ( - select - c.id, - count(*) as cnt - from - connection c - left join Jobs j on - j.scope::uuid = c.id - where - c.schedule is not null - and c.schedule != 'null' - and j.created_at > now() - interval '24 hours 1 minutes' - and c.status = 'active' - and j.config_type = 'sync' - and c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) = '"hours"' - group by 1 - having count(*) < 24 / cast(c.schedule::jsonb->'units' as integer)) as abnormal_jobs - """, countField); - - // Similar to the query above, this finds if the connection cadence's timeUnit is minutes. - // thus we use 1440 (=24 hours x 60 minutes) to divide the configured cadence. - final var queryForAbnormalSyncInMinutesInLastDay = - String.format(""" - select count(1) as %s from ( - select - c.id, - count(*) as cnt - from - connection c - left join Jobs j on - j.scope::uuid = c.id - where - c.schedule is not null - and c.schedule != 'null' - and j.created_at > now() - interval '24 hours 1 minutes' - and c.status = 'active' - and j.config_type = 'sync' - and c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) = '"minutes"' - group by 1 - having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer)) as abnormal_jobs - """, countField); - return ctx.fetch(queryForAbnormalSyncInHoursInLastDay).getValues(countField, long.class).get(0) - + ctx.fetch(queryForAbnormalSyncInMinutesInLastDay).getValues(countField, long.class).get(0); - } - - public static Long numScheduledActiveConnectionsInLastDay(final DSLContext ctx) { - final var countField = "cnt"; - final var queryForTotalConnections = String.format(""" - select count(1) as %s - from connection c - where - c.updated_at < now() - interval '24 hours 1 minutes' - and cast(c.schedule::jsonb->'timeUnit' as text) IN ('"hours"', '"minutes"') - and c.status = 'active' - """, countField); - - return ctx.fetch(queryForTotalConnections).getValues(countField, long.class).get(0); - } - } diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java index 67c750bd49f1..75e30ebf335d 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java @@ -9,15 +9,15 @@ /** * Enum source of truth of all Airbyte metrics. Each enum value represent a metric and is linked to * an application and contains a description to make it easier to understand. - * + *

    * Each object of the enum actually represent a metric, so the Registry name is misleading. The * reason 'Registry' is in the name is to emphasize this enum's purpose as a source of truth for all * metrics. This also helps code readability i.e. AirbyteMetricsRegistry.metricA. - * + *

    * Metric Name Convention (adapted from * https://docs.datadoghq.com/developers/guide/what-best-practices-are-recommended-for-naming-metrics-and-tags/): *

    - * - Use lowercase. Metric names are case sensitive. + * - Use lowercase. Metric names are case-sensitive. *

    * - Use underscore to delimit names with multiple words. *

    diff --git a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java index 7184b892ab04..ae94b43f07d5 100644 --- a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java +++ b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/MetricsQueriesTest.java @@ -15,7 +15,6 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; @@ -23,19 +22,13 @@ import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; -import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; import io.airbyte.db.instance.test.TestDatabaseProviders; import io.airbyte.test.utils.DatabaseConnectionHelper; import java.io.IOException; import java.sql.SQLException; -import java.time.OffsetDateTime; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.UUID; import javax.sql.DataSource; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.jooq.DSLContext; import org.jooq.JSONB; import org.jooq.SQLDialect; @@ -172,502 +165,4 @@ void shouldReturnNothingIfNotApplicable() throws SQLException { } - @Nested - class numJobs { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - void runningJobsShouldReturnCorrectCount() throws SQLException { - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) - .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) - .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) - .execute()); - final UUID activeConnectionId = UUID.randomUUID(); - final UUID inactiveConnectionId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, - CONNECTION.DESTINATION_ID, CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL) - .values(activeConnectionId, StatusType.active, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) - .values(inactiveConnectionId, StatusType.inactive, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) - .execute()); - - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, activeConnectionId.toString(), JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, activeConnectionId.toString(), JobStatus.failed).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, activeConnectionId.toString(), JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, activeConnectionId.toString(), JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(5L, inactiveConnectionId.toString(), JobStatus.running).execute()); - - assertEquals(2, configDb.query(MetricQueries::numberOfRunningJobs)); - assertEquals(1, configDb.query(MetricQueries::numberOfOrphanRunningJobs)); - } - - @Test - void runningJobsShouldReturnZero() throws SQLException { - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::numberOfRunningJobs); - assertEquals(0, res); - } - - @Test - void pendingJobsShouldReturnCorrectCount() throws SQLException { - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.running).execute()); - - final var res = configDb.query(MetricQueries::numberOfPendingJobs); - assertEquals(2, res); - } - - @Test - void pendingJobsShouldReturnZero() throws SQLException { - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::numberOfPendingJobs); - assertEquals(0, res); - } - - } - - @Nested - class oldestPendingJob { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - @DisplayName("should return only the pending job's age in seconds") - void shouldReturnOnlyPendingSeconds() throws SQLException { - final var expAgeSecs = 1000; - final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - // oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(1L, "", JobStatus.pending, oldestCreateAt) - .execute()); - // second oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(2L, "", JobStatus.pending, OffsetDateTime.now()) - .execute()); - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestPendingJobAgeSecs); - // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(999L, 1000L, 1001L).contains(res)); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.succeeded).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestPendingJobAgeSecs); - assertEquals(0L, res); - } - - } - - @Nested - class oldestRunningJob { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - @DisplayName("should return only the running job's age in seconds") - void shouldReturnOnlyRunningSeconds() throws SQLException { - final var expAgeSecs = 10000; - final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - // oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(1L, "", JobStatus.running, oldestCreateAt) - .execute()); - // second oldest pending job - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT).values(2L, "", JobStatus.running, OffsetDateTime.now()) - .execute()); - // non-pending jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(4L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestRunningJobAgeSecs); - // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.succeeded).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.pending).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.failed).execute()); - - final var res = configDb.query(MetricQueries::oldestRunningJobAgeSecs); - assertEquals(0L, res); - } - - } - - @Nested - class numActiveConnPerWorkspace { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(CONNECTION).cascade().execute()); - configDb.transaction(ctx -> ctx.truncate(ACTOR).cascade().execute()); - configDb.transaction(ctx -> ctx.truncate(WORKSPACE).cascade().execute()); - } - - @Test - @DisplayName("should return only connections per workspace") - void shouldReturnNumConnectionsBasic() throws SQLException { - final var workspaceId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", false) - .execute()); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .execute()); - - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(1, res.size()); - assertEquals(2, res.get(0)); - } - - @Test - @DisplayName("should ignore deleted connections") - void shouldIgnoreNonRunningConnections() throws SQLException { - final var workspaceId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", false) - .execute()); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.deprecated) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.inactive) - .execute()); - - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(1, res.size()); - assertEquals(2, res.get(0)); - } - - @Test - @DisplayName("should ignore deleted connections") - void shouldIgnoreDeletedWorkspaces() throws SQLException { - final var workspaceId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE).values(workspaceId, "test-0", true) - .execute()); - - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - configDb.transaction( - ctx -> ctx - .insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, - ACTOR.TOMBSTONE) - .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) - .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) - .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) - .execute()); - - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(0, res.size()); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - final var res = configDb.query(MetricQueries::numberOfActiveConnPerWorkspace); - assertEquals(0, res.size()); - } - - } - - @Nested - class overallJobRuntimeForTerminalJobsInLastHour { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - } - - @Test - @DisplayName("should ignore non terminal jobs") - void shouldIgnoreNonTerminalJobs() throws SQLException { - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.running).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.incomplete).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(3L, "", JobStatus.pending).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(0, res.size()); - } - - @Test - @DisplayName("should ignore jobs older than 1 hour") - void shouldIgnoreJobsOlderThan1Hour() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.UPDATED_AT).values(1L, "", JobStatus.succeeded, updateAt).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(0, res.size()); - } - - @Test - @DisplayName("should return correct duration for terminal jobs") - void shouldReturnTerminalJobs() throws SQLException { - final var updateAt = OffsetDateTime.now(); - final var expAgeSecs = 10000; - final var createAt = updateAt.minus(expAgeSecs, ChronoUnit.SECONDS); - - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(1L, "", JobStatus.succeeded, createAt, updateAt).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(2L, "", JobStatus.failed, createAt, updateAt).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(3L, "", JobStatus.cancelled, createAt, updateAt).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(3, res.size()); - - final var exp = List.of( - new ImmutablePair<>(JobStatus.succeeded, expAgeSecs * 1.0), - new ImmutablePair<>(JobStatus.cancelled, expAgeSecs * 1.0), - new ImmutablePair<>(JobStatus.failed, expAgeSecs * 1.0)); - assertTrue(res.containsAll(exp) && exp.containsAll(res)); - } - - @Test - @DisplayName("should return correct duration for jobs that terminated in the last hour") - void shouldReturnTerminalJobsComplex() throws SQLException { - final var updateAtNow = OffsetDateTime.now(); - final var expAgeSecs = 10000; - final var createAt = updateAtNow.minus(expAgeSecs, ChronoUnit.SECONDS); - - // terminal jobs in last hour - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(1L, "", JobStatus.succeeded, createAt, updateAtNow).execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(2L, "", JobStatus.failed, createAt, updateAtNow).execute()); - - // old terminal jobs - final var updateAtOld = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) - .values(3L, "", JobStatus.cancelled, createAt, updateAtOld).execute()); - - // non-terminal jobs - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) - .values(4L, "", JobStatus.running, createAt).execute()); - - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(2, res.size()); - - final var exp = List.of( - new ImmutablePair<>(JobStatus.succeeded, expAgeSecs * 1.0), - new ImmutablePair<>(JobStatus.failed, expAgeSecs * 1.0)); - assertTrue(res.containsAll(exp) && exp.containsAll(res)); - } - - @Test - @DisplayName(DISPLAY_NAME) - void shouldReturnNothingIfNotApplicable() throws SQLException { - final var res = configDb.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - assertEquals(0, res.size()); - } - - } - - @Nested - class AbnormalJobsInLastDay { - - @AfterEach - void tearDown() throws SQLException { - configDb.transaction(ctx -> ctx.truncate(JOBS).cascade().execute()); - configDb.transaction(ctx -> ctx.truncate(CONNECTION).cascade().execute()); - } - - @Test - @DisplayName("should return correct number for abnormal jobs") - void shouldCountInJobsWithMissingRun() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); - final var connectionId = UUID.randomUUID(); - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - final var syncConfigType = JobConfigType.sync; - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(connectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 6, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) - .execute()); - - // Jobs running in prior day will not be counted - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(5, ChronoUnit.HOURS), updateAt, syncConfigType) - .execute()); - - final var totalConnectionResult = configDb.query(MetricQueries::numScheduledActiveConnectionsInLastDay); - assertEquals(1, totalConnectionResult); - - final var abnormalConnectionResult = configDb.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); - assertEquals(1, abnormalConnectionResult); - } - - @Test - @DisplayName("normal jobs should not be counted") - void shouldNotCountNormalJobsInAbnormalMetric() throws SQLException { - final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); - final var inactiveConnectionId = UUID.randomUUID(); - final var activeConnectionId = UUID.randomUUID(); - final var srcId = UUID.randomUUID(); - final var dstId = UUID.randomUUID(); - final var syncConfigType = JobConfigType.sync; - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(inactiveConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.inactive, updateAt, updateAt) - .execute()); - - configDb.transaction( - ctx -> ctx - .insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, - CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, - CONNECTION.UPDATED_AT) - .values(activeConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), - JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) - .execute()); - - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(1L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, - syncConfigType) - .execute()); - configDb.transaction( - ctx -> ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) - .values(2L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, - syncConfigType) - .execute()); - - final var totalConnectionResult = configDb.query(MetricQueries::numScheduledActiveConnectionsInLastDay); - assertEquals(1, totalConnectionResult); - - final var abnormalConnectionResult = configDb.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); - assertEquals(0, abnormalConnectionResult); - } - - } - } diff --git a/airbyte-metrics/reporter/build.gradle b/airbyte-metrics/reporter/build.gradle index 806d48a52545..22885a77de9f 100644 --- a/airbyte-metrics/reporter/build.gradle +++ b/airbyte-metrics/reporter/build.gradle @@ -2,18 +2,38 @@ plugins { id 'application' } +configurations { + jdbc +} + dependencies { - implementation libs.flyway.core + annotationProcessor platform(libs.micronaut.bom) + annotationProcessor libs.bundles.micronaut.annotation.processor + + implementation platform(libs.micronaut.bom) + implementation libs.bundles.micronaut implementation project(':airbyte-config:config-models') implementation project(':airbyte-db:jooq') implementation project(':airbyte-db:db-lib') implementation project(':airbyte-metrics:metrics-lib') + + implementation(libs.jooq) { + force = true + } + + testAnnotationProcessor platform(libs.micronaut.bom) + testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor + + testImplementation project(':airbyte-test-utils') + testImplementation libs.bundles.micronaut.test + testImplementation libs.postgresql + testImplementation libs.platform.testcontainers.postgresql } application { applicationName = "airbyte-metrics-reporter" - mainClass = 'io.airbyte.metrics.reporter.ReporterApp' + mainClass = 'io.airbyte.metrics.reporter.Application' applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0'] } diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java new file mode 100644 index 000000000000..f8af2de13b23 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Application.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.metrics.lib.MetricClientFactory; +import io.airbyte.metrics.lib.MetricEmittingApps; +import io.micronaut.runtime.Micronaut; + +/** + * Metric Reporter application. + *

    + * Responsible for emitting metric information on a periodic basis. + */ +public class Application { + + public static void main(final String[] args) { + MetricClientFactory.initialize(MetricEmittingApps.METRICS_REPORTER); + Micronaut.run(Application.class, args); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java new file mode 100644 index 000000000000..12b2900a3e81 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.metrics.lib.MetricAttribute; +import io.airbyte.metrics.lib.MetricClient; +import io.airbyte.metrics.lib.MetricTags; +import io.airbyte.metrics.lib.OssMetricsRegistry; +import jakarta.inject.Singleton; +import java.lang.invoke.MethodHandles; +import java.time.Duration; +import java.util.concurrent.Callable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +final class NumPendingJobs extends Emitter { + + public NumPendingJobs(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var pending = db.numberOfPendingJobs(); + client.gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pending); + return null; + }); + } + +} + +@Singleton +final class NumRunningJobs extends Emitter { + + public NumRunningJobs(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var running = db.numberOfRunningJobs(); + client.gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, running); + return null; + }); + } + +} + +@Singleton +final class NumOrphanRunningJobs extends Emitter { + + NumOrphanRunningJobs(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var orphaned = db.numberOfOrphanRunningJobs(); + client.gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, orphaned); + return null; + }); + } + +} + +@Singleton +final class OldestRunningJob extends Emitter { + + OldestRunningJob(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var age = db.oldestRunningJobAgeSecs(); + client.gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); + return null; + }); + } + +} + +@Singleton +final class OldestPendingJob extends Emitter { + + OldestPendingJob(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var age = db.oldestPendingJobAgeSecs(); + client.gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); + return null; + }); + } + +} + +@Singleton +final class NumActiveConnectionsPerWorkspace extends Emitter { + + NumActiveConnectionsPerWorkspace(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var workspaceConns = db.numberOfActiveConnPerWorkspace(); + for (final long numCons : workspaceConns) { + client.distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, numCons); + } + return null; + }); + } + +} + +@Singleton +final class NumAbnormalScheduledSyncs extends Emitter { + + NumAbnormalScheduledSyncs(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var count = db.numberOfJobsNotRunningOnScheduleInLastDay(); + client.gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofHours(1); + } + +} + +@Singleton +final class TotalScheduledSyncs extends Emitter { + + TotalScheduledSyncs(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var count = db.numScheduledActiveConnectionsInLastDay(); + client.gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofHours(1); + } + +} + +@Singleton +final class TotalJobRuntimeByTerminalState extends Emitter { + + public TotalJobRuntimeByTerminalState(final MetricClient client, final MetricRepository db) { + super(client, () -> { + db.overallJobRuntimeForTerminalJobsInLastHour() + .forEach((jobStatus, time) -> client.distribution( + OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, + time, + new MetricAttribute(MetricTags.JOB_STATUS, jobStatus.getLiteral()))); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofHours(1); + } + +} + +/** + * Abstract base class for all emitted metrics. + *

    + * As this is a sealed class, all implementations of it are contained within this same file. + */ +sealed class Emitter { + + protected static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + protected final MetricClient client; + protected final Callable callable; + + Emitter(final MetricClient client, final Callable callable) { + this.client = client; + this.callable = callable; + } + + /** + * Emit the metrics by calling the callable. + *

    + * Any exception thrown by the callable will be logged. + * + * @TODO: replace log message with a published error-event of some kind. + */ + public void Emit() { + try { + callable.call(); + client.count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } catch (final Exception e) { + log.error("Exception querying database for metric: ", e); + } + } + + /** + * How often this metric should report, defaults to 15s if not overwritten. + * + * @return Duration of how often this metric should report. + */ + public Duration getDuration() { + return Duration.ofSeconds(15); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java new file mode 100644 index 000000000000..4bf30ef92cbc --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/EventListeners.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.micronaut.runtime.event.ApplicationShutdownEvent; +import io.micronaut.runtime.event.ApplicationStartupEvent; +import io.micronaut.runtime.event.annotation.EventListener; +import jakarta.inject.Singleton; +import java.lang.invoke.MethodHandles; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * EventListeners registers event listeners for the startup and shutdown events from Micronaut. + */ +@Singleton +class EventListeners { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private final List emitters; + private final ScheduledExecutorService executor; + + EventListeners(final List emitters) { + this.emitters = emitters; + this.executor = Executors.newScheduledThreadPool(emitters.size()); + } + + /** + * Manually registers all the emitters to run on startup. + * + * @param event unused but required in order to listen to the startup event. + */ + @EventListener + public void startEmitters(final ApplicationStartupEvent event) { + emitters.forEach(emitter -> executor.scheduleAtFixedRate(emitter::Emit, 0, emitter.getDuration().getSeconds(), TimeUnit.SECONDS)); + log.info("registered {} emitters", emitters.size()); + } + + /** + * Attempts to cleanly shutdown the running emitters + * + * @param event unused but required in order to listen to the shutdown event. + */ + @EventListener + public void stopEmitters(final ApplicationShutdownEvent event) { + log.info("shutting down emitters"); + executor.shutdown(); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java new file mode 100644 index 000000000000..8dda0ba29869 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; +import static org.jooq.impl.SQLDataType.VARCHAR; + +import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import jakarta.inject.Singleton; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jooq.DSLContext; + +@Singleton +class MetricRepository { + + private final DSLContext ctx; + + MetricRepository(final DSLContext ctx) { + this.ctx = ctx; + } + + int numberOfPendingJobs() { + return ctx.selectCount() + .from(JOBS) + .where(JOBS.STATUS.eq(JobStatus.pending)) + .fetchOne(0, int.class); + } + + int numberOfRunningJobs() { + return ctx.selectCount() + .from(JOBS) + .join(CONNECTION) + .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) + .fetchOne(0, int.class); + } + + int numberOfOrphanRunningJobs() { + return ctx.selectCount() + .from(JOBS) + .join(CONNECTION) + .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.ne(StatusType.active))) + .fetchOne(0, int.class); + } + + long oldestPendingJobAgeSecs() { + return oldestJobAgeSecs(JobStatus.pending); + } + + long oldestRunningJobAgeSecs() { + return oldestJobAgeSecs(JobStatus.running); + } + + List numberOfActiveConnPerWorkspace() { + final var query = """ + SELECT workspace_id, count(c.id) as num_conn + FROM actor + INNER JOIN workspace ws ON actor.workspace_id = ws.id + INNER JOIN connection c ON actor.id = c.source_id + WHERE ws.tombstone = false + AND actor.tombstone = false AND actor.actor_type = 'source' + AND c.status = 'active' + GROUP BY workspace_id; + """; + return ctx.fetch(query).getValues("num_conn", long.class); + } + + long numScheduledActiveConnectionsInLastDay() { + final var queryForTotalConnections = """ + select count(1) as connection_count + from connection c + where + c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) IN ('"hours"', '"minutes"') + and c.status = 'active' + """; + + return ctx.fetchOne(queryForTotalConnections).get("connection_count", long.class); + } + + long numberOfJobsNotRunningOnScheduleInLastDay() { + // This query finds all sync jobs ran in last 24 hours and count how many times they have run. + // Comparing this to the expected number of runs (24 hours divide by configured cadence in hours), + // if it runs below that expected number it will be considered as abnormal instance. + // For example, if it's configured to run every 6 hours but in last 24 hours it only has 3 runs, + // it will be considered as 1 abnormal instance. + final var queryForAbnormalSyncInHoursInLastDay = """ + select count(1) as cnt + from ( + select + c.id, + count(*) as cnt + from connection c + left join jobs j on j.scope::uuid = c.id + where + c.schedule is not null + and c.schedule != 'null' + and j.created_at > now() - interval '24 hours 1 minutes' + and c.status = 'active' + and j.config_type = 'sync' + and c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) = '"hours"' + group by 1 + having count(*) < 24 / cast(c.schedule::jsonb->'units' as integer) + ) as abnormal_jobs + """; + + // Similar to the query above, this finds if the connection cadence's timeUnit is minutes. + // thus we use 1440 (=24 hours x 60 minutes) to divide the configured cadence. + final var queryForAbnormalSyncInMinutesInLastDay = """ + select count(1) as cnt + from ( + select + c.id, + count(*) as cnt + from + connection c + left join Jobs j on + j.scope::uuid = c.id + where + c.schedule is not null + and c.schedule != 'null' + and j.created_at > now() - interval '24 hours 1 minutes' + and c.status = 'active' + and j.config_type = 'sync' + and c.updated_at < now() - interval '24 hours 1 minutes' + and cast(c.schedule::jsonb->'timeUnit' as text) = '"minutes"' + group by 1 + having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer) + ) as abnormal_jobs + """; + return ctx.fetchOne(queryForAbnormalSyncInHoursInLastDay).get("cnt", long.class) + + ctx.fetchOne(queryForAbnormalSyncInMinutesInLastDay).get("cnt", long.class); + } + + Map overallJobRuntimeForTerminalJobsInLastHour() { + final var query = """ + SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs + WHERE updated_at >= NOW() - INTERVAL '1 HOUR' + AND jobs.status IN ('failed', 'succeeded', 'cancelled'); + """; + final var queryResults = ctx.fetch(query); + final var statuses = queryResults.getValues("status", JobStatus.class); + final var times = queryResults.getValues("sec", double.class); + + final var results = new HashMap(); + for (int i = 0; i < statuses.size(); i++) { + results.put(statuses.get(i), times.get(i)); + } + + return results; + } + + private long oldestJobAgeSecs(final JobStatus status) { + final var query = """ + SELECT id, EXTRACT(EPOCH FROM (current_timestamp - created_at)) AS run_duration_seconds + FROM jobs WHERE status = ?::job_status + ORDER BY created_at ASC limit 1; + """; + final var result = ctx.fetchOne(query, status.getLiteral()); + if (result == null) { + return 0L; + } + // as double can have rounding errors, round down to remove noise. + return result.getValue("run_duration_seconds", Double.class).longValue(); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java deleted file mode 100644 index 17d8331dcc7b..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterApp.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.commons.lang.CloseableShutdownHook; -import io.airbyte.config.Configs; -import io.airbyte.config.EnvConfigs; -import io.airbyte.db.Database; -import io.airbyte.db.check.DatabaseCheckException; -import io.airbyte.db.factory.DSLContextFactory; -import io.airbyte.db.factory.DataSourceFactory; -import io.airbyte.db.factory.DatabaseCheckFactory; -import io.airbyte.db.factory.DatabaseDriver; -import io.airbyte.db.factory.FlywayFactory; -import io.airbyte.db.instance.configs.ConfigsDatabaseMigrator; -import io.airbyte.metrics.lib.MetricClientFactory; -import io.airbyte.metrics.lib.MetricEmittingApps; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import javax.sql.DataSource; -import lombok.extern.slf4j.Slf4j; -import org.flywaydb.core.Flyway; -import org.jooq.DSLContext; -import org.jooq.SQLDialect; - -@Slf4j -public class ReporterApp { - - public static Database configDatabase; - - public static void main(final String[] args) throws DatabaseCheckException { - final Configs configs = new EnvConfigs(); - - MetricClientFactory.initialize(MetricEmittingApps.METRICS_REPORTER); - - final DataSource dataSource = DataSourceFactory.create( - configs.getConfigDatabaseUser(), - configs.getConfigDatabasePassword(), - DatabaseDriver.POSTGRESQL.getDriverClassName(), - configs.getConfigDatabaseUrl()); - - try (final DSLContext dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES)) { - - final Flyway flyway = FlywayFactory.create(dataSource, ReporterApp.class.getSimpleName(), - ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); - - // Ensure that the database resources are closed on application shutdown - CloseableShutdownHook.registerRuntimeShutdownHook(dataSource, dslContext); - - // Ensure that the Configuration database is available - DatabaseCheckFactory.createConfigsDatabaseMigrationCheck(dslContext, flyway, configs.getConfigsDatabaseMinimumFlywayMigrationVersion(), - configs.getConfigsDatabaseInitializationTimeoutMs()).check(); - - configDatabase = new Database(dslContext); - - final var toEmits = ToEmit.values(); - final var pollers = Executors.newScheduledThreadPool(toEmits.length); - - log.info("Scheduling {} metrics for emission..", toEmits.length); - for (final ToEmit toEmit : toEmits) { - pollers.scheduleAtFixedRate(toEmit.emit, 0, toEmit.duration.getSeconds(), TimeUnit.SECONDS); - } - } - } - -} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java new file mode 100644 index 000000000000..2e5683fb55b1 --- /dev/null +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ReporterFactory.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import io.airbyte.metrics.lib.MetricClient; +import io.airbyte.metrics.lib.MetricClientFactory; +import io.micronaut.context.annotation.Factory; +import jakarta.inject.Singleton; + +/** + * Micronaut factory for creating the appropriate singletons utilized by the metric reporter + * service. + */ +@Factory +class ReporterFactory { + + @Singleton + public MetricClient metricClient() { + return MetricClientFactory.getMetricClient(); + } + +} diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java deleted file mode 100644 index 3313a73da4bc..000000000000 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/ToEmit.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.metrics.reporter; - -import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; -import io.airbyte.metrics.lib.MetricAttribute; -import io.airbyte.metrics.lib.MetricClientFactory; -import io.airbyte.metrics.lib.MetricQueries; -import io.airbyte.metrics.lib.MetricTags; -import io.airbyte.metrics.lib.OssMetricsRegistry; -import java.lang.invoke.MethodHandles; -import java.time.Duration; -import java.util.concurrent.Callable; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * This class contains all metrics emitted by the {@link ReporterApp}. - */ -public enum ToEmit { - - NUM_PENDING_JOBS(countMetricEmission(() -> { - final var pendingJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfPendingJobs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pendingJobs); - return null; - })), - NUM_RUNNING_JOBS(countMetricEmission(() -> { - final var runningJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfRunningJobs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, runningJobs); - return null; - })), - NUM_ORPHAN_RUNNING_JOB(countMetricEmission(() -> { - final var orphanRunningJobs = ReporterApp.configDatabase.query(MetricQueries::numberOfOrphanRunningJobs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, orphanRunningJobs); - return null; - })), - OLDEST_RUNNING_JOB_AGE_SECS(countMetricEmission(() -> { - final var age = ReporterApp.configDatabase.query(MetricQueries::oldestRunningJobAgeSecs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); - return null; - })), - OLDEST_PENDING_JOB_AGE_SECS(countMetricEmission(() -> { - final var age = ReporterApp.configDatabase.query(MetricQueries::oldestPendingJobAgeSecs); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); - return null; - })), - NUM_ACTIVE_CONN_PER_WORKSPACE(countMetricEmission(() -> { - final var age = ReporterApp.configDatabase.query(MetricQueries::numberOfActiveConnPerWorkspace); - for (final long count : age) { - MetricClientFactory.getMetricClient().distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, count); - } - return null; - })), - NUM_ABNORMAL_SCHEDULED_SYNCS_LAST_DAY(Duration.ofHours(1), countMetricEmission(() -> { - final var count = ReporterApp.configDatabase.query(MetricQueries::numberOfJobsNotRunningOnScheduleInLastDay); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); - return null; - })), - NUM_TOTAL_SCHEDULED_SYNCS_LAST_DAY(Duration.ofHours(1), countMetricEmission(() -> { - final var count = ReporterApp.configDatabase.query(MetricQueries::numScheduledActiveConnectionsInLastDay); - MetricClientFactory.getMetricClient().gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, count); - return null; - })), - OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS(Duration.ofHours(1), countMetricEmission(() -> { - final var times = ReporterApp.configDatabase.query(MetricQueries::overallJobRuntimeForTerminalJobsInLastHour); - for (final Pair pair : times) { - MetricClientFactory.getMetricClient().distribution( - OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, pair.getRight(), - new MetricAttribute(MetricTags.JOB_STATUS, MetricTags.getJobStatus(pair.getLeft()))); - } - return null; - })); - - private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - - // default constructor - /** A runnable that emits a metric. */ - final public Runnable emit; - /** How often this metric would emit data. */ - final public Duration duration; - - ToEmit(final Runnable emit) { - this(Duration.ofSeconds(15), emit); - } - - ToEmit(final Duration duration, final Runnable emit) { - this.duration = duration; - this.emit = emit; - } - - /** - * Wrapper callable to handle 1) query exception logging and 2) counting metric emissions so - * reporter app can be monitored too. - * - * @param metricQuery - * @return - */ - private static Runnable countMetricEmission(final Callable metricQuery) { - return () -> { - try { - metricQuery.call(); - MetricClientFactory.getMetricClient().count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); - } catch (final Exception e) { - log.error("Exception querying database for metric: ", e); - } - }; - } - -} diff --git a/airbyte-metrics/reporter/src/main/resources/application.yml b/airbyte-metrics/reporter/src/main/resources/application.yml new file mode 100644 index 000000000000..49dd8cb8d7ae --- /dev/null +++ b/airbyte-metrics/reporter/src/main/resources/application.yml @@ -0,0 +1,38 @@ +micronaut: + application: + name: airbyte-metrics-reporter + security: + intercept-url-map: + - pattern: /** + httpMethod: GET + access: + - isAnonymous() + server: + port: 9000 + +datasources: + config: + connection-test-query: SELECT 1 + connection-timeout: 30000 + idle-timeout: 600000 + maximum-pool-size: 10 + url: ${DATABASE_URL} + driverClassName: org.postgresql.Driver + username: ${DATABASE_USER} + password: ${DATABASE_PASSWORD} + +jooq: + datasources: + config: + jackson-converter-enabled: true + sql-dialect: POSTGRES + +endpoints: + all: + enabled: true + +logger: + levels: + io.airbyte.bootloader: DEBUG +# Uncomment to help resolve issues with conditional beans +# io.micronaut.context.condition: DEBUG diff --git a/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt b/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt new file mode 100644 index 000000000000..633f73326c1a --- /dev/null +++ b/airbyte-metrics/reporter/src/main/resources/micronaut-banner.txt @@ -0,0 +1,8 @@ + + ___ _ __ __ + / | (_)____/ /_ __ __/ /____ + / /| | / / ___/ __ \/ / / / __/ _ \ + / ___ |/ / / / /_/ / /_/ / /_/ __/ +/_/ |_/_/_/ /_.___/\__, /\__/\___/ + /____/ + : airbyte-metrics-reporter : \ No newline at end of file diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java new file mode 100644 index 000000000000..55a2433246ae --- /dev/null +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import io.airbyte.metrics.lib.MetricAttribute; +import io.airbyte.metrics.lib.MetricClient; +import io.airbyte.metrics.lib.MetricTags; +import io.airbyte.metrics.lib.OssMetricsRegistry; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class EmitterTest { + + private MetricClient client; + private MetricRepository repo; + + @BeforeEach + void setUp() { + client = mock(MetricClient.class); + repo = mock(MetricRepository.class); + } + + @Test + void TestNumPendingJobs() { + final var value = 101; + when(repo.numberOfPendingJobs()).thenReturn(value); + + final var emitter = new NumPendingJobs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfPendingJobs(); + verify(client).gauge(OssMetricsRegistry.NUM_PENDING_JOBS, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestNumRunningJobs() { + final var value = 101; + when(repo.numberOfRunningJobs()).thenReturn(value); + + final var emitter = new NumRunningJobs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfRunningJobs(); + verify(client).gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestNumOrphanRunningJobs() { + final var value = 101; + when(repo.numberOfOrphanRunningJobs()).thenReturn(value); + + final var emitter = new NumOrphanRunningJobs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfOrphanRunningJobs(); + verify(client).gauge(OssMetricsRegistry.NUM_ORPHAN_RUNNING_JOBS, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestOldestRunningJob() { + final var value = 101; + when(repo.oldestRunningJobAgeSecs()).thenReturn((long) value); + + final var emitter = new OldestRunningJob(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).oldestRunningJobAgeSecs(); + verify(client).gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestOldestPendingJob() { + final var value = 101; + when(repo.oldestPendingJobAgeSecs()).thenReturn((long) value); + + final var emitter = new OldestPendingJob(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).oldestPendingJobAgeSecs(); + verify(client).gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestNumActiveConnectionsPerWorkspace() { + final var values = List.of(101L, 202L); + when(repo.numberOfActiveConnPerWorkspace()).thenReturn(values); + + final var emitter = new NumActiveConnectionsPerWorkspace(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofSeconds(15), emitter.getDuration()); + verify(repo).numberOfActiveConnPerWorkspace(); + for (final var value : values) { + verify(client).distribution(OssMetricsRegistry.NUM_ACTIVE_CONN_PER_WORKSPACE, value); + } + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestNumAbnormalScheduledSyncs() { + final var value = 101; + when(repo.numberOfJobsNotRunningOnScheduleInLastDay()).thenReturn((long) value); + + final var emitter = new NumAbnormalScheduledSyncs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofHours(1), emitter.getDuration()); + verify(repo).numberOfJobsNotRunningOnScheduleInLastDay(); + verify(client).gauge(OssMetricsRegistry.NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestTotalScheduledSyncs() { + final var value = 101; + when(repo.numScheduledActiveConnectionsInLastDay()).thenReturn((long) value); + + final var emitter = new TotalScheduledSyncs(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofHours(1), emitter.getDuration()); + verify(repo).numScheduledActiveConnectionsInLastDay(); + verify(client).gauge(OssMetricsRegistry.NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY, value); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + + @Test + void TestTotalJobRuntimeByTerminalState() { + final var values = Map.of(JobStatus.cancelled, 101.0, JobStatus.succeeded, 202.0, + JobStatus.failed, 303.0); + when(repo.overallJobRuntimeForTerminalJobsInLastHour()).thenReturn(values); + + final var emitter = new TotalJobRuntimeByTerminalState(client, repo); + emitter.Emit(); + + assertEquals(Duration.ofHours(1), emitter.getDuration()); + verify(repo).overallJobRuntimeForTerminalJobsInLastHour(); + values.forEach((jobStatus, time) -> { + verify(client).distribution( + OssMetricsRegistry.OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS, time, + new MetricAttribute(MetricTags.JOB_STATUS, jobStatus.getLiteral())); + }); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); + } + +} diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java new file mode 100644 index 000000000000..8ff5f2e8cd69 --- /dev/null +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.reporter; + +import static io.airbyte.db.instance.configs.jooq.generated.Keys.ACTOR_CATALOG_FETCH_EVENT__ACTOR_CATALOG_FETCH_EVENT_ACTOR_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Keys.ACTOR__ACTOR_WORKSPACE_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Keys.CONNECTION__CONNECTION_DESTINATION_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Keys.CONNECTION__CONNECTION_SOURCE_ID_FKEY; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_CATALOG_FETCH_EVENT; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.airbyte.db.factory.DSLContextFactory; +import io.airbyte.db.init.DatabaseInitializationException; +import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; +import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; +import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; +import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; +import io.airbyte.db.instance.test.TestDatabaseProviders; +import io.airbyte.test.utils.DatabaseConnectionHelper; +import java.io.IOException; +import java.sql.SQLException; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.jooq.DSLContext; +import org.jooq.JSONB; +import org.jooq.SQLDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.PostgreSQLContainer; + +class MetricRepositoryTest { + + private static final String SRC = "src"; + private static final String DEST = "dst"; + private static final String CONN = "conn"; + private static final UUID SRC_DEF_ID = UUID.randomUUID(); + private static final UUID DST_DEF_ID = UUID.randomUUID(); + private static MetricRepository db; + private static DSLContext ctx; + + @BeforeAll + public static void setUpAll() throws DatabaseInitializationException, IOException { + final var psqlContainer = new PostgreSQLContainer<>("postgres:13-alpine") + .withUsername("user") + .withPassword("hunter2"); + psqlContainer.start(); + + final var dataSource = DatabaseConnectionHelper.createDataSource(psqlContainer); + ctx = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); + final var dbProviders = new TestDatabaseProviders(dataSource, ctx); + dbProviders.createNewConfigsDatabase(); + dbProviders.createNewJobsDatabase(); + + ctx.insertInto(ACTOR_DEFINITION, ACTOR_DEFINITION.ID, ACTOR_DEFINITION.NAME, ACTOR_DEFINITION.DOCKER_REPOSITORY, + ACTOR_DEFINITION.DOCKER_IMAGE_TAG, ACTOR_DEFINITION.SPEC, ACTOR_DEFINITION.ACTOR_TYPE, ACTOR_DEFINITION.RELEASE_STAGE) + .values(SRC_DEF_ID, "srcDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.source, ReleaseStage.beta) + .values(DST_DEF_ID, "dstDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.destination, ReleaseStage.generally_available) + .values(UUID.randomUUID(), "dstDef", "repository", "tag", JSONB.valueOf("{}"), ActorType.destination, ReleaseStage.alpha) + .execute(); + + // drop constraints to simplify test set up + ctx.alterTable(ACTOR).dropForeignKey(ACTOR__ACTOR_WORKSPACE_ID_FKEY.constraint()).execute(); + ctx.alterTable(CONNECTION).dropForeignKey(CONNECTION__CONNECTION_DESTINATION_ID_FKEY.constraint()).execute(); + ctx.alterTable(CONNECTION).dropForeignKey(CONNECTION__CONNECTION_SOURCE_ID_FKEY.constraint()).execute(); + ctx.alterTable(ACTOR_CATALOG_FETCH_EVENT) + .dropForeignKey(ACTOR_CATALOG_FETCH_EVENT__ACTOR_CATALOG_FETCH_EVENT_ACTOR_ID_FKEY.constraint()).execute(); + ctx.alterTable(WORKSPACE).alter(WORKSPACE.SLUG).dropNotNull().execute(); + ctx.alterTable(WORKSPACE).alter(WORKSPACE.INITIAL_SETUP_COMPLETE).dropNotNull().execute(); + + db = new MetricRepository(ctx); + } + + @BeforeEach + void setUp() { + ctx.truncate(ACTOR).execute(); + ctx.truncate(CONNECTION).cascade().execute(); + ctx.truncate(JOBS).cascade().execute(); + ctx.truncate(WORKSPACE).cascade().execute(); + } + + @AfterEach + void tearDown() { + + } + + @Nested + class NumJobs { + + @Test + void shouldReturnReleaseStages() { + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) + .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) + .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) + .execute(); + + final var activeConnectionId = UUID.randomUUID(); + final var inactiveConnectionId = UUID.randomUUID(); + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, + CONNECTION.DESTINATION_ID, CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL) + .values(activeConnectionId, StatusType.active, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) + .values(inactiveConnectionId, StatusType.inactive, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true) + .execute(); + + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, activeConnectionId.toString(), JobStatus.pending) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(2L, activeConnectionId.toString(), JobStatus.failed) + .values(3L, activeConnectionId.toString(), JobStatus.running) + .values(4L, activeConnectionId.toString(), JobStatus.running) + .values(5L, inactiveConnectionId.toString(), JobStatus.running) + .execute(); + + assertEquals(2, db.numberOfRunningJobs()); + assertEquals(1, db.numberOfOrphanRunningJobs()); + } + + @Test + void runningJobsShouldReturnZero() throws SQLException { + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute(); + + final var res = db.numberOfRunningJobs(); + assertEquals(0, res); + } + + @Test + void pendingJobsShouldReturnCorrectCount() throws SQLException { + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.pending) + .values(2L, "", JobStatus.failed) + .values(3L, "", JobStatus.pending) + .values(4L, "", JobStatus.running) + .execute(); + + final var res = db.numberOfPendingJobs(); + assertEquals(2, res); + } + + @Test + void pendingJobsShouldReturnZero() throws SQLException { + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.running) + .values(2L, "", JobStatus.failed) + .execute(); + + final var res = db.numberOfPendingJobs(); + assertEquals(0, res); + } + + } + + @Nested + class OldestPendingJob { + + @Test + void shouldReturnOnlyPendingSeconds() throws SQLException { + final var expAgeSecs = 1000; + final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + // oldest pending job + .values(1L, "", JobStatus.pending, oldestCreateAt) + // second-oldest pending job + .values(2L, "", JobStatus.pending, OffsetDateTime.now()) + .execute(); + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(3L, "", JobStatus.running) + .values(4L, "", JobStatus.failed) + .execute(); + + final var res = db.oldestPendingJobAgeSecs(); + // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(List.of(999L, 1000L, 1001L).contains(res)); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.succeeded) + .values(2L, "", JobStatus.running) + .values(3L, "", JobStatus.failed).execute(); + + final var res = db.oldestPendingJobAgeSecs(); + assertEquals(0L, res); + } + + } + + @Nested + class OldestRunningJob { + + @Test + void shouldReturnOnlyRunningSeconds() { + final var expAgeSecs = 10000; + final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + // oldest pending job + .values(1L, "", JobStatus.running, oldestCreateAt) + // second-oldest pending job + .values(2L, "", JobStatus.running, OffsetDateTime.now()) + .execute(); + + // non-pending jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(3L, "", JobStatus.pending) + .values(4L, "", JobStatus.failed) + .execute(); + + final var res = db.oldestRunningJobAgeSecs(); + // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.succeeded) + .values(2L, "", JobStatus.pending) + .values(3L, "", JobStatus.failed) + .execute(); + + final var res = db.oldestRunningJobAgeSecs(); + assertEquals(0L, res); + } + + } + + @Nested + class NumActiveConnsPerWorkspace { + + @Test + void shouldReturnNumConnectionsBasic() { + final var workspaceId = UUID.randomUUID(); + ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) + .values(workspaceId, "test-0", false) + .execute(); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .execute(); + + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(1, res.size()); + assertEquals(2, res.get(0)); + } + + @Test + @DisplayName("should ignore deleted connections") + void shouldIgnoreNonRunningConnections() { + final var workspaceId = UUID.randomUUID(); + ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) + .values(workspaceId, "test-0", false) + .execute(); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.deprecated) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.inactive) + .execute(); + + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(1, res.size()); + assertEquals(2, res.get(0)); + } + + @Test + @DisplayName("should ignore deleted connections") + void shouldIgnoreDeletedWorkspaces() { + final var workspaceId = UUID.randomUUID(); + ctx.insertInto(WORKSPACE, WORKSPACE.ID, WORKSPACE.NAME, WORKSPACE.TOMBSTONE) + .values(workspaceId, "test-0", true) + .execute(); + + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE, + ACTOR.TOMBSTONE) + .values(srcId, workspaceId, SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source, false) + .values(dstId, workspaceId, DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination, false) + .execute(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS) + .values(UUID.randomUUID(), NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active) + .execute(); + + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(0, res.size()); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + final var res = db.numberOfActiveConnPerWorkspace(); + assertEquals(0, res.size()); + } + + } + + @Nested + class OverallJobRuntimeForTerminalJobsInLastHour { + + @Test + void shouldIgnoreNonTerminalJobs() throws SQLException { + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) + .values(1L, "", JobStatus.running) + .values(2L, "", JobStatus.incomplete) + .values(3L, "", JobStatus.pending) + .execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(0, res.size()); + } + + @Test + void shouldIgnoreJobsOlderThan1Hour() { + final var updateAt = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.UPDATED_AT).values(1L, "", JobStatus.succeeded, updateAt).execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(0, res.size()); + } + + @Test + @DisplayName("should return correct duration for terminal jobs") + void shouldReturnTerminalJobs() { + final var updateAt = OffsetDateTime.now(); + final var expAgeSecs = 10000; + final var createAt = updateAt.minus(expAgeSecs, ChronoUnit.SECONDS); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(1L, "", JobStatus.succeeded, createAt, updateAt) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(2L, "", JobStatus.failed, createAt, updateAt) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(3L, "", JobStatus.cancelled, createAt, updateAt) + .execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(3, res.size()); + + final var exp = Map.of( + JobStatus.succeeded, expAgeSecs * 1.0, + JobStatus.cancelled, expAgeSecs * 1.0, + JobStatus.failed, expAgeSecs * 1.0); + assertEquals(exp, res); + } + + @Test + void shouldReturnTerminalJobsComplex() { + final var updateAtNow = OffsetDateTime.now(); + final var expAgeSecs = 10000; + final var createAt = updateAtNow.minus(expAgeSecs, ChronoUnit.SECONDS); + + // terminal jobs in last hour + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(1L, "", JobStatus.succeeded, createAt, updateAtNow) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(2L, "", JobStatus.failed, createAt, updateAtNow) + .execute(); + + // old terminal jobs + final var updateAtOld = OffsetDateTime.now().minus(2, ChronoUnit.HOURS); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT) + .values(3L, "", JobStatus.cancelled, createAt, updateAtOld) + .execute(); + + // non-terminal jobs + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) + .values(4L, "", JobStatus.running, createAt) + .execute(); + + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(2, res.size()); + + final var exp = Map.of( + JobStatus.succeeded, expAgeSecs * 1.0, + JobStatus.failed, expAgeSecs * 1.0); + assertEquals(exp, res); + } + + @Test + void shouldReturnNothingIfNotApplicable() { + final var res = db.overallJobRuntimeForTerminalJobsInLastHour(); + assertEquals(0, res.size()); + } + + } + + @Nested + class AbnormalJobsInLastDay { + + @Test + void shouldCountInJobsWithMissingRun() throws SQLException { + final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); + final var connectionId = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(connectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 6, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) + .execute(); + + // Jobs running in prior day will not be counted + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), updateAt, syncConfigType) + .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, syncConfigType) + .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, syncConfigType) + .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(5, ChronoUnit.HOURS), updateAt, syncConfigType) + .execute(); + + final var totalConnectionResult = db.numScheduledActiveConnectionsInLastDay(); + assertEquals(1, totalConnectionResult); + + final var abnormalConnectionResult = db.numberOfJobsNotRunningOnScheduleInLastDay(); + assertEquals(1, abnormalConnectionResult); + } + + @Test + void shouldNotCountNormalJobsInAbnormalMetric() { + final var updateAt = OffsetDateTime.now().minus(300, ChronoUnit.HOURS); + final var inactiveConnectionId = UUID.randomUUID(); + final var activeConnectionId = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.SCHEDULE, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.CREATED_AT, + CONNECTION.UPDATED_AT) + .values(inactiveConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.inactive, updateAt, updateAt) + .values(activeConnectionId, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), + JSONB.valueOf("{\"units\": 12, \"timeUnit\": \"hours\"}"), false, StatusType.active, updateAt, updateAt) + .execute(); + + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(1L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), updateAt, + syncConfigType) + .values(2L, activeConnectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(10, ChronoUnit.HOURS), updateAt, + syncConfigType) + .execute(); + + final var totalConnectionResult = db.numScheduledActiveConnectionsInLastDay(); + assertEquals(1, totalConnectionResult); + + final var abnormalConnectionResult = db.numberOfJobsNotRunningOnScheduleInLastDay(); + assertEquals(0, abnormalConnectionResult); + } + + } + +} From fc596588c73b4bc284daf85447eebb85f30805ec Mon Sep 17 00:00:00 2001 From: Lake Mossman Date: Thu, 13 Oct 2022 11:31:30 -0700 Subject: [PATCH 101/498] :window: :tada: Add /connector-builder page with embedded YAML editor (#17482) * [SPIKE] add Builder page with editor * add YamlEditor component * export colors from CSS * move template into dedicated file * fix initial load from local storage * move download button into separate component * fix stylelint * remove console log * add todo * make monaco background transparent and apply gradient to parent div background * clarify comment * remove unnecessary 180deg * lock the builder UI behind a feature * use rgb instead of hex to fix stylelint * use _colors.scss and disable hex length stylelint rule * disable rule in file * add darker gradient color to _colors.scss and use in gradient * move /builder to /connector-builder * restructure folders * remove Feature for connector builder to simplify development process --- .../YamlEditor/DownloadYamlButton.tsx | 26 +++++++ .../YamlEditor/YamlEditor.module.scss | 34 ++++++++++ .../src/components/YamlEditor/YamlEditor.tsx | 67 +++++++++++++++++++ .../src/components/YamlEditor/YamlTemplate.ts | 46 +++++++++++++ .../src/components/YamlEditor/index.tsx | 1 + airbyte-webapp/src/locales/en.json | 4 +- .../ConnectorBuilderPage.tsx | 5 ++ airbyte-webapp/src/pages/routePaths.tsx | 2 + airbyte-webapp/src/pages/routes.tsx | 2 + airbyte-webapp/src/scss/_colors.scss | 7 +- 10 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx create mode 100644 airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss create mode 100644 airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx create mode 100644 airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts create mode 100644 airbyte-webapp/src/components/YamlEditor/index.tsx create mode 100644 airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx diff --git a/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx new file mode 100644 index 000000000000..3527bfb3561f --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx @@ -0,0 +1,26 @@ +import { useIntl } from "react-intl"; + +import { Button } from "components/ui/Button"; + +import { downloadFile } from "utils/file"; + +interface DownloadYamlButtonProps { + yaml: string; + className?: string; +} + +export const DownloadYamlButton: React.FC = ({ yaml, className }) => { + const { formatMessage } = useIntl(); + + const downloadYaml = () => { + const file = new Blob([yaml], { type: "text/plain;charset=utf-8" }); + // TODO: pull name from connector name input or generate from yaml contents + downloadFile(file, "connector_builder.yaml"); + }; + + return ( + + ); +}; diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss b/airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss new file mode 100644 index 000000000000..6552c514205a --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss @@ -0,0 +1,34 @@ +@use "scss/colors"; + +.container { + display: flex; + flex-flow: column; + height: 100%; +} + +.control { + flex: 0 0 auto; + background-color: colors.$dark-blue; + display: flex; + padding: 10px; +} + +.editorContainer { + flex: 1 1 0; + background-image: linear-gradient(colors.$dark-blue-900, colors.$dark-blue-1000); +} + +.downloadButton { + margin-left: auto; +} + +// Export colors to be used in monaco editor +:export { + // Monaco editor requires 6-character hex values for theme colors + /* stylelint-disable-next-line color-hex-length, color-no-hex */ + tokenString: colors.$white; + tokenType: colors.$blue-300; + tokenNumber: colors.$orange-300; + tokenDelimiter: colors.$yellow-300; + tokenKeyword: colors.$green-300; +} diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx new file mode 100644 index 000000000000..8129d65dc3c4 --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -0,0 +1,67 @@ +import Editor, { Monaco } from "@monaco-editor/react"; +import { useState } from "react"; +import { useDebounce, useLocalStorage } from "react-use"; + +import { DownloadYamlButton } from "./DownloadYamlButton"; +import styles from "./YamlEditor.module.scss"; +import { template } from "./YamlTemplate"; + +export const YamlEditor: React.FC = () => { + const [locallyStoredEditorValue, setLocallyStoredEditorValue] = useLocalStorage( + "connectorBuilderEditorContent", + template + ); + const [editorValue, setEditorValue] = useState(locallyStoredEditorValue ?? ""); + useDebounce(() => setLocallyStoredEditorValue(editorValue), 500, [editorValue]); + + const handleEditorChange = (value: string | undefined) => { + setEditorValue(value ?? ""); + }; + + const setEditorTheme = (monaco: Monaco) => { + monaco.editor.defineTheme("airbyte", { + base: "vs-dark", + inherit: true, + rules: [ + { token: "string", foreground: styles.tokenString }, + { token: "type", foreground: styles.tokenType }, + { token: "number", foreground: styles.tokenNumber }, + { token: "delimiter", foreground: styles.tokenDelimiter }, + { token: "keyword", foreground: styles.tokenKeyword }, + ], + colors: { + "editor.background": "#00000000", // transparent, so that parent background is shown instead + }, + }); + + monaco.editor.setTheme("airbyte"); + }; + + return ( +

    +
    + +
    +
    + +
    +
    + ); +}; diff --git a/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts b/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts new file mode 100644 index 000000000000..cb0080225971 --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts @@ -0,0 +1,46 @@ +// TODO: replace with API call to get starting contents +export const template = `version: "0.1.0" + +definitions: + schema_loader: + type: JsonSchema + file_path: "./source/schemas/{{ options['name'] }}.json" + selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_pointer: [] + requester: + type: HttpRequester + name: "{{ options['name'] }}" + http_method: "GET" + authenticator: + type: BearerAuthenticator + api_token: "{{ config['api_key'] }}" + retriever: + type: SimpleRetriever + $options: + url_base: TODO "your_api_base_url" + name: "{{ options['name'] }}" + primary_key: "{{ options['primary_key'] }}" + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + type: NoPagination + +streams: + - type: DeclarativeStream + $options: + name: "customers" + primary_key: "id" + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: TODO "your_endpoint_path" +check: + type: CheckStream + stream_names: ["customers"] +`; diff --git a/airbyte-webapp/src/components/YamlEditor/index.tsx b/airbyte-webapp/src/components/YamlEditor/index.tsx new file mode 100644 index 000000000000..697628857580 --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/index.tsx @@ -0,0 +1 @@ +export * from "./YamlEditor"; diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 0de1529c47d1..f9883139c251 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -564,5 +564,7 @@ "airbyte.datatype.untyped": "Untyped", "airbyte.datatype.union": "Union", "airbyte.datatype.unknown": "Unknown", - "airbyte.datatype.boolean": "Boolean" + "airbyte.datatype.boolean": "Boolean", + + "builder.downloadYaml": "Download YAML" } diff --git a/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx new file mode 100644 index 000000000000..47728f1ea8cf --- /dev/null +++ b/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx @@ -0,0 +1,5 @@ +import { YamlEditor } from "components/YamlEditor"; + +export const ConnectorBuilderPage: React.FC = () => { + return ; +}; diff --git a/airbyte-webapp/src/pages/routePaths.tsx b/airbyte-webapp/src/pages/routePaths.tsx index 76764a8c9428..5c0b0b1db20b 100644 --- a/airbyte-webapp/src/pages/routePaths.tsx +++ b/airbyte-webapp/src/pages/routePaths.tsx @@ -14,4 +14,6 @@ export enum RoutePaths { ConnectionNew = "new-connection", SourceNew = "new-source", DestinationNew = "new-destination", + + ConnectorBuilder = "connector-builder", } diff --git a/airbyte-webapp/src/pages/routes.tsx b/airbyte-webapp/src/pages/routes.tsx index bb3b9205ed84..1010703eba2c 100644 --- a/airbyte-webapp/src/pages/routes.tsx +++ b/airbyte-webapp/src/pages/routes.tsx @@ -15,6 +15,7 @@ import MainView from "views/layout/MainView"; import { WorkspaceRead } from "../core/request/AirbyteClient"; import ConnectionPage from "./ConnectionPage"; +import { ConnectorBuilderPage } from "./connector-builder/ConnectorBuilderPage"; import DestinationPage from "./DestinationPage"; import OnboardingPage from "./OnboardingPage"; import PreferencesPage from "./PreferencesPage"; @@ -43,6 +44,7 @@ const MainViewRoutes: React.FC<{ workspace: WorkspaceRead }> = ({ workspace }) = } /> } /> } /> + } /> {workspace.displaySetupWizard ? ( } /> ) : null} diff --git a/airbyte-webapp/src/scss/_colors.scss b/airbyte-webapp/src/scss/_colors.scss index 3521285415b8..07941b491ea8 100644 --- a/airbyte-webapp/src/scss/_colors.scss +++ b/airbyte-webapp/src/scss/_colors.scss @@ -1,4 +1,4 @@ -/* stylelint-disable color-no-hex */ +/* stylelint-disable color-no-hex, color-hex-length */ $blue-50: #eae9ff; $blue-100: #cbc8ff; $blue-200: #a6a4ff; @@ -22,6 +22,7 @@ $dark-blue-600: #353b7b; $dark-blue-700: #2d3270; $dark-blue-800: #262963; $dark-blue-900: #1a194d; +$dark-blue-1000: #0a0a23; $dark-blue: $dark-blue-900; $grey-30: #fcfcfd; @@ -77,8 +78,8 @@ $beige-50: #fef9f4; $beige-100: #ffebd7; $beige: $beige-50; -$black: #000; -$white: #fff; +$black: #000000; +$white: #ffffff; $yellow-50: #fdf8e1; $yellow-100: #fbecb3; From 97912d53fc026dcb8524a50785d381e9efcd2b1d Mon Sep 17 00:00:00 2001 From: Nataly Merezhuk <65251165+natalyjazzviolin@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:07:11 -0400 Subject: [PATCH 102/498] =?UTF-8?q?=F0=9F=90=9B=20Correct=20kube=20annotat?= =?UTF-8?q?ions=20variable=20as=20per=20the=20docs.=20(#17972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Correct kube annotations variable as per the docs. * Requested changes. --- airbyte-workers/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-workers/src/main/resources/application.yml b/airbyte-workers/src/main/resources/application.yml index 465283f0f0f7..15a5c0248001 100644 --- a/airbyte-workers/src/main/resources/application.yml +++ b/airbyte-workers/src/main/resources/application.yml @@ -88,7 +88,7 @@ airbyte: check: enabled: ${SHOULD_RUN_CHECK_CONNECTION_WORKFLOWS:true} kube: - annotations: ${CHECK_JOB_KUBE_ANNOTATION:} + annotations: ${CHECK_JOB_KUBE_ANNOTATIONS:} node-selectors: ${CHECK_JOB_KUBE_NODE_SELECTORS:} max-workers: ${MAX_CHECK_WORKERS:5} main: From ce061d239e0ce73b31bcff960959debe80a28ee7 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Thu, 13 Oct 2022 13:28:54 -0700 Subject: [PATCH 103/498] capture metrics around json messages size (#17973) * add metrics to json data * format --- .../internal/DefaultAirbyteStreamFactory.java | 12 ++++++++++-- .../io/airbyte/metrics/lib/OssMetricsRegistry.java | 12 +++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java index 0fe70a947d40..8e002e39c21a 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.logging.MdcScope; +import io.airbyte.metrics.lib.MetricClientFactory; +import io.airbyte.metrics.lib.OssMetricsRegistry; import io.airbyte.protocol.models.AirbyteLogMessage; import io.airbyte.protocol.models.AirbyteMessage; import java.io.BufferedReader; @@ -42,7 +44,9 @@ public DefaultAirbyteStreamFactory(final MdcScope.Builder containerLogMdcBuilder this(new AirbyteProtocolPredicate(), LOGGER, containerLogMdcBuilder); } - DefaultAirbyteStreamFactory(final AirbyteProtocolPredicate protocolPredicate, final Logger logger, final MdcScope.Builder containerLogMdcBuilder) { + DefaultAirbyteStreamFactory(final AirbyteProtocolPredicate protocolPredicate, + final Logger logger, + final MdcScope.Builder containerLogMdcBuilder) { protocolValidator = protocolPredicate; this.logger = logger; this.containerLogMdcBuilder = containerLogMdcBuilder; @@ -50,9 +54,12 @@ public DefaultAirbyteStreamFactory(final MdcScope.Builder containerLogMdcBuilder @Override public Stream create(final BufferedReader bufferedReader) { + final var metricClient = MetricClientFactory.getMetricClient(); return bufferedReader .lines() + .peek(str -> metricClient.distribution(OssMetricsRegistry.JSON_STRING_LENGTH, str.length())) .flatMap(this::parseJson) + .peek(json -> metricClient.distribution(OssMetricsRegistry.JSON_SIZE, json.size())) .filter(this::validate) .flatMap(this::toAirbyteMessage) .filter(this::filterLog); @@ -99,7 +106,8 @@ protected boolean filterLog(final AirbyteMessage message) { protected void internalLog(final AirbyteLogMessage logMessage) { final String combinedMessage = - logMessage.getMessage() + (logMessage.getStackTrace() != null ? (System.lineSeparator() + "Stack Trace: " + logMessage.getStackTrace()) : ""); + logMessage.getMessage() + (logMessage.getStackTrace() != null ? (System.lineSeparator() + + "Stack Trace: " + logMessage.getStackTrace()) : ""); switch (logMessage.getLevel()) { case FATAL, ERROR -> logger.error(combinedMessage); diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java index 75e30ebf335d..3c5f81b0296c 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java @@ -71,6 +71,14 @@ public enum OssMetricsRegistry implements MetricsRegistry { MetricEmittingApps.WORKER, "kube_pod_process_create_time_millisecs", "time taken to create a new kube pod process"), + JSON_STRING_LENGTH( + MetricEmittingApps.WORKER, + "json_string_length", + "string length of a raw json string"), + JSON_SIZE( + MetricEmittingApps.WORKER, + "json_size", + "size of the json object"), NUM_PENDING_JOBS( MetricEmittingApps.METRICS_REPORTER, "num_pending_jobs", @@ -124,7 +132,9 @@ public enum OssMetricsRegistry implements MetricsRegistry { private final String metricName; private final String metricDescription; - OssMetricsRegistry(final MetricEmittingApp application, final String metricName, final String metricDescription) { + OssMetricsRegistry(final MetricEmittingApp application, + final String metricName, + final String metricDescription) { Preconditions.checkNotNull(metricDescription); Preconditions.checkNotNull(application); From 289a0654018d133a01651722d17a9f33cce7f40d Mon Sep 17 00:00:00 2001 From: Delena Malan Date: Fri, 14 Oct 2022 00:00:20 +0200 Subject: [PATCH 104/498] Use page_token_option instead of page_token (#17892) --- .../understanding-the-yaml-file/pagination.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md b/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md index d7284a6ff43c..71aaafbc92eb 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md @@ -103,7 +103,7 @@ paginator: pagination_strategy: type: "PageIncrement" page_size: 5 - page_token: + page_token_option: inject_into: "request_parameter" field_name: "page" ``` @@ -147,7 +147,7 @@ paginator: pagination_strategy: type: "OffsetIncrement" page_size: 5 - page_token: + page_token_option: field_name: "offset" inject_into: "request_parameter" ``` @@ -196,7 +196,7 @@ paginator: pagination_strategy: type: "CursorPagination" cursor_value: "{{ last_records[-1]['id'] }}" - page_token: + page_token_option: field_name: "from" inject_into: "request_parameter" ``` @@ -217,11 +217,11 @@ paginator: pagination_strategy: type: "CursorPagination" cursor_value: "{{ headers['urls']['next'] }}" - page_token: + page_token_option: inject_into: "path" ``` Assuming the endpoint to fetch data from is `https://cloud.airbyte.com/api/get_data`, the first request will be sent as `https://cloud.airbyte.com/api/get_data` Assuming the response's next url is `https://cloud.airbyte.com/api/get_data?page=1&page_size=100`, -the next request will be sent as `https://cloud.airbyte.com/api/get_data?page=1&page_size=100` \ No newline at end of file +the next request will be sent as `https://cloud.airbyte.com/api/get_data?page=1&page_size=100` From f338e47c2fcae52bab3615ce2433ac5c80a4f265 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 13 Oct 2022 16:16:06 -0700 Subject: [PATCH 105/498] Add additional sync timing information (#17643) * WIP - Add additional sync timing information * Fixup tests * fix PMD problem * send data to segment * Test JobTracker * respond to PR suggestions * fixup test * formatting * fix initializer for stats * Make thread-safe with synchronized * Don't clobber syncStats on init * add comments and fix init * Do what Pedro says * Extract timeTracker pojo --- .../general/DefaultReplicationWorker.java | 28 ++++++-- .../workers/helper/ThreadedTimeTracker.java | 68 +++++++++++++++++++ .../general/DefaultReplicationWorkerTest.java | 12 ++-- .../src/main/resources/types/SyncStats.yaml | 18 +++++ .../job/tracker/TrackingMetadata.java | 45 +++++++++--- .../job/tracker/JobTrackerTest.java | 20 ++++++ .../sync/ReplicationActivityImpl.java | 22 +++--- .../temporal/sync/SyncWorkflowTest.java | 10 +-- 8 files changed, 189 insertions(+), 34 deletions(-) create mode 100644 airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ThreadedTimeTracker.java diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java index d92a88e7bcda..d6c60ab1e47a 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java @@ -25,6 +25,7 @@ import io.airbyte.workers.exception.RecordSchemaValidationException; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.helper.FailureHelper; +import io.airbyte.workers.helper.ThreadedTimeTracker; import io.airbyte.workers.internal.AirbyteDestination; import io.airbyte.workers.internal.AirbyteMapper; import io.airbyte.workers.internal.AirbyteSource; @@ -129,7 +130,10 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path final WorkerDestinationConfig destinationConfig = WorkerUtils.syncToWorkerDestinationConfig(syncInput); destinationConfig.setCatalog(mapper.mapCatalog(destinationConfig.getCatalog())); + final ThreadedTimeTracker timeTracker = new ThreadedTimeTracker(); final long startTime = System.currentTimeMillis(); + timeTracker.trackReplicationStartTime(); + final AtomicReference replicationRunnableFailureRef = new AtomicReference<>(); final AtomicReference destinationRunnableFailureRef = new AtomicReference<>(); @@ -146,12 +150,14 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path // closed first (which is what we want). try (destination; source) { destination.start(destinationConfig, jobRoot); + timeTracker.trackSourceReadStartTime(); source.start(sourceConfig, jobRoot); + timeTracker.trackDestinationWriteStartTime(); // note: `whenComplete` is used instead of `exceptionally` so that the original exception is still // thrown final CompletableFuture destinationOutputThreadFuture = CompletableFuture.runAsync( - getDestinationOutputRunnable(destination, cancelled, messageTracker, mdc), + getDestinationOutputRunnable(destination, cancelled, messageTracker, mdc, timeTracker), executors).whenComplete((msg, ex) -> { if (ex != null) { if (ex.getCause() instanceof DestinationException) { @@ -163,7 +169,7 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path }); final CompletableFuture replicationThreadFuture = CompletableFuture.runAsync( - getReplicationRunnable(source, destination, cancelled, mapper, messageTracker, mdc, recordSchemaValidator, metricReporter), + getReplicationRunnable(source, destination, cancelled, mapper, messageTracker, mdc, recordSchemaValidator, metricReporter, timeTracker), executors).whenComplete((msg, ex) -> { if (ex != null) { if (ex.getCause() instanceof SourceException) { @@ -204,6 +210,8 @@ else if (hasFailed.get()) { outputStatus = ReplicationStatus.COMPLETED; } + timeTracker.trackReplicationEndTime(); + final SyncStats totalSyncStats = new SyncStats() .withRecordsEmitted(messageTracker.getTotalRecordsEmitted()) .withBytesEmitted(messageTracker.getTotalBytesEmitted()) @@ -212,7 +220,13 @@ else if (hasFailed.get()) { .withMaxSecondsBeforeSourceStateMessageEmitted(messageTracker.getMaxSecondsToReceiveSourceStateMessage()) .withMeanSecondsBeforeSourceStateMessageEmitted(messageTracker.getMeanSecondsToReceiveSourceStateMessage()) .withMaxSecondsBetweenStateMessageEmittedandCommitted(messageTracker.getMaxSecondsBetweenStateMessageEmittedAndCommitted().orElse(null)) - .withMeanSecondsBetweenStateMessageEmittedandCommitted(messageTracker.getMeanSecondsBetweenStateMessageEmittedAndCommitted().orElse(null)); + .withMeanSecondsBetweenStateMessageEmittedandCommitted(messageTracker.getMeanSecondsBetweenStateMessageEmittedAndCommitted().orElse(null)) + .withReplicationStartTime(timeTracker.getReplicationStartTime()) + .withReplicationEndTime(timeTracker.getReplicationEndTime()) + .withSourceReadStartTime(timeTracker.getSourceReadStartTime()) + .withSourceReadEndTime(timeTracker.getSourceReadEndTime()) + .withDestinationWriteStartTime(timeTracker.getDestinationWriteStartTime()) + .withDestinationWriteEndTime(timeTracker.getDestinationWriteEndTime()); if (outputStatus == ReplicationStatus.COMPLETED) { totalSyncStats.setRecordsCommitted(totalSyncStats.getRecordsEmitted()); @@ -318,7 +332,8 @@ private static Runnable getReplicationRunnable(final AirbyteSource source, final MessageTracker messageTracker, final Map mdc, final RecordSchemaValidator recordSchemaValidator, - final WorkerMetricReporter metricReporter) { + final WorkerMetricReporter metricReporter, + final ThreadedTimeTracker timeHolder) { return () -> { MDC.setContextMap(mdc); LOGGER.info("Replication thread started."); @@ -362,6 +377,7 @@ private static Runnable getReplicationRunnable(final AirbyteSource source, } } } + timeHolder.trackSourceReadEndTime(); LOGGER.info("Total records read: {} ({})", recordsRead, FileUtils.byteCountToDisplaySize(messageTracker.getTotalBytesEmitted())); if (!validationErrors.isEmpty()) { validationErrors.forEach((stream, errorPair) -> { @@ -431,7 +447,8 @@ private static void validateSchema(final RecordSchemaValidator recordSchemaValid private static Runnable getDestinationOutputRunnable(final AirbyteDestination destination, final AtomicBoolean cancelled, final MessageTracker messageTracker, - final Map mdc) { + final Map mdc, + final ThreadedTimeTracker timeHolder) { return () -> { MDC.setContextMap(mdc); LOGGER.info("Destination output thread started."); @@ -448,6 +465,7 @@ private static Runnable getDestinationOutputRunnable(final AirbyteDestination de messageTracker.acceptFromDestination(messageOptional.get()); } } + timeHolder.trackDestinationWriteEndTime(); if (!cancelled.get() && destination.getExitValue() != 0) { throw new DestinationException("Destination process exited with non-zero exit code " + destination.getExitValue()); } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ThreadedTimeTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ThreadedTimeTracker.java new file mode 100644 index 000000000000..705c3a77ea65 --- /dev/null +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ThreadedTimeTracker.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.helper; + +/** + * This class exists to track timing information for the sync. It needs to be thread-safe as + * multiple threads (source, destination, and worker) will be accessing it. + */ +public class ThreadedTimeTracker { + + private long replicationStartTime; + private long replicationEndTime; + private long sourceReadStartTime; + private long sourceReadEndTime; + private long destinationWriteStartTime; + private long destinationWriteEndTime; + + public synchronized void trackReplicationStartTime() { + this.replicationStartTime = System.currentTimeMillis(); + } + + public synchronized void trackReplicationEndTime() { + this.replicationEndTime = System.currentTimeMillis(); + } + + public synchronized void trackSourceReadStartTime() { + this.sourceReadStartTime = System.currentTimeMillis(); + } + + public synchronized void trackSourceReadEndTime() { + this.sourceReadEndTime = System.currentTimeMillis(); + } + + public synchronized void trackDestinationWriteStartTime() { + this.destinationWriteStartTime = System.currentTimeMillis(); + } + + public synchronized void trackDestinationWriteEndTime() { + this.destinationWriteEndTime = System.currentTimeMillis(); + } + + public synchronized long getReplicationStartTime() { + return this.replicationStartTime; + } + + public synchronized long getReplicationEndTime() { + return this.replicationEndTime; + } + + public synchronized long getSourceReadStartTime() { + return this.sourceReadStartTime; + } + + public synchronized long getSourceReadEndTime() { + return this.sourceReadEndTime; + } + + public synchronized long getDestinationWriteStartTime() { + return this.destinationWriteStartTime; + } + + public synchronized long getDestinationWriteEndTime() { + return this.destinationWriteEndTime; + } + +} diff --git a/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java index 5f722b3e95ed..f6a570b46fbc 100644 --- a/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/general/DefaultReplicationWorkerTest.java @@ -536,9 +536,11 @@ void testPopulatesOutputOnSuccess() throws WorkerException { Jsons.jsonNode(actual)); assertTrue(validate.isEmpty(), "Validation errors: " + Strings.join(validate, ",")); - // remove times so we can do the rest of the object <> object comparison. - actual.getReplicationAttemptSummary().withStartTime(null); - actual.getReplicationAttemptSummary().withEndTime(null); + // remove times, so we can do the rest of the object <> object comparison. + actual.getReplicationAttemptSummary().withStartTime(null).withEndTime(null).getTotalStats().withReplicationStartTime(null) + .withReplicationEndTime(null) + .withSourceReadStartTime(null).withSourceReadEndTime(null) + .withDestinationWriteStartTime(null).withDestinationWriteEndTime(null); assertEquals(replicationOutput, actual); } @@ -631,7 +633,9 @@ void testPopulatesStatsOnFailureIfAvailable() throws Exception { .withDestinationStateMessagesEmitted(null))); assertNotNull(actual); - assertEquals(expectedTotalStats, actual.getReplicationAttemptSummary().getTotalStats()); + // null out timing stats for assertion matching + assertEquals(expectedTotalStats, actual.getReplicationAttemptSummary().getTotalStats().withReplicationStartTime(null).withReplicationEndTime(null) + .withSourceReadStartTime(null).withSourceReadEndTime(null).withDestinationWriteStartTime(null).withDestinationWriteEndTime(null)); assertEquals(expectedStreamStats, actual.getReplicationAttemptSummary().getStreamStats()); } diff --git a/airbyte-config/config-models/src/main/resources/types/SyncStats.yaml b/airbyte-config/config-models/src/main/resources/types/SyncStats.yaml index bd21f4f8331c..6410a3695292 100644 --- a/airbyte-config/config-models/src/main/resources/types/SyncStats.yaml +++ b/airbyte-config/config-models/src/main/resources/types/SyncStats.yaml @@ -29,3 +29,21 @@ properties: type: integer meanSecondsBetweenStateMessageEmittedandCommitted: type: integer + replicationStartTime: + type: integer + description: The start of the replication activity + replicationEndTime: + type: integer + description: The end of the replication activity + sourceReadStartTime: + type: integer + description: The boot time of the source container/pod + sourceReadEndTime: + type: integer + description: The exit time of the source container/pod + destinationWriteStartTime: + type: integer + description: The boot time of the destination container/pod + destinationWriteEndTime: + type: integer + description: The exit time of the destination container/pod diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/TrackingMetadata.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/TrackingMetadata.java index 55d90c0441ee..71e39b46971b 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/TrackingMetadata.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/tracker/TrackingMetadata.java @@ -12,6 +12,7 @@ import io.airbyte.config.AttemptFailureSummary; import io.airbyte.config.FailureReason; import io.airbyte.config.JobOutput; +import io.airbyte.config.NormalizationSummary; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.ScheduleData; import io.airbyte.config.StandardDestinationDefinition; @@ -19,6 +20,7 @@ import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSync.ScheduleType; import io.airbyte.config.StandardSyncSummary; +import io.airbyte.config.SyncStats; import io.airbyte.config.helpers.ScheduleHelpers; import io.airbyte.persistence.job.models.Attempt; import io.airbyte.persistence.job.models.Job; @@ -110,6 +112,9 @@ public static Map generateJobAttemptMetadata(final Job job) { final JobOutput jobOutput = lastAttempt.getOutput().get(); if (jobOutput.getSync() != null) { final StandardSyncSummary syncSummary = jobOutput.getSync().getStandardSyncSummary(); + final SyncStats totalStats = syncSummary.getTotalStats(); + final NormalizationSummary normalizationSummary = jobOutput.getSync().getNormalizationSummary(); + if (syncSummary.getStartTime() != null) metadata.put("sync_start_time", syncSummary.getStartTime()); if (syncSummary.getEndTime() != null && syncSummary.getStartTime() != null) @@ -118,22 +123,42 @@ public static Map generateJobAttemptMetadata(final Job job) { metadata.put("volume_mb", syncSummary.getBytesSynced()); if (syncSummary.getRecordsSynced() != null) metadata.put("volume_rows", syncSummary.getRecordsSynced()); - if (syncSummary.getTotalStats().getSourceStateMessagesEmitted() != null) + if (totalStats.getSourceStateMessagesEmitted() != null) metadata.put("count_state_messages_from_source", syncSummary.getTotalStats().getSourceStateMessagesEmitted()); - if (syncSummary.getTotalStats().getDestinationStateMessagesEmitted() != null) + if (totalStats.getDestinationStateMessagesEmitted() != null) metadata.put("count_state_messages_from_destination", syncSummary.getTotalStats().getDestinationStateMessagesEmitted()); - if (syncSummary.getTotalStats().getMaxSecondsBeforeSourceStateMessageEmitted() != null) + if (totalStats.getMaxSecondsBeforeSourceStateMessageEmitted() != null) metadata.put("max_seconds_before_source_state_message_emitted", - syncSummary.getTotalStats().getMaxSecondsBeforeSourceStateMessageEmitted()); - if (syncSummary.getTotalStats().getMeanSecondsBeforeSourceStateMessageEmitted() != null) + totalStats.getMaxSecondsBeforeSourceStateMessageEmitted()); + if (totalStats.getMeanSecondsBeforeSourceStateMessageEmitted() != null) metadata.put("mean_seconds_before_source_state_message_emitted", - syncSummary.getTotalStats().getMeanSecondsBeforeSourceStateMessageEmitted()); - if (syncSummary.getTotalStats().getMaxSecondsBetweenStateMessageEmittedandCommitted() != null) + totalStats.getMeanSecondsBeforeSourceStateMessageEmitted()); + if (totalStats.getMaxSecondsBetweenStateMessageEmittedandCommitted() != null) metadata.put("max_seconds_between_state_message_emit_and_commit", - syncSummary.getTotalStats().getMaxSecondsBetweenStateMessageEmittedandCommitted()); - if (syncSummary.getTotalStats().getMeanSecondsBetweenStateMessageEmittedandCommitted() != null) + totalStats.getMaxSecondsBetweenStateMessageEmittedandCommitted()); + if (totalStats.getMeanSecondsBetweenStateMessageEmittedandCommitted() != null) metadata.put("mean_seconds_between_state_message_emit_and_commit", - syncSummary.getTotalStats().getMeanSecondsBetweenStateMessageEmittedandCommitted()); + totalStats.getMeanSecondsBetweenStateMessageEmittedandCommitted()); + + if (totalStats.getReplicationStartTime() != null) + metadata.put("replication_start_time", totalStats.getReplicationStartTime()); + if (totalStats.getReplicationEndTime() != null) + metadata.put("replication_end_time", totalStats.getReplicationEndTime()); + if (totalStats.getSourceReadStartTime() != null) + metadata.put("source_read_start_time", totalStats.getSourceReadStartTime()); + if (totalStats.getSourceReadEndTime() != null) + metadata.put("source_read_end_time", totalStats.getSourceReadEndTime()); + if (totalStats.getDestinationWriteStartTime() != null) + metadata.put("destination_write_start_time", totalStats.getDestinationWriteStartTime()); + if (totalStats.getDestinationWriteEndTime() != null) + metadata.put("destination_write_end_time", totalStats.getDestinationWriteEndTime()); + + if (normalizationSummary != null) { + if (normalizationSummary.getStartTime() != null) + metadata.put("normalization_start_time", normalizationSummary.getStartTime()); + if (normalizationSummary.getEndTime() != null) + metadata.put("normalization_end_time", normalizationSummary.getEndTime()); + } } } diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java index fa6eb014a707..b5e3361b2cec 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/tracker/JobTrackerTest.java @@ -27,6 +27,7 @@ import io.airbyte.config.JobSyncConfig; import io.airbyte.config.JobSyncConfig.NamespaceDefinitionType; import io.airbyte.config.Metadata; +import io.airbyte.config.NormalizationSummary; import io.airbyte.config.Schedule; import io.airbyte.config.Schedule.TimeUnit; import io.airbyte.config.StandardCheckConnectionOutput; @@ -124,6 +125,14 @@ class JobTrackerTest { .put("mean_seconds_before_source_state_message_emitted", 4L) .put("max_seconds_between_state_message_emit_and_commit", 7L) .put("mean_seconds_between_state_message_emit_and_commit", 6L) + .put("replication_start_time", 7L) + .put("replication_end_time", 8L) + .put("source_read_start_time", 9L) + .put("source_read_end_time", 10L) + .put("destination_write_start_time", 11L) + .put("destination_write_end_time", 12L) + .put("normalization_start_time", 13L) + .put("normalization_end_time", 14L) .build(); private static final ImmutableMap SYNC_CONFIG_METADATA = ImmutableMap.builder() .put(JobTracker.CONFIG + ".source.key", JobTracker.SET) @@ -566,6 +575,7 @@ private Attempt getAttemptMock() { final JobOutput jobOutput = mock(JobOutput.class); final StandardSyncOutput syncOutput = mock(StandardSyncOutput.class); final StandardSyncSummary syncSummary = mock(StandardSyncSummary.class); + final NormalizationSummary normalizationSummary = mock(NormalizationSummary.class); final SyncStats syncStats = mock(SyncStats.class); when(syncSummary.getStartTime()).thenReturn(SYNC_START_TIME); @@ -573,6 +583,7 @@ private Attempt getAttemptMock() { when(syncSummary.getBytesSynced()).thenReturn(SYNC_BYTES_SYNC); when(syncSummary.getRecordsSynced()).thenReturn(SYNC_RECORDS_SYNC); when(syncOutput.getStandardSyncSummary()).thenReturn(syncSummary); + when(syncOutput.getNormalizationSummary()).thenReturn(normalizationSummary); when(syncSummary.getTotalStats()).thenReturn(syncStats); when(jobOutput.getSync()).thenReturn(syncOutput); when(attempt.getOutput()).thenReturn(java.util.Optional.of(jobOutput)); @@ -582,6 +593,15 @@ private Attempt getAttemptMock() { when(syncStats.getMeanSecondsBeforeSourceStateMessageEmitted()).thenReturn(4L); when(syncStats.getMaxSecondsBetweenStateMessageEmittedandCommitted()).thenReturn(7L); when(syncStats.getMeanSecondsBetweenStateMessageEmittedandCommitted()).thenReturn(6L); + when(syncStats.getReplicationStartTime()).thenReturn(7L); + when(syncStats.getReplicationEndTime()).thenReturn(8L); + when(syncStats.getSourceReadStartTime()).thenReturn(9L); + when(syncStats.getSourceReadEndTime()).thenReturn(10L); + when(syncStats.getDestinationWriteStartTime()).thenReturn(11L); + when(syncStats.getDestinationWriteEndTime()).thenReturn(12L); + when(normalizationSummary.getStartTime()).thenReturn(13L); + when(normalizationSummary.getEndTime()).thenReturn(14L); + return attempt; } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java index 6b308968c509..aac2956e7a43 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java @@ -15,6 +15,7 @@ import io.airbyte.config.AirbyteConfigValidator; import io.airbyte.config.ConfigSchema; import io.airbyte.config.Configs.WorkerEnvironment; +import io.airbyte.config.ReplicationAttemptSummary; import io.airbyte.config.ReplicationOutput; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.StandardSyncInput; @@ -169,19 +170,18 @@ public StandardSyncOutput replicate(final JobRunConfig jobRunConfig, } private static StandardSyncOutput reduceReplicationOutput(final ReplicationOutput output) { - final long totalBytesReplicated = output.getReplicationAttemptSummary().getBytesSynced(); - final long totalRecordsReplicated = output.getReplicationAttemptSummary().getRecordsSynced(); - + final StandardSyncOutput standardSyncOutput = new StandardSyncOutput(); final StandardSyncSummary syncSummary = new StandardSyncSummary(); - syncSummary.setBytesSynced(totalBytesReplicated); - syncSummary.setRecordsSynced(totalRecordsReplicated); - syncSummary.setStartTime(output.getReplicationAttemptSummary().getStartTime()); - syncSummary.setEndTime(output.getReplicationAttemptSummary().getEndTime()); - syncSummary.setStatus(output.getReplicationAttemptSummary().getStatus()); - syncSummary.setTotalStats(output.getReplicationAttemptSummary().getTotalStats()); - syncSummary.setStreamStats(output.getReplicationAttemptSummary().getStreamStats()); + final ReplicationAttemptSummary replicationSummary = output.getReplicationAttemptSummary(); + + syncSummary.setBytesSynced(replicationSummary.getBytesSynced()); + syncSummary.setRecordsSynced(replicationSummary.getRecordsSynced()); + syncSummary.setStartTime(replicationSummary.getStartTime()); + syncSummary.setEndTime(replicationSummary.getEndTime()); + syncSummary.setStatus(replicationSummary.getStatus()); + syncSummary.setTotalStats(replicationSummary.getTotalStats()); + syncSummary.setStreamStats(replicationSummary.getStreamStats()); - final StandardSyncOutput standardSyncOutput = new StandardSyncOutput(); standardSyncOutput.setState(output.getState()); standardSyncOutput.setOutputCatalog(output.getOutputCatalog()); standardSyncOutput.setStandardSyncSummary(syncSummary); diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java index 8ac4fff6b202..9c062c38c55b 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java @@ -204,8 +204,9 @@ void testSuccess() { verifyNormalize(normalizationActivity, normalizationInput); verifyDbtTransform(dbtTransformationActivity, syncInput.getResourceRequirements(), operatorDbtInput); - assertEquals(replicationSuccessOutput.withNormalizationSummary(normalizationSummary), - actualOutput); + assertEquals( + replicationSuccessOutput.withNormalizationSummary(normalizationSummary).getStandardSyncSummary(), + actualOutput.getStandardSyncSummary()); } @Test @@ -244,8 +245,9 @@ void testReplicationFailedGracefully() { verifyNormalize(normalizationActivity, normalizationInput); verifyDbtTransform(dbtTransformationActivity, syncInput.getResourceRequirements(), operatorDbtInput); - assertEquals(replicationFailOutput.withNormalizationSummary(normalizationSummary), - actualOutput); + assertEquals( + replicationFailOutput.withNormalizationSummary(normalizationSummary).getStandardSyncSummary(), + actualOutput.getStandardSyncSummary()); } @Test From 21352b6003b2f0a53673bda92b8d63666d16b327 Mon Sep 17 00:00:00 2001 From: Davin Chia Date: Thu, 13 Oct 2022 17:21:02 -0700 Subject: [PATCH 106/498] Trigger the connectors build if there are worker changes. (#17976) Since the connector module is dependent on the worker module, also trigger the connector builds if anything in the worker file directory changes. --- .github/workflows/gradle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8f56fcdc063f..888d06234bec 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -59,6 +59,7 @@ jobs: - 'airbyte-cdk/**' - 'airbyte-protocol/**' - 'airbyte-integrations/**' + - 'airbyte-workers/**' db: - 'airbyte-db/**' frontend: From 19f74282be55f8de70e9f5ef8bc607da77fbae1e Mon Sep 17 00:00:00 2001 From: Conor Date: Thu, 13 Oct 2022 19:38:03 -0500 Subject: [PATCH 107/498] ci: upload test results to github for analysis (#17953) * ci: upload test results to github for analysis * fix: filter down to only test folders --- .github/workflows/gradle.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 888d06234bec..1682e4fc1f1e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -540,6 +540,15 @@ jobs: path: '/actions-runner/_work/airbyte/airbyte/*' key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} + + - name: Upload test results to Github for analysis + if: '!cancelled()' # Run this step even when the tests fail. Skip if the workflow is cancelled. + uses: actions/upload-artifact@v3 + with: + path: | + /actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml + /actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml + name: test-results-build # In case of self-hosted EC2 errors, remove this block. stop-platform-build-runner: @@ -689,6 +698,15 @@ jobs: path: '/actions-runner/_work/airbyte/airbyte/*' key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} + + - name: Upload test results to Github for analysis + if: '!cancelled()' # Run this step even when the tests fail. Skip if the workflow is cancelled. + uses: actions/upload-artifact@v3 + with: + path: | + /actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml + /actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml + name: test-results-kube - uses: actions/upload-artifact@v2 if: failure() From 6a48da9226132107fc702cb74a9a421d5bd62949 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Fri, 14 Oct 2022 02:59:02 +0200 Subject: [PATCH 108/498] Introduce webhook configs into workspace api and persistence (#17950) * wip * handle webhook configs in workspaces endpoint and split/hydrate secrets * style improvements to documentation around webhook configs * Clarify documentation around webhook auth tokens * More documentation clarification around webhook configs * Format. * unit test coverage for webhook config handling * use common json parsing libraries around webhook configs * clean up around testing webhook operation configs Co-authored-by: Davin Chia --- airbyte-api/src/main/openapi/config.yaml | 33 +++++++++ .../java/io/airbyte/commons/json/Jsons.java | 8 +++ .../java/io/airbyte/config/ConfigSchema.java | 3 + .../resources/types/StandardWorkspace.yaml | 6 ++ .../types/WebhookOperationConfigs.yaml | 29 ++++++++ .../DatabaseConfigPersistence.java | 4 ++ .../persistence/SecretsRepositoryReader.java | 3 +- .../persistence/SecretsRepositoryWriter.java | 43 +++++++++--- .../SecretsRepositoryWriterTest.java | 46 ++++++++++++ .../WebhookOperationConfigsConverter.java | 52 ++++++++++++++ .../server/handlers/WorkspacesHandler.java | 18 ++++- .../handlers/WorkspacesHandlerTest.java | 57 +++++++++++++-- .../api/generated-api-html/index.html | 70 +++++++++++++++++++ 13 files changed, 354 insertions(+), 18 deletions(-) create mode 100644 airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml create mode 100644 airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index a5ef224bc982..e5adea49f375 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -2280,6 +2280,22 @@ components: type: boolean defaultGeography: $ref: "#/components/schemas/Geography" + webhookConfigs: + type: array + items: + $ref: "#/components/schemas/WebhookConfigWrite" + WebhookConfigWrite: + type: object + properties: + name: + type: string + description: human readable name for this webhook e.g. for UI display. + authToken: + type: string + description: an auth token, to be passed as the value for an HTTP Authorization header. + validationUrl: + type: string + description: if supplied, the webhook config will be validated by checking that this URL returns a 2xx response. Notification: type: object required: @@ -2384,6 +2400,23 @@ components: type: boolean defaultGeography: $ref: "#/components/schemas/Geography" + webhookConfigs: + type: array + items: + # Note: this omits any sensitive info e.g. auth token + $ref: "#/components/schemas/WebhookConfigRead" + WebhookConfigRead: + type: object + description: the readable info for a webhook config; omits sensitive info e.g. auth token + required: + - id + properties: + id: + type: string + format: uuid + name: + type: string + description: human-readable name e.g. for display in UI WorkspaceUpdateName: type: object required: diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java b/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java index 9ff9158135d4..a92e2c49985c 100644 --- a/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java +++ b/airbyte-commons/src/main/java/io/airbyte/commons/json/Jsons.java @@ -16,10 +16,12 @@ import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import io.airbyte.commons.jackson.MoreMappers; import io.airbyte.commons.stream.MoreStreams; +import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; @@ -39,6 +41,8 @@ public class Jsons { // Object Mapper is thread-safe private static final ObjectMapper OBJECT_MAPPER = MoreMappers.initMapper(); + + private static final ObjectMapper YAML_OBJECT_MAPPER = MoreMappers.initYamlMapper(new YAMLFactory()); private static final ObjectWriter OBJECT_WRITER = OBJECT_MAPPER.writer(new JsonPrettyPrinter()); public static String serialize(final T object) { @@ -89,6 +93,10 @@ public static JsonNode jsonNode(final T object) { return OBJECT_MAPPER.valueToTree(object); } + public static JsonNode jsonNodeFromFile(final File file) throws IOException { + return YAML_OBJECT_MAPPER.readTree(file); + } + public static JsonNode emptyObject() { return jsonNode(Collections.emptyMap()); } diff --git a/airbyte-config/config-models/src/main/java/io/airbyte/config/ConfigSchema.java b/airbyte-config/config-models/src/main/java/io/airbyte/config/ConfigSchema.java index 84fa66740261..b2f05d26a4e5 100644 --- a/airbyte-config/config-models/src/main/java/io/airbyte/config/ConfigSchema.java +++ b/airbyte-config/config-models/src/main/java/io/airbyte/config/ConfigSchema.java @@ -23,6 +23,9 @@ public enum ConfigSchema implements AirbyteConfig { workspaceServiceAccount -> workspaceServiceAccount.getWorkspaceId().toString(), "workspaceId"), + WORKSPACE_WEBHOOK_OPERATION_CONFIGS("WebhookOperationConfigs.yaml", + WebhookOperationConfigs.class), + // source STANDARD_SOURCE_DEFINITION("StandardSourceDefinition.yaml", StandardSourceDefinition.class, diff --git a/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml b/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml index dd65857f7dbd..03c786a207a0 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardWorkspace.yaml @@ -50,3 +50,9 @@ properties: type: boolean defaultGeography: "$ref": Geography.yaml + webhookOperationConfigs: + description: + Configurations for webhooks operations, stored as a JSON object so we can replace sensitive info with + coordinates in the secrets manager. Must conform to WebhookOperationConfigs.yaml. + type: object + existingJavaType: com.fasterxml.jackson.databind.JsonNode diff --git a/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml b/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml new file mode 100644 index 000000000000..1aca08ee2e7c --- /dev/null +++ b/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml @@ -0,0 +1,29 @@ +--- +"$schema": http://json-schema.org/draft-07/schema# +"$id": https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/resources/types/WebhookOperationConfigs.yaml +title: WebhookOperationConfigs +description: List of configurations for webhook operations +additionalProperties: false +# NOTE: we have an extra layer of object nesting because the generator has some weird behavior with arrays. +# See https://github.com/OpenAPITools/openapi-generator/issues/7802. +type: object +properties: + webhookConfigs: + type: array + items: + type: object + required: + - id + - name + - authToken + properties: + id: + type: string + format: uuid + name: + type: string + description: human readable name for this webhook e.g., for UI display + authToken: + type: string + airbyte_secret: true + description: An auth token, to be passed as the value for an HTTP Authorization header. Note - must include prefix such as "Bearer ". diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index e512ae14f72d..725815ea62e8 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -766,6 +766,8 @@ private void writeStandardWorkspace(final List configs, final .set(WORKSPACE.FIRST_SYNC_COMPLETE, standardWorkspace.getFirstCompletedSync()) .set(WORKSPACE.FEEDBACK_COMPLETE, standardWorkspace.getFeedbackDone()) .set(WORKSPACE.UPDATED_AT, timestamp) + .set(WORKSPACE.WEBHOOK_OPERATION_CONFIGS, standardWorkspace.getWebhookOperationConfigs() == null ? null + : JSONB.valueOf(Jsons.serialize(standardWorkspace.getWebhookOperationConfigs()))) .where(WORKSPACE.ID.eq(standardWorkspace.getWorkspaceId())) .execute(); } else { @@ -786,6 +788,8 @@ private void writeStandardWorkspace(final List configs, final .set(WORKSPACE.FEEDBACK_COMPLETE, standardWorkspace.getFeedbackDone()) .set(WORKSPACE.CREATED_AT, timestamp) .set(WORKSPACE.UPDATED_AT, timestamp) + .set(WORKSPACE.WEBHOOK_OPERATION_CONFIGS, standardWorkspace.getWebhookOperationConfigs() == null ? null + : JSONB.valueOf(Jsons.serialize(standardWorkspace.getWebhookOperationConfigs()))) .execute(); } }); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java index 898ec634a663..5d22a12c6029 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java @@ -111,7 +111,8 @@ public WorkspaceServiceAccount getWorkspaceServiceAccountWithSecrets(final UUID public StandardWorkspace getWorkspaceWithSecrets(final UUID workspaceId, final boolean includeTombstone) throws JsonValidationException, ConfigNotFoundException, IOException { final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, includeTombstone); - // TODO: hydrate any secrets once they're introduced. + final JsonNode webhookConfigs = secretsHydrator.hydrate(workspace.getWebhookOperationConfigs()); + workspace.withWebhookOperationConfigs(webhookConfigs); return workspace; } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java index b6f8a19423b7..176ec83aebd6 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java @@ -83,7 +83,7 @@ public void writeSourceConnection(final SourceConnection source, final Connector source.getWorkspaceId(), previousSourceConnection, source.getConfiguration(), - connectorSpecification, + connectorSpecification.getConnectionSpecification(), source.getTombstone() == null || !source.getTombstone()); final SourceConnection partialSource = Jsons.clone(source).withConfiguration(partialConfig); @@ -107,7 +107,7 @@ public void writeDestinationConnection(final DestinationConnection destination, destination.getWorkspaceId(), previousDestinationConnection, destination.getConfiguration(), - connectorSpecification, + connectorSpecification.getConnectionSpecification(), destination.getTombstone() == null || !destination.getTombstone()); final DestinationConnection partialDestination = Jsons.clone(destination).withConfiguration(partialConfig); @@ -146,11 +146,11 @@ private JsonNode statefulSplitSecrets(final UUID workspaceId, final JsonNode ful private JsonNode statefulUpdateSecrets(final UUID workspaceId, final Optional oldConfig, final JsonNode fullConfig, - final ConnectorSpecification spec, + final JsonNode spec, final boolean validate) throws JsonValidationException { if (validate) { - validator.ensure(spec.getConnectionSpecification(), fullConfig); + validator.ensure(spec, fullConfig); } if (longLivedSecretPersistence.isEmpty()) { @@ -163,13 +163,13 @@ private JsonNode statefulUpdateSecrets(final UUID workspaceId, workspaceId, oldConfig.get(), fullConfig, - spec.getConnectionSpecification(), + spec, longLivedSecretPersistence.get()); } else { splitSecretConfig = SecretsHelpers.splitConfig( workspaceId, fullConfig, - spec.getConnectionSpecification()); + spec); } splitSecretConfig.getCoordinateToPayload().forEach(longLivedSecretPersistence.get()::write); return splitSecretConfig.getPartialConfig(); @@ -324,8 +324,35 @@ public Optional getOptionalWorkspaceServiceAccount(fina public void writeWorkspace(final StandardWorkspace workspace) throws JsonValidationException, IOException { - // TODO(msiega): split secrets once they're introduced. - configRepository.writeStandardWorkspaceNoSecrets(workspace); + // Get the schema for the webhook config so we can split out any secret fields. + final JsonNode webhookConfigSchema = Jsons.jsonNodeFromFile(ConfigSchema.WORKSPACE_WEBHOOK_OPERATION_CONFIGS.getConfigSchemaFile()); + // Check if there's an existing config, so we can re-use the secret coordinates. + final var previousWorkspace = getWorkspaceIfExists(workspace.getWorkspaceId(), false); + Optional previousWebhookConfigs = Optional.empty(); + if (previousWorkspace.isPresent() && previousWorkspace.get().getWebhookOperationConfigs() != null) { + previousWebhookConfigs = Optional.of(previousWorkspace.get().getWebhookOperationConfigs()); + } + // Split out the secrets from the webhook config. + final JsonNode partialConfig = workspace.getWebhookOperationConfigs() == null ? null + : statefulUpdateSecrets( + workspace.getWorkspaceId(), + previousWebhookConfigs, + workspace.getWebhookOperationConfigs(), + webhookConfigSchema, true); + final StandardWorkspace partialWorkspace = Jsons.clone(workspace); + if (partialConfig != null) { + partialWorkspace.withWebhookOperationConfigs(partialConfig); + } + configRepository.writeStandardWorkspaceNoSecrets(partialWorkspace); + } + + private Optional getWorkspaceIfExists(final UUID workspaceId, final boolean includeTombstone) { + try { + final StandardWorkspace existingWorkspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, includeTombstone); + return existingWorkspace == null ? Optional.empty() : Optional.of(existingWorkspace); + } catch (JsonValidationException | IOException | ConfigNotFoundException e) { + return Optional.empty(); + } } } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java index 85c5b86e5c8e..1ba17f029c73 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java @@ -11,6 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -30,9 +31,13 @@ import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; +import io.airbyte.config.Geography; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; +import io.airbyte.config.StandardWorkspace; +import io.airbyte.config.WebhookConfig; +import io.airbyte.config.WebhookOperationConfigs; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.config.persistence.split_secrets.MemorySecretPersistence; import io.airbyte.config.persistence.split_secrets.RealSecretsHydrator; @@ -42,6 +47,7 @@ import io.airbyte.validation.json.JsonSchemaValidator; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,6 +57,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -88,6 +95,11 @@ class SecretsRepositoryWriterTest { private static final String PASSWORD_PROPERTY_NAME = "password"; private static final String PASSWORD_FIELD_NAME = "_secret"; + private static final String TEST_EMAIL = "test-email"; + private static final String TEST_WORKSPACE_NAME = "test-workspace-name"; + private static final String TEST_WORKSPACE_SLUG = "test-workspace-slug"; + private static final String TEST_WEBHOOK_NAME = "test-webhook-name"; + private static final String TEST_AUTH_TOKEN = "test-auth-token"; private ConfigRepository configRepository; private MemorySecretPersistence longLivedSecretPersistence; @@ -433,4 +445,38 @@ void testWriteDifferentStagingConfiguration() throws JsonValidationException, Co Map.of(PASSWORD_FIELD_NAME, hmacSecretNewCoordinate.getFullCoordinate())))); } + @Test + @DisplayName("writeWorkspace should ensure that secret fields are replaced") + void testWriteWorkspaceSplitsAuthTokens() throws JsonValidationException, IOException { + final ConfigRepository configRepository = mock(ConfigRepository.class); + final SecretPersistence secretPersistence = mock(SecretPersistence.class); + final SecretsRepositoryWriter secretsRepositoryWriter = + spy(new SecretsRepositoryWriter(configRepository, jsonSchemaValidator, Optional.of(secretPersistence), Optional.of(secretPersistence))); + final var webhookConfigs = new WebhookOperationConfigs().withWebhookConfigs(List.of( + new WebhookConfig() + .withName(TEST_WEBHOOK_NAME) + .withAuthToken(TEST_AUTH_TOKEN) + .withId(UUID.randomUUID()))); + final var workspace = new StandardWorkspace() + .withWorkspaceId(UUID.randomUUID()) + .withCustomerId(UUID.randomUUID()) + .withEmail(TEST_EMAIL) + .withName(TEST_WORKSPACE_NAME) + .withSlug(TEST_WORKSPACE_SLUG) + .withInitialSetupComplete(false) + .withDisplaySetupWizard(true) + .withNews(false) + .withAnonymousDataCollection(false) + .withSecurityUpdates(false) + .withTombstone(false) + .withNotifications(Collections.emptyList()) + .withDefaultGeography(Geography.AUTO) + // Serialize it to a string, then deserialize it to a JsonNode. + .withWebhookOperationConfigs(Jsons.jsonNode(webhookConfigs)); + secretsRepositoryWriter.writeWorkspace(workspace); + final var workspaceArgumentCaptor = ArgumentCaptor.forClass(StandardWorkspace.class); + verify(configRepository, times(1)).writeStandardWorkspaceNoSecrets(workspaceArgumentCaptor.capture()); + assertFalse(Jsons.serialize(workspaceArgumentCaptor.getValue().getWebhookOperationConfigs()).contains(TEST_AUTH_TOKEN)); + } + } diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java b/airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java new file mode 100644 index 000000000000..2317a19706d9 --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.converters; + +import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.api.model.generated.WebhookConfigRead; +import io.airbyte.api.model.generated.WebhookConfigWrite; +import io.airbyte.commons.json.Jsons; +import io.airbyte.config.WebhookConfig; +import io.airbyte.config.WebhookOperationConfigs; +import java.util.Collections; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class WebhookOperationConfigsConverter { + + public static JsonNode toPersistenceWrite(List apiWebhookConfigs) { + if (apiWebhookConfigs == null) { + return Jsons.emptyObject(); + } + + final WebhookOperationConfigs configs = new WebhookOperationConfigs() + .withWebhookConfigs(apiWebhookConfigs.stream().map(WebhookOperationConfigsConverter::toPersistenceConfig).collect(Collectors.toList())); + + return Jsons.jsonNode(configs); + } + + public static List toApiReads(List persistenceConfig) { + if (persistenceConfig.isEmpty()) { + return Collections.emptyList(); + } + return persistenceConfig.stream().map(WebhookOperationConfigsConverter::toApiRead).collect(Collectors.toList()); + } + + private static WebhookConfig toPersistenceConfig(final WebhookConfigWrite input) { + return new WebhookConfig() + .withId(UUID.randomUUID()) + .withName(input.getName()) + .withAuthToken(input.getAuthToken()); + } + + private static WebhookConfigRead toApiRead(final WebhookConfig persistenceConfig) { + final var read = new WebhookConfigRead(); + read.setId(persistenceConfig.getId()); + read.setName(persistenceConfig.getName()); + return read; + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java index 71ece0d59250..7e949a89d201 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java @@ -24,18 +24,22 @@ import io.airbyte.api.model.generated.WorkspaceUpdate; import io.airbyte.api.model.generated.WorkspaceUpdateName; import io.airbyte.commons.enums.Enums; +import io.airbyte.commons.json.Jsons; import io.airbyte.config.StandardWorkspace; +import io.airbyte.config.WebhookOperationConfigs; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.notification.NotificationClient; import io.airbyte.server.converters.NotificationConverter; +import io.airbyte.server.converters.WebhookOperationConfigsConverter; import io.airbyte.server.errors.IdNotFoundKnownException; import io.airbyte.server.errors.InternalServerKnownException; import io.airbyte.server.errors.ValueConflictKnownException; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -103,7 +107,8 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate) .withDisplaySetupWizard(displaySetupWizard != null ? displaySetupWizard : false) .withTombstone(false) .withNotifications(NotificationConverter.toConfigList(workspaceCreate.getNotifications())) - .withDefaultGeography(defaultGeography); + .withDefaultGeography(defaultGeography) + .withWebhookOperationConfigs(WebhookOperationConfigsConverter.toPersistenceWrite(workspaceCreate.getWebhookConfigs())); if (!Strings.isNullOrEmpty(email)) { workspace.withEmail(email); @@ -250,7 +255,7 @@ private String generateUniqueSlug(final String workspaceName) throws JsonValidat } private static WorkspaceRead buildWorkspaceRead(final StandardWorkspace workspace) { - return new WorkspaceRead() + final WorkspaceRead result = new WorkspaceRead() .workspaceId(workspace.getWorkspaceId()) .customerId(workspace.getCustomerId()) .email(workspace.getEmail()) @@ -263,6 +268,15 @@ private static WorkspaceRead buildWorkspaceRead(final StandardWorkspace workspac .securityUpdates(workspace.getSecurityUpdates()) .notifications(NotificationConverter.toApiList(workspace.getNotifications())) .defaultGeography(Enums.convertTo(workspace.getDefaultGeography(), Geography.class)); + // Add read-only webhook configs. + final Optional persistedConfigs = Jsons.tryObject( + workspace.getWebhookOperationConfigs(), + WebhookOperationConfigs.class); + if (persistedConfigs.isPresent()) { + result.setWebhookConfigs(WebhookOperationConfigsConverter.toApiReads( + persistedConfigs.get().getWebhookConfigs())); + } + return result; } private void validateWorkspacePatch(final StandardWorkspace persistedWorkspace, final WorkspaceUpdate workspacePatch) { diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java index d8a431823e50..8220509c9eaa 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java @@ -54,6 +54,7 @@ class WorkspacesHandlerTest { public static final String FAILURE_NOTIFICATION_WEBHOOK = "http://airbyte.notifications/failure"; + public static final String NEW_WORKSPACE = "new workspace"; private ConfigRepository configRepository; private SecretsRepositoryWriter secretsRepositoryWriter; private ConnectionsHandler connectionsHandler; @@ -81,6 +82,7 @@ void setUp() { destinationHandler = mock(DestinationHandler.class); sourceHandler = mock(SourceHandler.class); uuidSupplier = mock(Supplier.class); + workspace = generateWorkspace(); workspacesHandler = new WorkspacesHandler(configRepository, secretsRepositoryWriter, connectionsHandler, destinationHandler, sourceHandler, uuidSupplier); @@ -118,8 +120,8 @@ private io.airbyte.api.model.generated.Notification generateApiNotification() { } @Test - void testCreateWorkspace() throws JsonValidationException, IOException { - when(configRepository.listStandardWorkspaces(false)).thenReturn(Collections.singletonList(workspace)); + void testCreateWorkspace() throws JsonValidationException, IOException, ConfigNotFoundException { + when(configRepository.getStandardWorkspaceNoSecrets(any(), eq(false))).thenReturn(workspace); final UUID uuid = UUID.randomUUID(); when(uuidSupplier.get()).thenReturn(uuid); @@ -127,7 +129,7 @@ void testCreateWorkspace() throws JsonValidationException, IOException { configRepository.writeStandardWorkspaceNoSecrets(workspace); final WorkspaceCreate workspaceCreate = new WorkspaceCreate() - .name("new workspace") + .name(NEW_WORKSPACE) .email(TEST_EMAIL) .news(false) .anonymousDataCollection(false) @@ -140,7 +142,7 @@ void testCreateWorkspace() throws JsonValidationException, IOException { .workspaceId(uuid) .customerId(uuid) .email(TEST_EMAIL) - .name("new workspace") + .name(NEW_WORKSPACE) .slug("new-workspace") .initialSetupComplete(false) .displaySetupWizard(false) @@ -148,17 +150,19 @@ void testCreateWorkspace() throws JsonValidationException, IOException { .anonymousDataCollection(false) .securityUpdates(false) .notifications(List.of(generateApiNotification())) - .defaultGeography(GEOGRAPHY_US); + .defaultGeography(GEOGRAPHY_US) + .webhookConfigs(Collections.emptyList()); assertEquals(expectedRead, actualRead); } @Test - void testCreateWorkspaceDuplicateSlug() throws JsonValidationException, IOException { + void testCreateWorkspaceDuplicateSlug() throws JsonValidationException, IOException, ConfigNotFoundException { when(configRepository.getWorkspaceBySlugOptional(any(String.class), eq(true))) .thenReturn(Optional.of(workspace)) .thenReturn(Optional.of(workspace)) .thenReturn(Optional.empty()); + when(configRepository.getStandardWorkspaceNoSecrets(any(), eq(false))).thenReturn(workspace); final UUID uuid = UUID.randomUUID(); when(uuidSupplier.get()).thenReturn(uuid); @@ -186,7 +190,8 @@ void testCreateWorkspaceDuplicateSlug() throws JsonValidationException, IOExcept .anonymousDataCollection(false) .securityUpdates(false) .notifications(Collections.emptyList()) - .defaultGeography(GEOGRAPHY_AUTO); + .defaultGeography(GEOGRAPHY_AUTO) + .webhookConfigs(Collections.emptyList()); assertTrue(actualRead.getSlug().startsWith(workspace.getSlug())); assertNotEquals(workspace.getSlug(), actualRead.getSlug()); @@ -461,4 +466,42 @@ void testSetFeedbackDone() throws JsonValidationException, ConfigNotFoundExcepti verify(configRepository).setFeedback(workspaceGiveFeedback.getWorkspaceId()); } + @Test + void testWorkspaceIsWrittenThroughSecretsWriter() throws JsonValidationException, IOException { + secretsRepositoryWriter = mock(SecretsRepositoryWriter.class); + workspacesHandler = new WorkspacesHandler(configRepository, secretsRepositoryWriter, connectionsHandler, + destinationHandler, sourceHandler, uuidSupplier); + + final UUID uuid = UUID.randomUUID(); + when(uuidSupplier.get()).thenReturn(uuid); + + final WorkspaceCreate workspaceCreate = new WorkspaceCreate() + .name(NEW_WORKSPACE) + .email(TEST_EMAIL) + .news(false) + .anonymousDataCollection(false) + .securityUpdates(false) + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_US); + + final WorkspaceRead actualRead = workspacesHandler.createWorkspace(workspaceCreate); + final WorkspaceRead expectedRead = new WorkspaceRead() + .workspaceId(uuid) + .customerId(uuid) + .email(TEST_EMAIL) + .name(NEW_WORKSPACE) + .slug("new-workspace") + .initialSetupComplete(false) + .displaySetupWizard(false) + .news(false) + .anonymousDataCollection(false) + .securityUpdates(false) + .notifications(List.of(generateApiNotification())) + .defaultGeography(GEOGRAPHY_US) + .webhookConfigs(Collections.emptyList()); + + assertEquals(expectedRead, actualRead); + verify(secretsRepositoryWriter, times(1)).writeWorkspace(any()); + } + } diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 2801d6cbc913..ca18b820a0da 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -9142,6 +9142,13 @@

    Example data

    Content-Type: application/json
    {
       "news" : true,
    +  "webhookConfigs" : [ {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  }, {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  } ],
       "displaySetupWizard" : true,
       "initialSetupComplete" : true,
       "anonymousDataCollection" : true,
    @@ -9271,6 +9278,13 @@ 

    Example data

    Content-Type: application/json
    {
       "news" : true,
    +  "webhookConfigs" : [ {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  }, {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  } ],
       "displaySetupWizard" : true,
       "initialSetupComplete" : true,
       "anonymousDataCollection" : true,
    @@ -9355,6 +9369,13 @@ 

    Example data

    Content-Type: application/json
    {
       "news" : true,
    +  "webhookConfigs" : [ {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  }, {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  } ],
       "displaySetupWizard" : true,
       "initialSetupComplete" : true,
       "anonymousDataCollection" : true,
    @@ -9428,6 +9449,13 @@ 

    Example data

    {
       "workspaces" : [ {
         "news" : true,
    +    "webhookConfigs" : [ {
    +      "name" : "name",
    +      "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +    }, {
    +      "name" : "name",
    +      "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +    } ],
         "displaySetupWizard" : true,
         "initialSetupComplete" : true,
         "anonymousDataCollection" : true,
    @@ -9456,6 +9484,13 @@ 

    Example data

    "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" }, { "news" : true, + "webhookConfigs" : [ { + "name" : "name", + "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" + }, { + "name" : "name", + "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" + } ], "displaySetupWizard" : true, "initialSetupComplete" : true, "anonymousDataCollection" : true, @@ -9535,6 +9570,13 @@

    Example data

    Content-Type: application/json
    {
       "news" : true,
    +  "webhookConfigs" : [ {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  }, {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  } ],
       "displaySetupWizard" : true,
       "initialSetupComplete" : true,
       "anonymousDataCollection" : true,
    @@ -9664,6 +9706,13 @@ 

    Example data

    Content-Type: application/json
    {
       "news" : true,
    +  "webhookConfigs" : [ {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  }, {
    +    "name" : "name",
    +    "id" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91"
    +  } ],
       "displaySetupWizard" : true,
       "initialSetupComplete" : true,
       "anonymousDataCollection" : true,
    @@ -9875,6 +9924,8 @@ 

    Table of Contents

  • WebBackendOperationCreateOrUpdate -
  • WebBackendWorkspaceState -
  • WebBackendWorkspaceStateResult -
  • +
  • WebhookConfigRead -
  • +
  • WebhookConfigWrite -
  • WorkspaceCreate -
  • WorkspaceGiveFeedback -
  • WorkspaceIdRequestBody -
  • @@ -11374,6 +11425,23 @@

    WebBackendWorkspaceStateResul
    hasDestinations
    +
    +

    WebhookConfigRead - Up

    +
    the readable info for a webhook config; omits sensitive info e.g. auth token
    +
    +
    id
    UUID format: uuid
    +
    name (optional)
    String human-readable name e.g. for display in UI
    +
    +
    +
    +

    WebhookConfigWrite - Up

    +
    +
    +
    name (optional)
    String human readable name for this webhook e.g. for UI display.
    +
    authToken (optional)
    String an auth token, to be passed as the value for an HTTP Authorization header.
    +
    validationUrl (optional)
    String if supplied, the webhook config will be validated by checking that this URL returns a 2xx response.
    +
    +
    displaySetupWizard (optional)
    defaultGeography (optional)
    +
    webhookConfigs (optional)
    feedbackDone (optional)
    defaultGeography (optional)
    +
    webhookConfigs (optional)
    From 888347a0d0f2976c1b84094e95208e0f77e9b2f6 Mon Sep 17 00:00:00 2001 From: Liren Tu Date: Fri, 14 Oct 2022 01:09:12 -0700 Subject: [PATCH 109/498] =?UTF-8?q?=F0=9F=8E=89=20JDBC=20sources:=20store?= =?UTF-8?q?=20cursor=20record=20count=20in=20db=20state=20(#15535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add cursor_record_count to db stream state * Add cursor record count to cursor info * Emit max cursor record count * Add original cursor record count * Unify logging format * Add backward compatible methods * Update unit tests for state decorating iterator * Update test (not done yet) * Fix one more unit test * Change where clause operator according to record count * Add branch for null cursor * Skip saving record count when it is 0 * Fix log wording * Set mock record count in test * Check cursor value instead of cursor info * Fix source jdbc test * Read record count from state * Fix tests * Add an acceptance test case * Fix npe * Change record count from int to long to avoid type conversion * Fix references * Fix oracle container * Use uppercase for snowflake * Use uppercase for db2 * Fix and use uppercase * Update test case to include the edge case * Format code * Remove extra assertion in clickhouse * Merge ms sql incremental query method * Log query for debugging * Clean up name_and_timestamp table * Fix db2 tests * Fix mssql tests * Fix oracle tests * Fix oracle tests * Fix cockroachdb tests * Fix snowflake tests * Add changelog * Fix mssql tests * Fix db2-strict-encrypt tests * Fix oracle-strict-encrypt tests * Bump postgres version * Fix oracle-strict-encrypt tests * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../source/bigquery/BigQuerySource.java | 10 +- .../source/clickhouse/ClickHouseSource.java | 4 +- .../CockroachDbJdbcSourceAcceptanceTest.java | 12 +- .../Db2JdbcSourceAcceptanceTest.java | 4 +- .../Db2Source.java | 5 + .../Db2JdbcSourceAcceptanceTest.java | 4 +- .../source/jdbc/AbstractJdbcSource.java | 84 ++++++++-- .../jdbc/test/JdbcSourceAcceptanceTest.java | 151 +++++++++++++++++- .../MongoDbSource.java | 8 +- ...StrictEncryptJdbcSourceAcceptanceTest.java | 4 + .../source/mssql/MssqlSource.java | 90 +++-------- .../mssql/MssqlJdbcSourceAcceptanceTest.java | 4 + ...StrictEncryptJdbcSourceAcceptanceTest.java | 3 +- .../source/mysql/MySqlCdcProperties.java | 3 +- .../mysql/helpers/CdcConfigurationHelper.java | 1 - .../mysql/MySqlJdbcSourceAcceptanceTest.java | 3 +- ...StrictEncryptJdbcSourceAcceptanceTest.java | 6 +- .../OracleJdbcSourceAcceptanceTest.java | 6 +- .../source-postgres-strict-encrypt/Dockerfile | 2 +- .../connectors/source-postgres/Dockerfile | 2 +- .../PostgresJdbcSourceAcceptanceTest.java | 3 +- .../source/relationaldb/AbstractDbSource.java | 28 ++-- .../source/relationaldb/CursorInfo.java | 39 ++++- .../relationaldb/StateDecoratingIterator.java | 24 ++- .../state/AbstractStateManager.java | 5 +- .../relationaldb/state/CursorManager.java | 35 +++- .../state/GlobalStateManager.java | 2 + .../state/LegacyStateManager.java | 18 ++- .../state/StateGeneratorUtils.java | 11 +- .../relationaldb/state/StateManager.java | 9 +- .../state/StreamStateManager.java | 6 +- .../main/resources/db_models/db_models.yaml | 3 + .../StateDecoratingIteratorTest.java | 41 +++-- .../relationaldb/state/CursorManagerTest.java | 36 +++-- .../state/GlobalStateManagerTest.java | 7 +- .../state/StateTestConstants.java | 10 +- .../state/StreamStateManagerTest.java | 3 +- .../SnowflakeSource.java | 5 + .../SnowflakeJdbcSourceAcceptanceTest.java | 6 +- .../airbyte/protocol/models/CommonField.java | 5 + docs/integrations/sources/alloydb.md | 9 +- docs/integrations/sources/bigquery.md | 1 + docs/integrations/sources/clickhouse.md | 1 + docs/integrations/sources/cockroachdb.md | 1 + docs/integrations/sources/db2.md | 1 + docs/integrations/sources/mssql.md | 1 + docs/integrations/sources/mysql.md | 1 + docs/integrations/sources/oracle.md | 1 + docs/integrations/sources/postgres.md | 3 +- docs/integrations/sources/redshift.md | 1 + docs/integrations/sources/snowflake.md | 1 + docs/integrations/sources/tidb.md | 1 + 54 files changed, 538 insertions(+), 190 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 5ea4c5006542..fc8cf2578bb4 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -836,7 +836,7 @@ - name: Postgres sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres - dockerImageTag: 1.0.15 + dockerImageTag: 1.0.16 documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index d6e64732c883..405a7a8989e2 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -8596,7 +8596,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-postgres:1.0.15" +- dockerImage: "airbyte/source-postgres:1.0.16" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-bigquery/src/main/java/io/airbyte/integrations/source/bigquery/BigQuerySource.java b/airbyte-integrations/connectors/source-bigquery/src/main/java/io/airbyte/integrations/source/bigquery/BigQuerySource.java index dce577a068f8..f15a35ac7caf 100644 --- a/airbyte-integrations/connectors/source-bigquery/src/main/java/io/airbyte/integrations/source/bigquery/BigQuerySource.java +++ b/airbyte-integrations/connectors/source-bigquery/src/main/java/io/airbyte/integrations/source/bigquery/BigQuerySource.java @@ -19,6 +19,7 @@ import io.airbyte.integrations.base.IntegrationRunner; import io.airbyte.integrations.base.Source; import io.airbyte.integrations.source.relationaldb.AbstractRelationalDbSource; +import io.airbyte.integrations.source.relationaldb.CursorInfo; import io.airbyte.integrations.source.relationaldb.TableInfo; import io.airbyte.protocol.models.CommonField; import io.airbyte.protocol.models.JsonSchemaType; @@ -136,14 +137,13 @@ public AutoCloseableIterator queryTableIncremental(final BigQueryDatab final List columnNames, final String schemaName, final String tableName, - final String cursorField, - final StandardSQLTypeName cursorFieldType, - final String cursorValue) { + final CursorInfo cursorInfo, + final StandardSQLTypeName cursorFieldType) { return queryTableWithParams(database, String.format("SELECT %s FROM %s WHERE %s > ?", enquoteIdentifierList(columnNames), getFullTableName(schemaName, tableName), - cursorField), - sourceOperations.getQueryParameter(cursorFieldType, cursorValue)); + cursorInfo.getCursorField()), + sourceOperations.getQueryParameter(cursorFieldType, cursorInfo.getCursor())); } @Override diff --git a/airbyte-integrations/connectors/source-clickhouse/src/main/java/io/airbyte/integrations/source/clickhouse/ClickHouseSource.java b/airbyte-integrations/connectors/source-clickhouse/src/main/java/io/airbyte/integrations/source/clickhouse/ClickHouseSource.java index 31b1cfabec04..1e5ca5b4202d 100644 --- a/airbyte-integrations/connectors/source-clickhouse/src/main/java/io/airbyte/integrations/source/clickhouse/ClickHouseSource.java +++ b/airbyte-integrations/connectors/source-clickhouse/src/main/java/io/airbyte/integrations/source/clickhouse/ClickHouseSource.java @@ -91,9 +91,9 @@ public JsonNode toDatabaseConfig(final JsonNode config) { config.get(JdbcUtils.PORT_KEY).asText(), config.get(JdbcUtils.DATABASE_KEY).asText())); - boolean isAdditionalParamsExists = + final boolean isAdditionalParamsExists = config.get(JdbcUtils.JDBC_URL_PARAMS_KEY) != null && !config.get(JdbcUtils.JDBC_URL_PARAMS_KEY).asText().isEmpty(); - List params = new ArrayList<>(); + final List params = new ArrayList<>(); // assume ssl if not explicitly mentioned. if (isSsl) { params.add(SSL_MODE); diff --git a/airbyte-integrations/connectors/source-cockroachdb/src/test/java/io/airbyte/integrations/source/cockroachdb/CockroachDbJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-cockroachdb/src/test/java/io/airbyte/integrations/source/cockroachdb/CockroachDbJdbcSourceAcceptanceTest.java index 976066cf0c32..18504b4e7c06 100644 --- a/airbyte-integrations/connectors/source-cockroachdb/src/test/java/io/airbyte/integrations/source/cockroachdb/CockroachDbJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-cockroachdb/src/test/java/io/airbyte/integrations/source/cockroachdb/CockroachDbJdbcSourceAcceptanceTest.java @@ -331,7 +331,8 @@ void testReadOneTableIncrementallyTwice() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("5"))))))); + .withCursor("5") + .withCursorRecordCount(1L))))))); setEmittedAtToNull(actualMessagesSecondSync); @@ -463,7 +464,8 @@ void testReadMultipleTablesIncrementally() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("3"), + .withCursor("3") + .withCursorRecordCount(1L), new DbStreamState() .withStreamName(streamName2) .withStreamNamespace(namespace) @@ -481,12 +483,14 @@ void testReadMultipleTablesIncrementally() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("3"), + .withCursor("3") + .withCursorRecordCount(1L), new DbStreamState() .withStreamName(streamName2) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("3"))))))); + .withCursor("3") + .withCursorRecordCount(1L))))))); setEmittedAtToNull(actualMessagesFirstSync); diff --git a/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/java/io/airbyte/integrations/source/db2_strict_encrypt/Db2JdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/java/io/airbyte/integrations/source/db2_strict_encrypt/Db2JdbcSourceAcceptanceTest.java index b1a7cbd4768f..8ff36d97b9db 100644 --- a/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/java/io/airbyte/integrations/source/db2_strict_encrypt/Db2JdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-db2-strict-encrypt/src/test/java/io/airbyte/integrations/source/db2_strict_encrypt/Db2JdbcSourceAcceptanceTest.java @@ -63,14 +63,16 @@ static void init() throws IOException, InterruptedException { TABLE_NAME_COMPOSITE_PK = "FULL_NAME_COMPOSITE_PK"; TABLE_NAME_WITHOUT_CURSOR_TYPE = "TABLE_NAME_WITHOUT_CURSOR_TYPE"; TABLE_NAME_WITH_NULLABLE_CURSOR_TYPE = "TABLE_NAME_WITH_NULLABLE_CURSOR_TYPE"; + TABLE_NAME_AND_TIMESTAMP = "NAME_AND_TIMESTAMP"; TEST_TABLES = ImmutableSet - .of(TABLE_NAME, TABLE_NAME_WITHOUT_PK, TABLE_NAME_COMPOSITE_PK); + .of(TABLE_NAME, TABLE_NAME_WITHOUT_PK, TABLE_NAME_COMPOSITE_PK, TABLE_NAME_AND_TIMESTAMP); COL_ID = "ID"; COL_NAME = "NAME"; COL_UPDATED_AT = "UPDATED_AT"; COL_FIRST_NAME = "FIRST_NAME"; COL_LAST_NAME = "LAST_NAME"; COL_LAST_NAME_WITH_SPACE = "LAST NAME"; + COL_TIMESTAMP = "TIMESTAMP"; // In Db2 PK columns must be declared with NOT NULL statement. COLUMN_CLAUSE_WITH_PK = "id INTEGER NOT NULL, name VARCHAR(200), updated_at DATE"; COLUMN_CLAUSE_WITH_COMPOSITE_PK = "first_name VARCHAR(200) NOT NULL, last_name VARCHAR(200) NOT NULL, updated_at DATE"; diff --git a/airbyte-integrations/connectors/source-db2/src/main/java/io.airbyte.integrations.source.db2/Db2Source.java b/airbyte-integrations/connectors/source-db2/src/main/java/io.airbyte.integrations.source.db2/Db2Source.java index de55925b1bc9..773d784f28d7 100644 --- a/airbyte-integrations/connectors/source-db2/src/main/java/io.airbyte.integrations.source.db2/Db2Source.java +++ b/airbyte-integrations/connectors/source-db2/src/main/java/io.airbyte.integrations.source.db2/Db2Source.java @@ -111,6 +111,11 @@ protected int getStateEmissionFrequency() { return INTERMEDIATE_STATE_EMISSION_FREQUENCY; } + @Override + protected String getCountColumnName() { + return "RECORD_COUNT"; + } + private CheckedFunction getPrivileges() { return connection -> connection.prepareStatement( "SELECT DISTINCT OBJECTNAME, OBJECTSCHEMA FROM SYSIBMADM.PRIVILEGES WHERE OBJECTTYPE = 'TABLE' AND PRIVILEGE = 'SELECT' AND AUTHID = SESSION_USER"); diff --git a/airbyte-integrations/connectors/source-db2/src/test/java/io.airbyte.integrations.source.db2/Db2JdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-db2/src/test/java/io.airbyte.integrations.source.db2/Db2JdbcSourceAcceptanceTest.java index 406d27c4a647..cd99b0ab5da6 100644 --- a/airbyte-integrations/connectors/source-db2/src/test/java/io.airbyte.integrations.source.db2/Db2JdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-db2/src/test/java/io.airbyte.integrations.source.db2/Db2JdbcSourceAcceptanceTest.java @@ -41,14 +41,16 @@ static void init() { TABLE_NAME_COMPOSITE_PK = "FULL_NAME_COMPOSITE_PK"; TABLE_NAME_WITHOUT_CURSOR_TYPE = "TABLE_NAME_WITHOUT_CURSOR_TYPE"; TABLE_NAME_WITH_NULLABLE_CURSOR_TYPE = "TABLE_NAME_WITH_NULLABLE_CURSOR_TYPE"; + TABLE_NAME_AND_TIMESTAMP = "NAME_AND_TIMESTAMP"; TEST_TABLES = ImmutableSet - .of(TABLE_NAME, TABLE_NAME_WITHOUT_PK, TABLE_NAME_COMPOSITE_PK); + .of(TABLE_NAME, TABLE_NAME_WITHOUT_PK, TABLE_NAME_COMPOSITE_PK, TABLE_NAME_AND_TIMESTAMP); COL_ID = "ID"; COL_NAME = "NAME"; COL_UPDATED_AT = "UPDATED_AT"; COL_FIRST_NAME = "FIRST_NAME"; COL_LAST_NAME = "LAST_NAME"; COL_LAST_NAME_WITH_SPACE = "LAST NAME"; + COL_TIMESTAMP = "TIMESTAMP"; // In Db2 PK columns must be declared with NOT NULL statement. COLUMN_CLAUSE_WITH_PK = "id INTEGER NOT NULL, name VARCHAR(200), updated_at DATE"; COLUMN_CLAUSE_WITH_COMPOSITE_PK = "first_name VARCHAR(200) NOT NULL, last_name VARCHAR(200) NOT NULL, updated_at DATE"; diff --git a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java index 59611c601fe4..3c694621e23a 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java +++ b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java @@ -41,6 +41,7 @@ import io.airbyte.integrations.base.Source; import io.airbyte.integrations.source.jdbc.dto.JdbcPrivilegeDto; import io.airbyte.integrations.source.relationaldb.AbstractRelationalDbSource; +import io.airbyte.integrations.source.relationaldb.CursorInfo; import io.airbyte.integrations.source.relationaldb.TableInfo; import io.airbyte.integrations.source.relationaldb.state.StateManager; import io.airbyte.protocol.models.CommonField; @@ -49,6 +50,7 @@ import io.airbyte.protocol.models.JsonSchemaType; import java.net.MalformedURLException; import java.net.URI; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -328,20 +330,37 @@ public AutoCloseableIterator queryTableIncremental(final JdbcDatabase final List columnNames, final String schemaName, final String tableName, - final String cursorField, - final Datatype cursorFieldType, - final String cursorValue) { + final CursorInfo cursorInfo, + final Datatype cursorFieldType) { LOGGER.info("Queueing query for table: {}", tableName); return AutoCloseableIterators.lazyIterator(() -> { try { final Stream stream = database.unsafeQuery( connection -> { LOGGER.info("Preparing query for table: {}", tableName); - final String quotedCursorField = sourceOperations.enquoteIdentifier(connection, cursorField); - final StringBuilder sql = new StringBuilder(String.format("SELECT %s FROM %s WHERE %s > ?", - sourceOperations.enquoteIdentifierList(connection, columnNames), - sourceOperations.getFullyQualifiedTableNameWithQuoting(connection, schemaName, tableName), - quotedCursorField)); + final String fullTableName = sourceOperations.getFullyQualifiedTableNameWithQuoting(connection, schemaName, tableName); + final String quotedCursorField = sourceOperations.enquoteIdentifier(connection, cursorInfo.getCursorField()); + + final String operator; + if (cursorInfo.getCursorRecordCount() <= 0L) { + operator = ">"; + } else { + final long actualRecordCount = getActualCursorRecordCount( + connection, fullTableName, quotedCursorField, cursorFieldType, cursorInfo.getCursor()); + LOGGER.info("Table {} cursor count: expected {}, actual {}", tableName, cursorInfo.getCursorRecordCount(), actualRecordCount); + if (actualRecordCount == cursorInfo.getCursorRecordCount()) { + operator = ">"; + } else { + operator = ">="; + } + } + + final String wrappedColumnNames = getWrappedColumnNames(database, connection, columnNames, schemaName, tableName); + final StringBuilder sql = new StringBuilder(String.format("SELECT %s FROM %s WHERE %s %s ?", + wrappedColumnNames, + fullTableName, + quotedCursorField, + operator)); // if the connector emits intermediate states, the incremental query must be sorted by the cursor // field if (getStateEmissionFrequency() > 0) { @@ -349,8 +368,8 @@ public AutoCloseableIterator queryTableIncremental(final JdbcDatabase } final PreparedStatement preparedStatement = connection.prepareStatement(sql.toString()); - sourceOperations.setStatementField(preparedStatement, 1, cursorFieldType, cursorValue); - LOGGER.info("Executing query for table: {}", tableName); + LOGGER.info("Executing query for table {}: {}", tableName, preparedStatement); + sourceOperations.setStatementField(preparedStatement, 1, cursorFieldType, cursorInfo.getCursor()); return preparedStatement; }, sourceOperations::rowToJson); @@ -361,6 +380,51 @@ public AutoCloseableIterator queryTableIncremental(final JdbcDatabase }); } + /** + * Some databases need special column names in the query. + */ + protected String getWrappedColumnNames(final JdbcDatabase database, + final Connection connection, + final List columnNames, + final String schemaName, + final String tableName) throws SQLException { + return sourceOperations.enquoteIdentifierList(connection, columnNames); + } + + protected String getCountColumnName() { + return "record_count"; + } + + private long getActualCursorRecordCount(final Connection connection, + final String fullTableName, + final String quotedCursorField, + final Datatype cursorFieldType, + final String cursor) + throws SQLException { + final String columnName = getCountColumnName(); + final PreparedStatement cursorRecordStatement; + if (cursor == null) { + final String cursorRecordQuery = String.format("SELECT COUNT(*) AS %s FROM %s WHERE %s IS NULL", + columnName, + fullTableName, + quotedCursorField); + cursorRecordStatement = connection.prepareStatement(cursorRecordQuery); + } else { + final String cursorRecordQuery = String.format("SELECT COUNT(*) AS %s FROM %s WHERE %s = ?", + columnName, + fullTableName, + quotedCursorField); + cursorRecordStatement = connection.prepareStatement(cursorRecordQuery);; + sourceOperations.setStatementField(cursorRecordStatement, 1, cursorFieldType, cursor); + } + final ResultSet resultSet = cursorRecordStatement.executeQuery(); + if (resultSet.next()) { + return resultSet.getLong(columnName); + } else { + return 0L; + } + } + protected DataSource createDataSource(final JsonNode config) { final JsonNode jdbcConfig = toDatabaseConfig(config); final DataSource dataSource = DataSourceFactory.create( diff --git a/airbyte-integrations/connectors/source-jdbc/src/testFixtures/java/io/airbyte/integrations/source/jdbc/test/JdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-jdbc/src/testFixtures/java/io/airbyte/integrations/source/jdbc/test/JdbcSourceAcceptanceTest.java index 54a10f609e3f..2522e7352622 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/testFixtures/java/io/airbyte/integrations/source/jdbc/test/JdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-jdbc/src/testFixtures/java/io/airbyte/integrations/source/jdbc/test/JdbcSourceAcceptanceTest.java @@ -92,6 +92,8 @@ public abstract class JdbcSourceAcceptanceTest { public static String TABLE_NAME_COMPOSITE_PK = "full_name_composite_pk"; public static String TABLE_NAME_WITHOUT_CURSOR_TYPE = "table_without_cursor_type"; public static String TABLE_NAME_WITH_NULLABLE_CURSOR_TYPE = "table_with_null_cursor_type"; + // this table is used in testing incremental sync with concurrent insertions + public static String TABLE_NAME_AND_TIMESTAMP = "name_and_timestamp"; public static String COL_ID = "id"; public static String COL_NAME = "name"; @@ -100,6 +102,8 @@ public abstract class JdbcSourceAcceptanceTest { public static String COL_LAST_NAME = "last_name"; public static String COL_LAST_NAME_WITH_SPACE = "last name"; public static String COL_CURSOR = "cursor_field"; + public static String COL_TIMESTAMP = "timestamp"; + public static String COL_TIMESTAMP_TYPE = "TIMESTAMP"; public static Number ID_VALUE_1 = 1; public static Number ID_VALUE_2 = 2; public static Number ID_VALUE_3 = 3; @@ -116,6 +120,8 @@ public abstract class JdbcSourceAcceptanceTest { public static String INSERT_TABLE_WITHOUT_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES(0);"; public static String CREATE_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "CREATE TABLE %s (%s VARCHAR(20));"; public static String INSERT_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES('Hello world :)');"; + public static String INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY = "INSERT INTO %s (name, timestamp) VALUES ('%s', '%s')"; + public JsonNode config; public DataSource dataSource; public JdbcDatabase database; @@ -707,7 +713,8 @@ protected List getExpectedAirbyteMessagesSecondSync(final String .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("5"); + .withCursor("5") + .withCursorRecordCount(1L); expectedMessages.addAll(createExpectedTestMessages(List.of(state))); return expectedMessages; } @@ -763,7 +770,8 @@ void testReadMultipleTablesIncrementally() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("3"), + .withCursor("3") + .withCursorRecordCount(1L), new DbStreamState() .withStreamName(streamName2) .withStreamNamespace(namespace) @@ -775,12 +783,14 @@ void testReadMultipleTablesIncrementally() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("3"), + .withCursor("3") + .withCursorRecordCount(1L), new DbStreamState() .withStreamName(streamName2) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("3")); + .withCursor("3") + .withCursorRecordCount(1L)); final List expectedMessagesFirstSync = new ArrayList<>(getTestMessages()); expectedMessagesFirstSync.add(createStateMessage(expectedStateStreams1.get(0), expectedStateStreams1)); @@ -818,6 +828,111 @@ protected void incrementalCursorCheck( expectedRecordMessages); } + // See https://github.com/airbytehq/airbyte/issues/14732 for rationale and details. + @Test + void testIncrementalWithConcurrentInsertion() throws Exception { + final String namespace = getDefaultNamespace(); + final String fullyQualifiedTableName = getFullyQualifiedTableName(TABLE_NAME_AND_TIMESTAMP); + final String columnDefinition = String.format("name VARCHAR(200) NOT NULL, timestamp %s NOT NULL", COL_TIMESTAMP_TYPE); + + // 1st sync + database.execute(ctx -> { + ctx.createStatement().execute(createTableQuery(fullyQualifiedTableName, columnDefinition, "")); + ctx.createStatement().execute(String.format(INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY, fullyQualifiedTableName, "a", "2021-01-01 00:00:00")); + ctx.createStatement().execute(String.format(INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY, fullyQualifiedTableName, "b", "2021-01-01 00:00:00")); + }); + + final ConfiguredAirbyteCatalog configuredCatalog = CatalogHelpers.toDefaultConfiguredCatalog( + new AirbyteCatalog().withStreams(List.of( + CatalogHelpers.createAirbyteStream( + TABLE_NAME_AND_TIMESTAMP, + namespace, + Field.of(COL_NAME, JsonSchemaType.STRING), + Field.of(COL_TIMESTAMP, JsonSchemaType.STRING_TIMESTAMP_WITHOUT_TIMEZONE))))); + configuredCatalog.getStreams().forEach(airbyteStream -> { + airbyteStream.setSyncMode(SyncMode.INCREMENTAL); + airbyteStream.setCursorField(List.of(COL_TIMESTAMP)); + airbyteStream.setDestinationSyncMode(DestinationSyncMode.APPEND); + }); + + final List firstSyncActualMessages = MoreIterators.toList( + source.read(config, configuredCatalog, createEmptyState(TABLE_NAME_AND_TIMESTAMP, namespace))); + + // cursor after 1st sync: 2021-01-01 00:00:00, count 2 + final Optional firstSyncStateOptional = firstSyncActualMessages.stream().filter(r -> r.getType() == Type.STATE).findFirst(); + assertTrue(firstSyncStateOptional.isPresent()); + final JsonNode firstSyncState = getStateData(firstSyncStateOptional.get(), TABLE_NAME_AND_TIMESTAMP); + assertEquals(firstSyncState.get("cursor_field").elements().next().asText(), COL_TIMESTAMP); + assertTrue(firstSyncState.get("cursor").asText().contains("2021-01-01")); + assertTrue(firstSyncState.get("cursor").asText().contains("00:00:00")); + assertEquals(2L, firstSyncState.get("cursor_record_count").asLong()); + + final List firstSyncNames = firstSyncActualMessages.stream() + .filter(r -> r.getType() == Type.RECORD) + .map(r -> r.getRecord().getData().get(COL_NAME).asText()) + .toList(); + assertEquals(List.of("a", "b"), firstSyncNames); + + // 2nd sync + database.execute(ctx -> { + ctx.createStatement().execute(String.format(INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY, fullyQualifiedTableName, "c", "2021-01-02 00:00:00")); + }); + + final List secondSyncActualMessages = MoreIterators.toList( + source.read(config, configuredCatalog, createState(TABLE_NAME_AND_TIMESTAMP, namespace, firstSyncState))); + + // cursor after 2nd sync: 2021-01-02 00:00:00, count 1 + final Optional secondSyncStateOptional = secondSyncActualMessages.stream().filter(r -> r.getType() == Type.STATE).findFirst(); + assertTrue(secondSyncStateOptional.isPresent()); + final JsonNode secondSyncState = getStateData(secondSyncStateOptional.get(), TABLE_NAME_AND_TIMESTAMP); + assertEquals(secondSyncState.get("cursor_field").elements().next().asText(), COL_TIMESTAMP); + assertTrue(secondSyncState.get("cursor").asText().contains("2021-01-02")); + assertTrue(secondSyncState.get("cursor").asText().contains("00:00:00")); + assertEquals(1L, secondSyncState.get("cursor_record_count").asLong()); + + final List secondSyncNames = secondSyncActualMessages.stream() + .filter(r -> r.getType() == Type.RECORD) + .map(r -> r.getRecord().getData().get(COL_NAME).asText()) + .toList(); + assertEquals(List.of("c"), secondSyncNames); + + // 3rd sync has records with duplicated cursors + database.execute(ctx -> { + ctx.createStatement().execute(String.format(INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY, fullyQualifiedTableName, "d", "2021-01-02 00:00:00")); + ctx.createStatement().execute(String.format(INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY, fullyQualifiedTableName, "e", "2021-01-02 00:00:00")); + ctx.createStatement().execute(String.format(INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY, fullyQualifiedTableName, "f", "2021-01-03 00:00:00")); + }); + + final List thirdSyncActualMessages = MoreIterators.toList( + source.read(config, configuredCatalog, createState(TABLE_NAME_AND_TIMESTAMP, namespace, secondSyncState))); + + // Cursor after 3rd sync is: 2021-01-03 00:00:00, count 1. + final Optional thirdSyncStateOptional = thirdSyncActualMessages.stream().filter(r -> r.getType() == Type.STATE).findFirst(); + assertTrue(thirdSyncStateOptional.isPresent()); + final JsonNode thirdSyncState = getStateData(thirdSyncStateOptional.get(), TABLE_NAME_AND_TIMESTAMP); + assertEquals(thirdSyncState.get("cursor_field").elements().next().asText(), COL_TIMESTAMP); + assertTrue(thirdSyncState.get("cursor").asText().contains("2021-01-03")); + assertTrue(thirdSyncState.get("cursor").asText().contains("00:00:00")); + assertEquals(1L, thirdSyncState.get("cursor_record_count").asLong()); + + // The c, d, e, f are duplicated records from this sync, because the cursor + // record count in the database is different from that in the state. + final List thirdSyncExpectedNames = thirdSyncActualMessages.stream() + .filter(r -> r.getType() == Type.RECORD) + .map(r -> r.getRecord().getData().get(COL_NAME).asText()) + .toList(); + assertEquals(List.of("c", "d", "e", "f"), thirdSyncExpectedNames); + } + + private JsonNode getStateData(final AirbyteMessage airbyteMessage, final String streamName) { + for (final JsonNode stream : airbyteMessage.getState().getData().get("streams")) { + if (stream.get("stream_name").asText().equals(streamName)) { + return stream; + } + } + throw new IllegalArgumentException("Stream not found in state message: " + streamName); + } + private void incrementalCursorCheck( final String initialCursorField, final String cursorField, @@ -849,7 +964,8 @@ private void incrementalCursorCheck( .withStreamName(airbyteStream.getStream().getName()) .withStreamNamespace(airbyteStream.getStream().getNamespace()) .withCursorField(List.of(initialCursorField)) - .withCursor(initialCursorValue); + .withCursor(initialCursorValue) + .withCursorRecordCount(1L); final List actualMessages = MoreIterators .toList(source.read(config, configuredCatalog, Jsons.jsonNode(createState(List.of(dbStreamState))))); @@ -861,7 +977,8 @@ private void incrementalCursorCheck( .withStreamName(airbyteStream.getStream().getName()) .withStreamNamespace(airbyteStream.getStream().getNamespace()) .withCursorField(List.of(cursorField)) - .withCursor(endCursorValue)); + .withCursor(endCursorValue) + .withCursorRecordCount(1L)); final List expectedMessages = new ArrayList<>(expectedRecordMessages); expectedMessages.addAll(createExpectedTestMessages(expectedStreams)); @@ -1082,6 +1199,28 @@ protected JsonNode createEmptyState(final String streamName, final String stream } } + protected JsonNode createState(final String streamName, final String streamNamespace, final JsonNode stateData) { + if (supportsPerStream()) { + final AirbyteStateMessage airbyteStateMessage = new AirbyteStateMessage() + .withType(AirbyteStateType.STREAM) + .withStream( + new AirbyteStreamState() + .withStreamDescriptor(new StreamDescriptor().withName(streamName).withNamespace(streamNamespace)) + .withStreamState(stateData)); + return Jsons.jsonNode(List.of(airbyteStateMessage)); + } else { + final List cursorFields = MoreIterators.toList(stateData.get("cursor_field").elements()).stream().map(JsonNode::asText).toList(); + final DbState dbState = new DbState().withStreams(List.of( + new DbStreamState() + .withStreamName(streamName) + .withStreamNamespace(streamNamespace) + .withCursor(stateData.get("cursor").asText()) + .withCursorField(cursorFields) + .withCursorRecordCount(stateData.get("cursor_record_count").asLong()))); + return Jsons.jsonNode(dbState); + } + } + /** * Extracts the state component from the provided {@link AirbyteMessage} based on the value returned * by {@link #supportsPerStream()}. diff --git a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java index 45d8ba700445..cd09d9b22eef 100644 --- a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java +++ b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java @@ -24,6 +24,7 @@ import io.airbyte.integrations.base.IntegrationRunner; import io.airbyte.integrations.base.Source; import io.airbyte.integrations.source.relationaldb.AbstractDbSource; +import io.airbyte.integrations.source.relationaldb.CursorInfo; import io.airbyte.integrations.source.relationaldb.TableInfo; import io.airbyte.protocol.models.CommonField; import io.airbyte.protocol.models.JsonSchemaType; @@ -179,10 +180,9 @@ public AutoCloseableIterator queryTableIncremental(final MongoDatabase final List columnNames, final String schemaName, final String tableName, - final String cursorField, - final BsonType cursorFieldType, - final String cursorValue) { - final Bson greaterComparison = gt(cursorField, MongoUtils.getBsonValue(cursorFieldType, cursorValue)); + final CursorInfo cursorInfo, + final BsonType cursorFieldType) { + final Bson greaterComparison = gt(cursorInfo.getCursorField(), MongoUtils.getBsonValue(cursorFieldType, cursorInfo.getCursor())); return queryTable(database, columnNames, tableName, greaterComparison); } diff --git a/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mssql/MssqlStrictEncryptJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mssql/MssqlStrictEncryptJdbcSourceAcceptanceTest.java index b160b66eaa37..d741e616d607 100644 --- a/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mssql/MssqlStrictEncryptJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mssql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mssql/MssqlStrictEncryptJdbcSourceAcceptanceTest.java @@ -38,6 +38,10 @@ public class MssqlStrictEncryptJdbcSourceAcceptanceTest extends JdbcSourceAccept @BeforeAll static void init() { + // In mssql, timestamp is generated automatically, so we need to use + // the datetime type instead so that we can set the value manually. + COL_TIMESTAMP_TYPE = "DATETIME"; + dbContainer = new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-latest").acceptLicense(); dbContainer.start(); } diff --git a/airbyte-integrations/connectors/source-mssql/src/main/java/io/airbyte/integrations/source/mssql/MssqlSource.java b/airbyte-integrations/connectors/source-mssql/src/main/java/io/airbyte/integrations/source/mssql/MssqlSource.java index 84d440940d6e..76bcf8ed7bbd 100644 --- a/airbyte-integrations/connectors/source-mssql/src/main/java/io/airbyte/integrations/source/mssql/MssqlSource.java +++ b/airbyte-integrations/connectors/source-mssql/src/main/java/io/airbyte/integrations/source/mssql/MssqlSource.java @@ -17,7 +17,6 @@ import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.util.AutoCloseableIterator; -import io.airbyte.commons.util.AutoCloseableIterators; import io.airbyte.db.factory.DatabaseDriver; import io.airbyte.db.jdbc.JdbcDatabase; import io.airbyte.db.jdbc.JdbcUtils; @@ -37,6 +36,7 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.SyncMode; import java.io.File; +import java.sql.Connection; import java.sql.JDBCType; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -49,7 +49,6 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; -import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,54 +79,13 @@ public AutoCloseableIterator queryTableFullRefresh(final JdbcDatabase final String tableName) { LOGGER.info("Queueing query for table: {}", tableName); - final List newIdentifiersList = getWrappedColumn(database, - columnNames, - schemaName, tableName, "\""); - final String preparedSqlQuery = String - .format("SELECT %s FROM %s", String.join(",", newIdentifiersList), - getFullTableName(schemaName, tableName)); + final String newIdentifiers = getWrappedColumnNames(database, null, columnNames, schemaName, tableName); + final String preparedSqlQuery = String.format("SELECT %s FROM %s", newIdentifiers, getFullTableName(schemaName, tableName)); LOGGER.info("Prepared SQL query for TableFullRefresh is: " + preparedSqlQuery); return queryTable(database, preparedSqlQuery); } - @Override - public AutoCloseableIterator queryTableIncremental(final JdbcDatabase database, - final List columnNames, - final String schemaName, - final String tableName, - final String cursorField, - final JDBCType cursorFieldType, - final String cursorValue) { - LOGGER.info("Queueing query for table: {}", tableName); - return AutoCloseableIterators.lazyIterator(() -> { - try { - final Stream stream = database.unsafeQuery( - connection -> { - LOGGER.info("Preparing query for table: {}", tableName); - - final String identifierQuoteString = connection.getMetaData().getIdentifierQuoteString(); - final List newColumnNames = getWrappedColumn(database, columnNames, schemaName, tableName, identifierQuoteString); - - final String sql = String.format("SELECT %s FROM %s WHERE %s > ?", - String.join(",", newColumnNames), - sourceOperations.getFullyQualifiedTableNameWithQuoting(connection, schemaName, tableName), - sourceOperations.enquoteIdentifier(connection, cursorField)); - LOGGER.info("Prepared SQL query for queryTableIncremental is: " + sql); - - final PreparedStatement preparedStatement = connection.prepareStatement(sql); - sourceOperations.setStatementField(preparedStatement, 1, cursorFieldType, cursorValue); - LOGGER.info("Executing query for table: {}", tableName); - return preparedStatement; - }, - sourceOperations::rowToJson); - return AutoCloseableIterators.fromStream(stream); - } catch (final SQLException e) { - throw new RuntimeException(e); - } - }); - } - /** * There is no support for hierarchyid even in the native SQL Server JDBC driver. Its value can be * converted to a nvarchar(4000) data type by calling the ToString() method. So we make a separate @@ -137,13 +95,15 @@ public AutoCloseableIterator queryTableIncremental(final JdbcDatabase * * @return the list with Column names updated to handle functions (if nay) properly */ - private List getWrappedColumn(final JdbcDatabase database, - final List columnNames, - final String schemaName, - final String tableName, - final String enquoteSymbol) { + @Override + protected String getWrappedColumnNames(final JdbcDatabase database, + final Connection connection, + final List columnNames, + final String schemaName, + final String tableName) { final List hierarchyIdColumns = new ArrayList<>(); try { + final String identifierQuoteString = database.getMetaData().getIdentifierQuoteString(); final SQLServerResultSetMetaData sqlServerResultSetMetaData = (SQLServerResultSetMetaData) database .queryMetadata(String .format("SELECT TOP 1 %s FROM %s", // only first row is enough to get field's type @@ -159,20 +119,20 @@ private List getWrappedColumn(final JdbcDatabase database, } } + // iterate through names and replace Hierarchyid field for query is with toString() function + // Eventually would get columns like this: testColumn.toString as "testColumn" + // toString function in SQL server is the only way to get human readable value, but not mssql + // specific HEX value + return String.join(", ", columnNames.stream() + .map( + el -> hierarchyIdColumns.contains(el) ? String + .format("%s.ToString() as %s%s%s", el, identifierQuoteString, el, identifierQuoteString) + : getIdentifierWithQuoting(el)) + .toList()); } catch (final SQLException e) { LOGGER.error("Failed to fetch metadata to prepare a proper request.", e); + throw new RuntimeException(e); } - - // iterate through names and replace Hierarchyid field for query is with toString() function - // Eventually would get columns like this: testColumn.toString as "testColumn" - // toString function in SQL server is the only way to get human readable value, but not mssql - // specific HEX value - return columnNames.stream() - .map( - el -> hierarchyIdColumns.contains(el) ? String - .format("%s.ToString() as %s%s%s", el, enquoteSymbol, el, enquoteSymbol) - : getIdentifierWithQuoting(el)) - .collect(toList()); } @Override @@ -245,15 +205,15 @@ public AirbyteCatalog discover(final JsonNode config) throws Exception { } @Override - public List>> discoverInternal(JdbcDatabase database) throws Exception { + public List>> discoverInternal(final JdbcDatabase database) throws Exception { final List>> internals = super.discoverInternal(database); if (schemas != null && !schemas.isEmpty()) { // process explicitly filtered (from UI) schemas - List>> resultInternals = internals + final List>> resultInternals = internals .stream() .filter(this::isTableInRequestedSchema) .toList(); - for (TableInfo> info : resultInternals) { + for (final TableInfo> info : resultInternals) { LOGGER.debug("Found table (schema: {}): {}", info.getNameSpace(), info.getName()); } return resultInternals; @@ -263,7 +223,7 @@ public List>> discoverInternal(JdbcDatabase data } } - private boolean isTableInRequestedSchema(TableInfo> tableInfo) { + private boolean isTableInRequestedSchema(final TableInfo> tableInfo) { return schemas .stream() .anyMatch(schema -> schema.equals(tableInfo.getNameSpace())); diff --git a/airbyte-integrations/connectors/source-mssql/src/test/java/io/airbyte/integrations/source/mssql/MssqlJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mssql/src/test/java/io/airbyte/integrations/source/mssql/MssqlJdbcSourceAcceptanceTest.java index a72a5991e7b5..f4f5cd2aeeef 100644 --- a/airbyte-integrations/connectors/source-mssql/src/test/java/io/airbyte/integrations/source/mssql/MssqlJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mssql/src/test/java/io/airbyte/integrations/source/mssql/MssqlJdbcSourceAcceptanceTest.java @@ -38,6 +38,10 @@ public class MssqlJdbcSourceAcceptanceTest extends JdbcSourceAcceptanceTest { @BeforeAll static void init() { + // In mssql, timestamp is generated automatically, so we need to use + // the datetime type instead so that we can set the value manually. + COL_TIMESTAMP_TYPE = "DATETIME"; + dbContainer = new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-latest").acceptLicense(); dbContainer.start(); } diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java index 51767bd1a8e0..ba22c3d1e924 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java @@ -231,7 +231,8 @@ protected List getExpectedAirbyteMessagesSecondSync(final String .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("5"); + .withCursor("5") + .withCursorRecordCount(1L); expectedMessages.addAll(createExpectedTestMessages(List.of(state))); return expectedMessages; } diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java index 82bca2a517d9..b50caaef548c 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java @@ -53,7 +53,8 @@ private static Properties commonProperties(final JdbcDatabase database) { props.setProperty("boolean.type", "io.debezium.connector.mysql.converters.TinyIntOneToBooleanConverter"); props.setProperty("datetime.type", "io.airbyte.integrations.debezium.internals.MySQLDateTimeConverter"); - // For CDC mode, the user cannot provide timezone arguments as JDBC parameters - they are specifically defined in the replication_method + // For CDC mode, the user cannot provide timezone arguments as JDBC parameters - they are + // specifically defined in the replication_method // config. if (sourceConfig.get("replication_method").has("server_time_zone")) { final String serverTimeZone = sourceConfig.get("replication_method").get("server_time_zone").asText(); diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java index 303efaa43242..682d4186c54a 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/helpers/CdcConfigurationHelper.java @@ -5,7 +5,6 @@ package io.airbyte.integrations.source.mysql.helpers; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.annotations.VisibleForTesting; import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.db.jdbc.JdbcDatabase; import java.time.Duration; diff --git a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java index fdbe4551a8d2..e8eec3bafac9 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test/java/io/airbyte/integrations/source/mysql/MySqlJdbcSourceAcceptanceTest.java @@ -284,7 +284,8 @@ protected List getExpectedAirbyteMessagesSecondSync(final String .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("5"); + .withCursor("5") + .withCursorRecordCount(1L); expectedMessages.addAll(createExpectedTestMessages(List.of(state))); return expectedMessages; } diff --git a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java index bdee5069be37..d7f713671d6c 100644 --- a/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle-strict-encrypt/src/test-integration/java/io/airbyte/integrations/source/oracle_strict_encrypt/OracleStrictEncryptJdbcSourceAcceptanceTest.java @@ -69,12 +69,14 @@ static void init() { TABLE_NAME_WITH_SPACES = "ID AND NAME"; TABLE_NAME_WITHOUT_PK = "ID_AND_NAME_WITHOUT_PK"; TABLE_NAME_COMPOSITE_PK = "FULL_NAME_COMPOSITE_PK"; + TABLE_NAME_AND_TIMESTAMP = "NAME_AND_TIMESTAMP"; COL_ID = "ID"; COL_NAME = "NAME"; COL_UPDATED_AT = "UPDATED_AT"; COL_FIRST_NAME = "FIRST_NAME"; COL_LAST_NAME = "LAST_NAME"; COL_LAST_NAME_WITH_SPACE = "LAST NAME"; + COL_TIMESTAMP = "TIMESTAMP"; ID_VALUE_1 = new BigDecimal(1); ID_VALUE_2 = new BigDecimal(2); ID_VALUE_3 = new BigDecimal(3); @@ -84,6 +86,7 @@ static void init() { INSERT_TABLE_WITHOUT_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES(to_clob('clob data'))"; CREATE_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "CREATE TABLE %s (%s VARCHAR(20))"; INSERT_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES('Hello world :)')"; + INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY = "INSERT INTO %s (name, timestamp) VALUES ('%s', TO_TIMESTAMP('%s', 'YYYY-MM-DD HH24:MI:SS'))"; ORACLE_DB = new AirbyteOracleTestContainer() .withUsername("test") @@ -380,7 +383,8 @@ void testReadOneTableIncrementallyTwice() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("5"))))))); + .withCursor("5") + .withCursorRecordCount(1L))))))); setEmittedAtToNull(actualMessagesSecondSync); diff --git a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java index dcaf1803f431..89ce3406fd75 100644 --- a/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-oracle/src/test-integration/java/io/airbyte/integrations/source/oracle/OracleJdbcSourceAcceptanceTest.java @@ -74,12 +74,14 @@ static void init() { TABLE_NAME_WITH_SPACES = "ID AND NAME"; TABLE_NAME_WITHOUT_PK = "ID_AND_NAME_WITHOUT_PK"; TABLE_NAME_COMPOSITE_PK = "FULL_NAME_COMPOSITE_PK"; + TABLE_NAME_AND_TIMESTAMP = "NAME_AND_TIMESTAMP"; COL_ID = "ID"; COL_NAME = "NAME"; COL_UPDATED_AT = "UPDATED_AT"; COL_FIRST_NAME = "FIRST_NAME"; COL_LAST_NAME = "LAST_NAME"; COL_LAST_NAME_WITH_SPACE = "LAST NAME"; + COL_TIMESTAMP = "TIMESTAMP"; ID_VALUE_1 = new BigDecimal(1); ID_VALUE_2 = new BigDecimal(2); ID_VALUE_3 = new BigDecimal(3); @@ -89,6 +91,7 @@ static void init() { INSERT_TABLE_WITHOUT_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES(to_clob('clob data'))"; CREATE_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "CREATE TABLE %s (%s VARCHAR(20))"; INSERT_TABLE_WITH_NULLABLE_CURSOR_TYPE_QUERY = "INSERT INTO %s VALUES('Hello world :)')"; + INSERT_TABLE_NAME_AND_TIMESTAMP_QUERY = "INSERT INTO %s (name, timestamp) VALUES ('%s', TO_TIMESTAMP('%s', 'YYYY-MM-DD HH24:MI:SS'))"; ORACLE_DB = new AirbyteOracleTestContainer() .withEnv("NLS_DATE_FORMAT", "YYYY-MM-DD") @@ -282,7 +285,8 @@ void testReadOneTableIncrementallyTwice() throws Exception { .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("5"))))))); + .withCursor("5") + .withCursorRecordCount(1L))))))); setEmittedAtToNull(actualMessagesSecondSync); diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile index 5cc9e8cb9e8e..ff44c65d602e 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.15 +LABEL io.airbyte.version=1.0.16 LABEL io.airbyte.name=airbyte/source-postgres-strict-encrypt diff --git a/airbyte-integrations/connectors/source-postgres/Dockerfile b/airbyte-integrations/connectors/source-postgres/Dockerfile index 8bb028fafeaa..7984704fde70 100644 --- a/airbyte-integrations/connectors/source-postgres/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.15 +LABEL io.airbyte.version=1.0.16 LABEL io.airbyte.name=airbyte/source-postgres diff --git a/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresJdbcSourceAcceptanceTest.java index e09667d7dcdf..c47aaa9497fc 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresJdbcSourceAcceptanceTest.java @@ -446,7 +446,8 @@ protected List getExpectedAirbyteMessagesSecondSync(final String .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(ImmutableList.of(COL_ID)) - .withCursor("5"); + .withCursor("5") + .withCursorRecordCount(1L); expectedMessages.addAll(createExpectedTestMessages(List.of(state))); return expectedMessages; } diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java index da1eb7c241fb..4cebc0373295 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java @@ -312,11 +312,17 @@ protected AutoCloseableIterator createReadIterator(final Databas // this is where the bifurcation between full refresh and incremental if (airbyteStream.getSyncMode() == SyncMode.INCREMENTAL) { final String cursorField = IncrementalUtils.getCursorField(airbyteStream); - final Optional cursorOptional = stateManager.getCursor(pair); + final Optional cursorInfo = stateManager.getCursorInfo(pair); final AutoCloseableIterator airbyteMessageIterator; - if (cursorOptional.isPresent()) { - airbyteMessageIterator = getIncrementalStream(database, airbyteStream, selectedDatabaseFields, table, cursorOptional.get(), emittedAt); + if (cursorInfo.map(CursorInfo::getCursor).isPresent()) { + airbyteMessageIterator = getIncrementalStream( + database, + airbyteStream, + selectedDatabaseFields, + table, + cursorInfo.get(), + emittedAt); } else { // if no cursor is present then this is the first read for is the same as doing a full refresh read. airbyteMessageIterator = getFullRefreshStream(database, streamName, namespace, selectedDatabaseFields, table, emittedAt); @@ -329,7 +335,7 @@ protected AutoCloseableIterator createReadIterator(final Databas stateManager, pair, cursorField, - cursorOptional.orElse(null), + cursorInfo.map(CursorInfo::getCursor).orElse(null), cursorType, getStateEmissionFrequency()), airbyteMessageIterator); @@ -356,7 +362,7 @@ protected AutoCloseableIterator createReadIterator(final Databas * @param airbyteStream represents an ingestion source (e.g. API endpoint or database table) * @param selectedDatabaseFields subset of database fields selected for replication * @param table information in tabular format - * @param cursor state of where to start the sync from + * @param cursorInfo state of where to start the sync from * @param emittedAt Time when data was emitted from the Source database * @return AirbyteMessage Iterator that */ @@ -364,7 +370,7 @@ protected AutoCloseableIterator getIncrementalStream(final Datab final ConfiguredAirbyteStream airbyteStream, final List selectedDatabaseFields, final TableInfo> table, - final String cursor, + final CursorInfo cursorInfo, final Instant emittedAt) { final String streamName = airbyteStream.getStream().getName(); final String namespace = airbyteStream.getStream().getNamespace(); @@ -383,9 +389,8 @@ protected AutoCloseableIterator getIncrementalStream(final Datab selectedDatabaseFields, table.getNameSpace(), table.getName(), - cursorField, - cursorType, - cursor); + cursorInfo, + cursorType); return getMessageIterator(queryIterator, streamName, namespace, emittedAt.toEpochMilli()); } @@ -606,9 +611,8 @@ public abstract AutoCloseableIterator queryTableIncremental(Database d List columnNames, String schemaName, String tableName, - String cursorField, - DataType cursorFieldType, - String cursorValue); + CursorInfo cursorInfo, + DataType cursorFieldType); /** * When larger than 0, the incremental iterator will emit intermediate state for every N records. diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/CursorInfo.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/CursorInfo.java index fc796c73d2b9..4122e95f4ef0 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/CursorInfo.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/CursorInfo.java @@ -10,18 +10,31 @@ public class CursorInfo { private final String originalCursorField; private final String originalCursor; + private final long originalCursorRecordCount; private final String cursorField; private String cursor; + private long cursorRecordCount; public CursorInfo(final String originalCursorField, final String originalCursor, final String cursorField, final String cursor) { + this(originalCursorField, originalCursor, 0L, cursorField, cursor, 0L); + } + + public CursorInfo(final String originalCursorField, + final String originalCursor, + final long originalCursorRecordCount, + final String cursorField, + final String cursor, + final long cursorRecordCount) { this.originalCursorField = originalCursorField; this.originalCursor = originalCursor; + this.originalCursorRecordCount = originalCursorRecordCount; this.cursorField = cursorField; this.cursor = cursor; + this.cursorRecordCount = cursorRecordCount; } public String getOriginalCursorField() { @@ -32,6 +45,10 @@ public String getOriginalCursor() { return originalCursor; } + public long getOriginalCursorRecordCount() { + return originalCursorRecordCount; + } + public String getCursorField() { return cursorField; } @@ -40,12 +57,21 @@ public String getCursor() { return cursor; } + public long getCursorRecordCount() { + return cursorRecordCount; + } + @SuppressWarnings("UnusedReturnValue") public CursorInfo setCursor(final String cursor) { this.cursor = cursor; return this; } + public CursorInfo setCursorRecordCount(final long cursorRecordCount) { + this.cursorRecordCount = cursorRecordCount; + return this; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -55,14 +81,17 @@ public boolean equals(final Object o) { return false; } final CursorInfo that = (CursorInfo) o; - return Objects.equals(originalCursorField, that.originalCursorField) && Objects - .equals(originalCursor, that.originalCursor) - && Objects.equals(cursorField, that.cursorField) && Objects.equals(cursor, that.cursor); + return Objects.equals(originalCursorField, that.originalCursorField) + && Objects.equals(originalCursor, that.originalCursor) + && Objects.equals(originalCursorRecordCount, that.originalCursorRecordCount) + && Objects.equals(cursorField, that.cursorField) + && Objects.equals(cursor, that.cursor) + && Objects.equals(cursorRecordCount, that.cursorRecordCount); } @Override public int hashCode() { - return Objects.hash(originalCursorField, originalCursor, cursorField, cursor); + return Objects.hash(originalCursorField, originalCursor, originalCursorRecordCount, cursorField, cursor, cursorRecordCount); } @Override @@ -70,8 +99,10 @@ public String toString() { return "CursorInfo{" + "originalCursorField='" + originalCursorField + '\'' + ", originalCursor='" + originalCursor + '\'' + + ", originalCursorRecordCount='" + originalCursorRecordCount + '\'' + ", cursorField='" + cursorField + '\'' + ", cursor='" + cursor + '\'' + + ", cursorRecordCount='" + cursorRecordCount + '\'' + '}'; } diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIterator.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIterator.java index 605c3aca8eba..c77a61fb9845 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIterator.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIterator.java @@ -30,6 +30,7 @@ public class StateDecoratingIterator extends AbstractIterator im private final String initialCursor; private String maxCursor; + private long maxCursorRecordCount = 0L; private boolean hasEmittedFinalState; /** @@ -125,13 +126,17 @@ protected AirbyteMessage computeNext() { final AirbyteMessage message = messageIterator.next(); if (message.getRecord().getData().hasNonNull(cursorField)) { final String cursorCandidate = getCursorCandidate(message); - if (IncrementalUtils.compareCursors(maxCursor, cursorCandidate, cursorType) < 0) { + final int cursorComparison = IncrementalUtils.compareCursors(maxCursor, cursorCandidate, cursorType); + if (cursorComparison < 0) { if (stateEmissionFrequency > 0 && !Objects.equals(maxCursor, initialCursor) && messageIterator.hasNext()) { // Only emit an intermediate state when it is not the first or last record message, // because the last state message will be taken care of in a different branch. intermediateStateMessage = createStateMessage(false); } maxCursor = cursorCandidate; + maxCursorRecordCount = 1L; + } else if (cursorComparison == 0) { + maxCursorRecordCount++; } } @@ -183,18 +188,21 @@ protected final Optional getIntermediateMessage() { * @return AirbyteMessage which includes information on state of records read so far */ public AirbyteMessage createStateMessage(final boolean isFinalState) { - final AirbyteStateMessage stateMessage = stateManager.updateAndEmit(pair, maxCursor); - LOGGER.info("State Report: stream name: {}, original cursor field: {}, original cursor value {}, cursor field: {}, new cursor value: {}", + final AirbyteStateMessage stateMessage = stateManager.updateAndEmit(pair, maxCursor, maxCursorRecordCount); + final Optional cursorInfo = stateManager.getCursorInfo(pair); + LOGGER.info("State report for stream {} - original: {} = {} (count {}) -> latest: {} = {} (count {})", pair, - stateManager.getOriginalCursorField(pair).orElse(null), - stateManager.getOriginalCursor(pair).orElse(null), - stateManager.getCursorField(pair).orElse(null), - stateManager.getCursor(pair).orElse(null)); + cursorInfo.map(CursorInfo::getOriginalCursorField).orElse(null), + cursorInfo.map(CursorInfo::getOriginalCursor).orElse(null), + cursorInfo.map(CursorInfo::getOriginalCursorRecordCount).orElse(null), + cursorInfo.map(CursorInfo::getCursorField).orElse(null), + cursorInfo.map(CursorInfo::getCursor).orElse(null), + cursorInfo.map(CursorInfo::getCursorRecordCount).orElse(null)); if (isFinalState) { hasEmittedFinalState = true; if (stateManager.getCursor(pair).isEmpty()) { - LOGGER.warn("Cursor was for stream {} was null. This stream will replicate all records on the next run", pair); + LOGGER.warn("Cursor for stream {} was null. This stream will replicate all records on the next run", pair); } } diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/AbstractStateManager.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/AbstractStateManager.java index dec78ec39fac..df8d1200b5b5 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/AbstractStateManager.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/AbstractStateManager.java @@ -40,6 +40,8 @@ public abstract class AbstractStateManager implements StateManager { * the connector's state. * @param cursorFieldFunction A {@link Function} that extracts the cursor field name from a stream * stored in the connector's state. + * @param cursorRecordCountFunction A {@link Function} that extracts the cursor record count for a + * stream stored in the connector's state. * @param namespacePairFunction A {@link Function} that generates a * {@link AirbyteStreamNameNamespacePair} that identifies each stream in the connector's * state. @@ -48,8 +50,9 @@ public AbstractStateManager(final ConfiguredAirbyteCatalog catalog, final Supplier> streamSupplier, final Function cursorFunction, final Function> cursorFieldFunction, + final Function cursorRecordCountFunction, final Function namespacePairFunction) { - cursorManager = new CursorManager(catalog, streamSupplier, cursorFunction, cursorFieldFunction, namespacePairFunction); + cursorManager = new CursorManager(catalog, streamSupplier, cursorFunction, cursorFieldFunction, cursorRecordCountFunction, namespacePairFunction); } @Override diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/CursorManager.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/CursorManager.java index 2fabade97726..ca5c0504d9e9 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/CursorManager.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/CursorManager.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -47,6 +48,8 @@ public class CursorManager { * the connector's state. * @param cursorFieldFunction A {@link Function} that extracts the cursor field name from a stream * stored in the connector's state. + * @param cursorRecordCountFunction A {@link Function} that extracts the cursor record count for a + * stream stored in the connector's state. * @param namespacePairFunction A {@link Function} that generates a * {@link AirbyteStreamNameNamespacePair} that identifies each stream in the connector's * state. @@ -55,8 +58,10 @@ public CursorManager(final ConfiguredAirbyteCatalog catalog, final Supplier> streamSupplier, final Function cursorFunction, final Function> cursorFieldFunction, + final Function cursorRecordCountFunction, final Function namespacePairFunction) { - pairToCursorInfo = createCursorInfoMap(catalog, streamSupplier, cursorFunction, cursorFieldFunction, namespacePairFunction); + pairToCursorInfo = createCursorInfoMap( + catalog, streamSupplier, cursorFunction, cursorFieldFunction, cursorRecordCountFunction, namespacePairFunction); } /** @@ -70,6 +75,8 @@ public CursorManager(final ConfiguredAirbyteCatalog catalog, * the connector's state. * @param cursorFieldFunction A {@link Function} that extracts the cursor field name from a stream * stored in the connector's state. + * @param cursorRecordCountFunction A {@link Function} that extracts the cursor record count for a + * stream stored in the connector's state. * @param namespacePairFunction A {@link Function} that generates a * {@link AirbyteStreamNameNamespacePair} that identifies each stream in the connector's * state. @@ -81,13 +88,14 @@ protected Map createCursorInfoMap( final Supplier> streamSupplier, final Function cursorFunction, final Function> cursorFieldFunction, + final Function cursorRecordCountFunction, final Function namespacePairFunction) { final Set allStreamNames = catalog.getStreams() .stream() .map(ConfiguredAirbyteStream::getStream) .map(AirbyteStreamNameNamespacePair::fromAirbyteSteam) .collect(Collectors.toSet()); - allStreamNames.addAll(streamSupplier.get().stream().map(namespacePairFunction).filter(n -> n != null).collect(Collectors.toSet())); + allStreamNames.addAll(streamSupplier.get().stream().map(namespacePairFunction).filter(Objects::nonNull).collect(Collectors.toSet())); final Map localMap = new HashMap<>(); final Map pairToState = streamSupplier.get() @@ -99,7 +107,8 @@ protected Map createCursorInfoMap( for (final AirbyteStreamNameNamespacePair pair : allStreamNames) { final Optional stateOptional = Optional.ofNullable(pairToState.get(pair)); final Optional streamOptional = Optional.ofNullable(pairToConfiguredAirbyteStream.get(pair)); - localMap.put(pair, createCursorInfoForStream(pair, stateOptional, streamOptional, cursorFunction, cursorFieldFunction)); + localMap.put(pair, + createCursorInfoForStream(pair, stateOptional, streamOptional, cursorFunction, cursorFieldFunction, cursorRecordCountFunction)); } return localMap; @@ -118,6 +127,8 @@ protected Map createCursorInfoMap( * associated with the stream. * @param cursorFieldFunction A {@link Function} that provides the cursor field name for the cursor * stored in the state associated with the stream. + * @param cursorRecordCountFunction A {@link Function} that extracts the cursor record count for a + * stream stored in the connector's state. * @return A {@link CursorInfo} object based on the data currently stored in the connector's state * for the given stream. */ @@ -127,15 +138,18 @@ protected CursorInfo createCursorInfoForStream(final AirbyteStreamNameNamespaceP final Optional stateOptional, final Optional streamOptional, final Function cursorFunction, - final Function> cursorFieldFunction) { + final Function> cursorFieldFunction, + final Function cursorRecordCountFunction) { final String originalCursorField = stateOptional .map(cursorFieldFunction) .flatMap(f -> f.size() > 0 ? Optional.of(f.get(0)) : Optional.empty()) .orElse(null); final String originalCursor = stateOptional.map(cursorFunction).orElse(null); + final long originalCursorRecordCount = stateOptional.map(cursorRecordCountFunction).orElse(0L); final String cursor; final String cursorField; + final long cursorRecordCount; // if cursor field is set in catalog. if (streamOptional.map(ConfiguredAirbyteStream::getCursorField).isPresent()) { @@ -148,19 +162,23 @@ protected CursorInfo createCursorInfoForStream(final AirbyteStreamNameNamespaceP // if cursor field in catalog and state are the same. if (stateOptional.map(cursorFieldFunction).equals(streamOptional.map(ConfiguredAirbyteStream::getCursorField))) { cursor = stateOptional.map(cursorFunction).orElse(null); - LOGGER.info("Found matching cursor in state. Stream: {}. Cursor Field: {} Value: {}", pair, cursorField, cursor); + cursorRecordCount = stateOptional.map(cursorRecordCountFunction).orElse(0L); + LOGGER.info("Found matching cursor in state. Stream: {}. Cursor Field: {} Value: {} Count: {}", + pair, cursorField, cursor, cursorRecordCount); // if cursor field in catalog and state are different. } else { cursor = null; + cursorRecordCount = 0L; LOGGER.info( - "Found cursor field. Does not match previous cursor field. Stream: {}. Original Cursor Field: {}. New Cursor Field: {}. Resetting cursor value.", - pair, originalCursorField, cursorField); + "Found cursor field. Does not match previous cursor field. Stream: {}. Original Cursor Field: {} (count {}). New Cursor Field: {}. Resetting cursor value.", + pair, originalCursorField, originalCursorRecordCount, cursorField); } // if cursor field is not set in state but is set in catalog. } else { LOGGER.info("No cursor field set in catalog but not present in state. Stream: {}, New Cursor Field: {}. Resetting cursor value", pair, cursorField); cursor = null; + cursorRecordCount = 0L; } // if cursor field is not set in catalog. } else { @@ -169,9 +187,10 @@ protected CursorInfo createCursorInfoForStream(final AirbyteStreamNameNamespaceP pair, originalCursorField, originalCursor); cursorField = null; cursor = null; + cursorRecordCount = 0L; } - return new CursorInfo(originalCursorField, originalCursor, cursorField, cursor); + return new CursorInfo(originalCursorField, originalCursor, originalCursorRecordCount, cursorField, cursor, cursorRecordCount); } /** diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManager.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManager.java index ff4cf6de52a5..ee170e5d518c 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManager.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManager.java @@ -6,6 +6,7 @@ import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.CURSOR_FIELD_FUNCTION; import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.CURSOR_FUNCTION; +import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.CURSOR_RECORD_COUNT_FUNCTION; import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.NAME_NAMESPACE_PAIR_FUNCTION; import io.airbyte.commons.json.Jsons; @@ -55,6 +56,7 @@ public GlobalStateManager(final AirbyteStateMessage airbyteStateMessage, final C getStreamsSupplier(airbyteStateMessage), CURSOR_FUNCTION, CURSOR_FIELD_FUNCTION, + CURSOR_RECORD_COUNT_FUNCTION, NAME_NAMESPACE_PAIR_FUNCTION); this.cdcStateManager = new CdcStateManager(extractCdcState(airbyteStateMessage), extractStreams(airbyteStateMessage)); diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/LegacyStateManager.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/LegacyStateManager.java index 1847faafe60b..a1e147e76d05 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/LegacyStateManager.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/LegacyStateManager.java @@ -4,17 +4,16 @@ package io.airbyte.integrations.source.relationaldb.state; -import com.google.common.base.Preconditions; import io.airbyte.commons.json.Jsons; import io.airbyte.integrations.base.AirbyteStreamNameNamespacePair; import io.airbyte.integrations.source.relationaldb.CdcStateManager; -import io.airbyte.integrations.source.relationaldb.CursorInfo; import io.airbyte.integrations.source.relationaldb.models.DbState; import io.airbyte.integrations.source.relationaldb.models.DbStreamState; import io.airbyte.protocol.models.AirbyteStateMessage; import io.airbyte.protocol.models.AirbyteStateMessage.AirbyteStateType; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.Function; import org.slf4j.Logger; @@ -44,6 +43,9 @@ public class LegacyStateManager extends AbstractStateManager> CURSOR_FIELD_FUNCTION = DbStreamState::getCursorField; + private static final Function CURSOR_RECORD_COUNT_FUNCTION = + stream -> Objects.requireNonNullElse(stream.getCursorRecordCount(), 0L); + /** * {@link Function} that creates an {@link AirbyteStreamNameNamespacePair} from the stream state. */ @@ -70,9 +72,10 @@ public class LegacyStateManager extends AbstractStateManager dbState.getStreams(), + dbState::getStreams, CURSOR_FUNCTION, CURSOR_FIELD_FUNCTION, + CURSOR_RECORD_COUNT_FUNCTION, NAME_NAMESPACE_PAIR_FUNCTION); this.cdcStateManager = new CdcStateManager(dbState.getCdcState(), AirbyteStreamNameNamespacePair.fromConfiguredCatalog(catalog)); @@ -99,11 +102,14 @@ public AirbyteStateMessage toState(final Optional cursorInfo = getCursorInfo(pair); - Preconditions.checkState(cursorInfo.isPresent(), "Could not find cursor information for stream: " + pair); - cursorInfo.get().setCursor(cursor); + return super.updateAndEmit(pair, cursor, cursorRecordCount); } return toState(Optional.ofNullable(pair)); diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateGeneratorUtils.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateGeneratorUtils.java index 40fa957c71b5..130a520e98b2 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateGeneratorUtils.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateGeneratorUtils.java @@ -52,6 +52,11 @@ public class StateGeneratorUtils { } }; + public static final Function CURSOR_RECORD_COUNT_FUNCTION = stream -> { + final Optional dbStreamState = StateGeneratorUtils.extractState(stream); + return dbStreamState.map(DbStreamState::getCursorRecordCount).orElse(0L); + }; + /** * {@link Function} that creates an {@link AirbyteStreamNameNamespacePair} from the stream state. */ @@ -120,11 +125,15 @@ public static DbState generateDbState(final Map 0L) { + state.setCursorRecordCount(cursorInfo.getCursorRecordCount()); + } + return state; } /** diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateManager.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateManager.java index a4234454b06f..3039758f9746 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateManager.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StateManager.java @@ -140,10 +140,17 @@ default AirbyteStateMessage emit(final Optional * manager. */ default AirbyteStateMessage updateAndEmit(final AirbyteStreamNameNamespacePair pair, final String cursor) { + return updateAndEmit(pair, cursor, 0L); + } + + default AirbyteStateMessage updateAndEmit(final AirbyteStreamNameNamespacePair pair, final String cursor, final long cursorRecordCount) { final Optional cursorInfo = getCursorInfo(pair); Preconditions.checkState(cursorInfo.isPresent(), "Could not find cursor information for stream: " + pair); - LOGGER.debug("Updating cursor value for {} to {}...", pair, cursor); cursorInfo.get().setCursor(cursor); + if (cursorRecordCount > 0L) { + cursorInfo.get().setCursorRecordCount(cursorRecordCount); + } + LOGGER.debug("Updating cursor value for {} to {} (count {})...", pair, cursor, cursorRecordCount); return emit(Optional.ofNullable(pair)); } diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManager.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManager.java index 701fc099edcc..2d1cd66673d1 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManager.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManager.java @@ -6,6 +6,7 @@ import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.CURSOR_FIELD_FUNCTION; import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.CURSOR_FUNCTION; +import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.CURSOR_RECORD_COUNT_FUNCTION; import static io.airbyte.integrations.source.relationaldb.state.StateGeneratorUtils.NAME_NAMESPACE_PAIR_FUNCTION; import io.airbyte.commons.json.Jsons; @@ -25,7 +26,7 @@ /** * Per-stream implementation of the {@link StateManager} interface. - * + *

    * This implementation generates a state object for each stream detected in catalog/map of known * streams to cursor information stored in this manager. */ @@ -44,9 +45,10 @@ public class StreamStateManager extends AbstractStateManager airbyteStateMessages, final ConfiguredAirbyteCatalog catalog) { super(catalog, - () -> airbyteStateMessages.stream().map(a -> a.getStream()).collect(Collectors.toList()), + () -> airbyteStateMessages.stream().map(AirbyteStateMessage::getStream).collect(Collectors.toList()), CURSOR_FUNCTION, CURSOR_FIELD_FUNCTION, + CURSOR_RECORD_COUNT_FUNCTION, NAME_NAMESPACE_PAIR_FUNCTION); } diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/resources/db_models/db_models.yaml b/airbyte-integrations/connectors/source-relational-db/src/main/resources/db_models/db_models.yaml index 28af27c8046b..ba6e769cac66 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/resources/db_models/db_models.yaml +++ b/airbyte-integrations/connectors/source-relational-db/src/main/resources/db_models/db_models.yaml @@ -46,3 +46,6 @@ definitions: cursor: description: string representation of the last value recorded for the cursor. type: string + cursor_record_count: + description: number of records that have the cursor value. + type: integer diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIteratorTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIteratorTest.java index 7532ca2517b5..088f46fdfd5b 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIteratorTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/StateDecoratingIteratorTest.java @@ -73,7 +73,7 @@ private static AirbyteMessage createStateMessage(final String recordValue) { } private Iterator createExceptionIterator() { - return new Iterator() { + return new Iterator<>() { final Iterator internalMessageIterator = MoreIterators.of(RECORD_MESSAGE_1, RECORD_MESSAGE_2, RECORD_MESSAGE_2, RECORD_MESSAGE_3); @@ -104,17 +104,14 @@ public AirbyteMessage next() { @BeforeEach void setup() { stateManager = mock(StateManager.class); - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, null)).thenReturn(EMPTY_STATE_MESSAGE.getState()); - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_1)).thenReturn(STATE_MESSAGE_1.getState()); - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_2)).thenReturn(STATE_MESSAGE_2.getState()); - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_3)).thenReturn(STATE_MESSAGE_3.getState()); - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_4)).thenReturn(STATE_MESSAGE_4.getState()); - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_5)).thenReturn(STATE_MESSAGE_5.getState()); - - when(stateManager.getOriginalCursorField(NAME_NAMESPACE_PAIR)).thenReturn(Optional.empty()); - when(stateManager.getOriginalCursor(NAME_NAMESPACE_PAIR)).thenReturn(Optional.empty()); - when(stateManager.getCursorField(NAME_NAMESPACE_PAIR)).thenReturn(Optional.empty()); - when(stateManager.getCursor(NAME_NAMESPACE_PAIR)).thenReturn(Optional.empty()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, null, 0)).thenReturn(EMPTY_STATE_MESSAGE.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_1, 1L)).thenReturn(STATE_MESSAGE_1.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_2, 1L)).thenReturn(STATE_MESSAGE_2.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_3, 1L)).thenReturn(STATE_MESSAGE_3.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_4, 1L)).thenReturn(STATE_MESSAGE_4.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_5, 1L)).thenReturn(STATE_MESSAGE_5.getState()); + + when(stateManager.getCursorInfo(NAME_NAMESPACE_PAIR)).thenReturn(Optional.empty()); } @Test @@ -137,6 +134,10 @@ void testWithoutInitialCursor() { @Test void testWithInitialCursor() { + // record 1 and 2 has smaller cursor value, so at the end, the initial cursor is emitted with 0 + // record count + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_5, 0L)).thenReturn(STATE_MESSAGE_5.getState()); + messageIterator = MoreIterators.of(RECORD_MESSAGE_1, RECORD_MESSAGE_2); final StateDecoratingIterator iterator = new StateDecoratingIterator( messageIterator, @@ -177,6 +178,12 @@ void testCursorFieldIsEmpty() { @Test void testIteratorCatchesExceptionWhenEmissionFrequencyNonZero() { final Iterator exceptionIterator = createExceptionIterator(); + + // The mock record count matches the number of records returned by the exception iterator. + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_1, 1L)).thenReturn(STATE_MESSAGE_1.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_2, 2L)).thenReturn(STATE_MESSAGE_2.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_3, 1L)).thenReturn(STATE_MESSAGE_3.getState()); + final StateDecoratingIterator iterator = new StateDecoratingIterator( exceptionIterator, stateManager, @@ -242,7 +249,7 @@ void testUnicodeNull() { // UTF8 null \u0000 is removed from the cursor value in the state message final AirbyteMessage stateMessageWithNull = STATE_MESSAGE_1; - when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, recordValueWithNull)).thenReturn(stateMessageWithNull.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, recordValueWithNull, 1L)).thenReturn(stateMessageWithNull.getState()); messageIterator = MoreIterators.of(recordMessageWithNull); @@ -364,11 +371,17 @@ void testStateEmissionWhenInitialCursorIsNotNull() { * start with `F1 > 16` and skip record 3. *

    * So intermediate state emission should only happen when all records with the same cursor value has - * been synced to destination. Reference: https://github.com/airbytehq/airbyte/issues/15427 + * been synced to destination. Reference: + * link */ @Test @DisplayName("When there are multiple records with the same cursor value") void testStateEmissionForRecordsSharingSameCursorValue() { + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_2, 2L)).thenReturn(STATE_MESSAGE_2.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_3, 3L)).thenReturn(STATE_MESSAGE_3.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_4, 1L)).thenReturn(STATE_MESSAGE_4.getState()); + when(stateManager.updateAndEmit(NAME_NAMESPACE_PAIR, RECORD_VALUE_5, 2L)).thenReturn(STATE_MESSAGE_5.getState()); + messageIterator = MoreIterators.of( RECORD_MESSAGE_2, RECORD_MESSAGE_2, RECORD_MESSAGE_3, RECORD_MESSAGE_3, RECORD_MESSAGE_3, diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/CursorManagerTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/CursorManagerTest.java index 67b7fddc23f5..5f85d99be4d8 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/CursorManagerTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/CursorManagerTest.java @@ -7,6 +7,7 @@ import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.CURSOR; import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.CURSOR_FIELD1; import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.CURSOR_FIELD2; +import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.CURSOR_RECORD_COUNT; import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.NAME_NAMESPACE_PAIR1; import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.NAME_NAMESPACE_PAIR2; import static io.airbyte.integrations.source.relationaldb.state.StateTestConstants.getCatalog; @@ -19,6 +20,7 @@ import io.airbyte.integrations.source.relationaldb.models.DbStreamState; import java.util.Collections; import java.util.Optional; +import java.util.function.Function; import org.junit.jupiter.api.Test; /** @@ -26,16 +28,25 @@ */ public class CursorManagerTest { + private static final Function CURSOR_RECORD_COUNT_FUNCTION = stream -> { + if (stream.getCursorRecordCount() != null) { + return stream.getCursorRecordCount(); + } else { + return 0L; + } + }; + @Test void testCreateCursorInfoCatalogAndStateSameCursorField() { final CursorManager cursorManager = createCursorManager(CURSOR_FIELD1, CURSOR, NAME_NAMESPACE_PAIR1); final CursorInfo actual = cursorManager.createCursorInfoForStream( NAME_NAMESPACE_PAIR1, - getState(CURSOR_FIELD1, CURSOR), + getState(CURSOR_FIELD1, CURSOR, CURSOR_RECORD_COUNT), getStream(CURSOR_FIELD1), DbStreamState::getCursor, - DbStreamState::getCursorField); - assertEquals(new CursorInfo(CURSOR_FIELD1, CURSOR, CURSOR_FIELD1, CURSOR), actual); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); + assertEquals(new CursorInfo(CURSOR_FIELD1, CURSOR, CURSOR_RECORD_COUNT, CURSOR_FIELD1, CURSOR, CURSOR_RECORD_COUNT), actual); } @Test @@ -46,7 +57,8 @@ void testCreateCursorInfoCatalogAndStateSameCursorFieldButNoCursor() { getState(CURSOR_FIELD1, null), getStream(CURSOR_FIELD1), DbStreamState::getCursor, - DbStreamState::getCursorField); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); assertEquals(new CursorInfo(CURSOR_FIELD1, null, CURSOR_FIELD1, null), actual); } @@ -58,7 +70,8 @@ void testCreateCursorInfoCatalogAndStateChangeInCursorFieldName() { getState(CURSOR_FIELD1, CURSOR), getStream(CURSOR_FIELD2), DbStreamState::getCursor, - DbStreamState::getCursorField); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); assertEquals(new CursorInfo(CURSOR_FIELD1, CURSOR, CURSOR_FIELD2, null), actual); } @@ -70,7 +83,8 @@ void testCreateCursorInfoCatalogAndNoState() { Optional.empty(), getStream(CURSOR_FIELD1), DbStreamState::getCursor, - DbStreamState::getCursorField); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); assertEquals(new CursorInfo(null, null, CURSOR_FIELD1, null), actual); } @@ -82,7 +96,8 @@ void testCreateCursorInfoStateAndNoCatalog() { getState(CURSOR_FIELD1, CURSOR), Optional.empty(), DbStreamState::getCursor, - DbStreamState::getCursorField); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); assertEquals(new CursorInfo(CURSOR_FIELD1, CURSOR, null, null), actual); } @@ -95,7 +110,8 @@ void testCreateCursorInfoNoCatalogAndNoState() { Optional.empty(), Optional.empty(), DbStreamState::getCursor, - DbStreamState::getCursorField); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); assertEquals(new CursorInfo(null, null, null, null), actual); } @@ -107,7 +123,8 @@ void testCreateCursorInfoStateAndCatalogButNoCursorField() { getState(CURSOR_FIELD1, CURSOR), getStream(null), DbStreamState::getCursor, - DbStreamState::getCursorField); + DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION); assertEquals(new CursorInfo(CURSOR_FIELD1, CURSOR, null, null), actual); } @@ -134,6 +151,7 @@ private CursorManager createCursorManager(final String cursorFiel () -> Collections.singleton(dbStreamState), DbStreamState::getCursor, DbStreamState::getCursorField, + CURSOR_RECORD_COUNT_FUNCTION, s -> nameNamespacePair); } diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java index 3ef46156c5dc..7df8b7c4ee5e 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java @@ -135,7 +135,7 @@ void testToStateFromLegacyState() { .withGlobal(expectedGlobalState) .withType(AirbyteStateType.GLOBAL); - final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a"); + final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a", 1L); assertEquals(expected, actualFirstEmission); } @@ -167,7 +167,8 @@ void testToState() { .withStreamName(STREAM_NAME1) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD1)) - .withCursor("a"), + .withCursor("a") + .withCursorRecordCount(1L), new DbStreamState() .withStreamName(STREAM_NAME2) .withStreamNamespace(NAMESPACE) @@ -204,7 +205,7 @@ void testToState() { .withGlobal(expectedGlobalState) .withType(AirbyteStateType.GLOBAL); - final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a"); + final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a", 1L); assertEquals(expected, actualFirstEmission); } diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StateTestConstants.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StateTestConstants.java index e939c9aea87d..1e6ac72d25b3 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StateTestConstants.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StateTestConstants.java @@ -28,10 +28,10 @@ public final class StateTestConstants { public static final String CURSOR_FIELD1 = "year"; public static final String CURSOR_FIELD2 = "generation"; public static final String CURSOR = "2000"; + public static final long CURSOR_RECORD_COUNT = 19L; private StateTestConstants() {} - @SuppressWarnings("SameParameterValue") public static Optional getState(final String cursorField, final String cursor) { return Optional.of(new DbStreamState() .withStreamName(STREAM_NAME1) @@ -39,6 +39,14 @@ public static Optional getState(final String cursorField, final S .withCursor(cursor)); } + public static Optional getState(final String cursorField, final String cursor, final long cursorRecordCount) { + return Optional.of(new DbStreamState() + .withStreamName(STREAM_NAME1) + .withCursorField(Lists.newArrayList(cursorField)) + .withCursor(cursor) + .withCursorRecordCount(cursorRecordCount)); + } + public static Optional getCatalog(final String cursorField) { return Optional.of(new ConfiguredAirbyteCatalog() .withStreams(List.of(getStream(cursorField).orElse(null)))); diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java index 4b6876987fe4..7484202848b9 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java @@ -106,7 +106,8 @@ void testToState() { .withStreamName(STREAM_NAME1) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD1)) - .withCursor("a"), + .withCursor("a") + .withCursorRecordCount(1L), new DbStreamState() .withStreamName(STREAM_NAME2) .withStreamNamespace(NAMESPACE) diff --git a/airbyte-integrations/connectors/source-snowflake/src/main/java/io.airbyte.integrations.source.snowflake/SnowflakeSource.java b/airbyte-integrations/connectors/source-snowflake/src/main/java/io.airbyte.integrations.source.snowflake/SnowflakeSource.java index 0224c01b6792..fc7a75539022 100644 --- a/airbyte-integrations/connectors/source-snowflake/src/main/java/io.airbyte.integrations.source.snowflake/SnowflakeSource.java +++ b/airbyte-integrations/connectors/source-snowflake/src/main/java/io.airbyte.integrations.source.snowflake/SnowflakeSource.java @@ -94,6 +94,11 @@ protected int getStateEmissionFrequency() { return INTERMEDIATE_STATE_EMISSION_FREQUENCY; } + @Override + protected String getCountColumnName() { + return "RECORD_COUNT"; + } + private JsonNode buildOAuthConfig(final JsonNode config, final String jdbcUrl) { final String accessToken; final var credentials = config.get("credentials"); diff --git a/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java index 60c2406032b2..c3e21bbd75ef 100644 --- a/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-snowflake/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/SnowflakeJdbcSourceAcceptanceTest.java @@ -57,12 +57,14 @@ static void init() { TABLE_NAME_WITH_SPACES = "ID AND NAME"; TABLE_NAME_WITHOUT_PK = "ID_AND_NAME_WITHOUT_PK"; TABLE_NAME_COMPOSITE_PK = "FULL_NAME_COMPOSITE_PK"; + TABLE_NAME_AND_TIMESTAMP = "NAME_AND_TIMESTAMP"; COL_ID = "ID"; COL_NAME = "NAME"; COL_UPDATED_AT = "UPDATED_AT"; COL_FIRST_NAME = "FIRST_NAME"; COL_LAST_NAME = "LAST_NAME"; COL_LAST_NAME_WITH_SPACE = "LAST NAME"; + COL_TIMESTAMP = "TIMESTAMP"; ID_VALUE_1 = new BigDecimal(1); ID_VALUE_2 = new BigDecimal(2); ID_VALUE_3 = new BigDecimal(3); @@ -165,6 +167,7 @@ protected AirbyteCatalog getCatalog(final String defaultNamespace) { List.of(List.of(COL_FIRST_NAME), List.of(COL_LAST_NAME))))); } + @Override protected List getTestMessages() { return List.of( new AirbyteMessage().withType(AirbyteMessage.Type.RECORD) @@ -216,7 +219,8 @@ protected List getExpectedAirbyteMessagesSecondSync(final String .withStreamName(streamName) .withStreamNamespace(namespace) .withCursorField(List.of(COL_ID)) - .withCursor("5"); + .withCursor("5") + .withCursorRecordCount(1L); expectedMessages.addAll(createExpectedTestMessages(List.of(state))); return expectedMessages; } diff --git a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CommonField.java b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CommonField.java index c5b096f786d4..61bc0de2af5d 100644 --- a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CommonField.java +++ b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CommonField.java @@ -57,4 +57,9 @@ public List> getProperties() { return properties; } + @Override + public String toString() { + return String.format("CommonField{name='%s', type=%s, properties=%s}", name, type, properties); + } + } diff --git a/docs/integrations/sources/alloydb.md b/docs/integrations/sources/alloydb.md index 289a79262ae4..fce3378b4c40 100644 --- a/docs/integrations/sources/alloydb.md +++ b/docs/integrations/sources/alloydb.md @@ -331,14 +331,7 @@ According to Postgres [documentation](https://www.postgresql.org/docs/14/datatyp | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------| +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Align with Postgres source v.1.0.15 | | 1.0.0 | 2022-09-15 | [16776](https://github.com/airbytehq/airbyte/pull/16776) | Align with strict-encrypt version | | 0.1.0 | 2022-09-05 | [16323](https://github.com/airbytehq/airbyte/pull/16323) | Initial commit. Based on source-postgres v.1.0.7 | - - -## Changelog (Strict Encrypt) - -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------| -| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Align with Postgres source v.1.0.15 | -| 1.0.0 | 2022-09-15 | [16776](https://github.com/airbytehq/airbyte/pull/16776) | Initial commit. Based on source-postgres-strict-encrypt | diff --git a/docs/integrations/sources/bigquery.md b/docs/integrations/sources/bigquery.md index 75881e800c2d..d8a6968701da 100644 --- a/docs/integrations/sources/bigquery.md +++ b/docs/integrations/sources/bigquery.md @@ -88,6 +88,7 @@ Once you've configured BigQuery as a source, delete the Service Account Key from | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.2.2 | 2022-09-22 | [16902](https://github.com/airbytehq/airbyte/pull/16902) | Source BigQuery: added user agent header | | 0.2.1 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | | 0.2.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | diff --git a/docs/integrations/sources/clickhouse.md b/docs/integrations/sources/clickhouse.md index b9fa131fc2d5..07df7cb267ba 100644 --- a/docs/integrations/sources/clickhouse.md +++ b/docs/integrations/sources/clickhouse.md @@ -96,6 +96,7 @@ Using this feature requires additional configuration, when creating the source. | Version | Date | Pull Request | Subject | |:---| :--- |:---------------------------------------------------------|:---------------------------------------------------------------------------| +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.1.14 | 2022-09-27 | [17031](https://github.com/airbytehq/airbyte/pull/17031) | Added custom jdbc url parameters field | | 0.1.13 | 2022-09-01 | [16238](https://github.com/airbytehq/airbyte/pull/16238) | Emit state messages more frequently | | 0.1.9 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | diff --git a/docs/integrations/sources/cockroachdb.md b/docs/integrations/sources/cockroachdb.md index 1538c0221a04..b6d6f5cc0c71 100644 --- a/docs/integrations/sources/cockroachdb.md +++ b/docs/integrations/sources/cockroachdb.md @@ -95,6 +95,7 @@ Your database user should now be ready for use with Airbyte. | Version | Date | Pull Request | Subject | |:--------|:-----------| :--- | :--- | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.1.18 | 2022-09-01 | [16394](https://github.com/airbytehq/airbyte/pull/16394) | Added custom jdbc properties field | | 0.1.17 | 2022-09-01 | [16238](https://github.com/airbytehq/airbyte/pull/16238) | Emit state messages more frequently | | 0.1.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | diff --git a/docs/integrations/sources/db2.md b/docs/integrations/sources/db2.md index 974a57a93c78..54207cef61e7 100644 --- a/docs/integrations/sources/db2.md +++ b/docs/integrations/sources/db2.md @@ -60,6 +60,7 @@ You can also enter your own password for the keystore, but if you don't, the pas | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.1.16 | 2022-09-06 | [16354](https://github.com/airbytehq/airbyte/pull/16354) | Add custom JDBC params | | 0.1.15 | 2022-09-01 | [16238](https://github.com/airbytehq/airbyte/pull/16238) | Emit state messages more frequently | | 0.1.14 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | diff --git a/docs/integrations/sources/mssql.md b/docs/integrations/sources/mssql.md index 14088fafa421..2d24d7e9c952 100644 --- a/docs/integrations/sources/mssql.md +++ b/docs/integrations/sources/mssql.md @@ -341,6 +341,7 @@ WHERE actor_definition_id ='b5ea17b1-f170-46dc-bc31-cc744ca984c1' AND (configura | Version | Date | Pull Request | Subject | |:--------|:-----------| :----------------------------------------------------- |:-------------------------------------------------------------------------------------------------------| +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | | 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | | 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | diff --git a/docs/integrations/sources/mysql.md b/docs/integrations/sources/mysql.md index 3d84b175170b..ee84f6747f26 100644 --- a/docs/integrations/sources/mysql.md +++ b/docs/integrations/sources/mysql.md @@ -251,6 +251,7 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura ## Changelog | Version | Date | Pull Request | Subject | |:--------|:-----------|:-----------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | | 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | | 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | diff --git a/docs/integrations/sources/oracle.md b/docs/integrations/sources/oracle.md index 5d5233780e0b..d7935841a078 100644 --- a/docs/integrations/sources/oracle.md +++ b/docs/integrations/sources/oracle.md @@ -132,6 +132,7 @@ Airbyte has the ability to connect to the Oracle source with 3 network connectiv | Version | Date | Pull Request | Subject | |:--------|:-----------| :--- |:------------------------------------------------| +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.3.21 | 2022-09-01 | [16238](https://github.com/airbytehq/airbyte/pull/16238) | Emit state messages more frequently | | 0.3.20 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | | 0.3.19 | 2022-08-03 | [14953](https://github.com/airbytehq/airbyte/pull/14953) | Use Service Name to connect to database | diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index fd6b229a3098..4c0669ced2ce 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -398,7 +398,8 @@ The root causes is that the WALs needed for the incremental sync has been remove | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | +| 1.0.16 | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | | 1.0.14 | 2022-10-03 | [17515](https://github.com/airbytehq/airbyte/pull/17515) | Fix an issue preventing connection using client certificate | | 1.0.13 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | | 1.0.12 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt postgres source | diff --git a/docs/integrations/sources/redshift.md b/docs/integrations/sources/redshift.md index 85e3f4c6b9e8..2311a930376a 100644 --- a/docs/integrations/sources/redshift.md +++ b/docs/integrations/sources/redshift.md @@ -54,6 +54,7 @@ All Redshift connections are encrypted using SSL | Version | Date | Pull Request | Subject | |:--------|:-----------| :----- |:----------------------------------------------------------------------------------------------------------| +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.3.14 | 2022-09-01 | [16258](https://github.com/airbytehq/airbyte/pull/16258) | Emit state messages more frequently | | 0.3.13 | 2022-05-25 | | Added JDBC URL params | | 0.3.12 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | diff --git a/docs/integrations/sources/snowflake.md b/docs/integrations/sources/snowflake.md index b72970bdf034..4004a08c2e07 100644 --- a/docs/integrations/sources/snowflake.md +++ b/docs/integrations/sources/snowflake.md @@ -122,6 +122,7 @@ To read more please check official [Snowflake documentation](https://docs.snowfl | Version | Date | Pull Request | Subject | |:----------| :--- | :--- | :--- | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.1.24 | 2022-09-26 | [17144](https://github.com/airbytehq/airbyte/pull/17144) | Fixed bug with incorrect date-time datatypes handling | | 0.1.23 | 2022-09-26 | [17116](https://github.com/airbytehq/airbyte/pull/17116) | added connection string identifier | | 0.1.22 | 2022-09-21 | [16766](https://github.com/airbytehq/airbyte/pull/16766) | Update JDBC Driver version to 3.13.22 | diff --git a/docs/integrations/sources/tidb.md b/docs/integrations/sources/tidb.md index 79582e758c43..e026f886c618 100644 --- a/docs/integrations/sources/tidb.md +++ b/docs/integrations/sources/tidb.md @@ -128,6 +128,7 @@ Now that you have set up the TiDB source connector, check out the following TiDB | Version | Date | Pull Request | Subject | | :------ | :--- | :----------- | ------- | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 0.2.1 | 2022-09-01 | [16238](https://github.com/airbytehq/airbyte/pull/16238) | Emit state messages more frequently | | 0.2.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | | 0.1.5 | 2022-07-25 | [14996](https://github.com/airbytehq/airbyte/pull/14996) | Removed additionalProperties:false from spec | From 5aa25a1e1aa16379244d50c2863c356c7335813d Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Fri, 14 Oct 2022 14:53:39 +0300 Subject: [PATCH 110/498] Source S3 - fix schema inference (#17991) * #678 oncall. Source S3 - fix schema inference * source s3: upd changelog * auto-bump connector version [ci skip] Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 2 +- airbyte-integrations/connectors/source-s3/Dockerfile | 2 +- .../source-s3/integration_tests/config_minio.json | 2 +- .../source_files_abstract/formats/abstract_file_parser.py | 2 +- .../source_files_abstract/formats/jsonl_parser.py | 7 ++++++- .../sample_files/jsonl/test_file_11_array_in_schema.jsonl | 3 +++ .../connectors/source-s3/unit_tests/test_jsonl_parser.py | 8 ++++++++ docs/integrations/sources/s3.md | 3 ++- 9 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 airbyte-integrations/connectors/source-s3/unit_tests/sample_files/jsonl/test_file_11_array_in_schema.jsonl diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index fc8cf2578bb4..9f5a5a571ffa 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -915,7 +915,7 @@ - name: S3 sourceDefinitionId: 69589781-7828-43c5-9f63-8925b1c1ccc2 dockerRepository: airbyte/source-s3 - dockerImageTag: 0.1.23 + dockerImageTag: 0.1.24 documentationUrl: https://docs.airbyte.com/integrations/sources/s3 icon: s3.svg sourceType: file diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 405a7a8989e2..80a03584d4ff 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9436,7 +9436,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-s3:0.1.23" +- dockerImage: "airbyte/source-s3:0.1.24" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/s3" changelogUrl: "https://docs.airbyte.com/integrations/sources/s3" diff --git a/airbyte-integrations/connectors/source-s3/Dockerfile b/airbyte-integrations/connectors/source-s3/Dockerfile index cb908e0cd51d..c61095cc1484 100644 --- a/airbyte-integrations/connectors/source-s3/Dockerfile +++ b/airbyte-integrations/connectors/source-s3/Dockerfile @@ -17,5 +17,5 @@ COPY source_s3 ./source_s3 ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.23 +LABEL io.airbyte.version=0.1.24 LABEL io.airbyte.name=airbyte/source-s3 diff --git a/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json b/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json index 726aef0143d0..c5b35c593f9d 100644 --- a/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json +++ b/airbyte-integrations/connectors/source-s3/integration_tests/config_minio.json @@ -6,7 +6,7 @@ "aws_access_key_id": "123456", "aws_secret_access_key": "123456key", "path_prefix": "", - "endpoint": "http://10.0.167.14:9000" + "endpoint": "http://10.0.92.4:9000" }, "format": { "filetype": "csv" diff --git a/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/abstract_file_parser.py b/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/abstract_file_parser.py index 1f9bb0fa69be..76f110b5bc73 100644 --- a/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/abstract_file_parser.py +++ b/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/abstract_file_parser.py @@ -13,7 +13,7 @@ class AbstractFileParser(ABC): logger = AirbyteLogger() - NON_SCALAR_TYPES = {"struct": "struct"} + NON_SCALAR_TYPES = {"struct": "struct", "list": "list"} TYPE_MAP = { "boolean": ("bool_", "bool"), "integer": ("int64", "int8", "int16", "int32", "uint8", "uint16", "uint32", "uint64"), diff --git a/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/jsonl_parser.py b/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/jsonl_parser.py index 6185c4459a36..f3e4595bcbd7 100644 --- a/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/jsonl_parser.py +++ b/airbyte-integrations/connectors/source-s3/source_s3/source_files_abstract/formats/jsonl_parser.py @@ -24,7 +24,10 @@ class JsonlParser(AbstractFileParser): "large_string", ), # TODO: support array type rather than coercing to string - "array": ("large_string",), + "array": ( + "list", + "large_string", + ), "null": ("large_string",), } @@ -80,6 +83,8 @@ def get_inferred_schema(self, file: Union[TextIO, BinaryIO]) -> Mapping[str, Any def field_type_to_str(type_: Any) -> str: if isinstance(type_, pa.lib.StructType): return "struct" + if isinstance(type_, pa.lib.ListType): + return "list" if isinstance(type_, pa.lib.DataType): return str(type_) raise Exception(f"Unknown PyArrow Type: {type_}") diff --git a/airbyte-integrations/connectors/source-s3/unit_tests/sample_files/jsonl/test_file_11_array_in_schema.jsonl b/airbyte-integrations/connectors/source-s3/unit_tests/sample_files/jsonl/test_file_11_array_in_schema.jsonl new file mode 100644 index 000000000000..cdf847c55d0e --- /dev/null +++ b/airbyte-integrations/connectors/source-s3/unit_tests/sample_files/jsonl/test_file_11_array_in_schema.jsonl @@ -0,0 +1,3 @@ +{"id": 1, "name": "Erich", "books": ["Shadows in Paradise", "The Dream Room", "The Night in Lisbon"]} +{"id": 2, "name": "Maria", "books": ["All Quiet on the Western Front"]} +{"id": 3, "name": "Remarque", "books": ["The Road Back", "Three Comrades"]} diff --git a/airbyte-integrations/connectors/source-s3/unit_tests/test_jsonl_parser.py b/airbyte-integrations/connectors/source-s3/unit_tests/test_jsonl_parser.py index 3019746da54f..1773a78cfeaa 100644 --- a/airbyte-integrations/connectors/source-s3/unit_tests/test_jsonl_parser.py +++ b/airbyte-integrations/connectors/source-s3/unit_tests/test_jsonl_parser.py @@ -168,4 +168,12 @@ def cases(cls) -> Mapping[str, Any]: "line_checks": {}, "fails": [], }, + "array_in_schema_test": { + "AbstractFileParser": JsonlParser(format={"filetype": "jsonl"}), + "filepath": os.path.join(SAMPLE_DIRECTORY, "jsonl/test_file_11_array_in_schema.jsonl"), + "num_records": 3, + "inferred_schema": {"id": "integer", "name": "string", "books": "array"}, + "line_checks": {}, + "fails": [], + }, } diff --git a/docs/integrations/sources/s3.md b/docs/integrations/sources/s3.md index 9ab0d44064fe..4c4a8388fcee 100644 --- a/docs/integrations/sources/s3.md +++ b/docs/integrations/sources/s3.md @@ -204,7 +204,8 @@ The Jsonl parser uses pyarrow hence,only the line-delimited JSON format is suppo ## Changelog | Version | Date | Pull Request | Subject | -| :------ | :--------- | :-------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | +|:--------|:-----------|:----------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------| +| 0.1.23 | 2022-10-10 | [17991](https://github.com/airbytehq/airbyte/pull/17991) | Fix pyarrow to JSON schema type conversion for arrays | | 0.1.23 | 2022-10-10 | [17800](https://github.com/airbytehq/airbyte/pull/17800) | Deleted `use_ssl` and `verify_ssl_cert` flags and hardcoded to `True` | | 0.1.22 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state | | 0.1.21 | 2022-09-20 | [16921](https://github.com/airbytehq/airbyte/pull/16921) | Upgrade pyarrow | From cdb8052db3d328504e909e5dd22755c1859d06b5 Mon Sep 17 00:00:00 2001 From: Rachel Rizk <69161287+rach-r@users.noreply.github.com> Date: Fri, 14 Oct 2022 15:38:11 +0200 Subject: [PATCH 111/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Bing=20Ads=20-?= =?UTF-8?q?=20Fix=20Campaigns=20stream=20misses=20Audience=20and=20Shoppin?= =?UTF-8?q?g=20(#17873)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-bing-ads/Dockerfile | 2 +- .../source-bing-ads/source_bing_ads/source.py | 21 +++++++++++++------ .../source-bing-ads/unit_tests/test_source.py | 1 + docs/integrations/sources/bing-ads.md | 3 ++- 6 files changed, 21 insertions(+), 10 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 9f5a5a571ffa..1dd9f34012e9 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -128,7 +128,7 @@ - name: Bing Ads sourceDefinitionId: 47f25999-dd5e-4636-8c39-e7cea2453331 dockerRepository: airbyte/source-bing-ads - dockerImageTag: 0.1.15 + dockerImageTag: 0.1.16 documentationUrl: https://docs.airbyte.com/integrations/sources/bing-ads icon: bingads.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 80a03584d4ff..1040a76999e0 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -1460,7 +1460,7 @@ - "overwrite" - "append" - "append_dedup" -- dockerImage: "airbyte/source-bing-ads:0.1.15" +- dockerImage: "airbyte/source-bing-ads:0.1.16" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/bing-ads" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-bing-ads/Dockerfile b/airbyte-integrations/connectors/source-bing-ads/Dockerfile index 371e95cff23a..cd473cc50954 100644 --- a/airbyte-integrations/connectors/source-bing-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-bing-ads/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.15 +LABEL io.airbyte.version=0.1.16 LABEL io.airbyte.name=airbyte/source-bing-ads diff --git a/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/source.py b/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/source.py index e3b97b7c5f64..b16bdbf600ab 100644 --- a/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/source.py +++ b/airbyte-integrations/connectors/source-bing-ads/source_bing_ads/source.py @@ -202,11 +202,19 @@ class Campaigns(BingAdsStream): data_field: str = "Campaign" service_name: str = "CampaignManagement" operation_name: str = "GetCampaignsByAccountId" - additional_fields: str = ( - "AdScheduleUseSearcherTimeZone BidStrategyId CpvCpmBiddingScheme DynamicDescriptionSetting" - " DynamicFeedSetting MaxConversionValueBiddingScheme MultimediaAdsBidAdjustment" - " TargetImpressionShareBiddingScheme TargetSetting VerifiedTrackingSetting" - ) + additional_fields: Iterable[str] = [ + "AdScheduleUseSearcherTimeZone", + "BidStrategyId", + "CpvCpmBiddingScheme", + "DynamicDescriptionSetting", + "DynamicFeedSetting", + "MaxConversionValueBiddingScheme", + "MultimediaAdsBidAdjustment", + "TargetImpressionShareBiddingScheme", + "TargetSetting", + "VerifiedTrackingSetting", + ] + campaign_types: Iterable[str] = ["Audience", "DynamicSearchAds", "Search", "Shopping"] def request_params( self, @@ -215,7 +223,8 @@ def request_params( ) -> MutableMapping[str, Any]: return { "AccountId": stream_slice["account_id"], - "ReturnAdditionalFields": self.additional_fields, + "CampaignType": " ".join(self.campaign_types), + "ReturnAdditionalFields": " ".join(self.additional_fields), } def stream_slices( diff --git a/airbyte-integrations/connectors/source-bing-ads/unit_tests/test_source.py b/airbyte-integrations/connectors/source-bing-ads/unit_tests/test_source.py index f6a0e0dbb081..e28cfa5c538f 100644 --- a/airbyte-integrations/connectors/source-bing-ads/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-bing-ads/unit_tests/test_source.py @@ -51,6 +51,7 @@ def test_campaigns_request_params(mocked_client, config): request_params = campaigns.request_params(stream_slice={"account_id": "account_id"}) assert request_params == { "AccountId": "account_id", + "CampaignType": "Audience DynamicSearchAds Search Shopping", "ReturnAdditionalFields": "AdScheduleUseSearcherTimeZone BidStrategyId CpvCpmBiddingScheme DynamicDescriptionSetting DynamicFeedSetting MaxConversionValueBiddingScheme MultimediaAdsBidAdjustment TargetImpressionShareBiddingScheme TargetSetting VerifiedTrackingSetting", } diff --git a/docs/integrations/sources/bing-ads.md b/docs/integrations/sources/bing-ads.md index 8cba79595792..603d3e62c8bc 100644 --- a/docs/integrations/sources/bing-ads.md +++ b/docs/integrations/sources/bing-ads.md @@ -96,7 +96,8 @@ The Bing Ads API limits the number of requests for all Microsoft Advertising cli ## Changelog | Version | Date | Pull Request | Subject | -| :------ | :--------- | :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | +| :------ |:-----------|:---------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------| +| 0.1.16 | 2022-10-12 | [17873](https://github.com/airbytehq/airbyte/pull/17873) | Fix: added missing campaign types in (Audience, Shopping and DynamicSearchAds) in campaigns stream | | 0.1.15 | 2022-10-03 | [17505](https://github.com/airbytehq/airbyte/pull/17505) | Fix: limit cache size for ServiceClient instances | | 0.1.14 | 2022-09-29 | [17403](https://github.com/airbytehq/airbyte/pull/17403) | Fix: limit cache size for ReportingServiceManager instances | | 0.1.13 | 2022-09-29 | [17386](https://github.com/airbytehq/airbyte/pull/17386) | Migrate to per-stream states. | From 1d2a4baf6bf6e2ea6a136bb89fe3dd9a2d371ccc Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Fri, 14 Oct 2022 09:22:37 -0700 Subject: [PATCH 112/498] Cypress initial cleanup pass (#16189) * Update README with instructions * Prevent test failures from intentionally-thrown errors * Rephrase cypress open instructions in README * Add CI repro documentation --- airbyte-webapp-e2e-tests/README.md | 29 +++++++++++++++++-- .../cypress/integration/base.spec.ts | 4 +++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/airbyte-webapp-e2e-tests/README.md b/airbyte-webapp-e2e-tests/README.md index b3f96ca1f9df..81efe0fe527c 100644 --- a/airbyte-webapp-e2e-tests/README.md +++ b/airbyte-webapp-e2e-tests/README.md @@ -1,5 +1,28 @@ -## Start Cypress +## Running an interactive Cypress session with `npm run cypress:open` +The most useful way to run tests locally is with the `cypress open` command. It opens a dispatcher window that lets you select which tests and browser to run; the Electron browser (whose devtools will be very familiar to chrome users) opens a child window, and having both cypress windows grouped behaves nicely when switching between applications. In an interactive session, you can use `it.skip` and `it.only` to focus on the tests you care about; any change to the source file of a running test will cause tests to be automatically rerun. At the end of a test run, the web page is left "dangling" with all state present at the end of the last test; you can click around, "inspect element", and interact with the page however you wish, which makes it easy to incrementally develop tests. -### `npm install` +By default, this command is configured to visit page urls from port 3000 (as used by a locally-run dev server), not port 8000 (as used by docker-compose's `webapp` service). If you want to run tests against the dockerized UI instead, leave the `webapp` docker-compose service running in step 4) and start the test runner with `CYPRESS_BASE_URL=http://localhost:8000 npm run cypress:open` in step 8). -### `npm run cypress:open` +Except as noted, all commands are written as if run from inside the `airbyte-webapp-e2e-tests/` directory. + +Steps: +1) If you have not already done so, run `npm install` to install the e2e test dependencies. +2) Build the OSS backend for the current commit with `SUB_BUILD=PLATFORM ../gradlew clean build`. +3) Create the test database: `npm run createdb`. +4) Start the OSS backend: `VERSION=dev docker-compose --file ../docker-compose.yaml up`. If you want, follow this with `docker-compose stop webapp` to turn off the dockerized frontend build; interactive cypress sessions don't use it. +5) The following two commands will start a separate long-running server, so open another terminal window. In it, `cd` into the `airbyte-webapp/` directory. +6) If you have not already done so, run `npm install` to install the frontend app's dependencies. +7) Start the frontend development server with `npm start`. +8) Back in the `airbyte-webapp-e2e-tests/` directory, start the cypress test runner with `npm run cypress:open`. + +## Reproducing CI test results with `npm run cypress:ci` or `npm run cypress:ci:record` +Unlike `npm run cypress:open`, `npm run cypress:ci` and `npm run cypress:ci:record` use the dockerized UI (i.e. they expect the UI at port 8000, rather than port 3000). If the OSS backend is running but you have run `docker-compose stop webapp`, you'll have to re-enable it with `docker-compose start webapp`. These trigger headless runs: you won't have a live browser to interact with, just terminal output. + +Except as noted, all commands are written as if run from inside the `airbyte-webapp-e2e-tests/` directory. + +Steps: +1) If you have not already done so, run `npm install` to install the e2e test dependencies. +2) Build the OSS backend for the current commit with `SUB_BUILD=PLATFORM ../gradlew clean build`. +3) Create the test database: `npm run createdb`. +4) Start the OSS backend: `VERSION=dev docker-compose --file ../docker-compose.yaml up`. +5) Start the cypress test run with `npm run cypress:ci` or `npm run cypress:ci:record`. diff --git a/airbyte-webapp-e2e-tests/cypress/integration/base.spec.ts b/airbyte-webapp-e2e-tests/cypress/integration/base.spec.ts index b70691a666f1..b4f59dd559d2 100644 --- a/airbyte-webapp-e2e-tests/cypress/integration/base.spec.ts +++ b/airbyte-webapp-e2e-tests/cypress/integration/base.spec.ts @@ -8,6 +8,8 @@ describe("Error handling view", () => { }, }); + cy.on("uncaught:exception", () => false); + cy.visit("/"); cy.get("div").contains("Version mismatch between 0.0.1-ci and 0.0.2-ci.").should("exist"); @@ -19,6 +21,8 @@ describe("Error handling view", () => { body: "Failed to fetch", }); + cy.on("uncaught:exception", () => false); + cy.visit("/"); cy.get("div").contains("Cannot reach server. The server may still be starting up.").should("exist"); From d2d8989b735480a8e338760035caf31cd4001e34 Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Fri, 14 Oct 2022 12:24:54 -0400 Subject: [PATCH 113/498] =?UTF-8?q?=F0=9F=AA=9F=20New=20=20(?= =?UTF-8?q?#17760)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP add creatable * appease the compiler * working on it * more work * adding and removing tags works * handle delimiters * component works, testing WIP * mostly working, add tests * cleanup tests * styling of tag items * note to self * cleanup * cleanup some more, delete old components * update style exports and render method * taginput into a describe block * find the tag input in service form tests * cleanup * use simplified queryselector * Update airbyte-webapp/src/components/ui/TagInput/TagInput.module.scss Co-authored-by: Vladimir Co-authored-by: Vladimir --- .../ui/TagInput/TagInput.module.scss | 9 + .../components/ui/TagInput/TagInput.test.tsx | 95 +++++++ .../src/components/ui/TagInput/TagInput.tsx | 265 ++++++++---------- .../src/components/ui/TagInput/TagItem.tsx | 62 ---- .../ServiceForm/ServiceForm.test.tsx | 6 +- .../components/Property/Control.tsx | 32 +-- 6 files changed, 235 insertions(+), 234 deletions(-) create mode 100644 airbyte-webapp/src/components/ui/TagInput/TagInput.module.scss create mode 100644 airbyte-webapp/src/components/ui/TagInput/TagInput.test.tsx delete mode 100644 airbyte-webapp/src/components/ui/TagInput/TagItem.tsx diff --git a/airbyte-webapp/src/components/ui/TagInput/TagInput.module.scss b/airbyte-webapp/src/components/ui/TagInput/TagInput.module.scss new file mode 100644 index 000000000000..36d3521be8f1 --- /dev/null +++ b/airbyte-webapp/src/components/ui/TagInput/TagInput.module.scss @@ -0,0 +1,9 @@ +@use "scss/colors"; +@use "scss/variables"; + +:export { + backgroundColor: colors.$dark-blue-800; + fontColor: colors.$white; + borderRadius: variables.$border-radius-sm; + paddingLeft: variables.$spacing-sm; +} diff --git a/airbyte-webapp/src/components/ui/TagInput/TagInput.test.tsx b/airbyte-webapp/src/components/ui/TagInput/TagInput.test.tsx new file mode 100644 index 000000000000..6b1b8625567b --- /dev/null +++ b/airbyte-webapp/src/components/ui/TagInput/TagInput.test.tsx @@ -0,0 +1,95 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { useState } from "react"; + +import { TagInput } from "./TagInput"; + +const TagInputWithWrapper = () => { + const [fieldValue, setFieldValue] = useState(["tag1", "tag2"]); + return setFieldValue(values)} disabled={false} />; +}; + +describe("", () => { + it("renders with defaultValue", () => { + render(); + const tag1 = screen.getByText("tag1"); + const tag2 = screen.getByText("tag2"); + expect(tag1).toBeInTheDocument(); + expect(tag2).toBeInTheDocument(); + }); + + describe("delimiters and keypress events create tags", () => { + it("adds a tag when user types a tag and hits enter", () => { + render(); + const input = screen.getByRole("combobox"); + userEvent.type(input, "tag3{enter}"); + const tag3 = screen.getByText("tag3"); + expect(tag3).toBeInTheDocument(); + }); + it("adds a tag when user types a tag and hits tab", () => { + render(); + const input = screen.getByRole("combobox"); + userEvent.type(input, "tag3{Tab}"); + const tag3 = screen.getByText("tag3"); + expect(tag3).toBeInTheDocument(); + }); + + it("adds multiple tags when a user enters a string with commas", () => { + render(); + const input = screen.getByRole("combobox"); + userEvent.type(input, "tag3, tag4,"); + const tag3 = screen.getByText("tag3"); + expect(tag3).toBeInTheDocument(); + const tag4 = screen.getByText("tag4"); + expect(tag4).toBeInTheDocument(); + }); + it("adds multiple tags when a user enters a string with semicolons", () => { + render(); + const input = screen.getByRole("combobox"); + userEvent.type(input, "tag3; tag4;"); + const tag3 = screen.getByText("tag3"); + expect(tag3).toBeInTheDocument(); + const tag4 = screen.getByText("tag4"); + expect(tag4).toBeInTheDocument(); + }); + it("handles a combination of methods at once", () => { + render(); + const input = screen.getByRole("combobox"); + userEvent.type(input, "tag3; tag4{Tab} tag5, tag6{enter}"); + const tag3 = screen.getByText("tag3"); + expect(tag3).toBeInTheDocument(); + const tag4 = screen.getByText("tag4"); + expect(tag4).toBeInTheDocument(); + const tag5 = screen.getByText("tag5"); + expect(tag5).toBeInTheDocument(); + const tag6 = screen.getByText("tag6"); + expect(tag6).toBeInTheDocument(); + }); + }); + + it("correctly removes a tag when user clicks its Remove button", () => { + render(); + const tag1 = screen.getByText("tag1"); + expect(tag1).toBeInTheDocument(); + + const tag2 = screen.getByText("tag2"); + expect(tag2).toBeInTheDocument(); + + const input = screen.getByRole("combobox"); + userEvent.type(input, "tag3{enter}"); + const tag3 = screen.getByText("tag3"); + expect(tag3).toBeInTheDocument(); + const removeTag2Button = screen.getByRole("button", { name: "Remove tag2" }); + userEvent.click(removeTag2Button); + + const tag1again = screen.getByText("tag1"); + expect(tag1again).toBeInTheDocument(); + + // queryBy because getBy will throw if not in the DOM + const tag2again = screen.queryByText("tag2"); + expect(tag2again).not.toBeInTheDocument(); + + const tag3again = screen.getByText("tag3"); + expect(tag3again).toBeInTheDocument(); + }); +}); diff --git a/airbyte-webapp/src/components/ui/TagInput/TagInput.tsx b/airbyte-webapp/src/components/ui/TagInput/TagInput.tsx index 8f3f6123ffd3..0cccff6ad178 100644 --- a/airbyte-webapp/src/components/ui/TagInput/TagInput.tsx +++ b/airbyte-webapp/src/components/ui/TagInput/TagInput.tsx @@ -1,171 +1,136 @@ -import React, { useRef, useState } from "react"; -import styled from "styled-components"; - -import { TagItem, IItemProps } from "./TagItem"; - -const MainContainer = styled.div<{ error?: boolean; disabled?: boolean }>` - width: 100%; - min-height: 36px; - border-radius: 4px; - padding: 6px 6px 0; - cursor: text; - max-height: 100%; - overflow: auto; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-self: stretch; - border: 1px solid ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor0)}; - background: ${(props) => (props.error ? props.theme.greyColor10 : props.theme.greyColor0)}; - caret-color: ${({ theme }) => theme.primaryColor}; - - ${({ disabled, theme, error }) => - !disabled && - ` - &:hover { - background: ${theme.greyColor20}; - border-color: ${error ? theme.dangerColor : theme.greyColor20}; - } - `} - - &:focus, - &:focus-within { - background: ${({ theme }) => theme.primaryColor12}; - border-color: ${({ theme }) => theme.primaryColor}; - } -`; - -const InputElement = styled.input` - margin-bottom: 4px; - border: none; - outline: none; - font-size: 14px; - line-height: 20px; - font-weight: normal; - color: ${({ theme }) => theme.textColor}; - flex: 1 1 auto; - background: rgba(0, 0, 0, 0); - - &::placeholder { - color: ${({ theme }) => theme.greyColor40}; - } -`; - -export interface TagInputProps { - inputProps?: React.InputHTMLAttributes; - value: IItemProps[]; - className?: string; - validationRegex?: RegExp; +import { uniqueId } from "lodash"; +import { KeyboardEventHandler, useMemo, useState } from "react"; +import { ActionMeta, MultiValue, OnChangeValue } from "react-select"; +import CreatableSelect from "react-select/creatable"; + +import styles from "./TagInput.module.scss"; + +const components = { + DropdownIndicator: null, +}; + +const customStyles = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- react-select's typing is lacking here + multiValue: (provided: any) => ({ + ...provided, + maxWidth: "100%", + display: "flex", + background: `${styles.backgroundColor}`, + color: `${styles.fontColor}`, + borderRadius: `${styles.borderRadius}`, + paddingLeft: `${styles.paddingLeft}`, + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- same as above + multiValueLabel: (provided: any) => ({ + ...provided, + color: `${styles.fontColor}`, + fontWeight: 500, + }), + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- same as above + multiValueRemove: (provided: any) => ({ + ...provided, + borderRadius: `${styles.borderRadius}`, + }), +}; + +interface Tag { + readonly label: string; + readonly value: string; +} + +interface TagInputProps { + name: string; + fieldValue: string[]; + onChange: (value: string[]) => void; error?: boolean; - addOnBlur?: boolean; disabled?: boolean; - name?: string; - - onEnter: (value?: string | number | readonly string[]) => void; - onDelete: (value: string) => void; - onError?: () => void; + id?: string; } -export const TagInput: React.FC = ({ - inputProps, - onEnter, - value, - className, - onDelete, - validationRegex, - error, - disabled, - onError, - addOnBlur, - name, -}) => { - const inputElement = useRef(null); - const [selectedElementId, setSelectedElementId] = useState(""); - const [currentInputValue, setCurrentInputValue] = useState(""); - - const handleContainerBlur = () => setSelectedElementId(""); - const handleContainerClick = () => { - if (inputElement.current !== null) { - inputElement.current.focus(); - } - }; +const generateTagFromString = (inputValue: string): Tag => ({ + label: inputValue, + value: uniqueId(`tag_value_`), +}); - const onAddValue = () => { - if (!inputElement.current?.value) { - return; - } +const generateStringFromTag = (tag: Tag): string => tag.label; + +const delimiters = [",", ";"]; - const isValid = validationRegex ? !!inputElement.current?.value.match(validationRegex) : true; +export const TagInput: React.FC = ({ onChange, fieldValue, name, disabled, id }) => { + const tags = useMemo(() => fieldValue.map(generateTagFromString), [fieldValue]); - if (isValid) { - onEnter(currentInputValue); - setCurrentInputValue(""); - } else if (onError) { - onError(); + // input value is a tag draft + const [inputValue, setInputValue] = useState(""); + + // handles various ways of deleting a value + const handleDelete = (_value: OnChangeValue, actionMeta: ActionMeta) => { + let updatedTags: MultiValue = tags; + + /** + * remove-value: user clicked x or used backspace/delete to remove tag + * clear: user clicked big x to clear all tags + * pop-value: user clicked backspace to remove tag + */ + if (actionMeta.action === "remove-value") { + updatedTags = updatedTags.filter((tag) => tag.value !== actionMeta.removedValue.value); + } else if (actionMeta.action === "clear") { + updatedTags = []; + } else if (actionMeta.action === "pop-value") { + updatedTags = updatedTags.slice(0, updatedTags.length - 1); } + onChange(updatedTags.map((tag) => generateStringFromTag(tag))); }; - const handleInputKeyDown = (event: React.KeyboardEvent) => { - const { keyCode } = event; - - // on ENTER click - if (keyCode === 13) { - event.stopPropagation(); - event.preventDefault(); - onAddValue(); - - // on DELETE or BACKSPACE click when input is empty (select or delete last tag in valuesList) - } else if ((keyCode === 46 || keyCode === 8) && currentInputValue === "") { - if (selectedElementId !== "") { - const nextId = value.length - 1 > 0 ? value[value.length - 2].id : ""; - onDelete(selectedElementId); - setSelectedElementId(nextId); - } else if (value.length) { - setSelectedElementId(value[value.length - 1].id); + // handle when a user types OR pastes in the input + const handleInputChange = (inputValue: string) => { + setInputValue(inputValue); + + delimiters.forEach((delimiter) => { + if (inputValue.includes(delimiter)) { + const newTagStrings = inputValue + .split(delimiter) + .map((tag) => tag.trim()) + .filter(Boolean); + + inputValue.trim().length > 1 && onChange([...fieldValue, ...newTagStrings]); + setInputValue(""); } - } + }); }; - const handleInputBlur = () => { - if (addOnBlur) { - onAddValue(); + // handle when user presses keyboard keys in the input + const handleKeyDown: KeyboardEventHandler = (event) => { + if (!inputValue || !inputValue.length) { + return; + } + switch (event.key) { + case "Enter": + case "Tab": + inputValue.trim().length > 1 && onChange([...fieldValue, inputValue.trim()]); + + event.preventDefault(); + setInputValue(""); } }; - const inputPlaceholder = !value.length && inputProps?.placeholder ? inputProps.placeholder : ""; - return ( - - {value.map((item, key) => ( - - ))} - + { - setSelectedElementId(""); - setCurrentInputValue(event.target.value); - }} + components={components} + inputValue={inputValue} + isClearable + isMulti + onBlur={() => handleDelete} + menuIsOpen={false} + onChange={handleDelete} + onInputChange={handleInputChange} + onKeyDown={handleKeyDown} + value={tags} + isDisabled={disabled} + styles={customStyles} /> - +

    ); }; diff --git a/airbyte-webapp/src/components/ui/TagInput/TagItem.tsx b/airbyte-webapp/src/components/ui/TagInput/TagItem.tsx deleted file mode 100644 index 1cf6f6aeb5d9..000000000000 --- a/airbyte-webapp/src/components/ui/TagInput/TagItem.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const Tag = styled.div<{ isSelected?: boolean }>` - max-width: 100%; - display: flex; - background: ${({ theme }) => theme.mediumPrimaryColor}; - color: ${({ theme }) => theme.whiteColor}; - font-size: 12px; - line-height: 20px; - font-weight: 500; - border-radius: 4px; - padding-left: 6px; - margin: 0 5px 4px 0; - border: 2px solid ${({ theme, isSelected }) => (isSelected ? theme.primaryColor : theme.mediumPrimaryColor)}; -`; - -const Text = styled.div` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const Delete = styled.button` - border: none; - outline: none; - font-weight: 300; - font-size: 14px; - line-height: 24px; - cursor: pointer; - color: ${({ theme }) => theme.greyColor55}; - background: none; - text-decoration: none; - padding: 0 4px; - height: 20px; - &:hover { - color: ${({ theme }) => theme.greyColor40}; - } -`; - -interface IProps { - item: IItemProps; - isSelected?: boolean; - disabled?: boolean; - onDeleteTag: (id: string) => void; -} - -export interface IItemProps { - value: string; - id: string; -} - -export const TagItem: React.FC = ({ item, onDeleteTag, isSelected, disabled }) => { - const clickOnDeleteButton = () => onDeleteTag(item.id); - - return ( - - {item.value} - - - ); -}; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx index 44200986b6dc..e4f71bcd80b6 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx @@ -268,8 +268,8 @@ describe("Service Form", () => { const password = container.querySelector("input[name='connectionConfiguration.password']"); const message = container.querySelector("textarea[name='connectionConfiguration.message']"); const apiKey = container.querySelector("input[name='connectionConfiguration.credentials.api_key']"); - const emails = container.querySelector("input[name='connectionConfiguration.emails']"); const workTime = container.querySelector("div[name='connectionConfiguration.workTime']"); + const emails = screen.getByTestId("tag-input").querySelector("input"); userEvent.type(name!, "{selectall}{del}name"); userEvent.type(host!, "test-host"); @@ -303,8 +303,8 @@ describe("Service Form", () => { }); it("should fill right values in array of simple entity field", async () => { - const emails = container.querySelector("input[name='connectionConfiguration.emails']"); - userEvent.type(emails!, "test1@test.com{enter}test2@test.com{enter}test3@test.com"); + const emails = screen.getByTestId("tag-input").querySelector("input"); + userEvent.type(emails!, "test1@test.com{enter}test2@test.com{enter}test3@test.com{enter}"); const submit = container.querySelector("button[type='submit']"); await waitFor(() => userEvent.click(submit!)); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx index 2d9495133d20..54ec635623d7 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx @@ -1,11 +1,11 @@ -import { FieldArray, useField } from "formik"; +import { Field, useField } from "formik"; import React from "react"; import { DropDown } from "components/ui/DropDown"; import { Input } from "components/ui/Input"; import { Multiselect } from "components/ui/Multiselect"; import { SecretTextArea } from "components/ui/SecretTextArea"; -import { TagInput } from "components/ui/TagInput"; +import { TagInput } from "components/ui/TagInput/TagInput"; import { TextArea } from "components/ui/TextArea"; import { FormBaseItem } from "core/form/types"; @@ -32,27 +32,21 @@ export const Control: React.FC = ({ disabled, error, }) => { - const [field, meta, form] = useField(name); + const [field, meta, helpers] = useField(name); if (property.type === "array" && !property.enum) { return ( - ( + + {() => ( ({ - id, - value, - }))} - onEnter={(newItem) => arrayHelpers.push(newItem)} - onDelete={(item) => arrayHelpers.remove(Number.parseInt(item))} - addOnBlur - error={!!meta.error} + fieldValue={field.value || []} + onChange={(tagLabels) => helpers.setValue(tagLabels)} + // error={!!meta.error} disabled={disabled} /> )} - /> + ); } @@ -65,7 +59,7 @@ export const Control: React.FC = ({ form.setValue(dataItems)} + onChange={(dataItems) => helpers.setValue(dataItems)} value={field.value} disabled={disabled} /> @@ -81,7 +75,7 @@ export const Control: React.FC = ({ label: dataItem?.toString() ?? "", value: dataItem?.toString() ?? "", }))} - onChange={(selectedItem) => selectedItem && form.setValue(selectedItem.value)} + onChange={(selectedItem) => selectedItem && helpers.setValue(selectedItem.value)} value={value} isDisabled={disabled} /> @@ -120,12 +114,12 @@ export const Control: React.FC = ({ onDone={() => removeUnfinishedFlow(name)} onStart={() => { addUnfinishedFlow(name, { startValue: field.value }); - form.setValue(""); + helpers.setValue(""); }} onCancel={() => { removeUnfinishedFlow(name); if (unfinishedSecret && unfinishedSecret.hasOwnProperty("startValue")) { - form.setValue(unfinishedSecret.startValue); + helpers.setValue(unfinishedSecret.startValue); } }} disabled={disabled} From 22c55b54f5cbe1b79e5fe8225c16280cc9c29d5f Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Fri, 14 Oct 2022 12:25:08 -0400 Subject: [PATCH 114/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=A7=B9=20Remove=20?= =?UTF-8?q?connection=20and=20mode=20prop=20drilling=20on=20connection=20v?= =?UTF-8?q?iew/edit=20pages=20(#17808)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * normalization field to scss and remove mode * remove instances of mode and connection being passed * connectionsettingstab tests pass * fix validation on blur/validation on change * redo broken snapshot * add validateOnBlur back * cleanup, migrate transformation tab to scss modules * update snapshot after rebase * update snapshot --- .../CreateConnection/CreateConnectionForm.tsx | 1 + .../ConnectionItemPage/ConnectionItemPage.tsx | 11 +-- .../ConnectionReplicationTab.tsx | 1 + .../ConnectionSettingsTab.test.tsx | 17 ++--- .../ConnectionSettingsTab.tsx | 9 +-- .../ConnectionStatusTab.tsx | 10 +-- .../ConnectionTransformationTab.module.scss | 14 ++++ .../ConnectionTransformationTab.tsx | 74 +++++-------------- .../components/NormalizationField.module.scss | 5 ++ .../components/NormalizationField.tsx | 21 ++---- .../src/views/Connection/FormCard.module.scss | 5 ++ .../src/views/Connection/FormCard.tsx | 14 ++-- 12 files changed, 75 insertions(+), 107 deletions(-) create mode 100644 airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss create mode 100644 airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.module.scss create mode 100644 airbyte-webapp/src/views/Connection/FormCard.module.scss diff --git a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx index 56612c8eaf93..01c12a7fed1e 100644 --- a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx +++ b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx @@ -103,6 +103,7 @@ const CreateConnectionFormInner: React.FC = ({ schem initialValues={initialValues} validationSchema={connectionValidationSchema(mode)} onSubmit={onFormSubmit} + validateOnChange={false} > {({ values, isSubmitting, isValid, dirty }) => (
    diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx index fb4d5654f631..6daba67181b9 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx @@ -45,17 +45,12 @@ export const ConnectionItemPageInner: React.FC = () => { > }> - } /> + } /> } /> - } - /> + } /> : - } + element={isConnectionDeleted ? : } /> } /> diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx index c30abf287aa2..d5544c366499 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx @@ -150,6 +150,7 @@ export const ConnectionReplicationTab: React.FC = () => { validationSchema={connectionValidationSchema(mode)} onSubmit={onFormSubmit} enableReinitialize + validateOnChange={false} > {({ values, isSubmitting, isValid, dirty, resetForm }) => ( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx index 665b728cecff..79f0f5b8e56e 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx @@ -1,4 +1,4 @@ -import { render, mockConnection } from "test-utils/testutils"; +import { mockConnection, render } from "test-utils/testutils"; import { ConnectionSettingsTab } from "./ConnectionSettingsTab"; @@ -23,13 +23,10 @@ jest.mock("hooks/services/Analytics/useAnalyticsService", () => { return analyticsService; }); -// Mocking the DeleteBlock component is a bit ugly, but it's simpler and less -// brittle than mocking the providers it depends on; at least it's a direct, -// visible dependency of the component under test here. -// -// This mock is intentionally trivial; if anything to do with this component is -// to be tested, we'll have to bite the bullet and render it properly, within -// the necessary providers. +jest.mock("hooks/services/ConnectionEdit/ConnectionEditService", () => ({ + useConnectionEditService: () => ({ connection: mockConnection }), +})); + jest.mock("components/DeleteBlock", () => () => { const MockDeleteBlock = () =>
    Does not actually delete anything
    ; return ; @@ -40,11 +37,11 @@ describe("", () => { let container: HTMLElement; setMockIsAdvancedMode(false); - ({ container } = await render()); + ({ container } = await render()); expect(container.textContent).not.toContain("Connection State"); setMockIsAdvancedMode(true); - ({ container } = await render()); + ({ container } = await render()); expect(container.textContent).toContain("Connection State"); }); }); diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx index 0fbe9c03c2a0..b28518c7cfad 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx @@ -2,19 +2,16 @@ import React from "react"; import DeleteBlock from "components/DeleteBlock"; -import { WebBackendConnectionRead } from "core/request/AirbyteClient"; import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics"; +import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService"; import { useAdvancedModeSetting } from "hooks/services/useAdvancedModeSetting"; import { useDeleteConnection } from "hooks/services/useConnectionHook"; import styles from "./ConnectionSettingsTab.module.scss"; import { StateBlock } from "./StateBlock"; -interface ConnectionSettingsTabProps { - connection: WebBackendConnectionRead; -} - -export const ConnectionSettingsTab: React.FC = ({ connection }) => { +export const ConnectionSettingsTab: React.FC = () => { + const { connection } = useConnectionEditService(); const { mutateAsync: deleteConnection } = useDeleteConnection(); const [isAdvancedMode] = useAdvancedModeSetting(); diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx index 0efa2a215992..ad492856ea66 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx @@ -13,10 +13,11 @@ import { Tooltip } from "components/ui/Tooltip"; import { Action, Namespace } from "core/analytics"; import { getFrequencyFromScheduleData } from "core/analytics/utils"; -import { ConnectionStatus, JobWithAttemptsRead, WebBackendConnectionRead } from "core/request/AirbyteClient"; +import { ConnectionStatus, JobWithAttemptsRead } from "core/request/AirbyteClient"; import Status from "core/statuses"; import { useTrackPage, PageTrackingCodes, useAnalyticsService } from "hooks/services/Analytics"; import { useConfirmationModalService } from "hooks/services/ConfirmationModal"; +import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService"; import { FeatureItem, useFeature } from "hooks/services/Feature"; import { useResetConnection, useSyncConnection } from "hooks/services/useConnectionHook"; import { useCancelJob, useListJobs } from "services/job/JobService"; @@ -37,10 +38,6 @@ interface ActiveJob { isCanceling: boolean; } -interface ConnectionStatusTabProps { - connection: WebBackendConnectionRead; -} - const getJobRunningOrPending = (jobs: JobWithAttemptsRead[]) => { return jobs.find((jobWithAttempts) => { const jobStatus = jobWithAttempts?.job?.status; @@ -48,7 +45,8 @@ const getJobRunningOrPending = (jobs: JobWithAttemptsRead[]) => { }); }; -export const ConnectionStatusTab: React.FC = ({ connection }) => { +export const ConnectionStatusTab: React.FC = () => { + const { connection } = useConnectionEditService(); useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_STATUS); const [activeJob, setActiveJob] = useState(); const [jobPageSize, setJobPageSize] = useState(JOB_PAGE_SIZE_INCREMENT); diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss new file mode 100644 index 000000000000..6903e64a247b --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss @@ -0,0 +1,14 @@ +.content { + max-width: 1073px; + margin: 0 auto; + padding-bottom: 10px; +} + +.customCard { + max-width: 500px; + margin: 0 auto; + min-height: 100px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx index 170ef75a7e9c..41752ba8ffe9 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx @@ -2,23 +2,16 @@ import { Field, FieldArray } from "formik"; import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useToggle } from "react-use"; -import styled from "styled-components"; import { Card } from "components/ui/Card"; import { Text } from "components/ui/Text"; -import { buildConnectionUpdate, NormalizationType } from "core/domain/connection"; -import { - ConnectionStatus, - OperationCreate, - OperationRead, - OperatorType, - WebBackendConnectionRead, -} from "core/request/AirbyteClient"; +import { NormalizationType } from "core/domain/connection"; +import { OperationCreate, OperationRead, OperatorType } from "core/request/AirbyteClient"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; -import { ConnectionFormMode } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { FeatureItem, useFeature } from "hooks/services/Feature"; -import { useUpdateConnection } from "hooks/services/useConnectionHook"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; import { useGetDestinationDefinitionSpecification } from "services/connector/DestinationDefinitionSpecificationService"; import { FormikOnSubmit } from "types/formik"; @@ -31,32 +24,14 @@ import { } from "views/Connection/ConnectionForm/formConfig"; import { FormCard } from "views/Connection/FormCard"; -interface ConnectionTransformationTabProps { - connection: WebBackendConnectionRead; -} - -const Content = styled.div` - max-width: 1073px; - margin: 0 auto; - padding-bottom: 10px; -`; - -const NoSupportedTransformationCard = styled(Card)` - max-width: 500px; - margin: 0 auto; - min-height: 100px; - display: flex; - justify-content: center; - align-items: center; -`; +import styles from "./ConnectionTransformationTab.module.scss"; const CustomTransformationsCard: React.FC<{ operations?: OperationCreate[]; onSubmit: FormikOnSubmit<{ transformations?: OperationRead[] }>; - mode: ConnectionFormMode; -}> = ({ operations, onSubmit, mode }) => { +}> = ({ operations, onSubmit }) => { const [editingTransformation, toggleEditingTransformation] = useToggle(false); - + const { mode } = useConnectionFormService(); const initialValues = useMemo( () => ({ transformations: getInitialTransformations(operations || []), @@ -75,7 +50,6 @@ const CustomTransformationsCard: React.FC<{ onSubmit, }} submitDisabled={editingTransformation} - mode={mode} > {(formProps) => ( @@ -94,8 +68,8 @@ const CustomTransformationsCard: React.FC<{ const NormalizationCard: React.FC<{ operations?: OperationRead[]; onSubmit: FormikOnSubmit<{ normalization?: NormalizationType }>; - mode: ConnectionFormMode; -}> = ({ operations, onSubmit, mode }) => { +}> = ({ operations, onSubmit }) => { + const { mode } = useConnectionFormService(); const initialValues = useMemo( () => ({ normalization: getInitialNormalization(operations, true), @@ -111,24 +85,22 @@ const NormalizationCard: React.FC<{ }} title={} collapsible - mode={mode} > ); }; -export const ConnectionTransformationTab: React.FC = ({ connection }) => { +export const ConnectionTransformationTab: React.FC = () => { + const { connection, updateConnection } = useConnectionEditService(); + const { mode } = useConnectionFormService(); const definition = useGetDestinationDefinitionSpecification(connection.destination.destinationDefinitionId); - const { mutateAsync: updateConnection } = useUpdateConnection(); const workspace = useCurrentWorkspace(); useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_TRANSFORMATION); const { supportsNormalization } = definition; const supportsDbt = useFeature(FeatureItem.AllowCustomDBT) && definition.supportsDbt; - const mode = connection.status === ConnectionStatus.deprecated ? "readonly" : "edit"; - const onSubmit: FormikOnSubmit<{ transformations?: OperationRead[]; normalization?: NormalizationType }> = async ( values, { resetForm } @@ -143,11 +115,7 @@ export const ConnectionTransformationTab: React.FC op.operatorConfiguration.operatorType === OperatorType.dbt) ); - await updateConnection( - buildConnectionUpdate(connection, { - operations, - }) - ); + await updateConnection({ connectionId: connection.connectionId, operations }); const nextFormValues: typeof values = {}; if (values.transformations) { @@ -159,25 +127,21 @@ export const ConnectionTransformationTab: React.FC +
    - {supportsNormalization && ( - - )} - {supportsDbt && ( - - )} + {supportsNormalization && } + {supportsDbt && } {!supportsNormalization && !supportsDbt && ( - + - + )}
    - +
    ); }; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.module.scss b/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.module.scss new file mode 100644 index 000000000000..044bcdd2bf93 --- /dev/null +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.module.scss @@ -0,0 +1,5 @@ +@use "../../../../scss/variables"; + +.normalizationField { + margin: variables.$spacing-lg 0; +} diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.tsx index 158c7811df18..b7173eb33fb0 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/NormalizationField.tsx @@ -1,25 +1,22 @@ import { FieldProps } from "formik"; import React from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; import { LabeledRadioButton, Link } from "components"; import { NormalizationType } from "core/domain/connection/operation"; -import { ConnectionFormMode } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { links } from "utils/links"; -const Normalization = styled.div` - margin: 16px 0; -`; +import styles from "./NormalizationField.module.scss"; -type NormalizationBlockProps = FieldProps & { - mode: ConnectionFormMode; -}; +type NormalizationBlockProps = FieldProps; + +export const NormalizationField: React.FC = ({ form, field }) => { + const { mode } = useConnectionFormService(); -const NormalizationField: React.FC = ({ form, field, mode }) => { return ( - +
    = ({ form, field, mo ) } /> - +
    ); }; - -export { NormalizationField }; diff --git a/airbyte-webapp/src/views/Connection/FormCard.module.scss b/airbyte-webapp/src/views/Connection/FormCard.module.scss new file mode 100644 index 000000000000..bcb8d01884c5 --- /dev/null +++ b/airbyte-webapp/src/views/Connection/FormCard.module.scss @@ -0,0 +1,5 @@ +@use "../../scss/variables"; + +.formCard { + padding: 22px 27px variables.$spacing-xl 24px; +} diff --git a/airbyte-webapp/src/views/Connection/FormCard.tsx b/airbyte-webapp/src/views/Connection/FormCard.tsx index 9de05d1d3a9a..1df3f5c02954 100644 --- a/airbyte-webapp/src/views/Connection/FormCard.tsx +++ b/airbyte-webapp/src/views/Connection/FormCard.tsx @@ -2,23 +2,19 @@ import { Form, Formik, FormikConfig, FormikHelpers } from "formik"; import React from "react"; import { useIntl } from "react-intl"; import { useMutation } from "react-query"; -import styled from "styled-components"; import { FormChangeTracker } from "components/FormChangeTracker"; -import { ConnectionFormMode } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { generateMessageFromError } from "utils/errorStatusMessage"; import { CollapsibleCardProps, CollapsibleCard } from "views/Connection/CollapsibleCard"; import EditControls from "views/Connection/ConnectionForm/components/EditControls"; -const FormContainer = styled(Form)` - padding: 22px 27px 15px 24px; -`; +import styles from "./FormCard.module.scss"; interface FormCardProps extends CollapsibleCardProps { bottomSeparator?: boolean; form: FormikConfig; - mode?: ConnectionFormMode; submitDisabled?: boolean; } @@ -26,11 +22,11 @@ export const FormCard = ({ children, form, bottomSeparator = true, - mode, submitDisabled, ...props }: React.PropsWithChildren>) => { const { formatMessage } = useIntl(); + const { mode } = useConnectionFormService(); const { mutateAsync, error, reset, isSuccess } = useMutation< void, @@ -46,7 +42,7 @@ export const FormCard = ({ mutateAsync({ values, formikHelpers })}> {({ resetForm, isSubmitting, dirty, isValid }) => ( - + {children}
    @@ -67,7 +63,7 @@ export const FormCard = ({ /> )}
    -
    +
    )}
    From c05135b336e3a8c45dfefca340d0c5b261c7a4f0 Mon Sep 17 00:00:00 2001 From: Adam Fletcher Date: Fri, 14 Oct 2022 10:14:28 -0700 Subject: [PATCH 115/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Destination:=20bit?= =?UTF-8?q?.io=20(#15821)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tests pass * solve conflict * Add readme * add bootstrap.md * solve doc conflict * update spec * fix spec test * add bitdotio icon * change dockerfile version * add releaseStage for tidb * fix icon name * correct conflict * remove dest def for bit.io * run config process seed Co-authored-by: marcosmarxm Co-authored-by: Marcos Marx --- .../NormalizationRunnerFactory.java | 1 + .../src/main/resources/icons/bitdotio.svg | 40 +++ .../seed/destination_definitions.yaml | 7 + .../resources/seed/destination_specs.yaml | 50 ++++ airbyte-integrations/builds.md | 1 + .../destination-bitdotio/.dockerignore | 3 + .../destination-bitdotio/Dockerfile | 20 ++ .../connectors/destination-bitdotio/README.md | 28 +++ .../destination-bitdotio/bootstrap.md | 18 ++ .../destination-bitdotio/build.gradle | 25 ++ .../postgres/BitDotIoDestination.java | 68 ++++++ .../BitDotIoDestinationAcceptanceTest.java | 228 ++++++++++++++++++ .../postgres/BitDotIoDestinationTest.java | 22 ++ .../src/test/resources/expected_spec.json | 53 ++++ docs/integrations/README.md | 3 +- docs/integrations/destinations/bitdotio.md | 51 ++++ 16 files changed, 617 insertions(+), 1 deletion(-) create mode 100644 airbyte-config/init/src/main/resources/icons/bitdotio.svg create mode 100644 airbyte-integrations/connectors/destination-bitdotio/.dockerignore create mode 100644 airbyte-integrations/connectors/destination-bitdotio/Dockerfile create mode 100644 airbyte-integrations/connectors/destination-bitdotio/README.md create mode 100644 airbyte-integrations/connectors/destination-bitdotio/bootstrap.md create mode 100644 airbyte-integrations/connectors/destination-bitdotio/build.gradle create mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java create mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java create mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json create mode 100644 docs/integrations/destinations/bitdotio.md diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java index cc1530d3fd49..7ba32daf18d1 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java @@ -30,6 +30,7 @@ public class NormalizationRunnerFactory { .put("airbyte/destination-oracle", ImmutablePair.of("airbyte/normalization-oracle", DestinationType.ORACLE)) .put("airbyte/destination-oracle-strict-encrypt", ImmutablePair.of("airbyte/normalization-oracle", DestinationType.ORACLE)) .put("airbyte/destination-postgres", ImmutablePair.of(BASE_NORMALIZATION_IMAGE_NAME, DestinationType.POSTGRES)) + .put("airbyte/destination-bitdotio", ImmutablePair.of(BASE_NORMALIZATION_IMAGE_NAME, DestinationType.POSTGRES)) .put("airbyte/destination-postgres-strict-encrypt", ImmutablePair.of(BASE_NORMALIZATION_IMAGE_NAME, DestinationType.POSTGRES)) .put("airbyte/destination-redshift", ImmutablePair.of("airbyte/normalization-redshift", DestinationType.REDSHIFT)) .put("airbyte/destination-snowflake", ImmutablePair.of("airbyte/normalization-snowflake", DestinationType.SNOWFLAKE)) diff --git a/airbyte-config/init/src/main/resources/icons/bitdotio.svg b/airbyte-config/init/src/main/resources/icons/bitdotio.svg new file mode 100644 index 000000000000..aef56c6946ef --- /dev/null +++ b/airbyte-config/init/src/main/resources/icons/bitdotio.svg @@ -0,0 +1,40 @@ + + + + + + + + diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 71c2d7305fd1..8bfe95fa55d1 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -50,6 +50,13 @@ memory_limit: "1Gi" memory_request: "1Gi" releaseStage: beta +- name: bit.io + destinationDefinitionId: 592af2e0-882c-4ed8-b0fe-a31761146a8d + dockerRepository: airbyte/destination-bitdotio + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.io/integrations/destinations/bitdotio + icon: bitdotio.svg + releaseStage: alpha - name: Cassandra destinationDefinitionId: 707456df-6f4f-4ced-b5c6-03f73bcad1c5 dockerRepository: airbyte/destination-cassandra diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 0c3794a58c19..bf49a62337d8 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -693,6 +693,56 @@ supported_destination_sync_modes: - "overwrite" - "append" +- dockerImage: "airbyte/destination-bitdotio:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.io/integrations/destinations/bitdotio" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Postgres Destination Spec" + type: "object" + required: + - "database" + - "username" + - "password" + additionalProperties: true + properties: + database: + title: "DB Name" + description: "Name of the database." + type: "string" + order: 2 + schema: + title: "Default Schema" + description: "The default schema tables are written to if the source does\ + \ not specify a namespace. The usual value for this field is \"public\"\ + ." + type: "string" + examples: + - "public" + default: "public" + order: 3 + username: + title: "User" + description: "Username to use to access the database." + type: "string" + order: 4 + password: + title: "Password" + description: "Password associated with the username." + type: "string" + airbyte_secret: true + order: 5 + ssl: "true" + host: "db.bit.io" + ssl_mode: "require" + port: "5432" + supportsIncremental: true + supportsNormalization: true + supportsDBT: true + supported_destination_sync_modes: + - "overwrite" + - "append" + - "append_dedup" - dockerImage: "airbyte/destination-cassandra:0.1.4" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/cassandra" diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 7af75b786c27..9d72e65aa1f1 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -131,6 +131,7 @@ | Azure Blob Storage | [![destination-azure-blob-storage](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-azure-blob-storage%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-azure-blob-storage) | | BigQuery | [![destination-bigquery](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-bigquery%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-bigquery) | | BigQuery Denormalized | [![destination-bigquery-denormalized](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-bigquery-denormalized%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-bigquery-denormalized) | +| bit.io | [![destination-bitdotio](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-bitdotio%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-bitdotio) | | ClickHouse | [![destination-clickhouse](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-clickhouse%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-clickhouse) | | Cassandra | [![destination-cassandra](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-cassandra%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-cassandra) | | Databricks | [![destination-databricks](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-databricks%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-databricks) | diff --git a/airbyte-integrations/connectors/destination-bitdotio/.dockerignore b/airbyte-integrations/connectors/destination-bitdotio/.dockerignore new file mode 100644 index 000000000000..65c7d0ad3e73 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/.dockerignore @@ -0,0 +1,3 @@ +* +!Dockerfile +!build diff --git a/airbyte-integrations/connectors/destination-bitdotio/Dockerfile b/airbyte-integrations/connectors/destination-bitdotio/Dockerfile new file mode 100644 index 000000000000..8bf1c694a354 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/Dockerfile @@ -0,0 +1,20 @@ +FROM airbyte/integration-base-java:dev AS build + +WORKDIR /airbyte + +ENV APPLICATION destination-bitdotio + +COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar + +RUN tar xf ${APPLICATION}.tar --strip-components=1 && rm -rf ${APPLICATION}.tar + +FROM airbyte/integration-base-java:dev + +WORKDIR /airbyte + +ENV APPLICATION destination-bitdotio + +COPY --from=build /airbyte /airbyte + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/destination-bitdotio diff --git a/airbyte-integrations/connectors/destination-bitdotio/README.md b/airbyte-integrations/connectors/destination-bitdotio/README.md new file mode 100644 index 000000000000..d3ec6022056b --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/README.md @@ -0,0 +1,28 @@ +bit.io Destination +================== +This allows the use of the bit.io service as a destination in Airbyte. + +The bit.io destination is a `SpecModifiyingDestination` that hardcodes the host, port, and +ssl options from the `PostgresDestination`. + +Testing +------- + +To test you need a bit.io user with an empty database. + +Then, create `secrets/credentials.json` + +``` +{ + "username": "", + "database": "", + "connect_password": "" +} +``` + +Then you can run the standard set of gradle-driven tests. + +NOTE THAT THE DATABASE CONTENTS WILL BE DESTROYED BY TESTS. So don't use a database +with anythign in it. + + diff --git a/airbyte-integrations/connectors/destination-bitdotio/bootstrap.md b/airbyte-integrations/connectors/destination-bitdotio/bootstrap.md new file mode 100644 index 000000000000..0f60ccb96299 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/bootstrap.md @@ -0,0 +1,18 @@ +# bit.io + +## Overview + +bit.io is a serverless, shareable cloud Postgres database. + +## Endpoints + +This destination connector uses the Postgres protocol. + +## Quick Notes + +- bit.io doesn't support `CREATE DATABASE` + +- This connectors primary change is to hardcode the hostname to `db.bit.io`, the port to `5432`, and the `sslmode` to `require` + + + diff --git a/airbyte-integrations/connectors/destination-bitdotio/build.gradle b/airbyte-integrations/connectors/destination-bitdotio/build.gradle new file mode 100644 index 000000000000..768f5b45ed3b --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'application' + id 'airbyte-docker' + id 'airbyte-integration-test-java' +} + +application { + mainClass = 'io.airbyte.integrations.destination.postgres.BitDotIoDestination' + applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0'] +} + +dependencies { + implementation project(':airbyte-db:db-lib') + implementation project(':airbyte-integrations:bases:base-java') + implementation project(':airbyte-protocol:protocol-models') + implementation project(':airbyte-integrations:connectors:destination-jdbc') + implementation project(':airbyte-integrations:connectors:destination-postgres') + + integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') + + integrationTestJavaImplementation libs.connectors.testcontainers.postgresql + + implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) + integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-normalization').airbyteDocker.outputs) +} diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java b/airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java new file mode 100644 index 000000000000..092171ad6ecb --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java @@ -0,0 +1,68 @@ +/* * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ +package io.airbyte.integrations.destination.postgres; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode ; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.airbyte.commons.json.Jsons; +import io.airbyte.db.jdbc.JdbcUtils; +import io.airbyte.integrations.base.Destination; +import io.airbyte.integrations.base.IntegrationRunner; +import io.airbyte.integrations.base.spec_modification.SpecModifyingDestination; +import io.airbyte.integrations.destination.postgres.PostgresDestination; +import io.airbyte.protocol.models.ConnectorSpecification; +import java.net.URI; +import java.net.URISyntaxException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BitDotIoDestination extends SpecModifyingDestination implements Destination { + + private static final Logger LOGGER = LoggerFactory.getLogger(BitDotIoDestination.class); + + public BitDotIoDestination() { + super(new PostgresDestination()); + } + + @Override + public ConnectorSpecification modifySpec(final ConnectorSpecification originalSpec) { + final ConnectorSpecification spec = Jsons.clone(originalSpec); + String json = "[ \"database\", \"username\", \"password\"] "; + + ObjectMapper objectMapper = new ObjectMapper(); + + JsonNode jsonNode; + try { + jsonNode = objectMapper.readTree(json); + spec.setDocumentationUrl(new URI("https://docs.airbyte.io/integrations/destinations/bitdotio")); + ((ObjectNode) spec.getConnectionSpecification()).replace("required", jsonNode); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.PORT_LIST_KEY); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.SSL_KEY); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.HOST_KEY); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.PORT_KEY); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.JDBC_URL_PARAMS_KEY); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.SSL_MODE_KEY); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.SSL_KEY, "true"); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.HOST_KEY, "db.bit.io"); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.SSL_MODE_KEY, "require"); + ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.PORT_KEY, "5432"); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } catch (URISyntaxException e) { + // This should never happen. + e.printStackTrace(); + } + + return spec; + } + + public static void main(final String[] args) throws Exception { + final Destination destination = new BitDotIoDestination(); + LOGGER.info("starting destination: {}", BitDotIoDestination.class); + new IntegrationRunner(destination).run(args); + LOGGER.info("completed destination: {}", BitDotIoDestination.class); + } + +} diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java new file mode 100644 index 000000000000..d0b46ece172a --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.postgres; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; +import io.airbyte.commons.json.Jsons; +import io.airbyte.db.Database; +import io.airbyte.db.factory.DSLContextFactory; +import io.airbyte.db.factory.DatabaseDriver; +import io.airbyte.db.jdbc.JdbcUtils; +import io.airbyte.integrations.base.JavaBaseConstants; +import io.airbyte.integrations.destination.ExtendedNameTransformer; +import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Collectors; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +import org.jooq.DSLContext; +import org.jooq.SQLDialect; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BitDotIoDestinationAcceptanceTest extends DestinationAcceptanceTest { + public class BitDotIoConfig { + private String username = ""; + private String database = ""; + private String password = ""; + public String getUsername() { + return username; + } + public String getDatabase() { + return database; + } + public String getPassword() { + return password; + } + public BitDotIoConfig(String username, String database, String password) + { + this.username = username; + this.database = database; + this.password = password; + } + + public String getJdbcUrl() { + String jdbcUrl = ""; + try { + jdbcUrl = "jdbc:postgresql://db.bit.io:5432" + "/" + URLEncoder.encode(database, "UTF-8") + "?sslmode=require"; + } catch (UnsupportedEncodingException e) { + // Should never happen + e.printStackTrace(); + } + return jdbcUrl; + } + } + + + private static final Logger LOGGER = LoggerFactory.getLogger(BitDotIoDestinationAcceptanceTest.class); + private BitDotIoConfig cfg; + private final ExtendedNameTransformer namingResolver = new ExtendedNameTransformer(); + + protected static final Path CREDENTIALS_PATH = Path.of("secrets/credentials.json"); + + protected static final String CONFIG_BITIO_USERNAME = "username"; + protected static final String CONFIG_BITIO_DATABASE = "database"; + protected static final String CONFIG_BITIO_CONNECT_PASSWORD = "connect_password"; + + @Override + protected String getImageName() { + return "airbyte/destination-bitdotio:dev"; + } + + @Override + protected JsonNode getConfig() { + + return Jsons.jsonNode(ImmutableMap.builder() + .put(JdbcUtils.HOST_KEY, "db.bit.io") + .put(JdbcUtils.PORT_KEY, 5432) + .put(JdbcUtils.SCHEMA_KEY, "public") + .put(JdbcUtils.USERNAME_KEY, cfg.getUsername()) + .put(JdbcUtils.PASSWORD_KEY, cfg.getPassword()) + .put(JdbcUtils.DATABASE_KEY, cfg.getDatabase()) + .put(JdbcUtils.SSL_KEY, true ) + .put("sslmode", "require" ) + .put("ssl_mode", ImmutableMap.builder().put("mode", "require").build()) + .build()); + } + + @Override + protected JsonNode getFailCheckConfig() { + return Jsons.jsonNode(ImmutableMap.builder() + .put(JdbcUtils.HOST_KEY, "db.bit.io") + .put(JdbcUtils.PORT_KEY, 5432) + .put(JdbcUtils.SCHEMA_KEY, "public") + .put(JdbcUtils.USERNAME_KEY, cfg.getUsername()) + .put(JdbcUtils.PASSWORD_KEY, "wrong password") + .put(JdbcUtils.DATABASE_KEY, cfg.getDatabase()) + .put(JdbcUtils.SSL_KEY, true) + .put("sslmode", "require" ) + .put("ssl_mode", ImmutableMap.builder().put("mode", "require").build()) + .build()); + } + + @Override + protected List retrieveRecords(final TestDestinationEnv env, + final String streamName, + final String namespace, + final JsonNode streamSchema) + throws Exception { + return retrieveRecordsFromTable(namingResolver.getRawTableName(streamName), namespace) + .stream() + .map(r -> Jsons.deserialize(r.get(JavaBaseConstants.COLUMN_NAME_DATA).asText())) + .collect(Collectors.toList()); + } + + @Override + protected boolean supportsNormalization() { + return true; + } + + @Override + protected boolean supportsDBT() { + return true; + } + + @Override + protected boolean implementsNamespaces() { + return true; + } + + @Override + protected List retrieveNormalizedRecords(final TestDestinationEnv env, final String streamName, final String namespace) + throws Exception { + final String tableName = namingResolver.getIdentifier(streamName); + return retrieveRecordsFromTable(tableName, namespace); + } + + @Override + protected List resolveIdentifier(final String identifier) { + final List result = new ArrayList<>(); + final String resolved = namingResolver.getIdentifier(identifier); + result.add(identifier); + result.add(resolved); + if (!resolved.startsWith("\"")) { + result.add(resolved.toLowerCase()); + result.add(resolved.toUpperCase()); + } + return result; + } + + private List retrieveRecordsFromTable(final String tableName, final String schemaName) throws SQLException { + try (final DSLContext dslContext = DSLContextFactory.create( + cfg.getUsername(), + cfg.getPassword(), + DatabaseDriver.POSTGRESQL.getDriverClassName(), + cfg.getJdbcUrl(), + SQLDialect.POSTGRES)) { + return new Database(dslContext) + .query( + ctx -> ctx + .fetch(String.format("SELECT * FROM %s.%s ORDER BY %s ASC;", schemaName, tableName, JavaBaseConstants.COLUMN_NAME_EMITTED_AT)) + .stream() + .map(r -> r.formatJSON(JdbcUtils.getDefaultJSONFormat())) + .map(Jsons::deserialize) + .collect(Collectors.toList())); + } + } + @Override + protected void setup(final TestDestinationEnv testEnv) throws Exception { + if (!Files.exists(CREDENTIALS_PATH)) { + throw new IllegalStateException( + "Must provide path to a bit.io query credentials file. By default {module-root}/" + CREDENTIALS_PATH + + ". Override by setting setting path with the CREDENTIALS_PATH constant."); + } + + final String fullConfigAsString = Files.readString(CREDENTIALS_PATH); + final JsonNode credentialsJson = Jsons.deserialize(fullConfigAsString); + final String username = credentialsJson.get(CONFIG_BITIO_USERNAME).asText(); + final String database = credentialsJson.get(CONFIG_BITIO_DATABASE).asText(); + final String password = credentialsJson.get(CONFIG_BITIO_CONNECT_PASSWORD).asText(); + + this.cfg = new BitDotIoConfig(username, database, password) ; + } + @Override + protected void tearDown(final TestDestinationEnv testEnv) { + try (final DSLContext dslContext = DSLContextFactory.create( + cfg.getUsername(), + cfg.getPassword(), + DatabaseDriver.POSTGRESQL.getDriverClassName(), + cfg.getJdbcUrl(), + SQLDialect.POSTGRES)) { + + Database db = new Database(dslContext); + List tables = db.query( + ctx -> ctx + .fetch( "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema not IN ('pg_catalog', 'information_schema');") + .stream() + .map(r -> r.formatJSON(JdbcUtils.getDefaultJSONFormat())) + .map(Jsons::deserialize) + .collect(Collectors.toList())); + for (JsonNode node : tables) { + db.query(ctx -> ctx.fetch(String.format("DROP TABLE IF EXISTS %s CASCADE", node.get("table_name")))); + } + List schemas = db.query( + ctx -> ctx + .fetch( "SELECT DISTINCT table_schema FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema not IN ('public', 'pg_catalog', 'information_schema');") + .stream() + .map(r -> r.formatJSON(JdbcUtils.getDefaultJSONFormat())) + .map(Jsons::deserialize) + .collect(Collectors.toList())); + for (JsonNode node : schemas) { + db.query(ctx -> ctx.fetch(String.format("DROP SCHEMA IF EXISTS %s CASCADE", node.get("table_schema")))); + } + } catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + LOGGER.info("Finished acceptance test for bit.io"); + } +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java b/airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java new file mode 100644 index 000000000000..d470188acc90 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.postgres; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.resources.MoreResources; +import io.airbyte.protocol.models.ConnectorSpecification; +import org.junit.jupiter.api.Test; + +class BitDotIoDestinationTest { + + @Test + void testGetSpec() throws Exception { + assertEquals(Jsons.deserialize(MoreResources.readResource("expected_spec.json"), ConnectorSpecification.class), + new BitDotIoDestination().spec()); + } + +} diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json new file mode 100644 index 000000000000..ca0f9bf4b315 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json @@ -0,0 +1,53 @@ +{ + "documentationUrl": "https://docs.airbyte.io/integrations/destinations/bitdotio", + "supportsIncremental": true, + "supportsNormalization": true, + "supportsDBT": true, + "supported_destination_sync_modes": ["overwrite", "append", "append_dedup"], + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Postgres Destination Spec", + "type": "object", + "required": [ + "database", + "username", + "password" + ], + "additionalProperties": true, + "properties": { + "database": { + "title": "DB Name", + "description": "Name of the database.", + "type": "string", + "order": 2 + }, + "schema": { + "title": "Default Schema", + "description": "The default schema tables are written to if the source does not specify a namespace. The usual value for this field is \"public\".", + "type": "string", + "examples": [ + "public" + ], + "default": "public", + "order": 3 + }, + "username": { + "title": "User", + "description": "Username to use to access the database.", + "type": "string", + "order": 4 + }, + "password": { + "title": "Password", + "description": "Password associated with the username.", + "type": "string", + "airbyte_secret": true, + "order": 5 + }, + "ssl": "true", + "host": "db.bit.io", + "ssl_mode": "require", + "port": "5432" + } + } +} diff --git a/docs/integrations/README.md b/docs/integrations/README.md index fe4fae5cccc1..78dfeb98f0b1 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -189,7 +189,8 @@ For more information about the grading system, see [Product Release Stages](http | [Amazon Datalake](destinations/aws-datalake.md) | Alpha | No | | [AzureBlobStorage](destinations/azureblobstorage.md) | Alpha | Yes | | [BigQuery](destinations/bigquery.md) | Generally Available | Yes | -| [Cassandra](destinations/cassandra.md) | Alpha | No | +| [bit.io](destinations/bitdotio.md) | Alpha | No | +| [Cassandra](destinations/cassandra.md) | Alpha | Yes | | [Chargify (Keen)](destinations/chargify.md) | Alpha | Yes | | [ClickHouse](destinations/clickhouse.md) | Alpha | Yes | | [Databricks](destinations/databricks.md) | Alpha | No | diff --git a/docs/integrations/destinations/bitdotio.md b/docs/integrations/destinations/bitdotio.md new file mode 100644 index 000000000000..528396500afb --- /dev/null +++ b/docs/integrations/destinations/bitdotio.md @@ -0,0 +1,51 @@ +# bit.io + +This page guides you through the process of setting up the bit.io destination connector. + +The bit.io connector is a modified version of the Postgres connector. + +## Prerequisites + +To use the bit.io destination, you'll need: + +* A bit.io account + +You'll need the following information to configure the bit.io destination: + +* **Username** +* **Connect Password** - Found on your database page on the `Connect` tab (this is NOT your bit.io login password) +* **Database** - The database name, including your username - eg, `adam/FCC`. + + +## Supported sync modes + +The bit.io destination connector supports the +following[ sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): + +| Feature | Supported?\(Yes/No\) | Notes | +| :--- | :--- | :--- | +| Full Refresh Sync | Yes | | +| Incremental - Append Sync | Yes | | +| Incremental - Deduped History | Yes | | +| Namespaces | Yes | | + +## Schema map + +#### Output Schema + +Each stream will be mapped to a separate table in bit.io. Each table will contain 3 columns: + +* `_airbyte_ab_id`: a uuid assigned by Airbyte to each event that is processed. The column type in + Postgres is `VARCHAR`. +* `_airbyte_emitted_at`: a timestamp representing when the event was pulled from the data source. + The column type in Postgres is `TIMESTAMP WITH TIME ZONE`. +* `_airbyte_data`: a json blob representing with the event data. The column type in Postgres + is `JSONB`. + + + +## Changelog + +| Version | Date | Pull Request | Subject | +|:--------|:-----------| :--- |:----------------------------------------------------------------------------------------------------| +| 0.1.0 | 2022-08-20 | | Initial Version From e5fccaaf035c01f79c41391902e403c622b817c5 Mon Sep 17 00:00:00 2001 From: Brian Lai <51336873+brianjlai@users.noreply.github.com> Date: Fri, 14 Oct 2022 13:18:38 -0400 Subject: [PATCH 116/498] add source s3 back to catalog (#17997) --- airbyte-webapp/src/core/domain/connector/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/airbyte-webapp/src/core/domain/connector/constants.ts b/airbyte-webapp/src/core/domain/connector/constants.ts index ec3c35a866f9..e01850fd84d5 100644 --- a/airbyte-webapp/src/core/domain/connector/constants.ts +++ b/airbyte-webapp/src/core/domain/connector/constants.ts @@ -29,7 +29,6 @@ export const getExcludedConnectorIds = (workspaceId: string) => "2340cbba-358e-11ec-8d3d-0242ac130203", // hide Pular Destination https://github.com/airbytehq/airbyte-cloud/issues/2614 "d4d3fef9-e319-45c2-881a-bd02ce44cc9f", // hide Redis Destination https://github.com/airbytehq/airbyte-cloud/issues/2593 "2c9d93a7-9a17-4789-9de9-f46f0097eb70", // hide Rockset Destination https://github.com/airbytehq/airbyte-cloud/issues/2615 - "69589781-7828-43c5-9f63-8925b1c1ccc2", // hide S3 Source https://github.com/airbytehq/airbyte-cloud/issues/2618 "2470e835-feaf-4db6-96f3-70fd645acc77", // Salesforce Singer "3dc6f384-cd6b-4be3-ad16-a41450899bf0", // hide Scylla Destination https://github.com/airbytehq/airbyte-cloud/issues/2617 "af7c921e-5892-4ff2-b6c1-4a5ab258fb7e", // hide MeiliSearch Destination https://github.com/airbytehq/airbyte/issues/16313 From 804bf47484cc7fe5850223f26c5359f5cde3ed36 Mon Sep 17 00:00:00 2001 From: Topher Lubaway Date: Fri, 14 Oct 2022 12:58:27 -0500 Subject: [PATCH 117/498] Major overhaul of check images (#17979) * Major overhaul of check images actually fail on missed images detect rate limit for dockerhub sleep when hit rate limit and registry more clear user messaging Reduce output from 5000 lines to ~ 1000 More clear help messages for failures * Addresses GH feedback minor bugfixes --- tools/bin/check_images_exist.sh | 115 ++++++++++++++++++++++++-------- 1 file changed, 87 insertions(+), 28 deletions(-) diff --git a/tools/bin/check_images_exist.sh b/tools/bin/check_images_exist.sh index efbab4272f04..839af118a377 100755 --- a/tools/bin/check_images_exist.sh +++ b/tools/bin/check_images_exist.sh @@ -1,63 +1,122 @@ #!/usr/bin/env bash -set -e +# ------------- Import some defaults for the shell + +# Source shell defaults +# $0 is the currently running program (this file) +this_file_directory=$(dirname $0) +relative_path_to_defaults=$this_file_directory/../shell_defaults + +# if a file exists there, source it. otherwise complain +if test -f $relative_path_to_defaults; then + # source and '.' are the same program + source $relative_path_to_defaults +else + echo -e "\033[31m\nFAILED TO SOURCE TEST RUNNING OPTIONS.\033[39m" + echo -e "\033[31mTried $relative_path_to_defaults\033[39m" + exit 1 +fi + +set +o xtrace # +x easier human reading here . tools/lib/lib.sh + function docker_tag_exists() { - # Added check for images pushed to github container registry - if [[ $1 == ghcr* ]] + # Is true for images stored in the Github Container Registry + repo=$1 + tag=$2 + # we user [[ here because test doesn't support globbing well + if [[ $repo == ghcr* ]] then TOKEN_URL=https://ghcr.io/token\?scope\="repository:$1:pull" - token=$(curl $TOKEN_URL | jq -r '.token') + token=$(curl $TOKEN_URL | jq -r '.token' > /dev/null) URL=https://ghcr.io/v2/$1/manifests/$2 - printf "\tURL: %s\n" "$URL" - curl --silent -H "Authorization: Bearer $token" -f -lSL "$URL" > /dev/null + echo -e "$blue_text""\n\n\tURL: $URL""$default_text" + curl -H "Authorization: Bearer $token" --location --silent --show-error --dump-header header.txt "$URL" > /dev/null + curl_success=$? else URL=https://hub.docker.com/v2/repositories/"$1"/tags/"$2" - printf "\tURL: %s\n" "$URL" - curl --silent -f -lSL "$URL" > /dev/null + echo -e "$blue_text""\n\n\tURL: $URL""$default_text" + curl --silent --show-error --location --dump-header header.txt "$URL" > /dev/null + curl_success=$? + # some bullshit to get the number out of a header that looks like this + # < content-length: 1039 + # < x-ratelimit-limit: 180 + # < x-ratelimit-reset: 1665683196 + # < x-ratelimit-remaining: 180 + docker_rate_limit_remaining=$(grep 'x-ratelimit-remaining: ' header.txt | grep --only-matching --extended-regexp "\d+") + # too noisy when set to < 1. Dockerhub starts complaining somewhere around 10 + if test "$docker_rate_limit_remaining" -lt 20; then + echo -e "$red_text""We are close to a sensitive dockerhub rate limit!""$default_text" + echo -e "$red_text""SLEEPING 60s sad times""$default_text" + sleep 60 + docker_tag_exists $1 $2 + elif test $docker_rate_limit_remaining -lt 50; then + echo -e "$red_text""Rate limit reported as $docker_rate_limit_remaining""$default_text" + fi + fi + if test $curl_success -ne 0; then + echo -e "$red_text""Curl Said this didn't work. Please investigate""$default_text" + exit 1 fi } checkPlatformImages() { - echo "Checking platform images exist..." - docker-compose pull || exit 1 - echo "Success! All platform images exist!" + echo -e "$blue_text""Checking platform images exist...""$default_text" + #Pull without printing progress information and send error stream because that where image names are + docker-compose pull --quiet 2> compose_output + docker_compose_success=$? + # quiet output is just SHAs ie: f8a3d002a8a6 + images_pulled_count=$(docker images --quiet | wc -l) + if test $images_pulled_count -eq 0; then + echo -e "$red_text""Nothing was pulled! This script may be broken! We expect to pull something""$default_text" + exit 1 + elif test $docker_compose_success -eq 0; then + echo -e "$blue_text""Docker successfully pulled all images""$default_text" + else + echo -e "$red_text""docker-compose failed to pull all images""$default_text" + cat compose_output + exit 1 + fi } checkNormalizationImages() { + echo -e "$blue_text""Checking Normalization images exist...""$default_text" # the only way to know what version of normalization the platform is using is looking in NormalizationRunnerFactory. local image_version; factory_path=airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java - # This check should fail if the file doesn't exist, so just try to ls the file - ls $factory_path > /dev/null + # -f True if file exists and is a regular file + if ! test -f $factory_path; then + echo -e "$red_text""No NormalizationRunnerFactory found at path! H4LP!!!""$default_text" + fi image_version=$(cat $factory_path | grep 'NORMALIZATION_VERSION =' | cut -d"=" -f2 | sed 's:;::' | sed -e 's:"::g' | sed -e 's:[[:space:]]::g') - echo "Checking normalization images with version $image_version exist..." - VERSION=$image_version docker-compose -f airbyte-integrations/bases/base-normalization/docker-compose.yaml pull || exit 1 - echo "Success! All normalization images exist!" + echo -e "$blue_text""Checking normalization images with version $image_version exist...""$default_text" + VERSION=$image_version docker-compose --file airbyte-integrations/bases/base-normalization/docker-compose.yaml pull --quiet + docker_compose_success=$? + if test $docker_compose_success -eq 0; then + echo -e "$blue_text""Docker successfully pulled all images for normalization""$default_text" + else + echo -e "$red_text""docker-compose failed to pull all images for normalization""$default_text" + exit 1 + fi } checkConnectorImages() { - echo "Checking connector images exist..." - + echo -e "$blue_text""Checking connector images exist...""$default_text" CONNECTOR_DEFINITIONS=$(grep "dockerRepository" -h -A1 airbyte-config/init/src/main/resources/seed/*.yaml | grep -v -- "^--$" | tr -d ' ') [ -z "CONNECTOR_DEFINITIONS" ] && echo "ERROR: Could not find any connector definition." && exit 1 while IFS=":" read -r _ REPO; do IFS=":" read -r _ TAG - printf "${REPO}: ${TAG}\n" + printf "\t${REPO}: ${TAG}\n" if docker_tag_exists "$REPO" "$TAG"; then printf "\tSTATUS: found\n" else printf "\tERROR: not found!\n" && exit 1 fi - # Docker hub has a rate limit of 180 requests per minute, so slow down our rate of calling the API - # https://docs.docker.com/docker-hub/api/latest/#tag/rate-limiting - sleep 1 done <<< "${CONNECTOR_DEFINITIONS}" - - echo "Success! All connector images exist!" + echo -e "$blue_text""Success! All connector images exist!""$default_text" } main() { @@ -65,14 +124,14 @@ main() { SUBSET=${1:-all} # default to all. [[ ! "$SUBSET" =~ ^(all|platform|connectors)$ ]] && echo "Usage ./tools/bin/check_image_exists.sh [all|platform|connectors]" && exit 1 - - echo "checking images for: $SUBSET" + echo -e "$blue_text""checking images for: $SUBSET""$default_text" [[ "$SUBSET" =~ ^(all|platform)$ ]] && checkPlatformImages [[ "$SUBSET" =~ ^(all|platform|connectors)$ ]] && checkNormalizationImages [[ "$SUBSET" =~ ^(all|connectors)$ ]] && checkConnectorImages - - echo "Image check complete." + echo -e "$blue_text""Image check complete.""$default_text" + test -f header.txt && rm header.txt + test -f compose_output && rm compose_output } main "$@" From 6039e6fa5fccf5d77039e5d9a0df54aa1c35eb14 Mon Sep 17 00:00:00 2001 From: Digambar Tupurwadi Date: Sat, 15 Oct 2022 00:12:31 +0530 Subject: [PATCH 118/498] Source Chargebee: altered docs for chargebee connector (#18007) --- docs/integrations/sources/chargebee.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/integrations/sources/chargebee.md b/docs/integrations/sources/chargebee.md index 2a537bb56f92..11ad4145fb18 100644 --- a/docs/integrations/sources/chargebee.md +++ b/docs/integrations/sources/chargebee.md @@ -12,22 +12,22 @@ This Chargebee source uses the [Chargebee Python Client Library](https://github. Log into Chargebee and then generate an [API Key](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2#api_authentication). Then follow [these](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2) instructions, under `API Version` section, on how to find your Product Catalog version. -## Step 2: Set up the Chargebee connector in Airbyte +### Step 2: Set up the Chargebee connector in Airbyte -### For Airbyte Cloud: -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. +#### For Airbyte Cloud: +1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. +2. Click **Sources** and then click **+New source**. 3. On the Set up the source page, enter the name for the Harvest connector and select **Chargebee** from the Source type dropdown. -4. Set the name for your source +4. Enter a name for your source 5. Enter your `site_api_key` 6. Enter your `site` 7. Enter your `product_catalog` 8. Enter the `start_date` you want your sync to start from 9. Click **Set up source** -### For Airbyte OSS: +#### For Airbyte OSS: 1. Navigate to the Airbyte Open Source dashboard -2. Set the name for your source +2. Enter a name for your source 3. Enter your `site_api_key` 4. Enter your `site` 5. Enter your `product_catalog` @@ -118,4 +118,3 @@ regardless of how many AttachedItems were actually changed or synced in a partic | 0.1.2 | 2021-07-30 | [5067](https://github.com/airbytehq/airbyte/pull/5067) | Prepare connector for publishing | | 0.1.1 | 2021-07-07 | [4539](https://github.com/airbytehq/airbyte/pull/4539) | Add entrypoint and bump version for connector | | 0.1.0 | 2021-06-30 | [3410](https://github.com/airbytehq/airbyte/pull/3410) | New Source: Chargebee | - From d8e9f512d6fa3ba6bb49f1c048a531055c833435 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Fri, 14 Oct 2022 22:23:43 +0200 Subject: [PATCH 119/498] Fix multiple breakages in the platform build (#18011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "🎉 New Destination: bit.io (#15821)" This reverts commit c05135b336e3a8c45dfefca340d0c5b261c7a4f0. * Fix broken acceptance tests --- .../NormalizationRunnerFactory.java | 1 - .../src/main/resources/icons/bitdotio.svg | 40 --- .../seed/destination_definitions.yaml | 7 - .../resources/seed/destination_specs.yaml | 50 ---- airbyte-integrations/builds.md | 1 - .../destination-bitdotio/.dockerignore | 3 - .../destination-bitdotio/Dockerfile | 20 -- .../connectors/destination-bitdotio/README.md | 28 --- .../destination-bitdotio/bootstrap.md | 18 -- .../destination-bitdotio/build.gradle | 25 -- .../postgres/BitDotIoDestination.java | 68 ------ .../BitDotIoDestinationAcceptanceTest.java | 228 ------------------ .../postgres/BitDotIoDestinationTest.java | 22 -- .../src/test/resources/expected_spec.json | 53 ---- .../test/acceptance/BasicAcceptanceTests.java | 2 +- docs/integrations/README.md | 3 +- docs/integrations/destinations/bitdotio.md | 51 ---- 17 files changed, 2 insertions(+), 618 deletions(-) delete mode 100644 airbyte-config/init/src/main/resources/icons/bitdotio.svg delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/.dockerignore delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/Dockerfile delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/README.md delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/bootstrap.md delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/build.gradle delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java delete mode 100644 airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json delete mode 100644 docs/integrations/destinations/bitdotio.md diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java index 7ba32daf18d1..cc1530d3fd49 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java @@ -30,7 +30,6 @@ public class NormalizationRunnerFactory { .put("airbyte/destination-oracle", ImmutablePair.of("airbyte/normalization-oracle", DestinationType.ORACLE)) .put("airbyte/destination-oracle-strict-encrypt", ImmutablePair.of("airbyte/normalization-oracle", DestinationType.ORACLE)) .put("airbyte/destination-postgres", ImmutablePair.of(BASE_NORMALIZATION_IMAGE_NAME, DestinationType.POSTGRES)) - .put("airbyte/destination-bitdotio", ImmutablePair.of(BASE_NORMALIZATION_IMAGE_NAME, DestinationType.POSTGRES)) .put("airbyte/destination-postgres-strict-encrypt", ImmutablePair.of(BASE_NORMALIZATION_IMAGE_NAME, DestinationType.POSTGRES)) .put("airbyte/destination-redshift", ImmutablePair.of("airbyte/normalization-redshift", DestinationType.REDSHIFT)) .put("airbyte/destination-snowflake", ImmutablePair.of("airbyte/normalization-snowflake", DestinationType.SNOWFLAKE)) diff --git a/airbyte-config/init/src/main/resources/icons/bitdotio.svg b/airbyte-config/init/src/main/resources/icons/bitdotio.svg deleted file mode 100644 index aef56c6946ef..000000000000 --- a/airbyte-config/init/src/main/resources/icons/bitdotio.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 8bfe95fa55d1..71c2d7305fd1 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -50,13 +50,6 @@ memory_limit: "1Gi" memory_request: "1Gi" releaseStage: beta -- name: bit.io - destinationDefinitionId: 592af2e0-882c-4ed8-b0fe-a31761146a8d - dockerRepository: airbyte/destination-bitdotio - dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.io/integrations/destinations/bitdotio - icon: bitdotio.svg - releaseStage: alpha - name: Cassandra destinationDefinitionId: 707456df-6f4f-4ced-b5c6-03f73bcad1c5 dockerRepository: airbyte/destination-cassandra diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index bf49a62337d8..0c3794a58c19 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -693,56 +693,6 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-bitdotio:0.1.0" - spec: - documentationUrl: "https://docs.airbyte.io/integrations/destinations/bitdotio" - connectionSpecification: - $schema: "http://json-schema.org/draft-07/schema#" - title: "Postgres Destination Spec" - type: "object" - required: - - "database" - - "username" - - "password" - additionalProperties: true - properties: - database: - title: "DB Name" - description: "Name of the database." - type: "string" - order: 2 - schema: - title: "Default Schema" - description: "The default schema tables are written to if the source does\ - \ not specify a namespace. The usual value for this field is \"public\"\ - ." - type: "string" - examples: - - "public" - default: "public" - order: 3 - username: - title: "User" - description: "Username to use to access the database." - type: "string" - order: 4 - password: - title: "Password" - description: "Password associated with the username." - type: "string" - airbyte_secret: true - order: 5 - ssl: "true" - host: "db.bit.io" - ssl_mode: "require" - port: "5432" - supportsIncremental: true - supportsNormalization: true - supportsDBT: true - supported_destination_sync_modes: - - "overwrite" - - "append" - - "append_dedup" - dockerImage: "airbyte/destination-cassandra:0.1.4" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/cassandra" diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 9d72e65aa1f1..7af75b786c27 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -131,7 +131,6 @@ | Azure Blob Storage | [![destination-azure-blob-storage](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-azure-blob-storage%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-azure-blob-storage) | | BigQuery | [![destination-bigquery](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-bigquery%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-bigquery) | | BigQuery Denormalized | [![destination-bigquery-denormalized](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-bigquery-denormalized%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-bigquery-denormalized) | -| bit.io | [![destination-bitdotio](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-bitdotio%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-bitdotio) | | ClickHouse | [![destination-clickhouse](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-clickhouse%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-clickhouse) | | Cassandra | [![destination-cassandra](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-cassandra%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-cassandra) | | Databricks | [![destination-databricks](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fdestination-databricks%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/destination-databricks) | diff --git a/airbyte-integrations/connectors/destination-bitdotio/.dockerignore b/airbyte-integrations/connectors/destination-bitdotio/.dockerignore deleted file mode 100644 index 65c7d0ad3e73..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -* -!Dockerfile -!build diff --git a/airbyte-integrations/connectors/destination-bitdotio/Dockerfile b/airbyte-integrations/connectors/destination-bitdotio/Dockerfile deleted file mode 100644 index 8bf1c694a354..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM airbyte/integration-base-java:dev AS build - -WORKDIR /airbyte - -ENV APPLICATION destination-bitdotio - -COPY build/distributions/${APPLICATION}*.tar ${APPLICATION}.tar - -RUN tar xf ${APPLICATION}.tar --strip-components=1 && rm -rf ${APPLICATION}.tar - -FROM airbyte/integration-base-java:dev - -WORKDIR /airbyte - -ENV APPLICATION destination-bitdotio - -COPY --from=build /airbyte /airbyte - -LABEL io.airbyte.version=0.1.0 -LABEL io.airbyte.name=airbyte/destination-bitdotio diff --git a/airbyte-integrations/connectors/destination-bitdotio/README.md b/airbyte-integrations/connectors/destination-bitdotio/README.md deleted file mode 100644 index d3ec6022056b..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/README.md +++ /dev/null @@ -1,28 +0,0 @@ -bit.io Destination -================== -This allows the use of the bit.io service as a destination in Airbyte. - -The bit.io destination is a `SpecModifiyingDestination` that hardcodes the host, port, and -ssl options from the `PostgresDestination`. - -Testing -------- - -To test you need a bit.io user with an empty database. - -Then, create `secrets/credentials.json` - -``` -{ - "username": "", - "database": "", - "connect_password": "" -} -``` - -Then you can run the standard set of gradle-driven tests. - -NOTE THAT THE DATABASE CONTENTS WILL BE DESTROYED BY TESTS. So don't use a database -with anythign in it. - - diff --git a/airbyte-integrations/connectors/destination-bitdotio/bootstrap.md b/airbyte-integrations/connectors/destination-bitdotio/bootstrap.md deleted file mode 100644 index 0f60ccb96299..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/bootstrap.md +++ /dev/null @@ -1,18 +0,0 @@ -# bit.io - -## Overview - -bit.io is a serverless, shareable cloud Postgres database. - -## Endpoints - -This destination connector uses the Postgres protocol. - -## Quick Notes - -- bit.io doesn't support `CREATE DATABASE` - -- This connectors primary change is to hardcode the hostname to `db.bit.io`, the port to `5432`, and the `sslmode` to `require` - - - diff --git a/airbyte-integrations/connectors/destination-bitdotio/build.gradle b/airbyte-integrations/connectors/destination-bitdotio/build.gradle deleted file mode 100644 index 768f5b45ed3b..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/build.gradle +++ /dev/null @@ -1,25 +0,0 @@ -plugins { - id 'application' - id 'airbyte-docker' - id 'airbyte-integration-test-java' -} - -application { - mainClass = 'io.airbyte.integrations.destination.postgres.BitDotIoDestination' - applicationDefaultJvmArgs = ['-XX:+ExitOnOutOfMemoryError', '-XX:MaxRAMPercentage=75.0'] -} - -dependencies { - implementation project(':airbyte-db:db-lib') - implementation project(':airbyte-integrations:bases:base-java') - implementation project(':airbyte-protocol:protocol-models') - implementation project(':airbyte-integrations:connectors:destination-jdbc') - implementation project(':airbyte-integrations:connectors:destination-postgres') - - integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') - - integrationTestJavaImplementation libs.connectors.testcontainers.postgresql - - implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) - integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-normalization').airbyteDocker.outputs) -} diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java b/airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java deleted file mode 100644 index 092171ad6ecb..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/src/main/java/io/airbyte/integrations/destination/postgres/BitDotIoDestination.java +++ /dev/null @@ -1,68 +0,0 @@ -/* * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.integrations.destination.postgres; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode ; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.airbyte.commons.json.Jsons; -import io.airbyte.db.jdbc.JdbcUtils; -import io.airbyte.integrations.base.Destination; -import io.airbyte.integrations.base.IntegrationRunner; -import io.airbyte.integrations.base.spec_modification.SpecModifyingDestination; -import io.airbyte.integrations.destination.postgres.PostgresDestination; -import io.airbyte.protocol.models.ConnectorSpecification; -import java.net.URI; -import java.net.URISyntaxException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BitDotIoDestination extends SpecModifyingDestination implements Destination { - - private static final Logger LOGGER = LoggerFactory.getLogger(BitDotIoDestination.class); - - public BitDotIoDestination() { - super(new PostgresDestination()); - } - - @Override - public ConnectorSpecification modifySpec(final ConnectorSpecification originalSpec) { - final ConnectorSpecification spec = Jsons.clone(originalSpec); - String json = "[ \"database\", \"username\", \"password\"] "; - - ObjectMapper objectMapper = new ObjectMapper(); - - JsonNode jsonNode; - try { - jsonNode = objectMapper.readTree(json); - spec.setDocumentationUrl(new URI("https://docs.airbyte.io/integrations/destinations/bitdotio")); - ((ObjectNode) spec.getConnectionSpecification()).replace("required", jsonNode); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.PORT_LIST_KEY); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.SSL_KEY); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.HOST_KEY); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.PORT_KEY); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.JDBC_URL_PARAMS_KEY); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).remove(JdbcUtils.SSL_MODE_KEY); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.SSL_KEY, "true"); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.HOST_KEY, "db.bit.io"); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.SSL_MODE_KEY, "require"); - ((ObjectNode) spec.getConnectionSpecification().get("properties")).put(JdbcUtils.PORT_KEY, "5432"); - } catch (JsonProcessingException e) { - e.printStackTrace(); - } catch (URISyntaxException e) { - // This should never happen. - e.printStackTrace(); - } - - return spec; - } - - public static void main(final String[] args) throws Exception { - final Destination destination = new BitDotIoDestination(); - LOGGER.info("starting destination: {}", BitDotIoDestination.class); - new IntegrationRunner(destination).run(args); - LOGGER.info("completed destination: {}", BitDotIoDestination.class); - } - -} diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java deleted file mode 100644 index d0b46ece172a..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/src/test-integration/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationAcceptanceTest.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.integrations.destination.postgres; - -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableMap; -import io.airbyte.commons.json.Jsons; -import io.airbyte.db.Database; -import io.airbyte.db.factory.DSLContextFactory; -import io.airbyte.db.factory.DatabaseDriver; -import io.airbyte.db.jdbc.JdbcUtils; -import io.airbyte.integrations.base.JavaBaseConstants; -import io.airbyte.integrations.destination.ExtendedNameTransformer; -import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.stream.Collectors; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; - -import org.jooq.DSLContext; -import org.jooq.SQLDialect; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BitDotIoDestinationAcceptanceTest extends DestinationAcceptanceTest { - public class BitDotIoConfig { - private String username = ""; - private String database = ""; - private String password = ""; - public String getUsername() { - return username; - } - public String getDatabase() { - return database; - } - public String getPassword() { - return password; - } - public BitDotIoConfig(String username, String database, String password) - { - this.username = username; - this.database = database; - this.password = password; - } - - public String getJdbcUrl() { - String jdbcUrl = ""; - try { - jdbcUrl = "jdbc:postgresql://db.bit.io:5432" + "/" + URLEncoder.encode(database, "UTF-8") + "?sslmode=require"; - } catch (UnsupportedEncodingException e) { - // Should never happen - e.printStackTrace(); - } - return jdbcUrl; - } - } - - - private static final Logger LOGGER = LoggerFactory.getLogger(BitDotIoDestinationAcceptanceTest.class); - private BitDotIoConfig cfg; - private final ExtendedNameTransformer namingResolver = new ExtendedNameTransformer(); - - protected static final Path CREDENTIALS_PATH = Path.of("secrets/credentials.json"); - - protected static final String CONFIG_BITIO_USERNAME = "username"; - protected static final String CONFIG_BITIO_DATABASE = "database"; - protected static final String CONFIG_BITIO_CONNECT_PASSWORD = "connect_password"; - - @Override - protected String getImageName() { - return "airbyte/destination-bitdotio:dev"; - } - - @Override - protected JsonNode getConfig() { - - return Jsons.jsonNode(ImmutableMap.builder() - .put(JdbcUtils.HOST_KEY, "db.bit.io") - .put(JdbcUtils.PORT_KEY, 5432) - .put(JdbcUtils.SCHEMA_KEY, "public") - .put(JdbcUtils.USERNAME_KEY, cfg.getUsername()) - .put(JdbcUtils.PASSWORD_KEY, cfg.getPassword()) - .put(JdbcUtils.DATABASE_KEY, cfg.getDatabase()) - .put(JdbcUtils.SSL_KEY, true ) - .put("sslmode", "require" ) - .put("ssl_mode", ImmutableMap.builder().put("mode", "require").build()) - .build()); - } - - @Override - protected JsonNode getFailCheckConfig() { - return Jsons.jsonNode(ImmutableMap.builder() - .put(JdbcUtils.HOST_KEY, "db.bit.io") - .put(JdbcUtils.PORT_KEY, 5432) - .put(JdbcUtils.SCHEMA_KEY, "public") - .put(JdbcUtils.USERNAME_KEY, cfg.getUsername()) - .put(JdbcUtils.PASSWORD_KEY, "wrong password") - .put(JdbcUtils.DATABASE_KEY, cfg.getDatabase()) - .put(JdbcUtils.SSL_KEY, true) - .put("sslmode", "require" ) - .put("ssl_mode", ImmutableMap.builder().put("mode", "require").build()) - .build()); - } - - @Override - protected List retrieveRecords(final TestDestinationEnv env, - final String streamName, - final String namespace, - final JsonNode streamSchema) - throws Exception { - return retrieveRecordsFromTable(namingResolver.getRawTableName(streamName), namespace) - .stream() - .map(r -> Jsons.deserialize(r.get(JavaBaseConstants.COLUMN_NAME_DATA).asText())) - .collect(Collectors.toList()); - } - - @Override - protected boolean supportsNormalization() { - return true; - } - - @Override - protected boolean supportsDBT() { - return true; - } - - @Override - protected boolean implementsNamespaces() { - return true; - } - - @Override - protected List retrieveNormalizedRecords(final TestDestinationEnv env, final String streamName, final String namespace) - throws Exception { - final String tableName = namingResolver.getIdentifier(streamName); - return retrieveRecordsFromTable(tableName, namespace); - } - - @Override - protected List resolveIdentifier(final String identifier) { - final List result = new ArrayList<>(); - final String resolved = namingResolver.getIdentifier(identifier); - result.add(identifier); - result.add(resolved); - if (!resolved.startsWith("\"")) { - result.add(resolved.toLowerCase()); - result.add(resolved.toUpperCase()); - } - return result; - } - - private List retrieveRecordsFromTable(final String tableName, final String schemaName) throws SQLException { - try (final DSLContext dslContext = DSLContextFactory.create( - cfg.getUsername(), - cfg.getPassword(), - DatabaseDriver.POSTGRESQL.getDriverClassName(), - cfg.getJdbcUrl(), - SQLDialect.POSTGRES)) { - return new Database(dslContext) - .query( - ctx -> ctx - .fetch(String.format("SELECT * FROM %s.%s ORDER BY %s ASC;", schemaName, tableName, JavaBaseConstants.COLUMN_NAME_EMITTED_AT)) - .stream() - .map(r -> r.formatJSON(JdbcUtils.getDefaultJSONFormat())) - .map(Jsons::deserialize) - .collect(Collectors.toList())); - } - } - @Override - protected void setup(final TestDestinationEnv testEnv) throws Exception { - if (!Files.exists(CREDENTIALS_PATH)) { - throw new IllegalStateException( - "Must provide path to a bit.io query credentials file. By default {module-root}/" + CREDENTIALS_PATH - + ". Override by setting setting path with the CREDENTIALS_PATH constant."); - } - - final String fullConfigAsString = Files.readString(CREDENTIALS_PATH); - final JsonNode credentialsJson = Jsons.deserialize(fullConfigAsString); - final String username = credentialsJson.get(CONFIG_BITIO_USERNAME).asText(); - final String database = credentialsJson.get(CONFIG_BITIO_DATABASE).asText(); - final String password = credentialsJson.get(CONFIG_BITIO_CONNECT_PASSWORD).asText(); - - this.cfg = new BitDotIoConfig(username, database, password) ; - } - @Override - protected void tearDown(final TestDestinationEnv testEnv) { - try (final DSLContext dslContext = DSLContextFactory.create( - cfg.getUsername(), - cfg.getPassword(), - DatabaseDriver.POSTGRESQL.getDriverClassName(), - cfg.getJdbcUrl(), - SQLDialect.POSTGRES)) { - - Database db = new Database(dslContext); - List tables = db.query( - ctx -> ctx - .fetch( "SELECT table_name FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema not IN ('pg_catalog', 'information_schema');") - .stream() - .map(r -> r.formatJSON(JdbcUtils.getDefaultJSONFormat())) - .map(Jsons::deserialize) - .collect(Collectors.toList())); - for (JsonNode node : tables) { - db.query(ctx -> ctx.fetch(String.format("DROP TABLE IF EXISTS %s CASCADE", node.get("table_name")))); - } - List schemas = db.query( - ctx -> ctx - .fetch( "SELECT DISTINCT table_schema FROM information_schema.tables WHERE table_type='BASE TABLE' AND table_schema not IN ('public', 'pg_catalog', 'information_schema');") - .stream() - .map(r -> r.formatJSON(JdbcUtils.getDefaultJSONFormat())) - .map(Jsons::deserialize) - .collect(Collectors.toList())); - for (JsonNode node : schemas) { - db.query(ctx -> ctx.fetch(String.format("DROP SCHEMA IF EXISTS %s CASCADE", node.get("table_schema")))); - } - } catch (SQLException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - LOGGER.info("Finished acceptance test for bit.io"); - } -} \ No newline at end of file diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java b/airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java deleted file mode 100644 index d470188acc90..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/src/test/java/io/airbyte/integrations/destination/postgres/BitDotIoDestinationTest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.integrations.destination.postgres; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import io.airbyte.commons.json.Jsons; -import io.airbyte.commons.resources.MoreResources; -import io.airbyte.protocol.models.ConnectorSpecification; -import org.junit.jupiter.api.Test; - -class BitDotIoDestinationTest { - - @Test - void testGetSpec() throws Exception { - assertEquals(Jsons.deserialize(MoreResources.readResource("expected_spec.json"), ConnectorSpecification.class), - new BitDotIoDestination().spec()); - } - -} diff --git a/airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json deleted file mode 100644 index ca0f9bf4b315..000000000000 --- a/airbyte-integrations/connectors/destination-bitdotio/src/test/resources/expected_spec.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "documentationUrl": "https://docs.airbyte.io/integrations/destinations/bitdotio", - "supportsIncremental": true, - "supportsNormalization": true, - "supportsDBT": true, - "supported_destination_sync_modes": ["overwrite", "append", "append_dedup"], - "connectionSpecification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Postgres Destination Spec", - "type": "object", - "required": [ - "database", - "username", - "password" - ], - "additionalProperties": true, - "properties": { - "database": { - "title": "DB Name", - "description": "Name of the database.", - "type": "string", - "order": 2 - }, - "schema": { - "title": "Default Schema", - "description": "The default schema tables are written to if the source does not specify a namespace. The usual value for this field is \"public\".", - "type": "string", - "examples": [ - "public" - ], - "default": "public", - "order": 3 - }, - "username": { - "title": "User", - "description": "Username to use to access the database.", - "type": "string", - "order": 4 - }, - "password": { - "title": "Password", - "description": "Password associated with the username.", - "type": "string", - "airbyte_secret": true, - "order": 5 - }, - "ssl": "true", - "host": "db.bit.io", - "ssl_mode": "require", - "port": "5432" - } - } -} diff --git a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java index 8dcb24e7064a..4560736b2938 100644 --- a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java +++ b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java @@ -925,7 +925,7 @@ void testSyncAfterUpgradeToPerStreamState(final TestInfo testInfo) throws Except // sync one more time. verify it is the equivalent of a full refresh. final String expectedState = - "{\"cursor\":\"6\",\"stream_name\":\"id_and_name\",\"cursor_field\":[\"id\"],\"stream_namespace\":\"public\"}"; + "{\"cursor\":\"6\",\"stream_name\":\"id_and_name\",\"cursor_field\":[\"id\"],\"stream_namespace\":\"public\",\"cursor_record_count\":1}"; LOGGER.info("Starting {} sync 3", testInfo.getDisplayName()); final JobInfoRead connectionSyncRead3 = apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 78dfeb98f0b1..fe4fae5cccc1 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -189,8 +189,7 @@ For more information about the grading system, see [Product Release Stages](http | [Amazon Datalake](destinations/aws-datalake.md) | Alpha | No | | [AzureBlobStorage](destinations/azureblobstorage.md) | Alpha | Yes | | [BigQuery](destinations/bigquery.md) | Generally Available | Yes | -| [bit.io](destinations/bitdotio.md) | Alpha | No | -| [Cassandra](destinations/cassandra.md) | Alpha | Yes | +| [Cassandra](destinations/cassandra.md) | Alpha | No | | [Chargify (Keen)](destinations/chargify.md) | Alpha | Yes | | [ClickHouse](destinations/clickhouse.md) | Alpha | Yes | | [Databricks](destinations/databricks.md) | Alpha | No | diff --git a/docs/integrations/destinations/bitdotio.md b/docs/integrations/destinations/bitdotio.md deleted file mode 100644 index 528396500afb..000000000000 --- a/docs/integrations/destinations/bitdotio.md +++ /dev/null @@ -1,51 +0,0 @@ -# bit.io - -This page guides you through the process of setting up the bit.io destination connector. - -The bit.io connector is a modified version of the Postgres connector. - -## Prerequisites - -To use the bit.io destination, you'll need: - -* A bit.io account - -You'll need the following information to configure the bit.io destination: - -* **Username** -* **Connect Password** - Found on your database page on the `Connect` tab (this is NOT your bit.io login password) -* **Database** - The database name, including your username - eg, `adam/FCC`. - - -## Supported sync modes - -The bit.io destination connector supports the -following[ sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): - -| Feature | Supported?\(Yes/No\) | Notes | -| :--- | :--- | :--- | -| Full Refresh Sync | Yes | | -| Incremental - Append Sync | Yes | | -| Incremental - Deduped History | Yes | | -| Namespaces | Yes | | - -## Schema map - -#### Output Schema - -Each stream will be mapped to a separate table in bit.io. Each table will contain 3 columns: - -* `_airbyte_ab_id`: a uuid assigned by Airbyte to each event that is processed. The column type in - Postgres is `VARCHAR`. -* `_airbyte_emitted_at`: a timestamp representing when the event was pulled from the data source. - The column type in Postgres is `TIMESTAMP WITH TIME ZONE`. -* `_airbyte_data`: a json blob representing with the event data. The column type in Postgres - is `JSONB`. - - - -## Changelog - -| Version | Date | Pull Request | Subject | -|:--------|:-----------| :--- |:----------------------------------------------------------------------------------------------------| -| 0.1.0 | 2022-08-20 | | Initial Version From 359b560de9f612e05663e18c3dee3a5e2a9c911a Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Fri, 14 Oct 2022 23:08:12 +0200 Subject: [PATCH 120/498] Introduce webhook operations to the operations API and persistence (#17984) * introduce webhook operations to the operations API and persistence * add unit tests for webhooks in operations endpoint handler * fixes and additional testing in webhook operations handler * cleanup refactor around operations handling to reduce duplicate code --- airbyte-api/src/main/openapi/config.yaml | 19 +++- .../main/resources/types/OperatorType.yaml | 1 + .../main/resources/types/OperatorWebhook.yaml | 16 +++ .../types/StandardSyncOperation.yaml | 2 + .../converters/OperationsConverter.java | 98 +++++++++++++++++++ ... => WorkspaceWebhookConfigsConverter.java} | 6 +- .../server/handlers/OperationsHandler.java | 66 ++----------- .../server/handlers/WorkspacesHandler.java | 6 +- .../handlers/OperationsHandlerTest.java | 83 +++++++++++++++- .../api/generated-api-html/index.html | 66 +++++++++++++ 10 files changed, 298 insertions(+), 65 deletions(-) create mode 100644 airbyte-config/config-models/src/main/resources/types/OperatorWebhook.yaml create mode 100644 airbyte-server/src/main/java/io/airbyte/server/converters/OperationsConverter.java rename airbyte-server/src/main/java/io/airbyte/server/converters/{WebhookOperationConfigsConverter.java => WorkspaceWebhookConfigsConverter.java} (86%) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index e5adea49f375..01b632095d57 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -3639,14 +3639,16 @@ components: $ref: "#/components/schemas/OperatorNormalization" dbt: $ref: "#/components/schemas/OperatorDbt" + webhook: + $ref: "#/components/schemas/OperatorWebhook" OperatorType: type: string enum: # - destination - normalization - dbt + - webhook # - docker - # - webhook OperatorNormalization: type: object properties: @@ -3668,6 +3670,21 @@ components: type: string dbtArguments: type: string + OperatorWebhook: + type: object + required: + - executionUrl + properties: + executionUrl: + type: string + description: The URL to call to execute the webhook operation via POST request. + executionBody: + type: string + description: If populated, this will be sent with the POST request. + webhookConfigId: + type: string + format: uuid + description: The id of the webhook configs to use from the workspace. CheckOperationRead: type: object required: diff --git a/airbyte-config/config-models/src/main/resources/types/OperatorType.yaml b/airbyte-config/config-models/src/main/resources/types/OperatorType.yaml index 756980539622..b31bf1941213 100644 --- a/airbyte-config/config-models/src/main/resources/types/OperatorType.yaml +++ b/airbyte-config/config-models/src/main/resources/types/OperatorType.yaml @@ -8,3 +8,4 @@ enum: # - destination - normalization - dbt + - webhook diff --git a/airbyte-config/config-models/src/main/resources/types/OperatorWebhook.yaml b/airbyte-config/config-models/src/main/resources/types/OperatorWebhook.yaml new file mode 100644 index 000000000000..3f27ed0f0940 --- /dev/null +++ b/airbyte-config/config-models/src/main/resources/types/OperatorWebhook.yaml @@ -0,0 +1,16 @@ +--- +"$schema": http://json-schema.org/draft-07/schema# +"$id": https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/resources/types/OperatorWebhook.yaml +title: OperatorWebhook +description: Settings for a webhook operation +type: object +required: + - executionUrl +properties: + executionUrl: + type: string + executionBody: + type: string + webhookConfigId: + type: string + format: uuid diff --git a/airbyte-config/config-models/src/main/resources/types/StandardSyncOperation.yaml b/airbyte-config/config-models/src/main/resources/types/StandardSyncOperation.yaml index d053e3fe8db1..833df4b3948d 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardSyncOperation.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardSyncOperation.yaml @@ -24,6 +24,8 @@ properties: "$ref": OperatorNormalization.yaml operatorDbt: "$ref": OperatorDbt.yaml + operatorWebhook: + "$ref": OperatorWebhook.yaml tombstone: description: if not set or false, the configuration is active. if true, then this diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/OperationsConverter.java b/airbyte-server/src/main/java/io/airbyte/server/converters/OperationsConverter.java new file mode 100644 index 000000000000..c5cae0249947 --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/OperationsConverter.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.converters; + +import com.google.common.base.Preconditions; +import io.airbyte.api.model.generated.OperationRead; +import io.airbyte.api.model.generated.OperatorConfiguration; +import io.airbyte.api.model.generated.OperatorNormalization.OptionEnum; +import io.airbyte.commons.enums.Enums; +import io.airbyte.config.OperatorDbt; +import io.airbyte.config.OperatorNormalization; +import io.airbyte.config.OperatorNormalization.Option; +import io.airbyte.config.OperatorWebhook; +import io.airbyte.config.StandardSyncOperation; +import io.airbyte.config.StandardSyncOperation.OperatorType; + +public class OperationsConverter { + + public static void populateOperatorConfigFromApi(final OperatorConfiguration operatorConfig, final StandardSyncOperation standardSyncOperation) { + standardSyncOperation.withOperatorType(Enums.convertTo(operatorConfig.getOperatorType(), OperatorType.class)); + switch (operatorConfig.getOperatorType()) { + case NORMALIZATION -> { + Preconditions.checkArgument(operatorConfig.getNormalization() != null); + standardSyncOperation.withOperatorNormalization(new OperatorNormalization() + .withOption(Enums.convertTo(operatorConfig.getNormalization().getOption(), Option.class))); + // Null out the other configs, since it's mutually exclusive. We need to do this if it's an update. + standardSyncOperation.withOperatorDbt(null); + standardSyncOperation.withOperatorWebhook(null); + } + case DBT -> { + Preconditions.checkArgument(operatorConfig.getDbt() != null); + standardSyncOperation.withOperatorDbt(new OperatorDbt() + .withGitRepoUrl(operatorConfig.getDbt().getGitRepoUrl()) + .withGitRepoBranch(operatorConfig.getDbt().getGitRepoBranch()) + .withDockerImage(operatorConfig.getDbt().getDockerImage()) + .withDbtArguments(operatorConfig.getDbt().getDbtArguments())); + // Null out the other configs, since they're mutually exclusive. We need to do this if it's an + // update. + standardSyncOperation.withOperatorNormalization(null); + standardSyncOperation.withOperatorWebhook(null); + } + case WEBHOOK -> { + Preconditions.checkArgument(operatorConfig.getWebhook() != null); + // TODO(mfsiega-airbyte): check that the webhook config id references a real webhook config. + standardSyncOperation.withOperatorWebhook(new OperatorWebhook() + .withExecutionUrl(operatorConfig.getWebhook().getExecutionUrl()) + .withExecutionBody(operatorConfig.getWebhook().getExecutionBody()) + .withWebhookConfigId(operatorConfig.getWebhook().getWebhookConfigId())); + // Null out the other configs, since it's mutually exclusive. We need to do this if it's an update. + standardSyncOperation.withOperatorNormalization(null); + standardSyncOperation.withOperatorDbt(null); + } + } + } + + public static OperationRead operationReadFromPersistedOperation(final StandardSyncOperation standardSyncOperation) { + final OperatorConfiguration operatorConfiguration = new OperatorConfiguration() + .operatorType(Enums.convertTo(standardSyncOperation.getOperatorType(), io.airbyte.api.model.generated.OperatorType.class)); + if (standardSyncOperation.getOperatorType() == null) { + // TODO(mfsiega-airbyte): this case shouldn't happen, but the API today would tolerate it. After + // verifying that it really can't happen, turn this into a precondition. + return new OperationRead() + .workspaceId(standardSyncOperation.getWorkspaceId()) + .operationId(standardSyncOperation.getOperationId()) + .name(standardSyncOperation.getName()); + } + switch (standardSyncOperation.getOperatorType()) { + case NORMALIZATION -> { + Preconditions.checkArgument(standardSyncOperation.getOperatorNormalization() != null); + operatorConfiguration.normalization(new io.airbyte.api.model.generated.OperatorNormalization() + .option(Enums.convertTo(standardSyncOperation.getOperatorNormalization().getOption(), OptionEnum.class))); + } + case DBT -> { + Preconditions.checkArgument(standardSyncOperation.getOperatorDbt() != null); + operatorConfiguration.dbt(new io.airbyte.api.model.generated.OperatorDbt() + .gitRepoUrl(standardSyncOperation.getOperatorDbt().getGitRepoUrl()) + .gitRepoBranch(standardSyncOperation.getOperatorDbt().getGitRepoBranch()) + .dockerImage(standardSyncOperation.getOperatorDbt().getDockerImage()) + .dbtArguments(standardSyncOperation.getOperatorDbt().getDbtArguments())); + } + case WEBHOOK -> { + Preconditions.checkArgument(standardSyncOperation.getOperatorWebhook() != null); + operatorConfiguration.webhook(new io.airbyte.api.model.generated.OperatorWebhook() + .webhookConfigId(standardSyncOperation.getOperatorWebhook().getWebhookConfigId()) + .executionUrl(standardSyncOperation.getOperatorWebhook().getExecutionUrl()) + .executionBody(standardSyncOperation.getOperatorWebhook().getExecutionBody())); + } + } + return new OperationRead() + .workspaceId(standardSyncOperation.getWorkspaceId()) + .operationId(standardSyncOperation.getOperationId()) + .name(standardSyncOperation.getName()) + .operatorConfiguration(operatorConfiguration); + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java b/airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java similarity index 86% rename from airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java rename to airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java index 2317a19706d9..f8ff8195865f 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/WebhookOperationConfigsConverter.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java @@ -15,7 +15,7 @@ import java.util.UUID; import java.util.stream.Collectors; -public class WebhookOperationConfigsConverter { +public class WorkspaceWebhookConfigsConverter { public static JsonNode toPersistenceWrite(List apiWebhookConfigs) { if (apiWebhookConfigs == null) { @@ -23,7 +23,7 @@ public static JsonNode toPersistenceWrite(List apiWebhookCon } final WebhookOperationConfigs configs = new WebhookOperationConfigs() - .withWebhookConfigs(apiWebhookConfigs.stream().map(WebhookOperationConfigsConverter::toPersistenceConfig).collect(Collectors.toList())); + .withWebhookConfigs(apiWebhookConfigs.stream().map(WorkspaceWebhookConfigsConverter::toPersistenceConfig).collect(Collectors.toList())); return Jsons.jsonNode(configs); } @@ -32,7 +32,7 @@ public static List toApiReads(List persistence if (persistenceConfig.isEmpty()) { return Collections.emptyList(); } - return persistenceConfig.stream().map(WebhookOperationConfigsConverter::toApiRead).collect(Collectors.toList()); + return persistenceConfig.stream().map(WorkspaceWebhookConfigsConverter::toApiRead).collect(Collectors.toList()); } private static WebhookConfig toPersistenceConfig(final WebhookConfigWrite input) { diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/OperationsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/OperationsHandler.java index ae6ab3be08b7..749a6ebf0e83 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/OperationsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/OperationsHandler.java @@ -16,17 +16,14 @@ import io.airbyte.api.model.generated.OperationReadList; import io.airbyte.api.model.generated.OperationUpdate; import io.airbyte.api.model.generated.OperatorConfiguration; -import io.airbyte.api.model.generated.OperatorNormalization.OptionEnum; import io.airbyte.commons.enums.Enums; import io.airbyte.config.ConfigSchema; -import io.airbyte.config.OperatorDbt; -import io.airbyte.config.OperatorNormalization; -import io.airbyte.config.OperatorNormalization.Option; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncOperation; import io.airbyte.config.StandardSyncOperation.OperatorType; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.server.converters.OperationsConverter; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.ArrayList; @@ -68,25 +65,13 @@ public OperationRead createOperation(final OperationCreate operationCreate) return persistOperation(standardSyncOperation); } - private static StandardSyncOperation toStandardSyncOperation(final OperationCreate operationCreate) { + private StandardSyncOperation toStandardSyncOperation(final OperationCreate operationCreate) { final StandardSyncOperation standardSyncOperation = new StandardSyncOperation() .withWorkspaceId(operationCreate.getWorkspaceId()) .withName(operationCreate.getName()) .withOperatorType(Enums.convertTo(operationCreate.getOperatorConfiguration().getOperatorType(), OperatorType.class)) .withTombstone(false); - if ((io.airbyte.api.model.generated.OperatorType.NORMALIZATION).equals(operationCreate.getOperatorConfiguration().getOperatorType())) { - Preconditions.checkArgument(operationCreate.getOperatorConfiguration().getNormalization() != null); - standardSyncOperation.withOperatorNormalization(new OperatorNormalization() - .withOption(Enums.convertTo(operationCreate.getOperatorConfiguration().getNormalization().getOption(), Option.class))); - } - if ((io.airbyte.api.model.generated.OperatorType.DBT).equals(operationCreate.getOperatorConfiguration().getOperatorType())) { - Preconditions.checkArgument(operationCreate.getOperatorConfiguration().getDbt() != null); - standardSyncOperation.withOperatorDbt(new OperatorDbt() - .withGitRepoUrl(operationCreate.getOperatorConfiguration().getDbt().getGitRepoUrl()) - .withGitRepoBranch(operationCreate.getOperatorConfiguration().getDbt().getGitRepoBranch()) - .withDockerImage(operationCreate.getOperatorConfiguration().getDbt().getDockerImage()) - .withDbtArguments(operationCreate.getOperatorConfiguration().getDbt().getDbtArguments())); - } + OperationsConverter.populateOperatorConfigFromApi(operationCreate.getOperatorConfiguration(), standardSyncOperation); return standardSyncOperation; } @@ -97,6 +82,9 @@ private void validateOperation(final OperatorConfiguration operatorConfiguration if ((io.airbyte.api.model.generated.OperatorType.DBT).equals(operatorConfiguration.getOperatorType())) { Preconditions.checkArgument(operatorConfiguration.getDbt() != null); } + if (io.airbyte.api.model.generated.OperatorType.WEBHOOK.equals(operatorConfiguration.getOperatorType())) { + Preconditions.checkArgument(operatorConfiguration.getWebhook() != null); + } } public OperationRead updateOperation(final OperationUpdate operationUpdate) @@ -113,25 +101,8 @@ private OperationRead persistOperation(final StandardSyncOperation standardSyncO public static StandardSyncOperation updateOperation(final OperationUpdate operationUpdate, final StandardSyncOperation standardSyncOperation) { standardSyncOperation - .withName(operationUpdate.getName()) - .withOperatorType(Enums.convertTo(operationUpdate.getOperatorConfiguration().getOperatorType(), OperatorType.class)); - if ((io.airbyte.api.model.generated.OperatorType.NORMALIZATION).equals(operationUpdate.getOperatorConfiguration().getOperatorType())) { - Preconditions.checkArgument(operationUpdate.getOperatorConfiguration().getNormalization() != null); - standardSyncOperation.withOperatorNormalization(new OperatorNormalization() - .withOption(Enums.convertTo(operationUpdate.getOperatorConfiguration().getNormalization().getOption(), Option.class))); - } else { - standardSyncOperation.withOperatorNormalization(null); - } - if ((io.airbyte.api.model.generated.OperatorType.DBT).equals(operationUpdate.getOperatorConfiguration().getOperatorType())) { - Preconditions.checkArgument(operationUpdate.getOperatorConfiguration().getDbt() != null); - standardSyncOperation.withOperatorDbt(new OperatorDbt() - .withGitRepoUrl(operationUpdate.getOperatorConfiguration().getDbt().getGitRepoUrl()) - .withGitRepoBranch(operationUpdate.getOperatorConfiguration().getDbt().getGitRepoBranch()) - .withDockerImage(operationUpdate.getOperatorConfiguration().getDbt().getDockerImage()) - .withDbtArguments(operationUpdate.getOperatorConfiguration().getDbt().getDbtArguments())); - } else { - standardSyncOperation.withOperatorDbt(null); - } + .withName(operationUpdate.getName()); + OperationsConverter.populateOperatorConfigFromApi(operationUpdate.getOperatorConfiguration(), standardSyncOperation); return standardSyncOperation; } @@ -214,26 +185,7 @@ private OperationRead buildOperationRead(final UUID operationId) } private static OperationRead buildOperationRead(final StandardSyncOperation standardSyncOperation) { - final OperatorConfiguration operatorConfiguration = new OperatorConfiguration() - .operatorType(Enums.convertTo(standardSyncOperation.getOperatorType(), io.airbyte.api.model.generated.OperatorType.class)); - if ((OperatorType.NORMALIZATION).equals(standardSyncOperation.getOperatorType())) { - Preconditions.checkArgument(standardSyncOperation.getOperatorNormalization() != null); - operatorConfiguration.normalization(new io.airbyte.api.model.generated.OperatorNormalization() - .option(Enums.convertTo(standardSyncOperation.getOperatorNormalization().getOption(), OptionEnum.class))); - } - if ((OperatorType.DBT).equals(standardSyncOperation.getOperatorType())) { - Preconditions.checkArgument(standardSyncOperation.getOperatorDbt() != null); - operatorConfiguration.dbt(new io.airbyte.api.model.generated.OperatorDbt() - .gitRepoUrl(standardSyncOperation.getOperatorDbt().getGitRepoUrl()) - .gitRepoBranch(standardSyncOperation.getOperatorDbt().getGitRepoBranch()) - .dockerImage(standardSyncOperation.getOperatorDbt().getDockerImage()) - .dbtArguments(standardSyncOperation.getOperatorDbt().getDbtArguments())); - } - return new OperationRead() - .workspaceId(standardSyncOperation.getWorkspaceId()) - .operationId(standardSyncOperation.getOperationId()) - .name(standardSyncOperation.getName()) - .operatorConfiguration(operatorConfiguration); + return OperationsConverter.operationReadFromPersistedOperation(standardSyncOperation); } } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java index 7e949a89d201..3b49e60539eb 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java @@ -32,7 +32,7 @@ import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.notification.NotificationClient; import io.airbyte.server.converters.NotificationConverter; -import io.airbyte.server.converters.WebhookOperationConfigsConverter; +import io.airbyte.server.converters.WorkspaceWebhookConfigsConverter; import io.airbyte.server.errors.IdNotFoundKnownException; import io.airbyte.server.errors.InternalServerKnownException; import io.airbyte.server.errors.ValueConflictKnownException; @@ -108,7 +108,7 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate) .withTombstone(false) .withNotifications(NotificationConverter.toConfigList(workspaceCreate.getNotifications())) .withDefaultGeography(defaultGeography) - .withWebhookOperationConfigs(WebhookOperationConfigsConverter.toPersistenceWrite(workspaceCreate.getWebhookConfigs())); + .withWebhookOperationConfigs(WorkspaceWebhookConfigsConverter.toPersistenceWrite(workspaceCreate.getWebhookConfigs())); if (!Strings.isNullOrEmpty(email)) { workspace.withEmail(email); @@ -273,7 +273,7 @@ private static WorkspaceRead buildWorkspaceRead(final StandardWorkspace workspac workspace.getWebhookOperationConfigs(), WebhookOperationConfigs.class); if (persistedConfigs.isPresent()) { - result.setWebhookConfigs(WebhookOperationConfigsConverter.toApiReads( + result.setWebhookConfigs(WorkspaceWebhookConfigsConverter.toApiReads( persistedConfigs.get().getWebhookConfigs())); } return result; diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/OperationsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/OperationsHandlerTest.java index 7e8e2cb05adf..a9784d06530b 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/OperationsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/OperationsHandlerTest.java @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -22,6 +23,7 @@ import io.airbyte.api.model.generated.OperatorNormalization; import io.airbyte.api.model.generated.OperatorNormalization.OptionEnum; import io.airbyte.api.model.generated.OperatorType; +import io.airbyte.api.model.generated.OperatorWebhook; import io.airbyte.commons.enums.Enums; import io.airbyte.config.OperatorNormalization.Option; import io.airbyte.config.StandardSync; @@ -41,9 +43,14 @@ class OperationsHandlerTest { + private static final String WEBHOOK_OPERATION_NAME = "fake-operation-name"; + private static final UUID WEBHOOK_CONFIG_ID = UUID.randomUUID(); + private static final String WEBHOOK_EXECUTION_URL = "fake-execution-url"; + private static final String WEBHOOK_EXECUTION_BODY = "fake-execution-body"; + private static final UUID WEBHOOK_OPERATION_ID = UUID.randomUUID(); + public static final String NEW_EXECUTION_URL = "new-execution-url"; private ConfigRepository configRepository; private Supplier uuidGenerator; - private OperationsHandler operationsHandler; private StandardSyncOperation standardSyncOperation; @@ -92,6 +99,43 @@ void testCreateOperation() throws JsonValidationException, ConfigNotFoundExcepti verify(configRepository).writeStandardSyncOperation(standardSyncOperation); } + @Test + void testCreateWebhookOperation() throws JsonValidationException, ConfigNotFoundException, IOException { + when(uuidGenerator.get()).thenReturn(WEBHOOK_OPERATION_ID); + final OperatorWebhook webhookConfig = new OperatorWebhook() + .webhookConfigId(WEBHOOK_CONFIG_ID) + .executionUrl(WEBHOOK_EXECUTION_URL) + .executionBody(WEBHOOK_EXECUTION_BODY); + final OperationCreate operationCreate = new OperationCreate() + .workspaceId(standardSyncOperation.getWorkspaceId()) + .name(WEBHOOK_OPERATION_NAME) + .operatorConfiguration(new OperatorConfiguration() + .operatorType(OperatorType.WEBHOOK).webhook(webhookConfig)); + + final StandardSyncOperation expectedPersistedOperation = new StandardSyncOperation() + .withWorkspaceId(standardSyncOperation.getWorkspaceId()) + .withOperationId(WEBHOOK_OPERATION_ID) + .withName(WEBHOOK_OPERATION_NAME) + .withOperatorType(StandardSyncOperation.OperatorType.WEBHOOK) + .withOperatorWebhook(new io.airbyte.config.OperatorWebhook() + .withWebhookConfigId(WEBHOOK_CONFIG_ID) + .withExecutionUrl(WEBHOOK_EXECUTION_URL) + .withExecutionBody(WEBHOOK_EXECUTION_BODY)) + .withTombstone(false); + + when(configRepository.getStandardSyncOperation(WEBHOOK_OPERATION_ID)).thenReturn(expectedPersistedOperation); + + final OperationRead actualOperationRead = operationsHandler.createOperation(operationCreate); + + assertEquals(operationCreate.getWorkspaceId(), actualOperationRead.getWorkspaceId()); + assertEquals(WEBHOOK_OPERATION_ID, actualOperationRead.getOperationId()); + assertEquals(WEBHOOK_OPERATION_NAME, actualOperationRead.getName()); + assertEquals(OperatorType.WEBHOOK, actualOperationRead.getOperatorConfiguration().getOperatorType()); + assertEquals(webhookConfig, actualOperationRead.getOperatorConfiguration().getWebhook()); + + verify(configRepository).writeStandardSyncOperation(eq(expectedPersistedOperation)); + } + @Test void testUpdateOperation() throws JsonValidationException, ConfigNotFoundException, IOException { final OperationUpdate operationUpdate = new OperationUpdate() @@ -140,6 +184,43 @@ void testUpdateOperation() throws JsonValidationException, ConfigNotFoundExcepti verify(configRepository).writeStandardSyncOperation(updatedStandardSyncOperation); } + @Test + void testUpdateWebhookOperation() throws JsonValidationException, ConfigNotFoundException, IOException { + when(uuidGenerator.get()).thenReturn(WEBHOOK_OPERATION_ID); + final OperatorWebhook webhookConfig = new OperatorWebhook() + .webhookConfigId(WEBHOOK_CONFIG_ID) + .executionUrl(NEW_EXECUTION_URL) + .executionBody(WEBHOOK_EXECUTION_BODY); + final OperationUpdate operationUpdate = new OperationUpdate() + .name(WEBHOOK_OPERATION_NAME) + .operationId(WEBHOOK_OPERATION_ID) + .operatorConfiguration(new OperatorConfiguration() + .operatorType(OperatorType.WEBHOOK).webhook(webhookConfig)); + + final StandardSyncOperation persistedOperation = new StandardSyncOperation() + .withWorkspaceId(standardSyncOperation.getWorkspaceId()) + .withOperationId(WEBHOOK_OPERATION_ID) + .withName(WEBHOOK_OPERATION_NAME) + .withOperatorType(StandardSyncOperation.OperatorType.WEBHOOK) + .withOperatorWebhook(new io.airbyte.config.OperatorWebhook() + .withWebhookConfigId(WEBHOOK_CONFIG_ID) + .withExecutionUrl(WEBHOOK_EXECUTION_URL) + .withExecutionBody(WEBHOOK_EXECUTION_BODY)); + + when(configRepository.getStandardSyncOperation(WEBHOOK_OPERATION_ID)).thenReturn(persistedOperation); + + final OperationRead actualOperationRead = operationsHandler.updateOperation(operationUpdate); + + assertEquals(WEBHOOK_OPERATION_ID, actualOperationRead.getOperationId()); + assertEquals(WEBHOOK_OPERATION_NAME, actualOperationRead.getName()); + assertEquals(OperatorType.WEBHOOK, actualOperationRead.getOperatorConfiguration().getOperatorType()); + assertEquals(webhookConfig, actualOperationRead.getOperatorConfiguration().getWebhook()); + + verify(configRepository) + .writeStandardSyncOperation(persistedOperation.withOperatorWebhook(persistedOperation.getOperatorWebhook().withExecutionUrl( + NEW_EXECUTION_URL))); + } + @Test void testGetOperation() throws JsonValidationException, ConfigNotFoundException, IOException { when(configRepository.getStandardSyncOperation(standardSyncOperation.getOperationId())).thenReturn(standardSyncOperation); diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index ca18b820a0da..85f426ab24bf 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -5665,6 +5665,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -5781,6 +5786,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -5853,6 +5863,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -5868,6 +5883,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -5940,6 +5960,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -8333,6 +8358,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -8348,6 +8378,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -8548,6 +8583,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -8563,6 +8603,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -9011,6 +9056,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -9026,6 +9076,11 @@

    Example data

    "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "operatorConfiguration" : { + "webhook" : { + "webhookConfigId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "executionUrl" : "executionUrl", + "executionBody" : "executionBody" + }, "normalization" : { "option" : "basic" }, @@ -9876,6 +9931,7 @@

    Table of Contents

  • OperatorDbt -
  • OperatorNormalization -
  • OperatorType -
  • +
  • OperatorWebhook -
  • Pagination -
  • PrivateDestinationDefinitionRead -
  • PrivateDestinationDefinitionReadList -
  • @@ -10920,6 +10976,7 @@

    OperatorConfiguration - operatorType
    normalization (optional)
    dbt (optional)
    +
    webhook (optional)
    @@ -10947,6 +11004,15 @@

    OperatorType -

    +
    +

    OperatorWebhook - Up

    +
    +
    +
    executionUrl
    String The URL to call to execute the webhook operation via POST request.
    +
    executionBody (optional)
    String If populated, this will be sent with the POST request.
    +
    webhookConfigId (optional)
    UUID The id of the webhook configs to use from the workspace. format: uuid
    +
    +

    Pagination - Up

    From 18c328da2b9d5ed25957b72c6aba349b16074d95 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Fri, 14 Oct 2022 14:55:39 -0700 Subject: [PATCH 121/498] Bmoric/build platform if connector pg changed (#18014) * Trigger platform build when the connectors use in the Acceptance test are updated * Remove the ci skip when publish --- .github/workflows/gradle.yml | 2 ++ .github/workflows/publish-command.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1682e4fc1f1e..68bf71198a9f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -47,6 +47,8 @@ jobs: filters: | backend: - 'airbyte-!(cdk|integrations|webapp|webapp-e2e-tests)/**' + - 'airbyte-integrations/connectors/(destination-jdbc|destination-postgres|source-jdbc|source-postgres)/**' + - 'airbyte-config/init/src/main/resources/seed/(source|destination)_definitions.yaml' build: - '.github/**' - 'buildSrc/**' diff --git a/.github/workflows/publish-command.yml b/.github/workflows/publish-command.yml index 242216dbc18e..0ef8a5f2b04d 100644 --- a/.github/workflows/publish-command.yml +++ b/.github/workflows/publish-command.yml @@ -343,7 +343,7 @@ jobs: if: github.event.inputs.auto-bump-version == 'true' && success() run: | git add -u - git commit -m "auto-bump connector version [ci skip]" + git commit -m "auto-bump connector version" git pull origin ${{ github.event.inputs.gitref }} git push origin ${{ github.event.inputs.gitref }} id: auto-bump From a73ae2b09d8e8e897f9c4836e8723ca8cedf6d28 Mon Sep 17 00:00:00 2001 From: Greg Solovyev Date: Fri, 14 Oct 2022 16:12:16 -0700 Subject: [PATCH 122/498] Add SSH tunneling to Elastic Search Destination (#17805) * Add support for using URL in tunneled config * Add support for SSH tunneling to destination-elasticsearch and destination-elasticsearch-strict-encrypt --- .../seed/destination_definitions.yaml | 2 +- .../resources/seed/destination_specs.yaml | 103 +++++++++++- .../integrations/base/ssh/SshTunnel.java | 148 +++++++++++++----- .../base/ssh/SshWrappedDestination.java | 16 +- .../integrations/base/ssh/SshTunnelTest.java | 76 ++++++++- .../Dockerfile | 2 +- .../destination-elasticsearch/Dockerfile | 2 +- .../ElasticsearchDestination.java | 7 +- ...lasticsearchDestinationAcceptanceTest.java | 20 ++- ...lasticsearchDestinationAcceptanceTest.java | 64 ++++++++ ...lasticsearchDestinationAcceptanceTest.java | 10 ++ ...lasticsearchDestinationAcceptanceTest.java | 11 ++ .../destinations/elasticsearch.md | 35 ++++- 13 files changed, 429 insertions(+), 67 deletions(-) create mode 100644 airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 71c2d7305fd1..95292e8f1949 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -100,7 +100,7 @@ - destinationDefinitionId: 68f351a7-2745-4bef-ad7f-996b8e51bb8c name: ElasticSearch dockerRepository: airbyte/destination-elasticsearch - dockerImageTag: 0.1.3 + dockerImageTag: 0.1.4 documentationUrl: https://docs.airbyte.com/integrations/destinations/elasticsearch icon: elasticsearch.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 0c3794a58c19..88ae685a454c 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -1654,7 +1654,7 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-elasticsearch:0.1.3" +- dockerImage: "airbyte/destination-elasticsearch:0.1.4" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/elasticsearch" connectionSpecification: @@ -1733,6 +1733,107 @@ \ server" type: "string" airbyte_secret: true + tunnel_method: + type: "object" + title: "SSH Tunnel Method" + description: "Whether to initiate an SSH tunnel before connecting to the\ + \ database, and if so, which kind of authentication to use." + oneOf: + - title: "No Tunnel" + required: + - "tunnel_method" + properties: + tunnel_method: + description: "No ssh tunnel needed to connect to database" + type: "string" + const: "NO_TUNNEL" + order: 0 + - title: "SSH Key Authentication" + required: + - "tunnel_method" + - "tunnel_host" + - "tunnel_port" + - "tunnel_user" + - "ssh_key" + properties: + tunnel_method: + description: "Connect through a jump server tunnel host using username\ + \ and ssh key" + type: "string" + const: "SSH_KEY_AUTH" + order: 0 + tunnel_host: + title: "SSH Tunnel Jump Server Host" + description: "Hostname of the jump server host that allows inbound\ + \ ssh tunnel." + type: "string" + order: 1 + tunnel_port: + title: "SSH Connection Port" + description: "Port on the proxy/jump server that accepts inbound ssh\ + \ connections." + type: "integer" + minimum: 0 + maximum: 65536 + default: 22 + examples: + - "22" + order: 2 + tunnel_user: + title: "SSH Login Username" + description: "OS-level username for logging into the jump server host." + type: "string" + order: 3 + ssh_key: + title: "SSH Private Key" + description: "OS-level user account ssh key credentials in RSA PEM\ + \ format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa )" + type: "string" + airbyte_secret: true + multiline: true + order: 4 + - title: "Password Authentication" + required: + - "tunnel_method" + - "tunnel_host" + - "tunnel_port" + - "tunnel_user" + - "tunnel_user_password" + properties: + tunnel_method: + description: "Connect through a jump server tunnel host using username\ + \ and password authentication" + type: "string" + const: "SSH_PASSWORD_AUTH" + order: 0 + tunnel_host: + title: "SSH Tunnel Jump Server Host" + description: "Hostname of the jump server host that allows inbound\ + \ ssh tunnel." + type: "string" + order: 1 + tunnel_port: + title: "SSH Connection Port" + description: "Port on the proxy/jump server that accepts inbound ssh\ + \ connections." + type: "integer" + minimum: 0 + maximum: 65536 + default: 22 + examples: + - "22" + order: 2 + tunnel_user: + title: "SSH Login Username" + description: "OS-level username for logging into the jump server host" + type: "string" + order: 3 + tunnel_user_password: + title: "Password" + description: "OS-level password for logging into the jump server host" + type: "string" + airbyte_secret: true + order: 4 supportsIncremental: true supportsNormalization: false supportsDBT: false diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java index 8a4ba2cb02a0..1c694def8e9a 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java @@ -10,12 +10,16 @@ import io.airbyte.commons.functional.CheckedFunction; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.string.Strings; +import io.airbyte.integrations.base.AirbyteTraceMessageUtility; import java.io.IOException; import java.io.StringReader; import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.URL; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.time.Duration; +import java.util.Arrays; import java.util.List; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier; @@ -54,10 +58,13 @@ public enum TunnelMethod { private final int tunnelPort; private final String tunnelUser; private final String sshKey; + private final String endPointKey; + private final String remoteServiceProtocol; + private final String remoteServicePath; private final String tunnelUserPassword; - private final String remoteDatabaseHost; - private final int remoteDatabasePort; - private int tunnelDatabasePort; + private final String remoteServiceHost; + private final int remoteServicePort; + protected int tunnelLocalPort; private SshClient sshclient; private ClientSession tunnelSession; @@ -69,6 +76,10 @@ public enum TunnelMethod { * in the config remoteDatabaseHost is found. * @param portKey - a list of keys that point to the database port. should be pointing to where in * the config remoteDatabasePort is found. + * @param endPointKey - key that points to the endpoint URL (this is commonly used for REST-based + * services such as Elastic and MongoDB) + * @param remoteServiceUrl - URL of the remote endpoint (this is commonly used for REST-based * + * services such as Elastic and MongoDB) * @param tunnelMethod - the type of ssh method that should be used (includes not using SSH at all). * @param tunnelHost - host name of the machine to which we will establish an ssh connection (e.g. * hostname of the bastion). @@ -79,25 +90,28 @@ public enum TunnelMethod { * using tunnelUserPassword instead. * @param tunnelUserPassword - the password for the tunnelUser. can be null if we are using sshKey * instead. - * @param remoteDatabaseHost - the actual host name of the database (as it is known to the tunnel + * @param remoteServiceHost - the actual host name of the remote service (as it is known to the + * tunnel host). + * @param remoteServicePort - the actual port of the remote service (as it is known to the tunnel * host). - * @param remoteDatabasePort - the actual port of the database (as it is known to the tunnel host). */ public SshTunnel(final JsonNode config, final List hostKey, final List portKey, + final String endPointKey, + final String remoteServiceUrl, final TunnelMethod tunnelMethod, final String tunnelHost, final int tunnelPort, final String tunnelUser, final String sshKey, final String tunnelUserPassword, - final String remoteDatabaseHost, - final int remoteDatabasePort) { + final String remoteServiceHost, + final int remoteServicePort) { this.config = config; this.hostKey = hostKey; this.portKey = portKey; - + this.endPointKey = endPointKey; Preconditions.checkNotNull(tunnelMethod); this.tunnelMethod = tunnelMethod; @@ -107,8 +121,10 @@ public SshTunnel(final JsonNode config, this.tunnelUser = null; this.sshKey = null; this.tunnelUserPassword = null; - this.remoteDatabaseHost = null; - this.remoteDatabasePort = 0; + this.remoteServiceHost = null; + this.remoteServicePort = 0; + this.remoteServiceProtocol = null; + this.remoteServicePath = null; } else { Preconditions.checkNotNull(tunnelHost); Preconditions.checkArgument(tunnelPort > 0); @@ -119,17 +135,34 @@ public SshTunnel(final JsonNode config, if (tunnelMethod.equals(TunnelMethod.SSH_PASSWORD_AUTH)) { Preconditions.checkNotNull(tunnelUserPassword); } - Preconditions.checkNotNull(remoteDatabaseHost); - Preconditions.checkArgument(remoteDatabasePort > 0); + // must provide either host/port or endpoint + Preconditions.checkArgument((hostKey != null && portKey != null) || endPointKey != null); + Preconditions.checkArgument((remoteServiceHost != null && remoteServicePort > 0) || remoteServiceUrl != null); + if (remoteServiceUrl != null) { + URL urlObject = null; + try { + urlObject = new URL(remoteServiceUrl); + } catch (MalformedURLException e) { + AirbyteTraceMessageUtility.emitConfigErrorTrace(e, + String.format("Provided value for remote service URL is not valid: %s", remoteServiceUrl)); + } + Preconditions.checkNotNull(urlObject, "Failed to parse URL of remote service"); + this.remoteServiceHost = urlObject.getHost(); + this.remoteServicePort = urlObject.getPort(); + this.remoteServiceProtocol = urlObject.getProtocol(); + this.remoteServicePath = urlObject.getPath(); + } else { + this.remoteServiceProtocol = null; + this.remoteServicePath = null; + this.remoteServiceHost = remoteServiceHost; + this.remoteServicePort = remoteServicePort; + } this.tunnelHost = tunnelHost; this.tunnelPort = tunnelPort; this.tunnelUser = tunnelUser; this.sshKey = sshKey; this.tunnelUserPassword = tunnelUserPassword; - this.remoteDatabaseHost = remoteDatabaseHost; - this.remoteDatabasePort = remoteDatabasePort; - this.sshclient = createClient(); this.tunnelSession = openTunnel(sshclient); } @@ -139,42 +172,37 @@ public JsonNode getOriginalConfig() { return config; } - public JsonNode getConfigInTunnel() { + public JsonNode getConfigInTunnel() throws Exception { if (tunnelMethod.equals(TunnelMethod.NO_TUNNEL)) { return getOriginalConfig(); } else { final JsonNode clone = Jsons.clone(config); - Jsons.replaceNestedString(clone, hostKey, SshdSocketAddress.LOCALHOST_ADDRESS.getHostName()); - Jsons.replaceNestedInt(clone, portKey, tunnelDatabasePort); + if (hostKey != null) { + Jsons.replaceNestedString(clone, hostKey, SshdSocketAddress.LOCALHOST_ADDRESS.getHostName()); + } + if (portKey != null) { + Jsons.replaceNestedInt(clone, portKey, tunnelLocalPort); + } + if (endPointKey != null) { + URL tunnelEndPointURL = new URL(remoteServiceProtocol, SshdSocketAddress.LOCALHOST_ADDRESS.getHostName(), tunnelLocalPort, remoteServicePath); + Jsons.replaceNestedString(clone, Arrays.asList(endPointKey), tunnelEndPointURL.toString()); + } return clone; } } - // /** - // * Finds a free port on the machine. As soon as this method returns, it is possible for process to - // bind to this port. Thus it only gives a guarantee that at the time - // */ - // private static int findFreePort() { - // // finds an available port. - // try (final var socket = new ServerSocket(0)) { - // return socket.getLocalPort(); - // } catch (final IOException e) { - // throw new RuntimeException(e); - // } - // } - public static SshTunnel getInstance(final JsonNode config, final List hostKey, final List portKey) { final TunnelMethod tunnelMethod = Jsons.getOptional(config, "tunnel_method", "tunnel_method") .map(method -> TunnelMethod.valueOf(method.asText().trim())) .orElse(TunnelMethod.NO_TUNNEL); LOGGER.info("Starting connection with method: {}", tunnelMethod); - // final int localPort = findFreePort(); - return new SshTunnel( config, hostKey, portKey, + null, + null, tunnelMethod, Strings.safeTrim(Jsons.getStringOrNull(config, "tunnel_method", "tunnel_host")), Jsons.getIntOrZero(config, "tunnel_method", "tunnel_port"), @@ -185,6 +213,27 @@ public static SshTunnel getInstance(final JsonNode config, final List ho Jsons.getIntOrZero(config, portKey)); } + public static SshTunnel getInstance(final JsonNode config, final String endPointKey) throws Exception { + final TunnelMethod tunnelMethod = Jsons.getOptional(config, "tunnel_method", "tunnel_method") + .map(method -> TunnelMethod.valueOf(method.asText().trim())) + .orElse(TunnelMethod.NO_TUNNEL); + LOGGER.info("Starting connection with method: {}", tunnelMethod); + + return new SshTunnel( + config, + null, + null, + endPointKey, + Jsons.getStringOrNull(config, endPointKey), + tunnelMethod, + Strings.safeTrim(Jsons.getStringOrNull(config, "tunnel_method", "tunnel_host")), + Jsons.getIntOrZero(config, "tunnel_method", "tunnel_port"), + Strings.safeTrim(Jsons.getStringOrNull(config, "tunnel_method", "tunnel_user")), + Strings.safeTrim(Jsons.getStringOrNull(config, "tunnel_method", "ssh_key")), + Strings.safeTrim(Jsons.getStringOrNull(config, "tunnel_method", "tunnel_user_password")), + null, 0); + } + public static void sshWrap(final JsonNode config, final List hostKey, final List portKey, @@ -196,6 +245,16 @@ public static void sshWrap(final JsonNode config, }); } + public static void sshWrap(final JsonNode config, + final String endPointKey, + final CheckedConsumer wrapped) + throws Exception { + sshWrap(config, endPointKey, (configInTunnel) -> { + wrapped.accept(configInTunnel); + return null; + }); + } + public static T sshWrap(final JsonNode config, final List hostKey, final List portKey, @@ -206,6 +265,15 @@ public static T sshWrap(final JsonNode config, } } + public static T sshWrap(final JsonNode config, + final String endPointKey, + final CheckedFunction wrapped) + throws Exception { + try (final SshTunnel sshTunnel = SshTunnel.getInstance(config, endPointKey)) { + return wrapped.apply(sshTunnel.getConfigInTunnel()); + } + } + /** * Closes a tunnel if one was open, and otherwise doesn't do anything (safe to run). */ @@ -281,13 +349,13 @@ ClientSession openTunnel(final SshClient client) { final SshdSocketAddress address = session.startLocalPortForwarding( // entering 0 lets the OS pick a free port for us. new SshdSocketAddress(InetSocketAddress.createUnresolved(SshdSocketAddress.LOCALHOST_ADDRESS.getHostName(), 0)), - new SshdSocketAddress(remoteDatabaseHost, remoteDatabasePort)); + new SshdSocketAddress(remoteServiceHost, remoteServicePort)); // discover the port that the OS picked and remember it so that we can use it when we try to connect - // later. - tunnelDatabasePort = address.getPort(); + tunnelLocalPort = address.getPort(); - LOGGER.info("Established tunneling session. Port forwarding started on " + address.toInetSocketAddress()); + LOGGER.info(String.format("Established tunneling session to %s:%d. Port forwarding started on %s ", + remoteServiceHost, remoteServicePort, address.toInetSocketAddress())); return session; } catch (final IOException | GeneralSecurityException e) { throw new RuntimeException(e); @@ -303,9 +371,9 @@ public String toString() { ", tunnelHost='" + tunnelHost + '\'' + ", tunnelPort=" + tunnelPort + ", tunnelUser='" + tunnelUser + '\'' + - ", remoteDatabaseHost='" + remoteDatabaseHost + '\'' + - ", remoteDatabasePort=" + remoteDatabasePort + - ", tunnelDatabasePort=" + tunnelDatabasePort + + ", remoteServiceHost='" + remoteServiceHost + '\'' + + ", remoteServicePort=" + remoteServicePort + + ", tunnelLocalPort=" + tunnelLocalPort + '}'; } diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java index b7397b3389d7..9dfea5c37729 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java @@ -30,6 +30,7 @@ public class SshWrappedDestination implements Destination { private final Destination delegate; private final List hostKey; private final List portKey; + private final String endPointKey; public SshWrappedDestination(final Destination delegate, final List hostKey, @@ -37,6 +38,15 @@ public SshWrappedDestination(final Destination delegate, this.delegate = delegate; this.hostKey = hostKey; this.portKey = portKey; + this.endPointKey = null; + } + + public SshWrappedDestination(final Destination delegate, + String endPointKey) { + this.delegate = delegate; + this.endPointKey = endPointKey; + this.portKey = null; + this.hostKey = null; } @Override @@ -50,7 +60,8 @@ public ConnectorSpecification spec() throws Exception { @Override public AirbyteConnectionStatus check(final JsonNode config) throws Exception { - return SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); + return (endPointKey != null) ? SshTunnel.sshWrap(config, endPointKey, delegate::check) + : SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); } @Override @@ -58,7 +69,8 @@ public AirbyteMessageConsumer getConsumer(final JsonNode config, final ConfiguredAirbyteCatalog catalog, final Consumer outputRecordCollector) throws Exception { - final SshTunnel tunnel = SshTunnel.getInstance(config, hostKey, portKey); + final SshTunnel tunnel = (endPointKey != null) ? SshTunnel.getInstance(config, endPointKey) : SshTunnel.getInstance(config, hostKey, portKey); + final AirbyteMessageConsumer delegateConsumer; try { delegateConsumer = delegate.getConsumer(tunnel.getConfigInTunnel(), catalog, outputRecordCollector); diff --git a/airbyte-integrations/bases/base-java/src/test/java/io/airbyte/integrations/base/ssh/SshTunnelTest.java b/airbyte-integrations/bases/base-java/src/test/java/io/airbyte/integrations/base/ssh/SshTunnelTest.java index 59bb1ff39d78..314acd532e17 100644 --- a/airbyte-integrations/bases/base-java/src/test/java/io/airbyte/integrations/base/ssh/SshTunnelTest.java +++ b/airbyte-integrations/bases/base-java/src/test/java/io/airbyte/integrations/base/ssh/SshTunnelTest.java @@ -4,11 +4,14 @@ package io.airbyte.integrations.base.ssh; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.airbyte.commons.json.Jsons; import io.airbyte.integrations.base.ssh.SshTunnel.TunnelMethod; import java.nio.charset.StandardCharsets; import java.security.KeyPair; @@ -70,19 +73,88 @@ class SshTunnelTest { + "5WlcOxfV1wZuaM0fOd+PBmIlFEE7Uf6AY/UahBAxaFV2+twgK9GCDcu1t4Ye9wZ9kZ4Nal\\n" + "0fkKD4uN4DRO8hAAAAFm10dWhhaUBrYnAxLWxocC1hMTQ1MzMBAgME\\n" + "-----END OPENSSH PRIVATE KEY-----"; - private static final String CONFIG = + private static final String HOST_PORT_CONFIG = "{\"ssl\":true,\"host\":\"fakehost.com\",\"port\":5432,\"schema\":\"public\",\"database\":\"postgres\",\"password\":\"\",\"username\":\"postgres\",\"tunnel_method\":{\"ssh_key\":\"" + "%s" + "\",\"tunnel_host\":\"faketunnel.com\",\"tunnel_port\":22,\"tunnel_user\":\"ec2-user\",\"tunnel_method\":\"SSH_KEY_AUTH\"}}"; + private static final String URL_CONFIG_WITH_PORT = + "{\"ssl\":true,\"endpoint\":\"http://fakehost.com:9090/service\",\"password\":\"\",\"username\":\"restuser\",\"tunnel_method\":{\"ssh_key\":\"" + + "%s" + + "\",\"tunnel_host\":\"faketunnel.com\",\"tunnel_port\":22,\"tunnel_user\":\"ec2-user\",\"tunnel_method\":\"SSH_KEY_AUTH\"}}"; + + private static final String URL_CONFIG_NO_PORT = + "{\"ssl\":true,\"endpoint\":\"http://fakehost.com/service\",\"password\":\"\",\"username\":\"restuser\",\"tunnel_method\":{\"ssh_key\":\"" + + "%s" + + "\",\"tunnel_host\":\"faketunnel.com\",\"tunnel_port\":22,\"tunnel_user\":\"ec2-user\",\"tunnel_method\":\"SSH_KEY_AUTH\"}}"; + + /** + * This test verifies that OpenSsh correctly replaces values in connector configuration in a spec + * with host/port config and in a spec with endpoint URL config + * + * @param configString + * @throws Exception + */ + @ParameterizedTest + @ValueSource(strings = {HOST_PORT_CONFIG, URL_CONFIG_WITH_PORT, URL_CONFIG_NO_PORT}) + public void testConfigInTunnel(final String configString) throws Exception { + final JsonNode config = (new ObjectMapper()).readTree(String.format(configString, SSH_RSA_PRIVATE_KEY)); + String endPointURL = Jsons.getStringOrNull(config, "endpoint"); + final SshTunnel sshTunnel = new SshTunnel( + config, + endPointURL == null ? Arrays.asList(new String[] {"host"}) : null, + endPointURL == null ? Arrays.asList(new String[] {"port"}) : null, + endPointURL == null ? null : "endpoint", + endPointURL, + TunnelMethod.SSH_KEY_AUTH, + "faketunnel.com", + 22, + "tunnelUser", + SSH_RSA_PRIVATE_KEY, + "tunnelUserPassword", + endPointURL == null ? "fakeHost.com" : null, + endPointURL == null ? 5432 : 0) { + + @Override + ClientSession openTunnel(final SshClient client) { + tunnelLocalPort = 8080; + return null; // Prevent tunnel from attempting to connect + } + + }; + + final JsonNode configInTunnel = sshTunnel.getConfigInTunnel(); + if (endPointURL == null) { + assertTrue(configInTunnel.has("port")); + assertTrue(configInTunnel.has("host")); + assertFalse(configInTunnel.has("endpoint")); + assertEquals(8080, configInTunnel.get("port").asInt()); + assertEquals("127.0.0.1", configInTunnel.get("host").asText()); + } else { + assertFalse(configInTunnel.has("port")); + assertFalse(configInTunnel.has("host")); + assertTrue(configInTunnel.has("endpoint")); + assertEquals("http://127.0.0.1:8080/service", configInTunnel.get("endpoint").asText()); + } + } + + /** + * This test verifies that SshTunnel correctly extracts private key pairs from keys formatted as + * EdDSA and OpenSSH + * + * @param privateKey + * @throws Exception + */ @ParameterizedTest @ValueSource(strings = {SSH_ED25519_PRIVATE_KEY, SSH_RSA_PRIVATE_KEY}) public void getKeyPair(final String privateKey) throws Exception { - final JsonNode config = (new ObjectMapper()).readTree(String.format(CONFIG, privateKey)); + final JsonNode config = (new ObjectMapper()).readTree(String.format(HOST_PORT_CONFIG, privateKey)); final SshTunnel sshTunnel = new SshTunnel( config, Arrays.asList(new String[] {"host"}), Arrays.asList(new String[] {"port"}), + null, + null, TunnelMethod.SSH_KEY_AUTH, "faketunnel.com", 22, diff --git a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile index 4f4ea189e065..830bab62e8f2 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-elasticsearch-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.3 +LABEL io.airbyte.version=0.1.4 LABEL io.airbyte.name=airbyte/destination-elasticsearch-strict-encrypt diff --git a/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile b/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile index 6f8d3f8d7ba9..362a4df7beaf 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile +++ b/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-elasticsearch COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.3 +LABEL io.airbyte.version=0.1.4 LABEL io.airbyte.name=airbyte/destination-elasticsearch diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestination.java b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestination.java index 33382e9f299f..5b5533e7d8b0 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestination.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestination.java @@ -10,6 +10,7 @@ import io.airbyte.integrations.base.AirbyteMessageConsumer; import io.airbyte.integrations.base.Destination; import io.airbyte.integrations.base.IntegrationRunner; +import io.airbyte.integrations.base.ssh.SshWrappedDestination; import io.airbyte.protocol.models.*; import java.io.IOException; import java.util.ArrayList; @@ -25,12 +26,16 @@ public class ElasticsearchDestination extends BaseConnector implements Destinati private final ObjectMapper mapper = new ObjectMapper(); public static void main(String[] args) throws Exception { - final var destination = new ElasticsearchDestination(); + final var destination = sshWrappedDestination(); LOGGER.info("starting destination: {}", ElasticsearchDestination.class); new IntegrationRunner(destination).run(args); LOGGER.info("completed destination: {}", ElasticsearchDestination.class); } + public static Destination sshWrappedDestination() { + return new SshWrappedDestination(new ElasticsearchDestination(), "endpoint"); + } + @Override public AirbyteConnectionStatus check(JsonNode config) { final ConnectorConfiguration configObject = convertConfig(config); diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java index ca133e64a54b..ce37a6fee19a 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java @@ -9,19 +9,14 @@ import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; import io.airbyte.integrations.standardtest.destination.comparator.AdvancedTestDataComparator; import io.airbyte.integrations.standardtest.destination.comparator.TestDataComparator; -import java.io.IOException; import java.time.Duration; import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.testcontainers.elasticsearch.ElasticsearchContainer; public class ElasticsearchDestinationAcceptanceTest extends DestinationAcceptanceTest { - private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchDestinationAcceptanceTest.class); - private ObjectMapper mapper = new ObjectMapper(); private static ElasticsearchContainer container; @@ -87,16 +82,18 @@ protected TestDataComparator getTestDataComparator() { } @Override - protected JsonNode getConfig() { + protected JsonNode getConfig() throws Exception { var configJson = mapper.createObjectNode(); configJson.put("endpoint", String.format("http://%s:%s", container.getHost(), container.getMappedPort(9200))); return configJson; } @Override - protected JsonNode getFailCheckConfig() { + protected JsonNode getFailCheckConfig() throws Exception { // should result in a failed connection check - return mapper.createObjectNode(); + var configJson = mapper.createObjectNode(); + configJson.put("endpoint", String.format("htp::/%s:-%s", container.getHost(), container.getMappedPort(9200))); + return configJson; } @Override @@ -104,7 +101,7 @@ protected List retrieveRecords(TestDestinationEnv testEnv, String streamName, String namespace, JsonNode streamSchema) - throws IOException { + throws Exception { // Records returned from this method will be compared against records provided to the connector // to verify they were written correctly final String indexName = new ElasticsearchWriteConfig() @@ -117,12 +114,13 @@ protected List retrieveRecords(TestDestinationEnv testEnv, } @Override - protected void setup(TestDestinationEnv testEnv) {} + protected void setup(TestDestinationEnv testEnv) throws Exception {} @Override - protected void tearDown(TestDestinationEnv testEnv) { + protected void tearDown(TestDestinationEnv testEnv) throws Exception { ElasticsearchConnection connection = new ElasticsearchConnection(mapper.convertValue(getConfig(), ConnectorConfiguration.class)); connection.allIndices().forEach(connection::deleteIndexIfPresent); + connection.close(); } } diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java new file mode 100644 index 000000000000..b3d19e4b6a48 --- /dev/null +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java @@ -0,0 +1,64 @@ +package io.airbyte.integrations.destination.elasticsearch; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.base.ssh.SshBastionContainer; +import io.airbyte.integrations.base.ssh.SshTunnel; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.Network; +import org.testcontainers.elasticsearch.ElasticsearchContainer; + +public abstract class SshElasticsearchDestinationAcceptanceTest extends ElasticsearchDestinationAcceptanceTest { + private static final Network network = Network.newNetwork(); + private static final SshBastionContainer bastion = new SshBastionContainer(); + private static ElasticsearchContainer container; + private ObjectMapper mapper = new ObjectMapper(); + private final static String ELASTIC_PASSWORD = "MagicWord"; + + public abstract SshTunnel.TunnelMethod getTunnelMethod(); + + private String getEndPoint() { + return String.format("http://%s:%d", + container.getContainerInfo().getNetworkSettings() + .getNetworks() + .entrySet().stream().findFirst().get().getValue().getIpAddress(), + container.getExposedPorts().get(0)); + } + + @Override + protected JsonNode getConfig() throws Exception { + return bastion.getTunnelConfig(getTunnelMethod(), ImmutableMap.builder().put("endpoint", getEndPoint()) + .put("upsert", false) + .put("authenticationMethod", Jsons.jsonNode(ImmutableMap.builder().put("method", "basic") + .put("username", "elastic") + .put("password", ELASTIC_PASSWORD).build()))); + } + + @Override + protected JsonNode getFailCheckConfig() throws Exception { + // should result in a failed connection check + return bastion.getTunnelConfig(getTunnelMethod(), ImmutableMap.builder().put("endpoint", getEndPoint()) + .put("upsert", true) + .put("authenticationMethod", Jsons.jsonNode(ImmutableMap.builder().put("method", "basic") + .put("username", "elastic") + .put("password", "wrongpassword").build()))); + } + + @BeforeAll + public static void beforeAll() { + bastion.initAndStartBastion(network); + container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.15.1") + .withNetwork(network) + .withPassword(ELASTIC_PASSWORD); + container.start(); + } + + @AfterAll + public static void afterAll() { + container.close(); + bastion.getContainer().close(); + } +} diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java new file mode 100644 index 000000000000..e9c94405ea32 --- /dev/null +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java @@ -0,0 +1,10 @@ +package io.airbyte.integrations.destination.elasticsearch; + +import io.airbyte.integrations.base.ssh.SshTunnel; + +public class SshKeyElasticsearchDestinationAcceptanceTest extends SshElasticsearchDestinationAcceptanceTest { + + public SshTunnel.TunnelMethod getTunnelMethod() { + return SshTunnel.TunnelMethod.SSH_KEY_AUTH; + } +} diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java new file mode 100644 index 000000000000..62c83133b5be --- /dev/null +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java @@ -0,0 +1,11 @@ +package io.airbyte.integrations.destination.elasticsearch; + +import io.airbyte.integrations.base.ssh.SshTunnel; + +public class SshPasswordElasticsearchDestinationAcceptanceTest extends SshElasticsearchDestinationAcceptanceTest { + @Override + public SshTunnel.TunnelMethod getTunnelMethod() { + return SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH; + } + +} diff --git a/docs/integrations/destinations/elasticsearch.md b/docs/integrations/destinations/elasticsearch.md index 2964b67cd403..ccae1d4a4d57 100644 --- a/docs/integrations/destinations/elasticsearch.md +++ b/docs/integrations/destinations/elasticsearch.md @@ -36,12 +36,12 @@ This section should contain a table mapping each of the connector's data types t This section should contain a table with the following format: | Feature | Supported?(Yes/No) | Notes | -| :--- | :--- | :--- | -| Full Refresh Sync | yes | | -| Incremental Sync | yes | | -| Replicate Incremental Deletes | no | | -| SSL connection | yes | | -| SSH Tunnel Support | ?? | | +| :--- |:-------------------| :--- | +| Full Refresh Sync | yes | | +| Incremental Sync | yes | | +| Replicate Incremental Deletes | no | | +| SSL connection | yes | | +| SSH Tunnel Support | yes | | ### Performance considerations @@ -63,12 +63,33 @@ The connector should be enhanced to support variable batch sizes. ### Setup guide +Enter the endpoint URL, select authentication method, and whether to use 'upsert' method when indexing new documents. + +### Connection via SSH Tunnel + +Airbyte has the ability to connect to an Elastic instance via an SSH Tunnel. +The reason you might want to do this because it is not possible \(or against security policy\) to connect to your Elastic instance directly \(e.g. it does not have a public IP address\). + +When using an SSH tunnel, you are configuring Airbyte to connect to an intermediate server \(a.k.a. a bastion sever\) that _does_ have direct access to the Elastic instance. +Airbyte connects to the bastion and then asks the bastion to connect directly to the server. + +Using this feature requires additional configuration, when creating the source. We will talk through what each piece of configuration means. + +1. Configure all fields for the source as you normally would, except `SSH Tunnel Method`. +2. `SSH Tunnel Method` defaults to `No Tunnel` \(meaning a direct connection\). If you want to use an SSH Tunnel choose `SSH Key Authentication` or `Password Authentication`. + 1. Choose `Key Authentication` if you will be using an RSA private key as your secret for establishing the SSH Tunnel \(see below for more information on generating this key\). + 2. Choose `Password Authentication` if you will be using a password as your secret for establishing the SSH Tunnel. +3. `SSH Tunnel Jump Server Host` refers to the intermediate \(bastion\) server that Airbyte will connect to. This should be a hostname or an IP Address. +4. `SSH Connection Port` is the port on the bastion server with which to make the SSH connection. The default port for SSH connections is `22`, so unless you have explicitly changed something, go with the default. +5. `SSH Login Username` is the username that Airbyte should use when connection to the bastion server. This is NOT the TiDB username. +6. If you are using `Password Authentication`, then `SSH Login Username` should be set to the password of the User from the previous step. If you are using `SSH Key Authentication` TiDB password, but the password for the OS-user that Airbyte is using to perform commands on the bastion. +7. If you are using `SSH Key Authentication`, then `SSH Private Key` should be set to the RSA Private Key that you are using to create the SSH connection. This should be the full contents of the key file starting with `-----BEGIN RSA PRIVATE KEY-----` and ending with `-----END RSA PRIVATE KEY-----`. -Enter the hostname and/or other configuration information ... ## CHANGELOG | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| 0.1.4 | 2022-10-14 | [17805](https://github.com/airbytehq/airbyte/pull/17805) | add SSH Tunneling | | 0.1.3 | 2022-05-30 | [14640](https://github.com/airbytehq/airbyte/pull/14640) | Include lifecycle management | | 0.1.2 | 2022-04-19 | [11752](https://github.com/airbytehq/airbyte/pull/11752) | Reduce batch size to 32Mb | | 0.1.1 | 2022-02-10 | [10256](https://github.com/airbytehq/airbyte/pull/1256) | Add ExitOnOutOfMemoryError connectors | From 1b134d866fe69b6ae7a148b2d328416749f86031 Mon Sep 17 00:00:00 2001 From: "Ashley Baer (PTC)" Date: Sat, 15 Oct 2022 04:37:08 -0400 Subject: [PATCH 123/498] =?UTF-8?q?=F0=9F=8E=89=20Destination=20Databricks?= =?UTF-8?q?:=20Support=20Azure=20storage=20(#15140)=20(#15329)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎉 Destination Databricks: Support Azure storage (#15140) * Update docs and version. * Revert unintentional change to S3 config parsing. * Revert unnecessary additional table property. * change spec.json * Ignore azure integration test * Add issue link * auto-bump connector version Co-authored-by: Marcos Marx Co-authored-by: marcosmarxm Co-authored-by: Liren Tu Co-authored-by: Octavia Squidington III --- .../seed/destination_definitions.yaml | 2 +- .../resources/seed/destination_specs.yaml | 48 +++- .../AzureBlobStorageConnectionChecker.java | 11 + .../destination-databricks/BOOTSTRAP.md | 4 +- .../destination-databricks/Dockerfile | 2 +- .../destination-databricks/README.md | 6 +- .../destination-databricks/build.gradle | 4 + .../sample_secrets/azure_config.json | 17 ++ .../DatabricksAzureBlobStorageConfig.java | 23 ++ ...DatabricksAzureBlobStorageDestination.java | 24 ++ ...atabricksAzureBlobStorageStreamCopier.java | 223 ++++++++++++++++++ ...ksAzureBlobStorageStreamCopierFactory.java | 43 ++++ .../databricks/DatabricksBaseDestination.java | 86 +++++++ .../databricks/DatabricksDestination.java | 87 +------ .../DatabricksDestinationConfig.java | 34 +-- .../DatabricksDestinationResolver.java | 42 ++++ .../databricks/DatabricksS3Destination.java | 24 ++ .../databricks/DatabricksS3StorageConfig.java | 37 +++ .../databricks/DatabricksS3StreamCopier.java | 151 ++++++++++++ .../DatabricksS3StreamCopierFactory.java | 44 ++++ .../databricks/DatabricksSqlOperations.java | 7 +- .../databricks/DatabricksStorageConfig.java | 45 ++++ .../databricks/DatabricksStorageType.java | 12 + .../databricks/DatabricksStreamCopier.java | 147 +++--------- .../DatabricksStreamCopierFactory.java | 35 +-- .../src/main/resources/spec.json | 49 ++++ ...eBlobStorageDestinationAcceptanceTest.java | 86 +++++++ .../DatabricksDestinationAcceptanceTest.java | 71 +----- ...DatabricksS3DestinationAcceptanceTest.java | 85 +++++++ .../DatabricksAzureBlobStorageConfigTest.java | 41 ++++ .../DatabricksDestinationConfigTest.java | 36 ++- .../DatabricksDestinationResolverTest.java | 73 ++++++ .../DatabricksS3StorageConfigTest.java | 43 ++++ ...java => DatabricksS3StreamCopierTest.java} | 4 +- .../src/test/resources/azure_config.json | 17 ++ .../src/test/resources/config.json | 16 ++ docs/integrations/destinations/databricks.md | 66 ++++-- 37 files changed, 1393 insertions(+), 352 deletions(-) create mode 100644 airbyte-integrations/connectors/destination-databricks/sample_secrets/azure_config.json create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfig.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestination.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopier.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopierFactory.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksBaseDestination.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolver.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3Destination.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfig.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopier.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierFactory.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageConfig.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageType.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksS3DestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfigTest.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolverTest.java create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfigTest.java rename airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/{DatabricksStreamCopierTest.java => DatabricksS3StreamCopierTest.java} (81%) create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test/resources/azure_config.json create mode 100644 airbyte-integrations/connectors/destination-databricks/src/test/resources/config.json diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 95292e8f1949..0fb22c22d2d4 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -80,7 +80,7 @@ - name: Databricks Lakehouse destinationDefinitionId: 072d5540-f236-4294-ba7c-ade8fd918496 dockerRepository: airbyte/destination-databricks - dockerImageTag: 0.2.6 + dockerImageTag: 0.3.0 documentationUrl: https://docs.airbyte.com/integrations/destinations/databricks icon: databricks.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 88ae685a454c..3a8937361ebc 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -1239,7 +1239,7 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-databricks:0.2.6" +- dockerImage: "airbyte/destination-databricks:0.3.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/databricks" connectionSpecification: @@ -1401,6 +1401,52 @@ - "{part_number}" - "{sync_id}" order: 7 + - title: "Azure Blob Storage" + required: + - "data_source_type" + - "azure_blob_storage_account_name" + - "azure_blob_storage_container_name" + - "azure_blob_storage_sas_token" + properties: + data_source_type: + type: "string" + enum: + - "Azure_Blob_Storage" + default: "Azure_Blob_Storage" + order: 0 + azure_blob_storage_endpoint_domain_name: + title: "Endpoint Domain Name" + type: "string" + default: "blob.core.windows.net" + description: "This is Azure Blob Storage endpoint domain name. Leave\ + \ default value (or leave it empty if run container from command\ + \ line) to use Microsoft native from example." + examples: + - "blob.core.windows.net" + order: 1 + azure_blob_storage_account_name: + title: "Azure Blob Storage Account Name" + type: "string" + description: "The account's name of the Azure Blob Storage." + examples: + - "airbyte5storage" + order: 2 + azure_blob_storage_container_name: + title: "Azure Blob Storage Container Name" + type: "string" + description: "The name of the Azure blob storage container." + examples: + - "airbytetestcontainername" + order: 3 + azure_blob_storage_sas_token: + title: "SAS Token" + type: "string" + airbyte_secret: true + description: "Shared access signature (SAS) token to grant limited\ + \ access to objects in your storage account." + examples: + - "?sv=2016-05-31&ss=b&srt=sco&sp=rwdl&se=2018-06-27T10:05:50Z&st=2017-06-27T02:05:50Z&spr=https,http&sig=bgqQwoXwxzuD2GJfagRg7VOS8hzNr3QLT7rhS8OFRLQ%3D" + order: 4 order: 7 purge_staging_data: title: "Purge Staging Files and Tables" diff --git a/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/java/io/airbyte/integrations/destination/azure_blob_storage/AzureBlobStorageConnectionChecker.java b/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/java/io/airbyte/integrations/destination/azure_blob_storage/AzureBlobStorageConnectionChecker.java index 08cf885260b9..5d476b7a32ab 100644 --- a/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/java/io/airbyte/integrations/destination/azure_blob_storage/AzureBlobStorageConnectionChecker.java +++ b/airbyte-integrations/connectors/destination-azure-blob-storage/src/main/java/io/airbyte/integrations/destination/azure_blob_storage/AzureBlobStorageConnectionChecker.java @@ -10,6 +10,7 @@ import com.azure.storage.blob.specialized.AppendBlobClient; import com.azure.storage.blob.specialized.SpecializedBlobClientBuilder; import com.azure.storage.common.StorageSharedKeyCredential; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -42,6 +43,16 @@ public AzureBlobStorageConnectionChecker( .buildAppendBlobClient(); } + public AzureBlobStorageConnectionChecker(final AzureBlobStorageConfig azureBlobStorageConfig) { + this.appendBlobClient = + new SpecializedBlobClientBuilder() + .endpoint(azureBlobStorageConfig.getEndpointUrl()) + .sasToken(azureBlobStorageConfig.getSasToken()) + .containerName(azureBlobStorageConfig.getContainerName()) // Like schema in DB + .blobName(TEST_BLOB_NAME_PREFIX + UUID.randomUUID()) // Like table in DB + .buildAppendBlobClient(); + } + /* * This a kinda test method that is used in CHECK operation to make sure all works fine with the * current config diff --git a/airbyte-integrations/connectors/destination-databricks/BOOTSTRAP.md b/airbyte-integrations/connectors/destination-databricks/BOOTSTRAP.md index 7f53edbea0ce..236f0f76d499 100644 --- a/airbyte-integrations/connectors/destination-databricks/BOOTSTRAP.md +++ b/airbyte-integrations/connectors/destination-databricks/BOOTSTRAP.md @@ -2,7 +2,7 @@ This destination syncs data to Delta Lake on Databricks Lakehouse. It does so in two steps: -1. Persist source data in S3 staging files in the Parquet format. -2. Create delta table based on the Parquet staging files. +1. Persist source data in S3 staging files in the Parquet format, or in Azure blob storage staging files in the CSV format. +2. Create delta table based on the staging files. See [this](https://docs.airbyte.io/integrations/destinations/databricks) link for the nuances about the connector. diff --git a/airbyte-integrations/connectors/destination-databricks/Dockerfile b/airbyte-integrations/connectors/destination-databricks/Dockerfile index d12822925308..47625fa00828 100644 --- a/airbyte-integrations/connectors/destination-databricks/Dockerfile +++ b/airbyte-integrations/connectors/destination-databricks/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-databricks COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.2.6 +LABEL io.airbyte.version=0.3.0 LABEL io.airbyte.name=airbyte/destination-databricks diff --git a/airbyte-integrations/connectors/destination-databricks/README.md b/airbyte-integrations/connectors/destination-databricks/README.md index d9cf5de58499..dfd176be10c8 100644 --- a/airbyte-integrations/connectors/destination-databricks/README.md +++ b/airbyte-integrations/connectors/destination-databricks/README.md @@ -15,12 +15,14 @@ From the Airbyte repository root, run: ``` #### Create credentials -**If you are a community contributor**, you will need access to AWS S3 and Databricks cluster to run the integration tests: +**If you are a community contributor**, you will need access to AWS S3, Azure blob storage, and Databricks cluster to run the integration tests: - Create a Databricks cluster. See [documentation](https://docs.databricks.com/clusters/create.html). - Create an S3 bucket. See [documentation](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). -- Grant the Databricks cluster full access to the S3 bucket. Or mount it as Databricks File System (DBFS). See [documentation](https://docs.databricks.com/data/data-sources/aws/amazon-s3.html). +- Create an Azure storage container. +- Grant the Databricks cluster full access to the S3 bucket and Azure container. Or mount it as Databricks File System (DBFS). See [documentation](https://docs.databricks.com/data/data-sources/aws/amazon-s3.html). - Place both Databricks and S3 credentials in `sample_secrets/config.json`, which conforms to the spec file in `src/main/resources/spec.json`. +- Place both Databricks and Azure credentials in `sample_secrets/azure_config.json`, which conforms to the spec file in `src/main/resources/spec.json`. - Rename the directory from `sample_secrets` to `secrets`. - Note that the `secrets` directory is git-ignored by default, so there is no danger of accidentally checking in sensitive information. diff --git a/airbyte-integrations/connectors/destination-databricks/build.gradle b/airbyte-integrations/connectors/destination-databricks/build.gradle index 4d4d1517714a..5ad636950d58 100644 --- a/airbyte-integrations/connectors/destination-databricks/build.gradle +++ b/airbyte-integrations/connectors/destination-databricks/build.gradle @@ -32,6 +32,8 @@ dependencies { implementation project(':airbyte-integrations:bases:base-java-s3') implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) implementation project(':airbyte-integrations:connectors:destination-jdbc') + implementation project(':airbyte-integrations:connectors:destination-s3') + implementation project(':airbyte-integrations:connectors:destination-azure-blob-storage') implementation group: 'com.databricks', name: 'databricks-jdbc', version: '2.6.25' // parquet @@ -46,6 +48,8 @@ dependencies { } implementation ('org.apache.parquet:parquet-avro:1.12.0') { exclude group: 'org.slf4j', module: 'slf4j-log4j12'} implementation ('com.github.airbytehq:json-avro-converter:1.0.1') { exclude group: 'ch.qos.logback', module: 'logback-classic'} + + implementation 'com.azure:azure-storage-blob:12.18.0' integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-databricks') diff --git a/airbyte-integrations/connectors/destination-databricks/sample_secrets/azure_config.json b/airbyte-integrations/connectors/destination-databricks/sample_secrets/azure_config.json new file mode 100644 index 000000000000..153ef677952e --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/sample_secrets/azure_config.json @@ -0,0 +1,17 @@ +{ + "accept_terms": true, + "databricks_server_hostname": "abc-12345678-wxyz.cloud.databricks.com", + "databricks_http_path": "sql/protocolvx/o/1234567489/0000-1111111-abcd90", + "databricks_port": "443", + "databricks_personal_access_token": "dapi0123456789abcdefghij0123456789AB", + "database_schema": "public", + "data_source": { + "data_source_type": "Azure_Blob_Storage", + "azure_blob_storage_endpoint_domain_name": "blob.core.windows.net", + "azure_blob_storage_account_name": "account", + "azure_blob_storage_sas_token": "token", + "azure_blob_storage_container_name": "container", + "azure_blob_storage_output_buffer_size": 5 + }, + "purge_staging_data": false +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfig.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfig.java new file mode 100644 index 000000000000..7b1319c87de6 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfig.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; + +public class DatabricksAzureBlobStorageConfig extends DatabricksStorageConfig { + + private final AzureBlobStorageConfig azureConfig; + + public DatabricksAzureBlobStorageConfig(JsonNode config) { + this.azureConfig = AzureBlobStorageConfig.getAzureBlobConfig(config); + } + + @Override + public AzureBlobStorageConfig getAzureBlobStorageConfigOrThrow() { + return azureConfig; + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestination.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestination.java new file mode 100644 index 000000000000..30e84f376dad --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestination.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import io.airbyte.integrations.destination.azure_blob_storage.AzureBlobStorageConnectionChecker; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; + +public class DatabricksAzureBlobStorageDestination extends DatabricksBaseDestination { + + @Override + protected void checkPersistence(DatabricksStorageConfig databricksConfig) { + AzureBlobStorageConfig azureConfig = databricksConfig.getAzureBlobStorageConfigOrThrow(); + final AzureBlobStorageConnectionChecker client = new AzureBlobStorageConnectionChecker(azureConfig); + client.attemptWriteAndDelete(); + } + + @Override + protected DatabricksStreamCopierFactory getStreamCopierFactory() { + return new DatabricksAzureBlobStorageStreamCopierFactory(); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopier.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopier.java new file mode 100644 index 000000000000..68035ece6374 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopier.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.specialized.AppendBlobClient; +import com.azure.storage.blob.specialized.SpecializedBlobClientBuilder; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.util.MoreIterators; +import io.airbyte.db.jdbc.JdbcDatabase; +import io.airbyte.integrations.destination.ExtendedNameTransformer; +import io.airbyte.integrations.destination.azure_blob_storage.AzureBlobStorageDestinationConfig; +import io.airbyte.integrations.destination.azure_blob_storage.AzureBlobStorageFormatConfig; +import io.airbyte.integrations.destination.azure_blob_storage.csv.AzureBlobStorageCsvFormatConfig; +import io.airbyte.integrations.destination.azure_blob_storage.csv.AzureBlobStorageCsvWriter; +import io.airbyte.integrations.destination.jdbc.SqlOperations; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; +import io.airbyte.protocol.models.AirbyteRecordMessage; +import io.airbyte.protocol.models.AirbyteStream; +import io.airbyte.protocol.models.ConfiguredAirbyteStream; +import io.airbyte.protocol.models.DestinationSyncMode; +import java.io.IOException; +import java.util.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This implementation does the following operations: + *
      + *
    • 1. CSV writers write data stream into staging CSV files in temporary storage location in + * Azure Blob Storage.
    • + *
    • 2. Create a tmp delta table based on the staging CSV files.
    • + *
    • 3. Create the destination delta table based on the tmp delta table schema.
    • + *
    • 4. Copy the temporary table into the destination delta table.
    • + *
    • 5. Delete the tmp delta table, and the staging CSV files.
    • + *
    + */ +public class DatabricksAzureBlobStorageStreamCopier extends DatabricksStreamCopier { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksAzureBlobStorageStreamCopier.class); + private static final String AZURE_BLOB_ENDPOINT_DOMAIN_NAME = "blob.core.windows.net"; + private static final String AZURE_DFS_ENDPOINT_DOMAIN_NAME = "dfs.core.windows.net"; + + private final SpecializedBlobClientBuilder specializedBlobClientBuilder; + private final AzureBlobStorageConfig azureConfig; + protected final Set azureStagingFiles = new HashSet<>(); + + protected final Set activeStagingWriterFileNames = new HashSet<>(); + protected final HashMap csvWriters = new HashMap<>(); + protected final HashMap blobClients = new HashMap<>(); + protected String currentFile; + + public DatabricksAzureBlobStorageStreamCopier(final String stagingFolder, + final String schema, + final ConfiguredAirbyteStream configuredStream, + final JdbcDatabase database, + final DatabricksDestinationConfig databricksConfig, + final ExtendedNameTransformer nameTransformer, + final SqlOperations sqlOperations, + SpecializedBlobClientBuilder specializedBlobClientBuilder, + AzureBlobStorageConfig azureConfig) { + super(stagingFolder, schema, configuredStream, database, databricksConfig, nameTransformer, sqlOperations); + + this.specializedBlobClientBuilder = specializedBlobClientBuilder; + this.azureConfig = azureConfig; + + LOGGER.info("[Stream {}] Tmp table {} location: {}", streamName, tmpTableName, getTmpTableLocation()); + LOGGER.info("[Stream {}] Data table {} location: {}", streamName, destTableName, getDestTableLocation()); + } + + public String getTmpTableLocation() { + return String.format("abfss://%s@%s.%s/%s/%s/%s/", + azureConfig.getContainerName(), azureConfig.getAccountName(), azureConfig.getEndpointDomainName(), + stagingFolder, schemaName, tmpTableName); + } + + public String getDestTableLocation() { + return String.format("abfss://%s@%s.%s/%s/%s/", + azureConfig.getContainerName(), azureConfig.getAccountName(), + // If this is .blob.core, we need to replace with .dfs.core to create table in Databricks + azureConfig.getEndpointDomainName().replace(AZURE_BLOB_ENDPOINT_DOMAIN_NAME, AZURE_DFS_ENDPOINT_DOMAIN_NAME), + schemaName, streamName); + } + + @Override + public String prepareStagingFile() { + currentFile = prepareAzureStagingFile(); + if (!azureStagingFiles.contains(currentFile)) { + + azureStagingFiles.add(currentFile); + activeStagingWriterFileNames.add(currentFile); + + final AppendBlobClient appendBlobClient = specializedBlobClientBuilder + .sasToken(azureConfig.getSasToken()) + .blobName(currentFile) + .buildAppendBlobClient(); + blobClients.put(currentFile, appendBlobClient); + getBlobContainerClient(appendBlobClient); + + try { + + String accountKey = "doesntmatter"; + String containerPath = String.format("%s/%s/%s/%s/", azureConfig.getContainerName(), stagingFolder, schemaName, streamName); + AzureBlobStorageFormatConfig formatConfig = + new AzureBlobStorageCsvFormatConfig(Jsons.jsonNode(Map.of("flattening", "Root level flattening"))); + AzureBlobStorageDestinationConfig config = new AzureBlobStorageDestinationConfig(azureConfig.getEndpointUrl(), + azureConfig.getAccountName(), accountKey, containerPath, 5, + formatConfig); + this.csvWriters.put(currentFile, new AzureBlobStorageCsvWriter(config, appendBlobClient, configuredStream)); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + return currentFile; + } + + protected String prepareAzureStagingFile() { + return String.join("/", stagingFolder, schemaName, tmpTableName, filenameGenerator.getStagingFilename()); + } + + @Override + public void write(final UUID id, final AirbyteRecordMessage recordMessage, final String fileName) throws Exception { + if (csvWriters.containsKey(fileName)) { + csvWriters.get(fileName).write(id, recordMessage); + } + } + + @Override + public void closeStagingUploader(final boolean hasFailed) throws Exception { + LOGGER.info("Uploading remaining data for {} stream.", streamName); + for (final var csvWriter : csvWriters.values()) { + csvWriter.close(hasFailed); + } + LOGGER.info("All data for {} stream uploaded.", streamName); + } + + @Override + protected String getCreateTempTableStatement() { + final AirbyteStream stream = configuredStream.getStream(); + LOGGER.info("Json schema for stream {}: {}", stream.getName(), stream.getJsonSchema()); + + String schemaString = getSchemaString(); + + LOGGER.info("[Stream {}] tmp table schema: {}", stream.getName(), schemaString); + + return String.format("CREATE TABLE %s.%s (%s) USING csv LOCATION '%s' " + + "options (\"header\" = \"true\", \"multiLine\" = \"true\") ;", + schemaName, tmpTableName, schemaString, + getTmpTableLocation().replace(AZURE_BLOB_ENDPOINT_DOMAIN_NAME, AZURE_DFS_ENDPOINT_DOMAIN_NAME)); + } + + private String getSchemaString() { + // Databricks requires schema to be provided when creating delta table from CSV + StringBuilder schemaString = new StringBuilder("_airbyte_ab_id string, _airbyte_emitted_at string"); + ObjectNode properties = (ObjectNode) configuredStream.getStream().getJsonSchema().get("properties"); + List recordHeaders = MoreIterators.toList(properties.fieldNames()) + .stream().sorted().toList(); + for (String header : recordHeaders) { + JsonNode node = properties.get(header); + String type = node.get("type").asText(); + schemaString.append(", `").append(header).append("` ").append(type.equals("number") ? "double" : type); + } + return schemaString.toString(); + } + + @Override + public String generateMergeStatement(final String destTableName) { + LOGGER.info("Preparing to merge tmp table {} to dest table: {}, schema: {}, in destination.", tmpTableName, destTableName, schemaName); + var queries = new StringBuilder(); + if (destinationSyncMode.equals(DestinationSyncMode.OVERWRITE)) { + queries.append(sqlOperations.truncateTableQuery(database, schemaName, destTableName)); + LOGGER.info("Destination OVERWRITE mode detected. Dest table: {}, schema: {}, truncated.", destTableName, schemaName); + } + queries.append(sqlOperations.copyTableQuery(database, schemaName, tmpTableName, destTableName)); + + return queries.toString(); + } + + @Override + protected void deleteStagingFile() { + LOGGER.info("Begin cleaning azure blob staging files."); + for (AppendBlobClient appendBlobClient : blobClients.values()) { + appendBlobClient.delete(); + } + LOGGER.info("Azure Blob staging files cleaned."); + } + + @Override + public void closeNonCurrentStagingFileWriters() throws Exception { + LOGGER.info("Begin closing non current file writers"); + Set removedKeys = new HashSet<>(); + for (String key : activeStagingWriterFileNames) { + if (!key.equals(currentFile)) { + csvWriters.get(key).close(false); + csvWriters.remove(key); + removedKeys.add(key); + } + } + activeStagingWriterFileNames.removeAll(removedKeys); + } + + @Override + public String getCurrentFile() { + return currentFile; + } + + private static BlobContainerClient getBlobContainerClient(AppendBlobClient appendBlobClient) { + BlobContainerClient containerClient = appendBlobClient.getContainerClient(); + if (!containerClient.exists()) { + containerClient.create(); + } + + if (!appendBlobClient.exists()) { + appendBlobClient.create(); + } + return containerClient; + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopierFactory.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopierFactory.java new file mode 100644 index 000000000000..ff1ef07dce97 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageStreamCopierFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.azure.storage.blob.specialized.SpecializedBlobClientBuilder; +import io.airbyte.db.jdbc.JdbcDatabase; +import io.airbyte.integrations.destination.ExtendedNameTransformer; +import io.airbyte.integrations.destination.jdbc.SqlOperations; +import io.airbyte.integrations.destination.jdbc.copy.StreamCopier; +import io.airbyte.integrations.destination.jdbc.copy.StreamCopierFactory; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; +import io.airbyte.protocol.models.AirbyteStream; +import io.airbyte.protocol.models.ConfiguredAirbyteStream; + +public class DatabricksAzureBlobStorageStreamCopierFactory implements DatabricksStreamCopierFactory { + + @Override + public StreamCopier create(final String configuredSchema, + final DatabricksDestinationConfig databricksConfig, + final String stagingFolder, + final ConfiguredAirbyteStream configuredStream, + final ExtendedNameTransformer nameTransformer, + final JdbcDatabase database, + final SqlOperations sqlOperations) { + try { + final AirbyteStream stream = configuredStream.getStream(); + final String schema = StreamCopierFactory.getSchema(stream.getNamespace(), configuredSchema, nameTransformer); + + final AzureBlobStorageConfig azureConfig = databricksConfig.getStorageConfig().getAzureBlobStorageConfigOrThrow(); + final SpecializedBlobClientBuilder specializedBlobClientBuilder = new SpecializedBlobClientBuilder() + .endpoint(azureConfig.getEndpointUrl()) + .sasToken(azureConfig.getSasToken()) + .containerName(azureConfig.getContainerName()); + return new DatabricksAzureBlobStorageStreamCopier(stagingFolder, schema, configuredStream, database, + databricksConfig, nameTransformer, sqlOperations, specializedBlobClientBuilder, azureConfig); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksBaseDestination.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksBaseDestination.java new file mode 100644 index 000000000000..dab489e9cb37 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksBaseDestination.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.db.factory.DataSourceFactory; +import io.airbyte.db.factory.DatabaseDriver; +import io.airbyte.db.jdbc.DefaultJdbcDatabase; +import io.airbyte.db.jdbc.JdbcDatabase; +import io.airbyte.integrations.base.AirbyteMessageConsumer; +import io.airbyte.integrations.destination.ExtendedNameTransformer; +import io.airbyte.integrations.destination.jdbc.SqlOperations; +import io.airbyte.integrations.destination.jdbc.copy.CopyConsumerFactory; +import io.airbyte.integrations.destination.jdbc.copy.CopyDestination; +import io.airbyte.protocol.models.AirbyteMessage; +import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; +import java.util.function.Consumer; +import javax.sql.DataSource; + +public abstract class DatabricksBaseDestination extends CopyDestination { + + public DatabricksBaseDestination() { + super("database_schema"); + } + + @Override + public void checkPersistence(JsonNode config) { + checkPersistence(DatabricksDestinationConfig.get(config).getStorageConfig()); + } + + protected abstract void checkPersistence(DatabricksStorageConfig databricksConfig); + + @Override + public AirbyteMessageConsumer getConsumer(final JsonNode config, + final ConfiguredAirbyteCatalog catalog, + final Consumer outputRecordCollector) { + final DatabricksDestinationConfig databricksConfig = DatabricksDestinationConfig.get(config); + final DataSource dataSource = getDataSource(config); + return CopyConsumerFactory.create( + outputRecordCollector, + dataSource, + getDatabase(dataSource), + getSqlOperations(), + getNameTransformer(), + databricksConfig, + catalog, + getStreamCopierFactory(), + databricksConfig.getDatabaseSchema()); + } + + protected abstract DatabricksStreamCopierFactory getStreamCopierFactory(); + + @Override + public ExtendedNameTransformer getNameTransformer() { + return new DatabricksNameTransformer(); + } + + @Override + public DataSource getDataSource(final JsonNode config) { + final DatabricksDestinationConfig databricksConfig = DatabricksDestinationConfig.get(config); + return DataSourceFactory.create( + DatabricksConstants.DATABRICKS_USERNAME, + databricksConfig.getDatabricksPersonalAccessToken(), + DatabricksConstants.DATABRICKS_DRIVER_CLASS, + getDatabricksConnectionString(databricksConfig)); + } + + @Override + public JdbcDatabase getDatabase(final DataSource dataSource) { + return new DefaultJdbcDatabase(dataSource); + } + + @Override + public SqlOperations getSqlOperations() { + return new DatabricksSqlOperations(); + } + + public static String getDatabricksConnectionString(final DatabricksDestinationConfig databricksConfig) { + return String.format(DatabaseDriver.DATABRICKS.getUrlFormatString(), + databricksConfig.getDatabricksServerHostname(), + databricksConfig.getDatabricksHttpPath()); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestination.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestination.java index 6a0e5b77d6fe..eaa1520e5973 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestination.java +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestination.java @@ -4,89 +4,24 @@ package io.airbyte.integrations.destination.databricks; -import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.db.factory.DataSourceFactory; -import io.airbyte.db.factory.DatabaseDriver; -import io.airbyte.db.jdbc.DefaultJdbcDatabase; -import io.airbyte.db.jdbc.JdbcDatabase; -import io.airbyte.integrations.base.AirbyteMessageConsumer; +import io.airbyte.integrations.base.Destination; import io.airbyte.integrations.base.IntegrationRunner; -import io.airbyte.integrations.destination.ExtendedNameTransformer; -import io.airbyte.integrations.destination.jdbc.SqlOperations; -import io.airbyte.integrations.destination.jdbc.copy.CopyConsumerFactory; -import io.airbyte.integrations.destination.jdbc.copy.CopyDestination; -import io.airbyte.integrations.destination.s3.S3BaseChecks; -import io.airbyte.integrations.destination.s3.S3DestinationConfig; -import io.airbyte.integrations.destination.s3.S3StorageOperations; -import io.airbyte.protocol.models.AirbyteMessage; -import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; -import java.util.function.Consumer; -import javax.sql.DataSource; +import io.airbyte.integrations.destination.jdbc.copy.SwitchingDestination; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; -public class DatabricksDestination extends CopyDestination { +public class DatabricksDestination extends SwitchingDestination { + + public static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1); public DatabricksDestination() { - super("database_schema"); + super(DatabricksStorageType.class, DatabricksDestinationResolver::getTypeFromConfig, DatabricksDestinationResolver.getTypeToDestination()); } public static void main(final String[] args) throws Exception { - new IntegrationRunner(new DatabricksDestination()).run(args); - } - - @Override - public AirbyteMessageConsumer getConsumer(final JsonNode config, - final ConfiguredAirbyteCatalog catalog, - final Consumer outputRecordCollector) { - final DatabricksDestinationConfig databricksConfig = DatabricksDestinationConfig.get(config); - final DataSource dataSource = getDataSource(config); - return CopyConsumerFactory.create( - outputRecordCollector, - dataSource, - getDatabase(dataSource), - getSqlOperations(), - getNameTransformer(), - databricksConfig, - catalog, - new DatabricksStreamCopierFactory(), - databricksConfig.getDatabaseSchema()); - } - - @Override - public void checkPersistence(final JsonNode config) { - final DatabricksDestinationConfig databricksConfig = DatabricksDestinationConfig.get(config); - final S3DestinationConfig s3Config = databricksConfig.getS3DestinationConfig(); - S3BaseChecks.attemptS3WriteAndDelete(new S3StorageOperations(getNameTransformer(), s3Config.getS3Client(), s3Config), s3Config, ""); - } - - @Override - public ExtendedNameTransformer getNameTransformer() { - return new DatabricksNameTransformer(); - } - - @Override - public DataSource getDataSource(final JsonNode config) { - final DatabricksDestinationConfig databricksConfig = DatabricksDestinationConfig.get(config); - return DataSourceFactory.create( - DatabricksConstants.DATABRICKS_USERNAME, - databricksConfig.getDatabricksPersonalAccessToken(), - DatabricksConstants.DATABRICKS_DRIVER_CLASS, - getDatabricksConnectionString(databricksConfig)); - } - - @Override - public JdbcDatabase getDatabase(final DataSource dataSource) { - return new DefaultJdbcDatabase(dataSource); - } - - @Override - public SqlOperations getSqlOperations() { - return new DatabricksSqlOperations(); - } - - static String getDatabricksConnectionString(final DatabricksDestinationConfig databricksConfig) { - return String.format(DatabaseDriver.DATABRICKS.getUrlFormatString(), - databricksConfig.getDatabricksServerHostname(), - databricksConfig.getDatabricksHttpPath()); + final Destination destination = new DatabricksDestination(); + new IntegrationRunner(destination).run(args); + SCHEDULED_EXECUTOR_SERVICE.shutdownNow(); } } diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfig.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfig.java index 5e7446392f04..489796081a34 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfig.java +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfig.java @@ -5,15 +5,8 @@ package io.airbyte.integrations.destination.databricks; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; -import io.airbyte.integrations.destination.s3.S3DestinationConfig; -import io.airbyte.integrations.destination.s3.constant.S3Constants; -import io.airbyte.integrations.destination.s3.parquet.S3ParquetFormatConfig; -/** - * Currently only S3 is supported. So the data source config is always {@link S3DestinationConfig}. - */ public class DatabricksDestinationConfig { static final String DEFAULT_DATABRICKS_PORT = "443"; @@ -26,7 +19,7 @@ public class DatabricksDestinationConfig { private final String databricksPersonalAccessToken; private final String databaseSchema; private final boolean purgeStagingData; - private final S3DestinationConfig s3DestinationConfig; + private final DatabricksStorageConfig storageConfig; public DatabricksDestinationConfig(final String databricksServerHostname, final String databricksHttpPath, @@ -34,14 +27,14 @@ public DatabricksDestinationConfig(final String databricksServerHostname, final String databricksPersonalAccessToken, final String databaseSchema, final boolean purgeStagingData, - final S3DestinationConfig s3DestinationConfig) { + DatabricksStorageConfig storageConfig) { this.databricksServerHostname = databricksServerHostname; this.databricksHttpPath = databricksHttpPath; this.databricksPort = databricksPort; this.databricksPersonalAccessToken = databricksPersonalAccessToken; this.databaseSchema = databaseSchema; this.purgeStagingData = purgeStagingData; - this.s3DestinationConfig = s3DestinationConfig; + this.storageConfig = storageConfig; } public static DatabricksDestinationConfig get(final JsonNode config) { @@ -56,22 +49,7 @@ public static DatabricksDestinationConfig get(final JsonNode config) { config.get("databricks_personal_access_token").asText(), config.has("database_schema") ? config.get("database_schema").asText() : DEFAULT_DATABASE_SCHEMA, config.has("purge_staging_data") ? config.get("purge_staging_data").asBoolean() : DEFAULT_PURGE_STAGING_DATA, - getDataSource(config.get("data_source"))); - } - - public static S3DestinationConfig getDataSource(final JsonNode dataSource) { - final S3DestinationConfig.Builder builder = S3DestinationConfig.create( - dataSource.get(S3Constants.S_3_BUCKET_NAME).asText(), - dataSource.get(S3Constants.S_3_BUCKET_PATH).asText(), - dataSource.get(S3Constants.S_3_BUCKET_REGION).asText()) - .withAccessKeyCredential( - dataSource.get(S3Constants.S_3_ACCESS_KEY_ID).asText(), - dataSource.get(S3Constants.S_3_SECRET_ACCESS_KEY).asText()) - .withFormatConfig(new S3ParquetFormatConfig(new ObjectMapper().createObjectNode())); - if (dataSource.has(S3Constants.FILE_NAME_PATTERN)) { - builder.withFileNamePattern(dataSource.get(S3Constants.FILE_NAME_PATTERN).asText()); - } - return builder.get(); + DatabricksStorageConfig.getDatabricksStorageConfig(config.get("data_source"))); } public String getDatabricksServerHostname() { @@ -98,8 +76,8 @@ public boolean isPurgeStagingData() { return purgeStagingData; } - public S3DestinationConfig getS3DestinationConfig() { - return s3DestinationConfig; + public DatabricksStorageConfig getStorageConfig() { + return storageConfig; } } diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolver.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolver.java new file mode 100644 index 000000000000..51c4ac6b4e5d --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolver.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; +import io.airbyte.integrations.base.Destination; +import java.util.Map; + +public class DatabricksDestinationResolver { + + public static DatabricksStorageType getTypeFromConfig(final JsonNode config) { + if (isS3Copy(config)) { + return DatabricksStorageType.S3; + } else if (isAzureBlobCopy(config)) { + return DatabricksStorageType.AZURE_BLOB_STORAGE; + } else { + throw new IllegalArgumentException("S3 or Azure Blob Storage configurations must be provided"); + } + } + + public static boolean isS3Copy(final JsonNode config) { + return config.has("data_source") && config.get("data_source").isObject() && config.get("data_source").has("s3_bucket_name"); + } + + public static boolean isAzureBlobCopy(final JsonNode config) { + return config.has("data_source") && config.get("data_source").isObject() + && config.get("data_source").has("azure_blob_storage_account_name"); + } + + public static Map getTypeToDestination() { + final DatabricksS3Destination s3Destination = new DatabricksS3Destination(); + final DatabricksAzureBlobStorageDestination azureBlobStorageDestination = new DatabricksAzureBlobStorageDestination(); + + return ImmutableMap.of( + DatabricksStorageType.S3, s3Destination, + DatabricksStorageType.AZURE_BLOB_STORAGE, azureBlobStorageDestination); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3Destination.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3Destination.java new file mode 100644 index 000000000000..b94ba22b9123 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3Destination.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import io.airbyte.integrations.destination.s3.S3BaseChecks; +import io.airbyte.integrations.destination.s3.S3DestinationConfig; +import io.airbyte.integrations.destination.s3.S3StorageOperations; + +public class DatabricksS3Destination extends DatabricksBaseDestination { + + @Override + protected void checkPersistence(DatabricksStorageConfig databricksConfig) { + S3DestinationConfig s3Config = databricksConfig.getS3DestinationConfigOrThrow(); + S3BaseChecks.attemptS3WriteAndDelete(new S3StorageOperations(getNameTransformer(), s3Config.getS3Client(), s3Config), s3Config, ""); + } + + @Override + protected DatabricksStreamCopierFactory getStreamCopierFactory() { + return new DatabricksS3StreamCopierFactory(); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfig.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfig.java new file mode 100644 index 000000000000..f570fb382ea1 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.airbyte.integrations.destination.s3.S3DestinationConfig; +import io.airbyte.integrations.destination.s3.constant.S3Constants; +import io.airbyte.integrations.destination.s3.parquet.S3ParquetFormatConfig; + +public class DatabricksS3StorageConfig extends DatabricksStorageConfig { + + private final S3DestinationConfig s3Config; + + public DatabricksS3StorageConfig(JsonNode config) { + final S3DestinationConfig.Builder builder = S3DestinationConfig.create( + config.get(S3Constants.S_3_BUCKET_NAME).asText(), + config.get(S3Constants.S_3_BUCKET_PATH).asText(), + config.get(S3Constants.S_3_BUCKET_REGION).asText()) + .withAccessKeyCredential( + config.get(S3Constants.S_3_ACCESS_KEY_ID).asText(), + config.get(S3Constants.S_3_SECRET_ACCESS_KEY).asText()) + .withFormatConfig(new S3ParquetFormatConfig(new ObjectMapper().createObjectNode())); + if (config.has(S3Constants.FILE_NAME_PATTERN)) { + builder.withFileNamePattern(config.get(S3Constants.FILE_NAME_PATTERN).asText()); + } + this.s3Config = builder.get(); + } + + @Override + public S3DestinationConfig getS3DestinationConfigOrThrow() { + return s3Config; + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopier.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopier.java new file mode 100644 index 000000000000..f272c813644a --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopier.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import static org.apache.logging.log4j.util.Strings.EMPTY; + +import com.amazonaws.services.s3.AmazonS3; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.airbyte.db.jdbc.JdbcDatabase; +import io.airbyte.integrations.destination.ExtendedNameTransformer; +import io.airbyte.integrations.destination.jdbc.SqlOperations; +import io.airbyte.integrations.destination.jdbc.copy.StreamCopier; +import io.airbyte.integrations.destination.s3.S3DestinationConfig; +import io.airbyte.integrations.destination.s3.parquet.S3ParquetFormatConfig; +import io.airbyte.integrations.destination.s3.parquet.S3ParquetWriter; +import io.airbyte.integrations.destination.s3.writer.S3WriterFactory; +import io.airbyte.protocol.models.AirbyteRecordMessage; +import io.airbyte.protocol.models.ConfiguredAirbyteStream; +import java.sql.Timestamp; +import java.util.Optional; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This implementation is similar to {@link StreamCopier}. The difference is that this + * implementation creates Parquet staging files, instead of CSV ones. + *

    + *

    + * It does the following operations: + *
      + *
    • 1. Parquet writer writes data stream into staging parquet file in + * s3://bucket-name/bucket-path/staging-folder.
    • + *
    • 2. Create a tmp delta table based on the staging parquet file.
    • + *
    • 3. Create the destination delta table based on the tmp delta table schema in + * s3://bucket/stream-name.
    • + *
    • 4. Copy the staging parquet file into the destination delta table.
    • + *
    • 5. Delete the tmp delta table, and the staging parquet file.
    • + *
    + */ +public class DatabricksS3StreamCopier extends DatabricksStreamCopier { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksS3StreamCopier.class); + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private final AmazonS3 s3Client; + private final S3DestinationConfig s3Config; + private final S3ParquetWriter parquetWriter; + + public DatabricksS3StreamCopier(final String stagingFolder, + final String schema, + final ConfiguredAirbyteStream configuredStream, + final AmazonS3 s3Client, + final JdbcDatabase database, + final DatabricksDestinationConfig databricksConfig, + final ExtendedNameTransformer nameTransformer, + final SqlOperations sqlOperations, + final S3WriterFactory writerFactory, + final Timestamp uploadTime) + throws Exception { + super(stagingFolder, schema, configuredStream, database, databricksConfig, nameTransformer, sqlOperations); + this.s3Client = s3Client; + this.s3Config = databricksConfig.getStorageConfig().getS3DestinationConfigOrThrow(); + final S3DestinationConfig stagingS3Config = getStagingS3DestinationConfig(s3Config, stagingFolder); + this.parquetWriter = (S3ParquetWriter) writerFactory.create(stagingS3Config, s3Client, configuredStream, uploadTime); + + LOGGER.info("[Stream {}] Parquet schema: {}", streamName, parquetWriter.getSchema()); + + parquetWriter.initialize(); + + LOGGER.info("[Stream {}] Tmp table {} location: {}", streamName, tmpTableName, getTmpTableLocation()); + LOGGER.info("[Stream {}] Data table {} location: {}", streamName, destTableName, getDestTableLocation()); + } + + @Override + public String prepareStagingFile() { + return String.join("/", s3Config.getBucketPath(), stagingFolder); + } + + @Override + public void write(final UUID id, final AirbyteRecordMessage recordMessage, final String fileName) throws Exception { + parquetWriter.write(id, recordMessage); + } + + @Override + public void closeStagingUploader(final boolean hasFailed) throws Exception { + parquetWriter.close(hasFailed); + } + + @Override + protected String getTmpTableLocation() { + return String.format("s3://%s/%s", + s3Config.getBucketName(), parquetWriter.getOutputPrefix()); + } + + @Override + protected String getDestTableLocation() { + return String.format("s3://%s/%s/%s/%s", + s3Config.getBucketName(), s3Config.getBucketPath(), databricksConfig.getDatabaseSchema(), streamName); + } + + @Override + protected String getCreateTempTableStatement() { + return String.format("CREATE TABLE %s.%s USING parquet LOCATION '%s';", schemaName, tmpTableName, getTmpTableLocation()); + } + + @Override + public String generateMergeStatement(final String destTableName) { + final String copyData = String.format( + "COPY INTO %s.%s " + + "FROM '%s' " + + "FILEFORMAT = PARQUET " + + "PATTERN = '%s'", + schemaName, destTableName, + getTmpTableLocation(), + parquetWriter.getOutputFilename()); + LOGGER.info(copyData); + return copyData; + } + + @Override + protected void deleteStagingFile() { + LOGGER.info("[Stream {}] Deleting staging file: {}", streamName, parquetWriter.getOutputFilePath()); + s3Client.deleteObject(s3Config.getBucketName(), parquetWriter.getOutputFilePath()); + } + + @Override + public void closeNonCurrentStagingFileWriters() throws Exception { + parquetWriter.close(false); + } + + @Override + public String getCurrentFile() { + return ""; + } + + /** + * The staging data location is s3:////. This method + * creates an {@link S3DestinationConfig} whose bucket path is /. + */ + static S3DestinationConfig getStagingS3DestinationConfig(final S3DestinationConfig config, final String stagingFolder) { + return S3DestinationConfig.create(config) + .withBucketPath(String.join("/", config.getBucketPath(), stagingFolder)) + .withFormatConfig(new S3ParquetFormatConfig(MAPPER.createObjectNode())) + .withFileNamePattern(Optional.ofNullable(config.getFileNamePattern()).orElse(EMPTY)) + .get(); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierFactory.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierFactory.java new file mode 100644 index 000000000000..8d6b3433fd46 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.amazonaws.services.s3.AmazonS3; +import io.airbyte.db.jdbc.JdbcDatabase; +import io.airbyte.integrations.destination.ExtendedNameTransformer; +import io.airbyte.integrations.destination.jdbc.SqlOperations; +import io.airbyte.integrations.destination.jdbc.copy.StreamCopier; +import io.airbyte.integrations.destination.jdbc.copy.StreamCopierFactory; +import io.airbyte.integrations.destination.s3.S3DestinationConfig; +import io.airbyte.integrations.destination.s3.writer.ProductionWriterFactory; +import io.airbyte.protocol.models.AirbyteStream; +import io.airbyte.protocol.models.ConfiguredAirbyteStream; +import java.sql.Timestamp; + +public class DatabricksS3StreamCopierFactory implements DatabricksStreamCopierFactory { + + @Override + public StreamCopier create(final String configuredSchema, + final DatabricksDestinationConfig databricksConfig, + final String stagingFolder, + final ConfiguredAirbyteStream configuredStream, + final ExtendedNameTransformer nameTransformer, + final JdbcDatabase database, + final SqlOperations sqlOperations) { + try { + final AirbyteStream stream = configuredStream.getStream(); + final String schema = StreamCopierFactory.getSchema(stream.getNamespace(), configuredSchema, nameTransformer); + + S3DestinationConfig s3Config = databricksConfig.getStorageConfig().getS3DestinationConfigOrThrow(); + final AmazonS3 s3Client = s3Config.getS3Client(); + final ProductionWriterFactory writerFactory = new ProductionWriterFactory(); + final Timestamp uploadTimestamp = new Timestamp(System.currentTimeMillis()); + return new DatabricksS3StreamCopier(stagingFolder, schema, configuredStream, s3Client, database, + databricksConfig, nameTransformer, sqlOperations, writerFactory, uploadTimestamp); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksSqlOperations.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksSqlOperations.java index 432b4b543d6e..2c8179c8d2a1 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksSqlOperations.java +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksSqlOperations.java @@ -15,7 +15,12 @@ public class DatabricksSqlOperations extends JdbcSqlOperations { @Override public void executeTransaction(final JdbcDatabase database, final List queries) throws Exception { for (final String query : queries) { - database.execute(query); + for (String q : query.split(";")) { + if (q.isBlank()) + continue; + + database.execute(q); + } } } diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageConfig.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageConfig.java new file mode 100644 index 000000000000..11edca47dfba --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; +import io.airbyte.integrations.destination.s3.S3DestinationConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DatabricksStorageConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksStorageConfig.class); + + public static DatabricksStorageConfig getDatabricksStorageConfig(final JsonNode config) { + final JsonNode typeConfig = config.get("data_source_type"); + LOGGER.info("Databricks storage type config: {}", typeConfig.toString()); + final DatabricksStorageType storageType = DatabricksStorageType + .valueOf(typeConfig.asText().toUpperCase()); + + switch (storageType) { + case S3 -> { + return new DatabricksS3StorageConfig(config); + } + case AZURE_BLOB_STORAGE -> { + return new DatabricksAzureBlobStorageConfig(config); + } + default -> { + throw new RuntimeException("Unexpected output format: " + Jsons.serialize(config)); + } + } + } + + public AzureBlobStorageConfig getAzureBlobStorageConfigOrThrow() { + throw new UnsupportedOperationException("Cannot get Azure Blob Storage config from " + this.getClass().getSimpleName()); + } + + public S3DestinationConfig getS3DestinationConfigOrThrow() { + throw new UnsupportedOperationException("Cannot get S3 destination config from " + this.getClass().getSimpleName()); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageType.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageType.java new file mode 100644 index 000000000000..0c77f70bc3a0 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStorageType.java @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +public enum DatabricksStorageType { + + S3, + AZURE_BLOB_STORAGE; + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopier.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopier.java index 95370e331e08..9d3c3b987a97 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopier.java +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopier.java @@ -4,118 +4,74 @@ package io.airbyte.integrations.destination.databricks; -import static org.apache.logging.log4j.util.Strings.EMPTY; - -import com.amazonaws.services.s3.AmazonS3; -import com.fasterxml.jackson.databind.ObjectMapper; import io.airbyte.db.jdbc.JdbcDatabase; import io.airbyte.integrations.destination.ExtendedNameTransformer; import io.airbyte.integrations.destination.jdbc.SqlOperations; +import io.airbyte.integrations.destination.jdbc.StagingFilenameGenerator; +import io.airbyte.integrations.destination.jdbc.constants.GlobalDataSizeConstants; import io.airbyte.integrations.destination.jdbc.copy.StreamCopier; -import io.airbyte.integrations.destination.s3.S3DestinationConfig; -import io.airbyte.integrations.destination.s3.parquet.S3ParquetFormatConfig; -import io.airbyte.integrations.destination.s3.parquet.S3ParquetWriter; -import io.airbyte.integrations.destination.s3.writer.S3WriterFactory; -import io.airbyte.protocol.models.AirbyteRecordMessage; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.DestinationSyncMode; -import java.sql.Timestamp; -import java.util.Optional; -import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This implementation is similar to {@link StreamCopier}. The difference is that this - * implementation creates Parquet staging files, instead of CSV ones. - *

    - *

    - * It does the following operations: + * This implementation is similar to {@link StreamCopier}. It does the following operations: *
      - *
    • 1. Parquet writer writes data stream into staging parquet file in - * s3://bucket-name/bucket-path/staging-folder.
    • - *
    • 2. Create a tmp delta table based on the staging parquet file.
    • - *
    • 3. Create the destination delta table based on the tmp delta table schema in - * s3://bucket/stream-name.
    • - *
    • 4. Copy the staging parquet file into the destination delta table.
    • - *
    • 5. Delete the tmp delta table, and the staging parquet file.
    • + *
    • 1. Writes data stream into staging file(s) in cloud storage.
    • + *
    • 2. Create a tmp delta table based on the staging file(s).
    • + *
    • 3. Create the destination delta table based on the tmp delta table schema.
    • + *
    • 4. Copy the staging file(s) into the destination delta table.
    • + *
    • 5. Delete the tmp delta table, and the staging file(s).
    • *
    */ -public class DatabricksStreamCopier implements StreamCopier { +public abstract class DatabricksStreamCopier implements StreamCopier { private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksStreamCopier.class); - private static final ObjectMapper MAPPER = new ObjectMapper(); - private final String schemaName; - private final String streamName; - private final DestinationSyncMode destinationSyncMode; - private final AmazonS3 s3Client; - private final S3DestinationConfig s3Config; + protected final String schemaName; + protected final String streamName; + protected final DestinationSyncMode destinationSyncMode; private final boolean purgeStagingData; - private final JdbcDatabase database; - private final DatabricksSqlOperations sqlOperations; + protected final JdbcDatabase database; + protected final DatabricksSqlOperations sqlOperations; - private final String tmpTableName; - private final String destTableName; - private final S3ParquetWriter parquetWriter; - private final String tmpTableLocation; - private final String destTableLocation; - private final String stagingFolder; + protected final String tmpTableName; + protected final String destTableName; + protected final String stagingFolder; + protected final StagingFilenameGenerator filenameGenerator; + protected final ConfiguredAirbyteStream configuredStream; + protected final DatabricksDestinationConfig databricksConfig; public DatabricksStreamCopier(final String stagingFolder, final String schema, final ConfiguredAirbyteStream configuredStream, - final AmazonS3 s3Client, final JdbcDatabase database, final DatabricksDestinationConfig databricksConfig, final ExtendedNameTransformer nameTransformer, - final SqlOperations sqlOperations, - final S3WriterFactory writerFactory, - final Timestamp uploadTime) - throws Exception { + final SqlOperations sqlOperations) { this.schemaName = schema; this.streamName = configuredStream.getStream().getName(); this.destinationSyncMode = configuredStream.getDestinationSyncMode(); - this.s3Client = s3Client; - this.s3Config = databricksConfig.getS3DestinationConfig(); this.purgeStagingData = databricksConfig.isPurgeStagingData(); this.database = database; this.sqlOperations = (DatabricksSqlOperations) sqlOperations; + this.databricksConfig = databricksConfig; + this.tmpTableName = nameTransformer.getTmpTableName(streamName); this.destTableName = nameTransformer.getIdentifier(streamName); this.stagingFolder = stagingFolder; - final S3DestinationConfig stagingS3Config = getStagingS3DestinationConfig(s3Config, stagingFolder); - this.parquetWriter = (S3ParquetWriter) writerFactory.create(stagingS3Config, s3Client, configuredStream, uploadTime); - - this.tmpTableLocation = String.format("s3://%s/%s", - s3Config.getBucketName(), parquetWriter.getOutputPrefix()); - this.destTableLocation = String.format("s3://%s/%s/%s/%s", - s3Config.getBucketName(), s3Config.getBucketPath(), databricksConfig.getDatabaseSchema(), streamName); + this.filenameGenerator = new StagingFilenameGenerator(streamName, GlobalDataSizeConstants.DEFAULT_MAX_BATCH_SIZE_BYTES); + this.configuredStream = configuredStream; LOGGER.info("[Stream {}] Database schema: {}", streamName, schemaName); - LOGGER.info("[Stream {}] Parquet schema: {}", streamName, parquetWriter.getSchema()); - LOGGER.info("[Stream {}] Tmp table {} location: {}", streamName, tmpTableName, tmpTableLocation); - LOGGER.info("[Stream {}] Data table {} location: {}", streamName, destTableName, destTableLocation); - - parquetWriter.initialize(); } - @Override - public String prepareStagingFile() { - return String.join("/", s3Config.getBucketPath(), stagingFolder); - } + protected abstract String getTmpTableLocation(); - @Override - public void write(final UUID id, final AirbyteRecordMessage recordMessage, final String fileName) throws Exception { - parquetWriter.write(id, recordMessage); - } - - @Override - public void closeStagingUploader(final boolean hasFailed) throws Exception { - parquetWriter.close(hasFailed); - } + protected abstract String getDestTableLocation(); @Override public void createDestinationSchema() throws Exception { @@ -125,14 +81,18 @@ public void createDestinationSchema() throws Exception { @Override public void createTemporaryTable() throws Exception { - LOGGER.info("[Stream {}] Creating tmp table {} from staging file: {}", streamName, tmpTableName, tmpTableLocation); + LOGGER.info("[Stream {}] Creating tmp table {} from staging file: {}", streamName, tmpTableName, getTmpTableLocation()); sqlOperations.dropTableIfExists(database, schemaName, tmpTableName); - final String createTmpTable = String.format("CREATE TABLE %s.%s USING parquet LOCATION '%s';", schemaName, tmpTableName, tmpTableLocation); + + final String createTmpTable = getCreateTempTableStatement(); + LOGGER.info(createTmpTable); database.execute(createTmpTable); } + protected abstract String getCreateTempTableStatement(); + @Override public void copyStagingFileToTemporaryTable() { // The tmp table is created directly based on the staging file. So no separate copying step is @@ -158,7 +118,7 @@ public String createDestinationTable() throws Exception { "AS SELECT * FROM %s.%s LIMIT 0", createStatement, schemaName, destTableName, - destTableLocation, + getDestTableLocation(), streamName, destinationSyncMode.value(), String.join(", ", DatabricksConstants.DEFAULT_TBL_PROPERTIES), @@ -169,51 +129,16 @@ public String createDestinationTable() throws Exception { return destTableName; } - @Override - public String generateMergeStatement(final String destTableName) { - final String copyData = String.format( - "COPY INTO %s.%s " + - "FROM '%s' " + - "FILEFORMAT = PARQUET " + - "PATTERN = '%s'", - schemaName, destTableName, - tmpTableLocation, - parquetWriter.getOutputFilename()); - LOGGER.info(copyData); - return copyData; - } - @Override public void removeFileAndDropTmpTable() throws Exception { if (purgeStagingData) { LOGGER.info("[Stream {}] Deleting tmp table: {}", streamName, tmpTableName); sqlOperations.dropTableIfExists(database, schemaName, tmpTableName); - LOGGER.info("[Stream {}] Deleting staging file: {}", streamName, parquetWriter.getOutputFilePath()); - s3Client.deleteObject(s3Config.getBucketName(), parquetWriter.getOutputFilePath()); + deleteStagingFile(); } } - @Override - public void closeNonCurrentStagingFileWriters() throws Exception { - parquetWriter.close(false); - } - - @Override - public String getCurrentFile() { - return ""; - } - - /** - * The staging data location is s3:////. This method - * creates an {@link S3DestinationConfig} whose bucket path is /. - */ - static S3DestinationConfig getStagingS3DestinationConfig(final S3DestinationConfig config, final String stagingFolder) { - return S3DestinationConfig.create(config) - .withBucketPath(String.join("/", config.getBucketPath(), stagingFolder)) - .withFormatConfig(new S3ParquetFormatConfig(MAPPER.createObjectNode())) - .withFileNamePattern(Optional.ofNullable(config.getFileNamePattern()).orElse(EMPTY)) - .get(); - } + protected abstract void deleteStagingFile(); } diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierFactory.java b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierFactory.java index 684c1aeef56e..7d84ff1b98c3 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierFactory.java +++ b/airbyte-integrations/connectors/destination-databricks/src/main/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierFactory.java @@ -4,41 +4,8 @@ package io.airbyte.integrations.destination.databricks; -import com.amazonaws.services.s3.AmazonS3; -import io.airbyte.db.jdbc.JdbcDatabase; -import io.airbyte.integrations.destination.ExtendedNameTransformer; -import io.airbyte.integrations.destination.jdbc.SqlOperations; -import io.airbyte.integrations.destination.jdbc.copy.StreamCopier; import io.airbyte.integrations.destination.jdbc.copy.StreamCopierFactory; -import io.airbyte.integrations.destination.s3.writer.ProductionWriterFactory; -import io.airbyte.integrations.destination.s3.writer.S3WriterFactory; -import io.airbyte.protocol.models.AirbyteStream; -import io.airbyte.protocol.models.ConfiguredAirbyteStream; -import java.sql.Timestamp; -public class DatabricksStreamCopierFactory implements StreamCopierFactory { - - @Override - public StreamCopier create(final String configuredSchema, - final DatabricksDestinationConfig databricksConfig, - final String stagingFolder, - final ConfiguredAirbyteStream configuredStream, - final ExtendedNameTransformer nameTransformer, - final JdbcDatabase database, - final SqlOperations sqlOperations) { - try { - final AirbyteStream stream = configuredStream.getStream(); - final String schema = StreamCopierFactory.getSchema(stream.getNamespace(), configuredSchema, nameTransformer); - final AmazonS3 s3Client = databricksConfig.getS3DestinationConfig().getS3Client(); - final S3WriterFactory writerFactory = new ProductionWriterFactory(); - final Timestamp uploadTimestamp = new Timestamp(System.currentTimeMillis()); - - return new DatabricksStreamCopier(stagingFolder, schema, configuredStream, s3Client, database, - databricksConfig, nameTransformer, sqlOperations, writerFactory, uploadTimestamp); - } catch (final Exception e) { - throw new RuntimeException(e); - } - - } +public interface DatabricksStreamCopierFactory extends StreamCopierFactory { } diff --git a/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json index 50d52db6ae6d..55e0e4fe71a7 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-databricks/src/main/resources/spec.json @@ -162,6 +162,55 @@ "order": 7 } } + }, + { + "title": "Azure Blob Storage", + "required": [ + "data_source_type", + "azure_blob_storage_account_name", + "azure_blob_storage_container_name", + "azure_blob_storage_sas_token" + ], + "properties": { + "data_source_type": { + "type": "string", + "enum": ["Azure_Blob_Storage"], + "default": "Azure_Blob_Storage", + "order": 0 + }, + "azure_blob_storage_endpoint_domain_name": { + "title": "Endpoint Domain Name", + "type": "string", + "default": "blob.core.windows.net", + "description": "This is Azure Blob Storage endpoint domain name. Leave default value (or leave it empty if run container from command line) to use Microsoft native from example.", + "examples": ["blob.core.windows.net"], + "order": 1 + }, + "azure_blob_storage_account_name": { + "title": "Azure Blob Storage Account Name", + "type": "string", + "description": "The account's name of the Azure Blob Storage.", + "examples": ["airbyte5storage"], + "order": 2 + }, + "azure_blob_storage_container_name": { + "title": "Azure Blob Storage Container Name", + "type": "string", + "description": "The name of the Azure blob storage container.", + "examples": ["airbytetestcontainername"], + "order": 3 + }, + "azure_blob_storage_sas_token": { + "title": "SAS Token", + "type": "string", + "airbyte_secret": true, + "description": "Shared access signature (SAS) token to grant limited access to objects in your storage account.", + "examples": [ + "?sv=2016-05-31&ss=b&srt=sco&sp=rwdl&se=2018-06-27T10:05:50Z&st=2017-06-27T02:05:50Z&spr=https,http&sig=bgqQwoXwxzuD2GJfagRg7VOS8hzNr3QLT7rhS8OFRLQ%3D" + ], + "order": 4 + } + } } ], "order": 7 diff --git a/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestinationAcceptanceTest.java new file mode 100644 index 000000000000..be870752bbf7 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageDestinationAcceptanceTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.blob.specialized.SpecializedBlobClientBuilder; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.airbyte.commons.io.IOs; +import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.destination.jdbc.copy.azure.AzureBlobStorageConfig; +import java.nio.file.Path; +import java.sql.SQLException; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Disabled; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This test is disabled because we have not set up a Databricks cluster with Azure storage. This + * issue is tracked in #18026. + */ +@Disabled +public class DatabricksAzureBlobStorageDestinationAcceptanceTest extends DatabricksDestinationAcceptanceTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksAzureBlobStorageDestinationAcceptanceTest.class); + private static final String SECRETS_CONFIG_JSON = "secrets/azure_config.json"; + + private AzureBlobStorageConfig azureBlobStorageConfig; + protected SpecializedBlobClientBuilder specializedBlobClientBuilder; + + @Override + protected JsonNode getFailCheckConfig() { + final JsonNode configJson = Jsons.clone(this.configJson); + final JsonNode dataSource = configJson.get("data_source"); + ((ObjectNode) dataSource).put("azure_blob_storage_account_name", "someInvalidName"); + return configJson; + } + + @Override + protected void setup(final TestDestinationEnv testEnv) { + final JsonNode baseConfigJson = Jsons.deserialize(IOs.readFile(Path.of(SECRETS_CONFIG_JSON))); + + // Set a random Azure path and database schema for each integration test + final String randomString = RandomStringUtils.randomAlphanumeric(5); + final JsonNode configJson = Jsons.clone(baseConfigJson); + ((ObjectNode) configJson).put("database_schema", "integration_test_" + randomString); + final JsonNode dataSource = configJson.get("data_source"); + ((ObjectNode) dataSource).put("azure_blob_storage_container_name", "test-" + randomString.toLowerCase()); + + this.configJson = configJson; + this.databricksConfig = DatabricksDestinationConfig.get(configJson); + this.azureBlobStorageConfig = databricksConfig.getStorageConfig().getAzureBlobStorageConfigOrThrow(); + LOGGER.info("Test full path: {}/{}", azureBlobStorageConfig.getEndpointUrl(), azureBlobStorageConfig.getContainerName(), + azureBlobStorageConfig); + + this.specializedBlobClientBuilder = new SpecializedBlobClientBuilder() + .endpoint(azureBlobStorageConfig.getEndpointUrl()) + .sasToken(azureBlobStorageConfig.getSasToken()) + .containerName( + azureBlobStorageConfig.getContainerName());// Like user\schema in DB + } + + @Override + protected void tearDown(final TestDestinationEnv testEnv) throws SQLException { + final BlobServiceClient storageClient = new BlobServiceClientBuilder() + .endpoint(azureBlobStorageConfig.getEndpointUrl()) + .sasToken(azureBlobStorageConfig.getSasToken()) + .buildClient(); + + final BlobContainerClient blobContainerClient = storageClient + .getBlobContainerClient(azureBlobStorageConfig.getContainerName()); + + if (blobContainerClient.exists()) { + LOGGER.info("Deleting test env: " + azureBlobStorageConfig.getContainerName()); + blobContainerClient.delete(); + } + + super.tearDown(testEnv); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationAcceptanceTest.java index e3748408f620..20c2c494c986 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationAcceptanceTest.java @@ -7,14 +7,7 @@ import static org.jooq.impl.DSL.asterisk; import static org.jooq.impl.DSL.field; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.model.DeleteObjectsRequest; -import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion; -import com.amazonaws.services.s3.model.DeleteObjectsResult; -import com.amazonaws.services.s3.model.S3ObjectSummary; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import io.airbyte.commons.io.IOs; import io.airbyte.commons.json.Jsons; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; @@ -22,31 +15,24 @@ import io.airbyte.integrations.base.JavaBaseConstants; import io.airbyte.integrations.destination.ExtendedNameTransformer; import io.airbyte.integrations.destination.jdbc.copy.StreamCopierFactory; -import io.airbyte.integrations.destination.s3.S3DestinationConfig; import io.airbyte.integrations.destination.s3.avro.JsonFieldNameUpdater; import io.airbyte.integrations.destination.s3.util.AvroRecordHelper; import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; -import java.nio.file.Path; import java.sql.SQLException; -import java.util.LinkedList; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.RandomStringUtils; import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class DatabricksDestinationAcceptanceTest extends DestinationAcceptanceTest { +public abstract class DatabricksDestinationAcceptanceTest extends DestinationAcceptanceTest { private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksDestinationAcceptanceTest.class); - private static final String SECRETS_CONFIG_JSON = "secrets/config.json"; private final ExtendedNameTransformer nameTransformer = new DatabricksNameTransformer(); - private JsonNode configJson; - private DatabricksDestinationConfig databricksConfig; - private S3DestinationConfig s3Config; - private AmazonS3 s3Client; + protected JsonNode configJson; + protected DatabricksDestinationConfig databricksConfig; @Override protected String getImageName() { @@ -58,16 +44,6 @@ protected JsonNode getConfig() { return configJson; } - @Override - protected JsonNode getFailCheckConfig() { - final JsonNode failCheckJson = Jsons.clone(configJson); - // set invalid credential - ((ObjectNode) failCheckJson.get("data_source")) - .put("s3_access_key_id", "fake-key") - .put("s3_secret_access_key", "fake-secret"); - return failCheckJson; - } - @Override protected List retrieveRecords(final TestDestinationEnv testEnv, final String streamName, @@ -93,45 +69,8 @@ protected List retrieveRecords(final TestDestinationEnv testEnv, } } - @Override - protected void setup(final TestDestinationEnv testEnv) { - final JsonNode baseConfigJson = Jsons.deserialize(IOs.readFile(Path.of(SECRETS_CONFIG_JSON))); - - // Set a random s3 bucket path and database schema for each integration test - final String randomString = RandomStringUtils.randomAlphanumeric(5); - final JsonNode configJson = Jsons.clone(baseConfigJson); - ((ObjectNode) configJson).put("database_schema", "integration_test_" + randomString); - final JsonNode dataSource = configJson.get("data_source"); - ((ObjectNode) dataSource).put("s3_bucket_path", "test_" + randomString); - - this.configJson = configJson; - this.databricksConfig = DatabricksDestinationConfig.get(configJson); - this.s3Config = databricksConfig.getS3DestinationConfig(); - LOGGER.info("Test full path: s3://{}/{}", s3Config.getBucketName(), s3Config.getBucketPath()); - - this.s3Client = s3Config.getS3Client(); - } - @Override protected void tearDown(final TestDestinationEnv testEnv) throws SQLException { - // clean up s3 - final List keysToDelete = new LinkedList<>(); - final List objects = s3Client - .listObjects(s3Config.getBucketName(), s3Config.getBucketPath()) - .getObjectSummaries(); - for (final S3ObjectSummary object : objects) { - keysToDelete.add(new KeyVersion(object.getKey())); - } - - if (keysToDelete.size() > 0) { - LOGGER.info("Tearing down test bucket path: {}/{}", s3Config.getBucketName(), - s3Config.getBucketPath()); - final DeleteObjectsResult result = s3Client - .deleteObjects(new DeleteObjectsRequest(s3Config.getBucketName()).withKeys(keysToDelete)); - LOGGER.info("Deleted {} file(s).", result.getDeletedObjects().size()); - } - s3Client.shutdown(); - // clean up database LOGGER.info("Dropping database schema {}", databricksConfig.getDatabaseSchema()); try (final DSLContext dslContext = getDslContext(databricksConfig)) { @@ -144,10 +83,10 @@ protected void tearDown(final TestDestinationEnv testEnv) throws SQLException { } } - private static DSLContext getDslContext(final DatabricksDestinationConfig databricksConfig) { + protected static DSLContext getDslContext(final DatabricksDestinationConfig databricksConfig) { return DSLContextFactory.create(DatabricksConstants.DATABRICKS_USERNAME, databricksConfig.getDatabricksPersonalAccessToken(), DatabricksConstants.DATABRICKS_DRIVER_CLASS, - DatabricksDestination.getDatabricksConnectionString(databricksConfig), SQLDialect.DEFAULT); + DatabricksBaseDestination.getDatabricksConnectionString(databricksConfig), SQLDialect.DEFAULT); } } diff --git a/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksS3DestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksS3DestinationAcceptanceTest.java new file mode 100644 index 000000000000..96f73a83c457 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test-integration/java/io/airbyte/integrations/destination/databricks/DatabricksS3DestinationAcceptanceTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.DeleteObjectsRequest; +import com.amazonaws.services.s3.model.DeleteObjectsRequest.KeyVersion; +import com.amazonaws.services.s3.model.DeleteObjectsResult; +import com.amazonaws.services.s3.model.S3ObjectSummary; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.airbyte.commons.io.IOs; +import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.destination.s3.S3DestinationConfig; +import java.nio.file.Path; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import org.apache.commons.lang3.RandomStringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DatabricksS3DestinationAcceptanceTest extends DatabricksDestinationAcceptanceTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatabricksS3DestinationAcceptanceTest.class); + private static final String SECRETS_CONFIG_JSON = "secrets/config.json"; + + private S3DestinationConfig s3Config; + private AmazonS3 s3Client; + + @Override + protected JsonNode getFailCheckConfig() { + final JsonNode failCheckJson = Jsons.clone(configJson); + // set invalid credential + ((ObjectNode) failCheckJson.get("data_source")) + .put("s3_access_key_id", "fake-key") + .put("s3_secret_access_key", "fake-secret"); + return failCheckJson; + } + + @Override + protected void setup(final TestDestinationEnv testEnv) { + final JsonNode baseConfigJson = Jsons.deserialize(IOs.readFile(Path.of(SECRETS_CONFIG_JSON))); + + // Set a random s3 bucket path and database schema for each integration test + final String randomString = RandomStringUtils.randomAlphanumeric(5); + final JsonNode configJson = Jsons.clone(baseConfigJson); + ((ObjectNode) configJson).put("database_schema", "integration_test_" + randomString); + final JsonNode dataSource = configJson.get("data_source"); + ((ObjectNode) dataSource).put("s3_bucket_path", "test_" + randomString); + + this.configJson = configJson; + this.databricksConfig = DatabricksDestinationConfig.get(configJson); + this.s3Config = databricksConfig.getStorageConfig().getS3DestinationConfigOrThrow(); + LOGGER.info("Test full path: s3://{}/{}", s3Config.getBucketName(), s3Config.getBucketPath()); + + this.s3Client = s3Config.getS3Client(); + } + + @Override + protected void tearDown(final TestDestinationEnv testEnv) throws SQLException { + // clean up s3 + final List keysToDelete = new LinkedList<>(); + final List objects = s3Client + .listObjects(s3Config.getBucketName(), s3Config.getBucketPath()) + .getObjectSummaries(); + for (final S3ObjectSummary object : objects) { + keysToDelete.add(new KeyVersion(object.getKey())); + } + + if (keysToDelete.size() > 0) { + LOGGER.info("Tearing down test bucket path: {}/{}", s3Config.getBucketName(), + s3Config.getBucketPath()); + final DeleteObjectsResult result = s3Client + .deleteObjects(new DeleteObjectsRequest(s3Config.getBucketName()).withKeys(keysToDelete)); + LOGGER.info("Deleted {} file(s).", result.getDeletedObjects().size()); + } + s3Client.shutdown(); + + super.tearDown(testEnv); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfigTest.java b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfigTest.java new file mode 100644 index 000000000000..28890fc6327f --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksAzureBlobStorageConfigTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class DatabricksAzureBlobStorageConfigTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private DatabricksStorageConfig storageConfig; + + @BeforeEach + public void setup() { + final ObjectNode dataSourceConfig = OBJECT_MAPPER.createObjectNode() + .put("data_source_type", "Azure_Blob_Storage") + .put("azure_blob_storage_account_name", "bucket_name") + .put("azure_blob_storage_container_name", "bucket_path") + .put("azure_blob_storage_sas_token", "sas_token"); + + storageConfig = DatabricksStorageConfig.getDatabricksStorageConfig(dataSourceConfig); + } + + @Test + public void testRetrieveAzureConfig() { + assertNotNull(storageConfig.getAzureBlobStorageConfigOrThrow()); + } + + @Test + public void testCannotRetrieveS3Config() { + assertThrows(UnsupportedOperationException.class, () -> storageConfig.getS3DestinationConfigOrThrow()); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfigTest.java b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfigTest.java index 075b7c56c140..cb70c5507652 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfigTest.java +++ b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationConfigTest.java @@ -16,7 +16,7 @@ class DatabricksDestinationConfigTest { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Test - public void testConfigCreationFromJson() { + public void testConfigCreationFromJsonS3() { final ObjectNode dataSourceConfig = OBJECT_MAPPER.createObjectNode() .put("data_source_type", "S3") .put("s3_bucket_name", "bucket_name") @@ -45,6 +45,40 @@ public void testConfigCreationFromJson() { final DatabricksDestinationConfig config2 = DatabricksDestinationConfig.get(databricksConfig); assertEquals("1000", config2.getDatabricksPort()); assertEquals("testing_schema", config2.getDatabaseSchema()); + + assertEquals(DatabricksS3StorageConfig.class, config2.getStorageConfig().getClass()); + } + + @Test + public void testConfigCreationFromJsonAzure() { + final ObjectNode dataSourceConfig = OBJECT_MAPPER.createObjectNode() + .put("data_source_type", "Azure_Blob_Storage") + .put("azure_blob_storage_account_name", "bucket_name") + .put("azure_blob_storage_container_name", "bucket_path") + .put("azure_blob_storage_sas_token", "sas_token"); + + final ObjectNode databricksConfig = OBJECT_MAPPER.createObjectNode() + .put("databricks_server_hostname", "server_hostname") + .put("databricks_http_path", "http_path") + .put("databricks_personal_access_token", "pak") + .set("data_source", dataSourceConfig); + + assertThrows(IllegalArgumentException.class, () -> DatabricksDestinationConfig.get(databricksConfig)); + + databricksConfig.put("accept_terms", false); + assertThrows(IllegalArgumentException.class, () -> DatabricksDestinationConfig.get(databricksConfig)); + + databricksConfig.put("accept_terms", true); + final DatabricksDestinationConfig config1 = DatabricksDestinationConfig.get(databricksConfig); + assertEquals(DatabricksDestinationConfig.DEFAULT_DATABRICKS_PORT, config1.getDatabricksPort()); + assertEquals(DatabricksDestinationConfig.DEFAULT_DATABASE_SCHEMA, config1.getDatabaseSchema()); + + databricksConfig.put("databricks_port", "1000").put("database_schema", "testing_schema"); + final DatabricksDestinationConfig config2 = DatabricksDestinationConfig.get(databricksConfig); + assertEquals("1000", config2.getDatabricksPort()); + assertEquals("testing_schema", config2.getDatabaseSchema()); + + assertEquals(DatabricksAzureBlobStorageConfig.class, config2.getStorageConfig().getClass()); } } diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolverTest.java b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolverTest.java new file mode 100644 index 000000000000..054fee5c077f --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksDestinationResolverTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.airbyte.commons.jackson.MoreMappers; +import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.resources.MoreResources; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class DatabricksDestinationResolverTest { + + private static final ObjectMapper mapper = MoreMappers.initMapper(); + + @Test + @DisplayName("When given S3 credentials should use S3") + public void useS3Test() { + final var stubLoadingMethod = mapper.createObjectNode(); + stubLoadingMethod.put("s3_bucket_name", "fake-bucket"); + + final var stubConfig = mapper.createObjectNode(); + stubConfig.set("data_source", stubLoadingMethod); + + assertTrue(DatabricksDestinationResolver.isS3Copy(stubConfig)); + assertFalse(DatabricksDestinationResolver.isAzureBlobCopy(stubConfig)); + } + + @Test + @DisplayName("When given Azure credentials should use Azure") + public void useAzureTest() { + final var stubLoadingMethod = mapper.createObjectNode(); + stubLoadingMethod.put("azure_blob_storage_account_name", "fake-account"); + final var stubConfig = mapper.createObjectNode(); + stubConfig.set("data_source", stubLoadingMethod); + assertFalse(DatabricksDestinationResolver.isS3Copy(stubConfig)); + assertTrue(DatabricksDestinationResolver.isAzureBlobCopy(stubConfig)); + } + + @Test + @DisplayName("Storage credentials required") + public void storageCredentialsRequiredTest() { + final var stubLoadingMethod = mapper.createObjectNode(); + final var stubConfig = mapper.createObjectNode(); + stubConfig.set("data_source", stubLoadingMethod); + assertThrows(IllegalArgumentException.class, () -> DatabricksDestinationResolver.getTypeFromConfig(stubConfig)); + } + + @ParameterizedTest + @MethodSource("destinationTypeToConfig") + public void testS3ConfigType(final String configFileName, final DatabricksStorageType expectedDestinationType) throws Exception { + final JsonNode config = Jsons.deserialize(MoreResources.readResource(configFileName), JsonNode.class); + final DatabricksStorageType typeFromConfig = DatabricksDestinationResolver.getTypeFromConfig(config); + assertEquals(expectedDestinationType, typeFromConfig); + } + + private static Stream destinationTypeToConfig() { + return Stream.of( + arguments("config.json", DatabricksStorageType.S3), + arguments("azure_config.json", DatabricksStorageType.AZURE_BLOB_STORAGE)); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfigTest.java b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfigTest.java new file mode 100644 index 000000000000..98c8c9b34ca4 --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StorageConfigTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.databricks; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class DatabricksS3StorageConfigTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private DatabricksStorageConfig storageConfig; + + @BeforeEach + public void setup() { + final ObjectNode dataSourceConfig = OBJECT_MAPPER.createObjectNode() + .put("data_source_type", "S3") + .put("s3_bucket_name", "bucket_name") + .put("s3_bucket_path", "bucket_path") + .put("s3_bucket_region", "bucket_region") + .put("s3_access_key_id", "access_key_id") + .put("s3_secret_access_key", "secret_access_key"); + + storageConfig = DatabricksStorageConfig.getDatabricksStorageConfig(dataSourceConfig); + } + + @Test + public void testRetrieveS3Config() { + assertNotNull(storageConfig.getS3DestinationConfigOrThrow()); + } + + @Test + public void testCannotRetrieveAzureConfig() { + assertThrows(UnsupportedOperationException.class, () -> storageConfig.getAzureBlobStorageConfigOrThrow()); + } + +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierTest.java b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierTest.java similarity index 81% rename from airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierTest.java rename to airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierTest.java index e6b5e178661c..d193441f875f 100644 --- a/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksStreamCopierTest.java +++ b/airbyte-integrations/connectors/destination-databricks/src/test/java/io/airbyte/integrations/destination/databricks/DatabricksS3StreamCopierTest.java @@ -10,14 +10,14 @@ import java.util.UUID; import org.junit.jupiter.api.Test; -class DatabricksStreamCopierTest { +class DatabricksS3StreamCopierTest { @Test public void testGetStagingS3DestinationConfig() { final String bucketPath = UUID.randomUUID().toString(); final S3DestinationConfig config = S3DestinationConfig.create("", bucketPath, "").get(); final String stagingFolder = UUID.randomUUID().toString(); - final S3DestinationConfig stagingConfig = DatabricksStreamCopier.getStagingS3DestinationConfig(config, stagingFolder); + final S3DestinationConfig stagingConfig = DatabricksS3StreamCopier.getStagingS3DestinationConfig(config, stagingFolder); assertEquals(String.format("%s/%s", bucketPath, stagingFolder), stagingConfig.getBucketPath()); } diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/resources/azure_config.json b/airbyte-integrations/connectors/destination-databricks/src/test/resources/azure_config.json new file mode 100644 index 000000000000..153ef677952e --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test/resources/azure_config.json @@ -0,0 +1,17 @@ +{ + "accept_terms": true, + "databricks_server_hostname": "abc-12345678-wxyz.cloud.databricks.com", + "databricks_http_path": "sql/protocolvx/o/1234567489/0000-1111111-abcd90", + "databricks_port": "443", + "databricks_personal_access_token": "dapi0123456789abcdefghij0123456789AB", + "database_schema": "public", + "data_source": { + "data_source_type": "Azure_Blob_Storage", + "azure_blob_storage_endpoint_domain_name": "blob.core.windows.net", + "azure_blob_storage_account_name": "account", + "azure_blob_storage_sas_token": "token", + "azure_blob_storage_container_name": "container", + "azure_blob_storage_output_buffer_size": 5 + }, + "purge_staging_data": false +} diff --git a/airbyte-integrations/connectors/destination-databricks/src/test/resources/config.json b/airbyte-integrations/connectors/destination-databricks/src/test/resources/config.json new file mode 100644 index 000000000000..12beab579a6b --- /dev/null +++ b/airbyte-integrations/connectors/destination-databricks/src/test/resources/config.json @@ -0,0 +1,16 @@ +{ + "accept_terms": true, + "databricks_server_hostname": "abc-12345678-wxyz.cloud.databricks.com", + "databricks_http_path": "sql/protocolvx/o/1234567489/0000-1111111-abcd90", + "databricks_port": "443", + "databricks_personal_access_token": "dapi0123456789abcdefghij0123456789AB", + "database_schema": "public", + "data_source": { + "data_source_type": "S3", + "s3_bucket_name": "required", + "s3_bucket_path": "required", + "s3_bucket_region": "required", + "s3_access_key_id": "required", + "s3_secret_access_key": "required" + } +} diff --git a/docs/integrations/destinations/databricks.md b/docs/integrations/destinations/databricks.md index 6821168aa0b5..414755acdb3b 100644 --- a/docs/integrations/destinations/databricks.md +++ b/docs/integrations/destinations/databricks.md @@ -19,29 +19,34 @@ Currently, this connector requires 30+MB of memory for each stream. When syncing ## Data Source -Databricks Delta Lake supports various cloud storage as the [data source](https://docs.databricks.com/data/data-sources/index.html). Currently, only Amazon S3 is supported by this connector. +Databricks Delta Lake supports various cloud storage as the [data source](https://docs.databricks.com/data/data-sources/index.html). Currently, only Amazon S3 and Azure Blob Storage are supported by this connector. ## Configuration -| Category | Parameter | Type | Notes | -|:-----------------|:----------------------|:-------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Databricks | Server Hostname | string | Required. Example: `abc-12345678-wxyz.cloud.databricks.com`. See [documentation](https://docs.databricks.com/integrations/bi/jdbc-odbc-bi.html#get-server-hostname-port-http-path-and-jdbc-url). Please note that this is the server for the Databricks Cluster. It is different from the SQL Endpoint Cluster. | -| | HTTP Path | string | Required. Example: `sql/protocolvx/o/1234567489/0000-1111111-abcd90`. See [documentation](https://docs.databricks.com/integrations/bi/jdbc-odbc-bi.html#get-server-hostname-port-http-path-and-jdbc-url). | -| | Port | string | Optional. Default to "443". See [documentation](https://docs.databricks.com/integrations/bi/jdbc-odbc-bi.html#get-server-hostname-port-http-path-and-jdbc-url). | -| | Personal Access Token | string | Required. Example: `dapi0123456789abcdefghij0123456789AB`. See [documentation](https://docs.databricks.com/sql/user/security/personal-access-tokens.html). | -| General | Database schema | string | Optional. Default to "public". Each data stream will be written to a table under this database schema. | -| | Purge Staging Data | boolean | The connector creates staging files and tables on S3. By default, they will be purged when the data sync is complete. Set it to `false` for debugging purposes. | -| Data Source - S3 | Bucket Name | string | Name of the bucket to sync data into. | -| | Bucket Path | string | Subdirectory under the above bucket to sync the data into. | -| | Region | string | See [documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) for all region codes. | -| | Access Key ID | string | AWS/Minio credential. | -| | Secret Access Key | string | AWS/Minio credential. | -| | S3 Filename pattern | string | The pattern allows you to set the file-name format for the S3 staging file(s), next placeholders combinations are currently supported: {date}, {date:yyyy_MM}, {timestamp}, {timestamp:millis}, {timestamp:micros}, {part_number}, {sync_id}, {format_extension}. Please, don't use empty space and not supportable placeholders, as they won't recognized. | - -⚠️ Please note that under "Full Refresh Sync" mode, data in the configured bucket and path will be wiped out before each sync. We recommend you provision a dedicated S3 resource for this sync to prevent unexpected data deletion from misconfiguration. ⚠️ - -## Staging Parquet Files (Delta Format) - +| Category | Parameter | Type | Notes | +|:--------------------|:----------------------|:-------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Databricks | Server Hostname | string | Required. Example: `abc-12345678-wxyz.cloud.databricks.com`. See [documentation](https://docs.databricks.com/integrations/bi/jdbc-odbc-bi.html#get-server-hostname-port-http-path-and-jdbc-url). Please note that this is the server for the Databricks Cluster. It is different from the SQL Endpoint Cluster. | +| | HTTP Path | string | Required. Example: `sql/protocolvx/o/1234567489/0000-1111111-abcd90`. See [documentation](https://docs.databricks.com/integrations/bi/jdbc-odbc-bi.html#get-server-hostname-port-http-path-and-jdbc-url). | +| | Port | string | Optional. Default to "443". See [documentation](https://docs.databricks.com/integrations/bi/jdbc-odbc-bi.html#get-server-hostname-port-http-path-and-jdbc-url). | +| | Personal Access Token | string | Required. Example: `dapi0123456789abcdefghij0123456789AB`. See [documentation](https://docs.databricks.com/sql/user/security/personal-access-tokens.html). | +| General | Database schema | string | Optional. Default to "public". Each data stream will be written to a table under this database schema. | +| | Purge Staging Data | boolean | The connector creates staging files and tables on S3 or Azure. By default, they will be purged when the data sync is complete. Set it to `false` for debugging purposes. | +| Data Source - S3 | Bucket Name | string | Name of the bucket to sync data into. | +| | Bucket Path | string | Subdirectory under the above bucket to sync the data into. | +| | Region | string | See [documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html#concepts-available-regions) for all region codes. | +| | Access Key ID | string | AWS/Minio credential. | +| | Secret Access Key | string | AWS/Minio credential. | +| | S3 Filename pattern | string | The pattern allows you to set the file-name format for the S3 staging file(s), next placeholders combinations are currently supported: {date}, {date:yyyy_MM}, {timestamp}, {timestamp:millis}, {timestamp:micros}, {part_number}, {sync_id}, {format_extension}. Please, don't use empty space and not supportable placeholders, as they won't recognized. | +| Data Source - Azure | Account Name | string | Name of the account to sync data into. | +| | Container Name | string | Container under the above account to sync the data into. | +| | SAS token | string | Shared-access signature token for the above account. | +| | Endpoint domain name | string | Usually blob.core.windows.net. | + +⚠️ Please note that under "Full Refresh Sync" mode, data in the configured bucket and path will be wiped out before each sync. We recommend you provision a dedicated S3 or Azure resource for this sync to prevent unexpected data deletion from misconfiguration. ⚠️ + +## Staging Files (Delta Format) + +### S3 Data streams are first written as staging delta-table ([Parquet](https://parquet.apache.org/) + [Transaction Log](https://databricks.com/blog/2019/08/21/diving-into-delta-lake-unpacking-the-transaction-log.html)) files on S3, and then loaded into Databricks delta-tables. All the staging files will be deleted after the sync is done. For debugging purposes, here is the full path for a staging file: ```text @@ -60,9 +65,12 @@ s3://testing_bucket/data_output_path/98c450be-5b1c-422d-b8b5-6ca9903727d9/users/ bucket name ``` +### Azure +Similarly, streams are first written to a staging location, but the Azure option uses CSV format. A staging table is created from the CSV files. + ## Unmanaged Spark SQL Table -Currently, all streams are synced into unmanaged Spark SQL tables. See [documentation](https://docs.databricks.com/data/tables.html#managed-and-unmanaged-tables) for details. In summary, you have full control of the location of the data underlying an unmanaged table. The full path of each data stream is: +Currently, all streams are synced into unmanaged Spark SQL tables. See [documentation](https://docs.databricks.com/data/tables.html#managed-and-unmanaged-tables) for details. In summary, you have full control of the location of the data underlying an unmanaged table. In S3, the full path of each data stream is: ```text s3:///// @@ -79,7 +87,12 @@ s3://testing_bucket/data_output_path/public/users bucket name ``` -Please keep these data directories on S3. Otherwise, the corresponding tables will have no data in Databricks. +In Azure, the full path of each data stream is: + +```text +abfss://@.dfs.core.windows.net// +``` +Please keep these data directories on S3/Azure. Otherwise, the corresponding tables will have no data in Databricks. ## Output Schema @@ -89,7 +102,7 @@ Each table will have the following columns: | :--- | :---: | :--- | | `_airbyte_ab_id` | string | UUID. | | `_airbyte_emitted_at` | timestamp | Data emission timestamp. | -| Data fields from the source stream | various | All fields in the staging Parquet files will be expanded in the table. | +| Data fields from the source stream | various | All fields in the staging files will be expanded in the table. | Under the hood, an Airbyte data stream in Json schema is first converted to an Avro schema, then the Json object is converted to an Avro record, and finally the Avro record is outputted to the Parquet format. Because the data stream can come from any data source, the Json to Avro conversion process has arbitrary rules and limitations. Learn more about how source data is converted to Avro and the current limitations [here](https://docs.airbyte.io/understanding-airbyte/json-avro-conversion). @@ -98,8 +111,8 @@ Under the hood, an Airbyte data stream in Json schema is first converted to an A ### Requirements 1. Credentials for a Databricks cluster. See [documentation](https://docs.databricks.com/clusters/create.html). -2. Credentials for an S3 bucket. See [documentation](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). -3. Grant the Databricks cluster full access to the S3 bucket. Or mount it as Databricks File System \(DBFS\). See [documentation](https://docs.databricks.com/data/data-sources/aws/amazon-s3.html). +2. Credentials for an S3 bucket or Azure container. See [documentation](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys). +3. Grant the Databricks cluster full access to the S3 bucket or Azure container. Or mount it as Databricks File System \(DBFS\). See [documentation](https://docs.databricks.com/data/data-sources/aws/amazon-s3.html). ## Related tutorial Suppose you are interested in learning more about the Databricks connector or details on how the Delta Lake tables are created. You may want to consult the tutorial on [How to Load Data into Delta Lake on Databricks Lakehouse](https://airbyte.com/tutorials/load-data-into-delta-lake-on-databricks-lakehouse). @@ -108,7 +121,8 @@ Suppose you are interested in learning more about the Databricks connector or de | Version | Date | Pull Request | Subject | |:--------|:-----------|:--------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------| -| (unpublished) | 2022-09-01 | [\#16243](https://github.com/airbytehq/airbyte/pull/16243) | Fix Json to Avro conversion when there is field name clash from combined restrictions (`anyOf`, `oneOf`, `allOf` fields) | +| 0.3.0 | 2022-08-04 | [\#15329](https://github.com/airbytehq/airbyte/pull/15329) | Add support for Azure storage. | +| | 2022-09-01 | [\#16243](https://github.com/airbytehq/airbyte/pull/16243) | Fix Json to Avro conversion when there is field name clash from combined restrictions (`anyOf`, `oneOf`, `allOf` fields) | | 0.2.6 | 2022-08-05 | [\#14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiply log bindings | | 0.2.5 | 2022-07-15 | [\#14494](https://github.com/airbytehq/airbyte/pull/14494) | Make S3 output filename configurable. | | 0.2.4 | 2022-07-14 | [\#14618](https://github.com/airbytehq/airbyte/pull/14618) | Removed additionalProperties: false from JDBC destination connectors | From c81ea485cec757160a1576522d8875bb3875c100 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Sat, 15 Oct 2022 13:31:12 -0700 Subject: [PATCH 124/498] Faster Faker (#18021) * Faster Faker * unit tests * tests passing * update pr link * guard against small ranges * Fixup product timezones * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-faker/Dockerfile | 2 +- .../connectors/source-faker/README.md | 2 +- .../integration_tests/catalog.json | 27 +-- .../integration_tests/configured_catalog.json | 35 +-- .../integration_tests/expected_records.txt | 20 +- .../connectors/source-faker/setup.py | 4 +- .../source-faker/source_faker/products.json | 200 +++++++++--------- .../source_faker/products_catalog.json | 2 +- .../source_faker/purchases_catalog.json | 6 +- .../source-faker/source_faker/source.py | 56 +++-- .../source_faker/users_catalog.json | 27 +-- .../source-faker/unit_tests/unit_test.py | 31 +-- docs/integrations/sources/faker.md | 1 + 15 files changed, 229 insertions(+), 188 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 1dd9f34012e9..3975c20aa834 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -295,7 +295,7 @@ - name: Faker sourceDefinitionId: dfd88b22-b603-4c3d-aad7-3701784586b1 dockerRepository: airbyte/source-faker - dockerImageTag: 0.1.8 + dockerImageTag: 0.2.0 documentationUrl: https://docs.airbyte.com/integrations/sources/faker sourceType: api releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 1040a76999e0..e027db5e0214 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2948,7 +2948,7 @@ oauthFlowInitParameters: [] oauthFlowOutputParameters: - - "access_token" -- dockerImage: "airbyte/source-faker:0.1.8" +- dockerImage: "airbyte/source-faker:0.2.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/faker" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-faker/Dockerfile b/airbyte-integrations/connectors/source-faker/Dockerfile index 8691ac121aa0..86f7f29a80a0 100644 --- a/airbyte-integrations/connectors/source-faker/Dockerfile +++ b/airbyte-integrations/connectors/source-faker/Dockerfile @@ -34,5 +34,5 @@ COPY source_faker ./source_faker ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.8 +LABEL io.airbyte.version=0.2.0 LABEL io.airbyte.name=airbyte/source-faker diff --git a/airbyte-integrations/connectors/source-faker/README.md b/airbyte-integrations/connectors/source-faker/README.md index 544140802cd2..4773805d8b82 100644 --- a/airbyte-integrations/connectors/source-faker/README.md +++ b/airbyte-integrations/connectors/source-faker/README.md @@ -97,7 +97,7 @@ Make sure to familiarize yourself with [pytest test discovery](https://docs.pyte First install test dependencies into your virtual environment: ``` -pip install .[tests] +pip install ".[tests]" ``` ### Unit Tests diff --git a/airbyte-integrations/connectors/source-faker/integration_tests/catalog.json b/airbyte-integrations/connectors/source-faker/integration_tests/catalog.json index 5ebcdfb9c62f..6530001f43d6 100644 --- a/airbyte-integrations/connectors/source-faker/integration_tests/catalog.json +++ b/airbyte-integrations/connectors/source-faker/integration_tests/catalog.json @@ -10,25 +10,26 @@ "created_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" }, "updated_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" }, - "job": { "type": "string" }, - "company": { "type": "string" }, - "ssn": { "type": "string" }, - "residence": { "type": "string" }, - "current_location": { "type": "array" }, - "blood_group": { "type": "string" }, - "website": { "type": "array" }, - "username": { "type": "string" }, "name": { "type": "string" }, - "sex": { "type": "string" }, - "address": { "type": "string" }, - "mail": { "type": "string" } + "title": { "type": "string" }, + "age": { "type": "integer" }, + "email": { "type": "string" }, + "telephone": { "type": "string" }, + "gender": { "type": "string" }, + "language": { "type": "string" }, + "academic_degree": { "type": "string" }, + "nationality": { "type": "string" }, + "occupation": { "type": "string" }, + "height": { "type": "string" }, + "blood_type": { "type": "string" }, + "weight": { "type": "integer" } } }, "supported_sync_modes": ["incremental", "full_refresh"], diff --git a/airbyte-integrations/connectors/source-faker/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-faker/integration_tests/configured_catalog.json index d089cabcd4b1..1e4be19ffda7 100644 --- a/airbyte-integrations/connectors/source-faker/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-faker/integration_tests/configured_catalog.json @@ -8,20 +8,29 @@ "type": "object", "properties": { "id": { "type": "number" }, - "created_at": { "type": "date" }, - "updated_at": { "type": "date" }, - "job": { "type": "string" }, - "company": { "type": "string" }, - "ssn": { "type": "string" }, - "residence": { "type": "string" }, - "current_location": { "type": "array" }, - "blood_group": { "type": "string" }, - "website": { "type": "array" }, - "username": { "type": "string" }, + "created_at": { + "type": "string", + "format": "date-time", + "airbyte_type": "timestamp_with_timezone" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "airbyte_type": "timestamp_with_timezone" + }, "name": { "type": "string" }, - "sex": { "type": "string" }, - "address": { "type": "string" }, - "mail": { "type": "string" } + "title": { "type": "string" }, + "age": { "type": "integer" }, + "email": { "type": "string" }, + "telephone": { "type": "string" }, + "gender": { "type": "string" }, + "language": { "type": "string" }, + "academic_degree": { "type": "string" }, + "nationality": { "type": "string" }, + "occupation": { "type": "string" }, + "height": { "type": "string" }, + "blood_type": { "type": "string" }, + "weight": { "type": "integer" } } }, "supported_sync_modes": ["incremental", "full_refresh"], diff --git a/airbyte-integrations/connectors/source-faker/integration_tests/expected_records.txt b/airbyte-integrations/connectors/source-faker/integration_tests/expected_records.txt index 185bd299ad99..aecffc24112e 100644 --- a/airbyte-integrations/connectors/source-faker/integration_tests/expected_records.txt +++ b/airbyte-integrations/connectors/source-faker/integration_tests/expected_records.txt @@ -1,10 +1,10 @@ -{"stream": "Users", "data": {"job": "Musician", "company": "Williams-Sheppard", "ssn": "498-52-4970", "residence": "Unit 5938 Box 2421\nDPO AP 33335", "current_location": [52.958961, 143.143712], "blood_group": "B+", "website": ["http://www.rivera.com/", "http://grimes-green.net/", "http://www.larsen.com/"], "username": "leeashley", "name": "Gary Cross", "sex": "M", "address": "711 Golden Overpass\nWest Andreaville, MA 71317", "mail": "tamaramorrison@hotmail.com", "id": 1, "created_at": "1976-03-27T12:40:22", "updated_at": "2000-04-13T06:17:38"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Therapist, occupational", "company": "Stewart-Allen", "ssn": "189-25-3060", "residence": "1122 Megan Squares Suite 848\nPort Jason, OR 55475", "current_location": [65.610695, -32.24732], "blood_group": "O-", "website": ["https://www.salazar-tucker.com/", "http://www.dennis.com/", "https://www.simmons-brown.com/", "http://www.walters.com/"], "username": "myersmitchell", "name": "Chelsea Greer", "sex": "F", "address": "Unit 0903 Box 2173\nDPO AP 08507", "mail": "stephenschristine@yahoo.com", "id": 2, "created_at": "1974-11-24T20:47:03", "updated_at": "1985-01-10T17:47:40"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Illustrator", "company": "Graham-Brown", "ssn": "479-06-9773", "residence": "45792 Tammy Centers Apt. 258\nDavidmouth, HI 02231", "current_location": [36.600983, 40.066283], "blood_group": "AB+", "website": ["https://castro.info/", "http://williams.info/", "https://howard.org/"], "username": "smithjames", "name": "Jasmine Perry", "sex": "F", "address": "76960 Savage Port\nBartonton, NV 08874", "mail": "jacqueline78@yahoo.com", "id": 3, "created_at": "2011-02-06T22:32:40", "updated_at": "2021-05-30T16:13:19"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Academic librarian", "company": "Simpson LLC", "ssn": "858-60-0817", "residence": "81206 Stewart Forest Apt. 089\nEast Davidborough, ME 37198", "current_location": [-41.0853825, -126.215901], "blood_group": "O-", "website": ["http://www.sloan-marsh.com/", "https://www.meyer.com/", "https://www.williams.com/", "https://browning.org/"], "username": "monica23", "name": "Steven Bowman", "sex": "M", "address": "22455 Higgins Junction Apt. 042\nNew Keith, OH 17493", "mail": "danny30@yahoo.com", "id": 4, "created_at": "2005-08-05T11:36:52", "updated_at": "2019-08-02T23:04:14"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Building services engineer", "company": "Powell-Murphy", "ssn": "425-14-1629", "residence": "5517 Holly Meadow Apt. 452\nLake Anne, SC 11894", "current_location": [-72.327639, -134.543372], "blood_group": "B-", "website": ["http://garcia.net/", "https://kramer-klein.com/"], "username": "ryanhoward", "name": "Daniel Duarte", "sex": "M", "address": "731 Sanders Fords\nPort Jasonberg, ID 60585", "mail": "jward@gmail.com", "id": 5, "created_at": "1980-02-06T13:15:11", "updated_at": "2003-03-15T20:31:32"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Surveyor, rural practice", "company": "Little-Henderson", "ssn": "534-64-5284", "residence": "389 Alec Squares Suite 508\nPort Jonathan, FL 50177", "current_location": [66.4839605, -21.954682], "blood_group": "O-", "website": ["http://www.perez.com/"], "username": "mistymurray", "name": "Joan Atkins", "sex": "F", "address": "809 Erika Valley Apt. 634\nPetersenfort, WY 51431", "mail": "melissayates@hotmail.com", "id": 6, "created_at": "1982-05-20T20:41:25", "updated_at": "2020-05-29T07:50:41"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Immigration officer", "company": "Hudson, Cook and Snyder", "ssn": "269-39-6686", "residence": "22345 Sheri Orchard Suite 279\nLake Hollystad, AZ 80687", "current_location": [35.165804, 27.598858], "blood_group": "O-", "website": ["http://smith.org/"], "username": "james71", "name": "Ashley Dunn", "sex": "F", "address": "519 Kramer Crossroad Suite 418\nNorth Kimberly, MN 99672", "mail": "stanleyclarke@gmail.com", "id": 7, "created_at": "1972-09-20T17:24:07", "updated_at": "2003-01-31T16:46:49"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Freight forwarder", "company": "Duffy Ltd", "ssn": "583-74-3539", "residence": "412 Snow Manors Apt. 161\nSouth Kimtown, NV 57004", "current_location": [21.942823, -163.821807], "blood_group": "O-", "website": ["https://www.jones-howard.com/", "https://smith.com/", "http://www.smith.com/"], "username": "michelelopez", "name": "Melissa Cantu", "sex": "F", "address": "420 Michael Mountains Suite 485\nNew Victoria, ND 76634", "mail": "eric22@gmail.com", "id": 8, "created_at": "1986-07-11T18:55:27", "updated_at": "2004-08-04T16:06:58"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Fast food restaurant manager", "company": "Carter Group", "ssn": "680-06-2167", "residence": "12648 Yang Divide Suite 451\nSouth Cynthia, NC 08084", "current_location": [11.047629, 39.379532], "blood_group": "A+", "website": ["https://watson.com/", "http://www.rodriguez-jacobs.com/", "https://saunders.com/", "http://giles-thomas.info/"], "username": "amontgomery", "name": "Christina Smith", "sex": "F", "address": "489 Roger Terrace\nDavisfort, IN 71770", "mail": "bramirez@gmail.com", "id": 9, "created_at": "1972-10-04T19:18:09", "updated_at": "2015-06-29T09:01:24"}, "emitted_at": 1649892395000} -{"stream": "Users", "data": {"job": "Academic librarian", "company": "Ross-Zamora", "ssn": "728-51-7285", "residence": "4391 Chad Greens Suite 851\nPort Frank, LA 37561", "current_location": [49.5419055, -107.833532], "blood_group": "B-", "website": ["https://osborne.com/"], "username": "qtaylor", "name": "Melissa James", "sex": "F", "address": "637 Neal Island Suite 074\nLake Tyler, RI 28775", "mail": "kellypeter@gmail.com", "id": 10, "created_at": "1974-07-10T19:02:25", "updated_at": "2005-11-07T09:09:06"}, "emitted_at": 1649892395000} +{"stream": "Users", "data": {"id": 1, "name": "Reda", "title": "M.Sc.Tech.", "age": 47, "email": "locations1983@protonmail.com", "telephone": "+1-(110)-795-7610", "gender": "Male", "language": "Tamil", "academic_degree": "Master", "nationality": "Italian", "occupation": "Word Processing Operator", "height": "1.55", "blood_type": "B\u2212", "weight": 58, "created_at": "2009-08-12T18:57:58+00:00", "updated_at": "2012-07-02T08:32:31+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 2, "name": "Tristan", "title": "M.Sc.Tech.", "age": 32, "email": "variations1847@duck.com", "telephone": "683-770-9281", "gender": "Other", "language": "Bosnian", "academic_degree": "Bachelor", "nationality": "Estonian", "occupation": "Tiler", "height": "2.00", "blood_type": "AB\u2212", "weight": 44, "created_at": "2008-09-23T19:57:09+00:00", "updated_at": "2016-03-10T04:48:06+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 3, "name": "Yuki", "title": "Miss", "age": 50, "email": "vacuum2027@yahoo.com", "telephone": "1-321-809-2061", "gender": "Female", "language": "Armenian", "academic_degree": "Bachelor", "nationality": "Swiss", "occupation": "Valuer", "height": "1.84", "blood_type": "O\u2212", "weight": 71, "created_at": "2003-06-14T10:39:40+00:00", "updated_at": "2003-12-03T21:21:30+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 4, "name": "Fred", "title": "MMath", "age": 47, "email": "causes1859@outlook.com", "telephone": "(827) 127-3811", "gender": "Female", "language": "Assamese", "academic_degree": "PhD", "nationality": "Russian", "occupation": "Turkey Farmer", "height": "1.80", "blood_type": "A+", "weight": 39, "created_at": "2001-09-30T00:05:46+00:00", "updated_at": "2006-09-16T14:55:33+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 5, "name": "Emmitt", "title": "DPhil", "age": 39, "email": "athens1899@gmail.com", "telephone": "(470) 656-8003", "gender": "Other", "language": "English", "academic_degree": "Bachelor", "nationality": "Jordanian", "occupation": "Stone Sawyer", "height": "1.52", "blood_type": "A+", "weight": 82, "created_at": "2012-12-27T21:40:00+00:00", "updated_at": "2015-06-08T23:20:45+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 6, "name": "Hollis", "title": "MSc", "age": 52, "email": "fisheries1881@yandex.com", "telephone": "(519) 606-9896", "gender": "Other", "language": "Swati", "academic_degree": "Master", "nationality": "Chilean", "occupation": "Writer", "height": "1.85", "blood_type": "AB\u2212", "weight": 85, "created_at": "2002-04-30T18:14:15+00:00", "updated_at": "2004-09-15T02:05:20+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 7, "name": "Kip", "title": "M.D.", "age": 31, "email": "numbers1983@example.com", "telephone": "346-013-2638", "gender": "Other", "language": "Armenian", "academic_degree": "Master", "nationality": "Swiss", "occupation": "Salesman", "height": "1.89", "blood_type": "O+", "weight": 48, "created_at": "2003-09-11T17:13:51+00:00", "updated_at": "2016-08-04T09:35:18+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 8, "name": "Carie", "title": "Madam", "age": 49, "email": "watershed1819@example.com", "telephone": "(348) 881-9607", "gender": "Male", "language": "Kyrgyz", "academic_degree": "PhD", "nationality": "Guatemalan", "occupation": "Park Ranger", "height": "1.50", "blood_type": "O+", "weight": 83, "created_at": "2012-06-19T07:18:11+00:00", "updated_at": "2017-10-10T14:05:38+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 9, "name": "Steven", "title": "Mr.", "age": 54, "email": "llp1893@yahoo.com", "telephone": "830.247.8156", "gender": "Fluid", "language": "Catalan", "academic_degree": "Bachelor", "nationality": "Egyptian", "occupation": "Ambulance Driver", "height": "1.52", "blood_type": "AB+", "weight": 81, "created_at": "2002-11-25T04:56:09+00:00", "updated_at": "2005-01-20T21:16:30+00:00"}, "emitted_at": 1665791425000} +{"stream": "Users", "data": {"id": 10, "name": "Lore", "title": "Madam", "age": 61, "email": "resident2075@example.com", "telephone": "321.233.0702", "gender": "Female", "language": "Polish", "academic_degree": "Master", "nationality": "French", "occupation": "Registrar", "height": "1.99", "blood_type": "B+", "weight": 56, "created_at": "2001-02-23T17:43:25+00:00", "updated_at": "2022-09-09T16:51:15+00:00"}, "emitted_at": 1665791425000} diff --git a/airbyte-integrations/connectors/source-faker/setup.py b/airbyte-integrations/connectors/source-faker/setup.py index 7bb17317d8b5..ab62499037f5 100644 --- a/airbyte-integrations/connectors/source-faker/setup.py +++ b/airbyte-integrations/connectors/source-faker/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "Faker==13.3.1"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "mimesis==6.1.1"] TEST_REQUIREMENTS = [ "pytest~=6.1", @@ -14,7 +14,7 @@ setup( name="source_faker", - description="Source implementation for Faker.", + description="Source implementation for fake but realistic looking data.", author="Airbyte", author_email="evan@airbyte.io", packages=find_packages(), diff --git a/airbyte-integrations/connectors/source-faker/source_faker/products.json b/airbyte-integrations/connectors/source-faker/source_faker/products.json index 2381a1a3d9b2..3969d502111a 100644 --- a/airbyte-integrations/connectors/source-faker/source_faker/products.json +++ b/airbyte-integrations/connectors/source-faker/source_faker/products.json @@ -5,7 +5,7 @@ "model": "MX-5", "year": 2008, "price": 2869, - "created_at": "2022-02-01T17:02:19Z" + "created_at": "2022-02-01T17:02:19+00:00" }, { "id": 2, @@ -13,7 +13,7 @@ "model": "C-Class", "year": 2009, "price": 42397, - "created_at": "2021-01-25T14:31:33" + "created_at": "2021-01-25T14:31:33+00:00" }, { "id": 3, @@ -21,7 +21,7 @@ "model": "Accord Crosstour", "year": 2011, "price": 63293, - "created_at": "2021-02-11T05:36:03Z" + "created_at": "2021-02-11T05:36:03+00:00" }, { "id": 4, @@ -29,7 +29,7 @@ "model": "Jimmy", "year": 1998, "price": 34079, - "created_at": "2022-01-24T03:00:03Z" + "created_at": "2022-01-24T03:00:03+00:00" }, { "id": 5, @@ -37,7 +37,7 @@ "model": "FX", "year": 2004, "price": 17036, - "created_at": "2021-10-02T03:55:44Z" + "created_at": "2021-10-02T03:55:44+00:00" }, { "id": 6, @@ -45,7 +45,7 @@ "model": "Intrepid", "year": 2002, "price": 65498, - "created_at": "2022-01-18T00:41:08Z" + "created_at": "2022-01-18T00:41:08+00:00" }, { "id": 7, @@ -53,7 +53,7 @@ "model": "Frontier", "year": 2005, "price": 14516, - "created_at": "2021-04-22T16:37:44Z" + "created_at": "2021-04-22T16:37:44+00:00" }, { "id": 8, @@ -61,7 +61,7 @@ "model": "Express 1500", "year": 2007, "price": 13023, - "created_at": "2021-07-12T07:13:04Z" + "created_at": "2021-07-12T07:13:04+00:00" }, { "id": 9, @@ -69,7 +69,7 @@ "model": "Continental GTC", "year": 2008, "price": 43458, - "created_at": "2021-03-17T05:43:15Z" + "created_at": "2021-03-17T05:43:15+00:00" }, { "id": 10, @@ -77,7 +77,7 @@ "model": "DTS", "year": 2008, "price": 43859, - "created_at": "2021-08-12T07:33:58Z" + "created_at": "2021-08-12T07:33:58+00:00" }, { "id": 11, @@ -85,7 +85,7 @@ "model": "Ram 2500", "year": 2000, "price": 82904, - "created_at": "2021-09-03T10:51:16Z" + "created_at": "2021-09-03T10:51:16+00:00" }, { "id": 12, @@ -93,7 +93,7 @@ "model": "SJ 410", "year": 1984, "price": 38667, - "created_at": "2021-01-11T00:15:46Z" + "created_at": "2021-01-11T00:15:46+00:00" }, { "id": 13, @@ -101,7 +101,7 @@ "model": "S4", "year": 2005, "price": 2391, - "created_at": "2021-09-06T03:31:10Z" + "created_at": "2021-09-06T03:31:10+00:00" }, { "id": 14, @@ -109,7 +109,7 @@ "model": "Suburban 2500", "year": 1998, "price": 55733, - "created_at": "2021-10-18T17:26:05Z" + "created_at": "2021-10-18T17:26:05+00:00" }, { "id": 15, @@ -117,7 +117,7 @@ "model": "Ranger", "year": 2000, "price": 20228, - "created_at": "2022-03-24T04:03:19Z" + "created_at": "2022-03-24T04:03:19+00:00" }, { "id": 16, @@ -125,7 +125,7 @@ "model": "Corvette", "year": 2009, "price": 75052, - "created_at": "2021-12-31T03:38:21Z" + "created_at": "2021-12-31T03:38:21+00:00" }, { "id": 17, @@ -133,7 +133,7 @@ "model": "Pajero", "year": 1993, "price": 84058, - "created_at": "2021-10-15T00:25:34Z" + "created_at": "2021-10-15T00:25:34+00:00" }, { "id": 18, @@ -141,7 +141,7 @@ "model": "LS", "year": 2002, "price": 34081, - "created_at": "2022-02-14T22:12:01Z" + "created_at": "2022-02-14T22:12:01+00:00" }, { "id": 19, @@ -149,7 +149,7 @@ "model": "Magnum", "year": 2005, "price": 85545, - "created_at": "2021-07-25T22:49:48Z" + "created_at": "2021-07-25T22:49:48+00:00" }, { "id": 20, @@ -157,7 +157,7 @@ "model": "Grand Am", "year": 2001, "price": 54837, - "created_at": "2021-10-15T14:08:30Z" + "created_at": "2021-10-15T14:08:30+00:00" }, { "id": 21, @@ -165,7 +165,7 @@ "model": "Suburban 1500", "year": 2006, "price": 89410, - "created_at": "2021-03-23T15:40:43Z" + "created_at": "2021-03-23T15:40:43+00:00" }, { "id": 22, @@ -173,7 +173,7 @@ "model": "Sierra 1500", "year": 2005, "price": 14288, - "created_at": "2021-08-30T13:40:04Z" + "created_at": "2021-08-30T13:40:04+00:00" }, { "id": 23, @@ -181,7 +181,7 @@ "model": "3500", "year": 1995, "price": 12011, - "created_at": "2022-04-24T13:11:08Z" + "created_at": "2022-04-24T13:11:08+00:00" }, { "id": 24, @@ -189,7 +189,7 @@ "model": "Mazda5", "year": 2006, "price": 6393, - "created_at": "2021-07-07T14:14:33Z" + "created_at": "2021-07-07T14:14:33+00:00" }, { "id": 25, @@ -197,7 +197,7 @@ "model": "Camaro", "year": 1967, "price": 71590, - "created_at": "2021-01-10T21:50:22Z" + "created_at": "2021-01-10T21:50:22+00:00" }, { "id": 26, @@ -205,7 +205,7 @@ "model": "Explorer Sport Trac", "year": 2010, "price": 23498, - "created_at": "2022-04-20T00:52:20Z" + "created_at": "2022-04-20T00:52:20+00:00" }, { "id": 27, @@ -213,7 +213,7 @@ "model": "Caravan", "year": 1985, "price": 50071, - "created_at": "2022-01-05T10:13:31Z" + "created_at": "2022-01-05T10:13:31+00:00" }, { "id": 28, @@ -221,7 +221,7 @@ "model": "240SX", "year": 1992, "price": 38379, - "created_at": "2022-04-07T04:48:48Z" + "created_at": "2022-04-07T04:48:48+00:00" }, { "id": 29, @@ -229,7 +229,7 @@ "model": "Intrigue", "year": 2002, "price": 21376, - "created_at": "2021-10-01T13:30:49Z" + "created_at": "2021-10-01T13:30:49+00:00" }, { "id": 30, @@ -237,7 +237,7 @@ "model": "TT", "year": 2011, "price": 40893, - "created_at": "2021-02-28T23:06:37Z" + "created_at": "2021-02-28T23:06:37+00:00" }, { "id": 31, @@ -245,7 +245,7 @@ "model": "Crown Victoria", "year": 2006, "price": 86225, - "created_at": "2021-01-28T23:33:27Z" + "created_at": "2021-01-28T23:33:27+00:00" }, { "id": 32, @@ -253,7 +253,7 @@ "model": "Tacoma", "year": 2003, "price": 73558, - "created_at": "2022-01-28T22:02:04Z" + "created_at": "2022-01-28T22:02:04+00:00" }, { "id": 33, @@ -261,7 +261,7 @@ "model": "Regal", "year": 1994, "price": 32279, - "created_at": "2022-04-04T13:35:49Z" + "created_at": "2022-04-04T13:35:49+00:00" }, { "id": 34, @@ -269,7 +269,7 @@ "model": "C-Class", "year": 2001, "price": 98732, - "created_at": "2021-03-30T23:16:05Z" + "created_at": "2021-03-30T23:16:05+00:00" }, { "id": 35, @@ -277,7 +277,7 @@ "model": "Sierra 3500", "year": 2002, "price": 48267, - "created_at": "2021-07-30T20:29:51Z" + "created_at": "2021-07-30T20:29:51+00:00" }, { "id": 36, @@ -285,7 +285,7 @@ "model": "G6", "year": 2005, "price": 16766, - "created_at": "2021-03-24T07:53:33Z" + "created_at": "2021-03-24T07:53:33+00:00" }, { "id": 37, @@ -293,7 +293,7 @@ "model": "Outback Sport", "year": 2002, "price": 34523, - "created_at": "2021-12-23T22:47:32Z" + "created_at": "2021-12-23T22:47:32+00:00" }, { "id": 38, @@ -301,7 +301,7 @@ "model": "F430", "year": 2007, "price": 31677, - "created_at": "2021-01-11T04:49:57Z" + "created_at": "2021-01-11T04:49:57+00:00" }, { "id": 39, @@ -309,7 +309,7 @@ "model": "Montero", "year": 2003, "price": 67136, - "created_at": "2021-05-10T07:37:56Z" + "created_at": "2021-05-10T07:37:56+00:00" }, { "id": 40, @@ -317,7 +317,7 @@ "model": "Sentra", "year": 1993, "price": 78236, - "created_at": "2021-11-10T23:48:26Z" + "created_at": "2021-11-10T23:48:26+00:00" }, { "id": 41, @@ -325,7 +325,7 @@ "model": "3000GT", "year": 1993, "price": 58150, - "created_at": "2021-09-08T06:55:22Z" + "created_at": "2021-09-08T06:55:22+00:00" }, { "id": 42, @@ -333,7 +333,7 @@ "model": "E350", "year": 2012, "price": 55270, - "created_at": "2021-03-24T13:17:37Z" + "created_at": "2021-03-24T13:17:37+00:00" }, { "id": 43, @@ -341,7 +341,7 @@ "model": "Taurus", "year": 1987, "price": 13522, - "created_at": "2021-10-27T21:03:59Z" + "created_at": "2021-10-27T21:03:59+00:00" }, { "id": 44, @@ -349,7 +349,7 @@ "model": "Avalanche", "year": 2012, "price": 9862, - "created_at": "2021-07-13T12:22:26Z" + "created_at": "2021-07-13T12:22:26+00:00" }, { "id": 45, @@ -357,7 +357,7 @@ "model": "Charger", "year": 2012, "price": 81887, - "created_at": "2021-04-24T01:48:24Z" + "created_at": "2021-04-24T01:48:24+00:00" }, { "id": 46, @@ -365,7 +365,7 @@ "model": "S-Type", "year": 2005, "price": 34372, - "created_at": "2021-04-03T08:56:17Z" + "created_at": "2021-04-03T08:56:17+00:00" }, { "id": 47, @@ -373,7 +373,7 @@ "model": "Grand Voyager", "year": 1994, "price": 90637, - "created_at": "2022-04-21T09:21:08Z" + "created_at": "2022-04-21T09:21:08+00:00" }, { "id": 48, @@ -381,7 +381,7 @@ "model": "6000", "year": 1989, "price": 65165, - "created_at": "2021-10-30T13:03:07Z" + "created_at": "2021-10-30T13:03:07+00:00" }, { "id": 49, @@ -389,7 +389,7 @@ "model": "IS", "year": 2006, "price": 22434, - "created_at": "2021-01-16T10:45:52Z" + "created_at": "2021-01-16T10:45:52+00:00" }, { "id": 50, @@ -397,7 +397,7 @@ "model": "VehiCROSS", "year": 2001, "price": 38180, - "created_at": "2021-12-13T16:29:27Z" + "created_at": "2021-12-13T16:29:27+00:00" }, { "id": 51, @@ -405,7 +405,7 @@ "model": "Regal", "year": 2000, "price": 38680, - "created_at": "2021-12-29T22:25:54Z" + "created_at": "2021-12-29T22:25:54+00:00" }, { "id": 52, @@ -413,7 +413,7 @@ "model": "E-Class", "year": 2007, "price": 51556, - "created_at": "2021-07-06T11:42:23Z" + "created_at": "2021-07-06T11:42:23+00:00" }, { "id": 53, @@ -421,7 +421,7 @@ "model": "LeSabre", "year": 2001, "price": 10904, - "created_at": "2022-01-05T18:23:35Z" + "created_at": "2022-01-05T18:23:35+00:00" }, { "id": 54, @@ -429,7 +429,7 @@ "model": "928", "year": 1989, "price": 70917, - "created_at": "2022-01-02T23:16:45Z" + "created_at": "2022-01-02T23:16:45+00:00" }, { "id": 55, @@ -437,7 +437,7 @@ "model": "RX", "year": 2007, "price": 5212, - "created_at": "2021-07-10T15:02:53Z" + "created_at": "2021-07-10T15:02:53+00:00" }, { "id": 56, @@ -445,7 +445,7 @@ "model": "Econoline E250", "year": 1996, "price": 75095, - "created_at": "2021-02-04T16:17:18Z" + "created_at": "2021-02-04T16:17:18+00:00" }, { "id": 57, @@ -453,7 +453,7 @@ "model": "Blazer", "year": 2001, "price": 61918, - "created_at": "2021-12-08T07:25:30Z" + "created_at": "2021-12-08T07:25:30+00:00" }, { "id": 58, @@ -461,7 +461,7 @@ "model": "Savana 3500", "year": 2003, "price": 30307, - "created_at": "2021-11-21T23:11:45Z" + "created_at": "2021-11-21T23:11:45+00:00" }, { "id": 59, @@ -469,7 +469,7 @@ "model": "M", "year": 2002, "price": 24598, - "created_at": "2021-05-28T04:08:53Z" + "created_at": "2021-05-28T04:08:53+00:00" }, { "id": 60, @@ -477,7 +477,7 @@ "model": "S-Series", "year": 1992, "price": 96288, - "created_at": "2021-08-24T04:43:43Z" + "created_at": "2021-08-24T04:43:43+00:00" }, { "id": 61, @@ -485,7 +485,7 @@ "model": "Sebring", "year": 2003, "price": 34753, - "created_at": "2021-02-11T11:25:35Z" + "created_at": "2021-02-11T11:25:35+00:00" }, { "id": 62, @@ -493,7 +493,7 @@ "model": "Evora", "year": 2010, "price": 42760, - "created_at": "2021-08-31T00:29:05Z" + "created_at": "2021-08-31T00:29:05+00:00" }, { "id": 63, @@ -501,7 +501,7 @@ "model": "Wrangler", "year": 2011, "price": 8684, - "created_at": "2021-06-24T10:38:05Z" + "created_at": "2021-06-24T10:38:05+00:00" }, { "id": 64, @@ -509,7 +509,7 @@ "model": "Expedition", "year": 2012, "price": 25653, - "created_at": "2021-07-01T16:13:20Z" + "created_at": "2021-07-01T16:13:20+00:00" }, { "id": 65, @@ -517,7 +517,7 @@ "model": "Avalanche 2500", "year": 2006, "price": 3158, - "created_at": "2021-08-14T10:55:13Z" + "created_at": "2021-08-14T10:55:13+00:00" }, { "id": 66, @@ -525,7 +525,7 @@ "model": "Mazda3", "year": 2012, "price": 79820, - "created_at": "2021-05-25T21:55:52Z" + "created_at": "2021-05-25T21:55:52+00:00" }, { "id": 67, @@ -533,7 +533,7 @@ "model": "Tacoma", "year": 2005, "price": 73572, - "created_at": "2021-01-22T09:56:02Z" + "created_at": "2021-01-22T09:56:02+00:00" }, { "id": 68, @@ -541,7 +541,7 @@ "model": "Explorer Sport", "year": 2000, "price": 64579, - "created_at": "2021-02-16T06:56:06Z" + "created_at": "2021-02-16T06:56:06+00:00" }, { "id": 69, @@ -549,7 +549,7 @@ "model": "Savana Cargo Van", "year": 2006, "price": 65944, - "created_at": "2021-09-12T14:08:53Z" + "created_at": "2021-09-12T14:08:53+00:00" }, { "id": 70, @@ -557,7 +557,7 @@ "model": "HHR", "year": 2009, "price": 8953, - "created_at": "2021-08-17T04:25:43Z" + "created_at": "2021-08-17T04:25:43+00:00" }, { "id": 71, @@ -565,7 +565,7 @@ "model": "Bronco II", "year": 1989, "price": 41811, - "created_at": "2021-07-14T14:20:28Z" + "created_at": "2021-07-14T14:20:28+00:00" }, { "id": 72, @@ -573,7 +573,7 @@ "model": "Suburban 2500", "year": 2011, "price": 57488, - "created_at": "2021-09-22T12:32:57Z" + "created_at": "2021-09-22T12:32:57+00:00" }, { "id": 73, @@ -581,7 +581,7 @@ "model": "Grand Vitara", "year": 2008, "price": 6408, - "created_at": "2021-11-12T23:19:52Z" + "created_at": "2021-11-12T23:19:52+00:00" }, { "id": 74, @@ -589,7 +589,7 @@ "model": "Mazda6", "year": 2012, "price": 14805, - "created_at": "2021-06-01T01:55:32Z" + "created_at": "2021-06-01T01:55:32+00:00" }, { "id": 75, @@ -597,7 +597,7 @@ "model": "Tahoe", "year": 1998, "price": 33585, - "created_at": "2022-01-09T04:28:54Z" + "created_at": "2022-01-09T04:28:54+00:00" }, { "id": 76, @@ -605,7 +605,7 @@ "model": "Explorer Sport Trac", "year": 2010, "price": 2087, - "created_at": "2022-03-28T00:28:16Z" + "created_at": "2022-03-28T00:28:16+00:00" }, { "id": 77, @@ -613,7 +613,7 @@ "model": "F150", "year": 2007, "price": 17621, - "created_at": "2021-03-23T15:08:10Z" + "created_at": "2021-03-23T15:08:10+00:00" }, { "id": 78, @@ -621,7 +621,7 @@ "model": "Taurus", "year": 1995, "price": 16478, - "created_at": "2021-06-07T22:29:50Z" + "created_at": "2021-06-07T22:29:50+00:00" }, { "id": 79, @@ -629,7 +629,7 @@ "model": "Truck", "year": 1992, "price": 70616, - "created_at": "2022-01-30T05:14:02Z" + "created_at": "2022-01-30T05:14:02+00:00" }, { "id": 80, @@ -637,7 +637,7 @@ "model": "Colt", "year": 1994, "price": 34163, - "created_at": "2022-04-02T18:06:30Z" + "created_at": "2022-04-02T18:06:30+00:00" }, { "id": 81, @@ -645,7 +645,7 @@ "model": "RX-7", "year": 1991, "price": 29634, - "created_at": "2021-01-06T10:30:59Z" + "created_at": "2021-01-06T10:30:59+00:00" }, { "id": 82, @@ -653,7 +653,7 @@ "model": "Grand Prix", "year": 1984, "price": 88575, - "created_at": "2021-02-24T06:06:57Z" + "created_at": "2021-02-24T06:06:57+00:00" }, { "id": 83, @@ -661,7 +661,7 @@ "model": "Mazdaspeed 3", "year": 2012, "price": 77723, - "created_at": "2021-11-11T22:48:05Z" + "created_at": "2021-11-11T22:48:05+00:00" }, { "id": 84, @@ -669,7 +669,7 @@ "model": "Spider", "year": 1992, "price": 64288, - "created_at": "2021-01-06T03:50:27Z" + "created_at": "2021-01-06T03:50:27+00:00" }, { "id": 85, @@ -677,7 +677,7 @@ "model": "S8", "year": 2002, "price": 33718, - "created_at": "2021-07-21T11:14:54Z" + "created_at": "2021-07-21T11:14:54+00:00" }, { "id": 86, @@ -685,7 +685,7 @@ "model": "Amigo", "year": 1992, "price": 53335, - "created_at": "2022-03-02T10:42:21Z" + "created_at": "2022-03-02T10:42:21+00:00" }, { "id": 87, @@ -693,7 +693,7 @@ "model": "Paseo", "year": 1996, "price": 74558, - "created_at": "2021-10-02 14:54:58Z" + "created_at": "2021-10-02 14:54:58+00:00" }, { "id": 88, @@ -701,7 +701,7 @@ "model": "Continental Mark VII", "year": 1986, "price": 42150, - "created_at": "2021-10-02T04:48:53Z" + "created_at": "2021-10-02T04:48:53+00:00" }, { "id": 89, @@ -709,7 +709,7 @@ "model": "Dakota", "year": 1997, "price": 64516, - "created_at": "2021-09-09T23:13:26Z" + "created_at": "2021-09-09T23:13:26+00:00" }, { "id": 90, @@ -717,7 +717,7 @@ "model": "Tahoe", "year": 1998, "price": 51461, - "created_at": "2021-04-06T08:29:19Z" + "created_at": "2021-04-06T08:29:19+00:00" }, { "id": 91, @@ -725,7 +725,7 @@ "model": "Vibe", "year": 2006, "price": 12134, - "created_at": "2021-01-11T22:30:14Z" + "created_at": "2021-01-11T22:30:14+00:00" }, { "id": 92, @@ -733,7 +733,7 @@ "model": "Eos", "year": 2011, "price": 53128, - "created_at": "2021-01-12T23:25:06Z" + "created_at": "2021-01-12T23:25:06+00:00" }, { "id": 93, @@ -741,7 +741,7 @@ "model": "Mazdaspeed6", "year": 2007, "price": 90902, - "created_at": "2021-12-29T14:29:03Z" + "created_at": "2021-12-29T14:29:03+00:00" }, { "id": 94, @@ -749,7 +749,7 @@ "model": "Xterra", "year": 2005, "price": 41532, - "created_at": "2021-09-07 09:00:49Z" + "created_at": "2021-09-07 09:00:49+00:00" }, { "id": 95, @@ -757,7 +757,7 @@ "model": "Sable", "year": 2005, "price": 71337, - "created_at": "2021-01-31T22:13:44Z" + "created_at": "2021-01-31T22:13:44+00:00" }, { "id": 96, @@ -765,7 +765,7 @@ "model": "330", "year": 2006, "price": 14494, - "created_at": "2021-09-17T20:52:48Z" + "created_at": "2021-09-17T20:52:48+00:00" }, { "id": 97, @@ -773,7 +773,7 @@ "model": "R8", "year": 2008, "price": 17642, - "created_at": "2021-09-21T11:56:24Z" + "created_at": "2021-09-21T11:56:24+00:00" }, { "id": 98, @@ -781,7 +781,7 @@ "model": "CTS-V", "year": 2007, "price": 19914, - "created_at": "2021-09-02T15:38:46Z" + "created_at": "2021-09-02T15:38:46+00:00" }, { "id": 99, @@ -789,7 +789,7 @@ "model": "1500 Club Coupe", "year": 1997, "price": 82288, - "created_at": "2021-04-20T18:58:15Z" + "created_at": "2021-04-20T18:58:15+00:00" }, { "id": 100, @@ -797,6 +797,6 @@ "model": "Somerset", "year": 1986, "price": 64148, - "created_at": "2021-06-10T19:07:38Z" + "created_at": "2021-06-10T19:07:38+00:00" } ] diff --git a/airbyte-integrations/connectors/source-faker/source_faker/products_catalog.json b/airbyte-integrations/connectors/source-faker/source_faker/products_catalog.json index 484f58f8bd13..cf745b06ed58 100644 --- a/airbyte-integrations/connectors/source-faker/source_faker/products_catalog.json +++ b/airbyte-integrations/connectors/source-faker/source_faker/products_catalog.json @@ -10,7 +10,7 @@ "created_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" } } } diff --git a/airbyte-integrations/connectors/source-faker/source_faker/purchases_catalog.json b/airbyte-integrations/connectors/source-faker/source_faker/purchases_catalog.json index 7d07183a028f..d79a797ad83f 100644 --- a/airbyte-integrations/connectors/source-faker/source_faker/purchases_catalog.json +++ b/airbyte-integrations/connectors/source-faker/source_faker/purchases_catalog.json @@ -8,17 +8,17 @@ "added_to_cart_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" }, "purchased_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" }, "returned_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" } } } diff --git a/airbyte-integrations/connectors/source-faker/source_faker/source.py b/airbyte-integrations/connectors/source-faker/source_faker/source.py index 3908f3c2e9d2..6e664751df24 100644 --- a/airbyte-integrations/connectors/source-faker/source_faker/source.py +++ b/airbyte-integrations/connectors/source-faker/source_faker/source.py @@ -23,7 +23,8 @@ Type, ) from airbyte_cdk.sources import Source -from faker import Faker +from mimesis import Datetime, Person +from mimesis.locales import Locale class SourceFaker(Source): @@ -108,8 +109,8 @@ def read( records_per_sync: int = config["records_per_sync"] if "records_per_sync" in config else 500 records_per_slice: int = config["records_per_slice"] if "records_per_slice" in config else 100 - Faker.seed(seed) - fake = Faker() + person = Person(locale=Locale.EN, seed=seed) + dt = Datetime(seed=seed) to_generate_users = False to_generate_purchases = False @@ -136,14 +137,14 @@ def read( records_in_page = 0 for i in range(cursor, count): - user = generate_user(fake, i) + user = generate_user(person, dt, i) yield generate_record(stream, user) total_records += 1 records_in_sync += 1 records_in_page += 1 if to_generate_purchases: - purchases = generate_purchases(fake, user, purchases_count) + purchases = generate_purchases(user, purchases_count) for p in purchases: yield generate_record(purchases_stream, p) purchases_count += 1 @@ -184,7 +185,7 @@ def generate_record(stream: any, data: any): # timestamps need to be emitted in ISO format for key in dict: if isinstance(dict[key], datetime.datetime): - dict[key] = dict[key].isoformat() + dict[key] = format_airbyte_time(dict[key]) return AirbyteMessage( type=Type.RECORD, @@ -210,22 +211,39 @@ def generate_state(state: Dict[str, any], stream: any, data: any): return AirbyteMessage(type=Type.STATE, state=AirbyteStateMessage(data=state)) -def generate_user(fake: Faker, user_id: int): - profile = fake.profile() - del profile["birthdate"] # the birthdate field seems to not obey the seed at the moment, so we'll ignore it +def generate_user(person: Person, dt: Datetime, user_id: int): + time_a = dt.datetime() + time_b = dt.datetime() - time_a = fake.date_time() - time_b = fake.date_time() - metadata = { + profile = { "id": user_id + 1, "created_at": time_a if time_a <= time_b else time_b, "updated_at": time_a if time_a > time_b else time_b, + "name": person.name(), + "title": person.title(), + "age": person.age(), + "email": person.email(), + "telephone": person.telephone(), + "gender": person.gender(), + "language": person.language(), + "academic_degree": person.academic_degree(), + "nationality": person.nationality(), + "occupation": person.occupation(), + "height": person.height(), + "blood_type": person.blood_type(), + "weight": person.weight(), } - profile.update(metadata) + + while not profile["created_at"]: + profile["created_at"] = dt.datetime() + + if not profile["updated_at"]: + profile["updated_at"] = profile["created_at"] + 1 + return profile -def generate_purchases(fake: Faker, user: any, purchases_count: int) -> list[Dict]: +def generate_purchases(user: any, purchases_count: int) -> list[Dict]: purchases: list[Dict] = [] purchase_percent_remaining = 80 # ~ 20% of people will have no purchases total_products = len(generate_products()) @@ -269,6 +287,16 @@ def read_json(filepath): def random_date_in_range(start_date: datetime.datetime, end_date: datetime.datetime = datetime.datetime.now()) -> datetime.datetime: time_between_dates = end_date - start_date days_between_dates = time_between_dates.days + if days_between_dates < 2: + days_between_dates = 2 random_number_of_days = random.randrange(days_between_dates) random_date = start_date + datetime.timedelta(days=random_number_of_days) return random_date + + +def format_airbyte_time(d: datetime): + s = f"{d}" + s = s.split(".")[0] + s = s.replace(" ", "T") + s += "+00:00" + return s diff --git a/airbyte-integrations/connectors/source-faker/source_faker/users_catalog.json b/airbyte-integrations/connectors/source-faker/source_faker/users_catalog.json index 28b2410e0a8a..1a9f8bbb4aa5 100644 --- a/airbyte-integrations/connectors/source-faker/source_faker/users_catalog.json +++ b/airbyte-integrations/connectors/source-faker/source_faker/users_catalog.json @@ -6,24 +6,25 @@ "created_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" }, "updated_at": { "type": "string", "format": "date-time", - "airbyte_type": "timestamp_without_timezone" + "airbyte_type": "timestamp_with_timezone" }, - "job": { "type": "string" }, - "company": { "type": "string" }, - "ssn": { "type": "string" }, - "residence": { "type": "string" }, - "current_location": { "type": "array" }, - "blood_group": { "type": "string" }, - "website": { "type": "array" }, - "username": { "type": "string" }, "name": { "type": "string" }, - "sex": { "type": "string" }, - "address": { "type": "string" }, - "mail": { "type": "string" } + "title": { "type": "string" }, + "age": { "type": "integer" }, + "email": { "type": "string" }, + "telephone": { "type": "string" }, + "gender": { "type": "string" }, + "language": { "type": "string" }, + "academic_degree": { "type": "string" }, + "nationality": { "type": "string" }, + "occupation": { "type": "string" }, + "height": { "type": "string" }, + "blood_type": { "type": "string" }, + "weight": { "type": "integer" } } } diff --git a/airbyte-integrations/connectors/source-faker/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-faker/unit_tests/unit_test.py index 93df146d0c0d..0db54325bffa 100644 --- a/airbyte-integrations/connectors/source-faker/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-faker/unit_tests/unit_test.py @@ -18,20 +18,21 @@ def test_source_streams(): assert len(schemas) == 3 assert schemas[0]["properties"] == { "id": {"type": "number"}, - "created_at": {"type": "string", "format": "date-time", "airbyte_type": "timestamp_without_timezone"}, - "updated_at": {"type": "string", "format": "date-time", "airbyte_type": "timestamp_without_timezone"}, - "job": {"type": "string"}, - "company": {"type": "string"}, - "ssn": {"type": "string"}, - "residence": {"type": "string"}, - "current_location": {"type": "array"}, - "blood_group": {"type": "string"}, - "website": {"type": "array"}, - "username": {"type": "string"}, + "created_at": {"type": "string", "format": "date-time", "airbyte_type": "timestamp_with_timezone"}, + "updated_at": {"type": "string", "format": "date-time", "airbyte_type": "timestamp_with_timezone"}, "name": {"type": "string"}, - "sex": {"type": "string"}, - "address": {"type": "string"}, - "mail": {"type": "string"}, + "title": {"type": "string"}, + "age": {"type": "integer"}, + "email": {"type": "string"}, + "telephone": {"type": "string"}, + "gender": {"type": "string"}, + "language": {"type": "string"}, + "academic_degree": {"type": "string"}, + "nationality": {"type": "string"}, + "occupation": {"type": "string"}, + "height": {"type": "string"}, + "blood_type": {"type": "string"}, + "weight": {"type": "integer"}, } for schema in schemas: @@ -162,8 +163,8 @@ def test_read_with_seed(): iterator = source.read(logger, config, catalog, state) records = [row for row in iterator if row.type is Type.RECORD] - assert records[0].record.data["company"] == "Gibson-Townsend" - assert records[0].record.data["mail"] == "zamoradenise@yahoo.com" + assert records[0].record.data["occupation"] == "Roadworker" + assert records[0].record.data["email"] == "reproduce1856@outlook.com" def test_ensure_no_purchases_without_users(): diff --git a/docs/integrations/sources/faker.md b/docs/integrations/sources/faker.md index 020ca88deeac..54332a4f0ad0 100644 --- a/docs/integrations/sources/faker.md +++ b/docs/integrations/sources/faker.md @@ -41,6 +41,7 @@ N/A | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :-------------------------------------------------------- | +| 0.2.0 | 2022-10-14 | [18021](https://github.com/airbytehq/airbyte/pull/18021) | Move to mimesis for speed! | | 0.1.8 | 2022-10-12 | [17889](https://github.com/airbytehq/airbyte/pull/17889) | Bump to test publish command (2) | | 0.1.7 | 2022-10-11 | [17848](https://github.com/airbytehq/airbyte/pull/17848) | Bump to test publish command | | 0.1.6 | 2022-09-07 | [16418](https://github.com/airbytehq/airbyte/pull/16418) | Log start of each stream | From c872042a6bab4956f7552dd6e110c027d2b5c55c Mon Sep 17 00:00:00 2001 From: Liren Tu Date: Sat, 15 Oct 2022 14:10:48 -0700 Subject: [PATCH 125/498] Databricks destination: ensure encrypted JDBC connection (#18032) * Add ssl=1 to databricks jdbc url * Bump version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/destination_definitions.yaml | 2 +- .../init/src/main/resources/seed/destination_specs.yaml | 2 +- .../src/main/java/io/airbyte/db/factory/DatabaseDriver.java | 2 +- .../connectors/destination-databricks/Dockerfile | 2 +- docs/integrations/destinations/databricks.md | 3 ++- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 0fb22c22d2d4..ec71a03c143b 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -80,7 +80,7 @@ - name: Databricks Lakehouse destinationDefinitionId: 072d5540-f236-4294-ba7c-ade8fd918496 dockerRepository: airbyte/destination-databricks - dockerImageTag: 0.3.0 + dockerImageTag: 0.3.1 documentationUrl: https://docs.airbyte.com/integrations/destinations/databricks icon: databricks.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 3a8937361ebc..23ac25f84604 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -1239,7 +1239,7 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-databricks:0.3.0" +- dockerImage: "airbyte/destination-databricks:0.3.1" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/databricks" connectionSpecification: diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/factory/DatabaseDriver.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/factory/DatabaseDriver.java index eee395a4e2e9..0bd19d2e196e 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/factory/DatabaseDriver.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/factory/DatabaseDriver.java @@ -10,7 +10,7 @@ public enum DatabaseDriver { CLICKHOUSE("com.clickhouse.jdbc.ClickHouseDriver", "jdbc:clickhouse:%s://%s:%d/%s"), - DATABRICKS("com.databricks.client.jdbc.Driver", "jdbc:databricks://%s;HttpPath=%s;UserAgentEntry=Airbyte"), + DATABRICKS("com.databricks.client.jdbc.Driver", "jdbc:databricks://%s;HttpPath=%s;SSL=1;UserAgentEntry=Airbyte"), DB2("com.ibm.db2.jcc.DB2Driver", "jdbc:db2://%s:%d/%s"), MARIADB("org.mariadb.jdbc.Driver", "jdbc:mariadb://%s:%d/%s"), MSSQLSERVER("com.microsoft.sqlserver.jdbc.SQLServerDriver", "jdbc:sqlserver://%s:%d/%s"), diff --git a/airbyte-integrations/connectors/destination-databricks/Dockerfile b/airbyte-integrations/connectors/destination-databricks/Dockerfile index 47625fa00828..3bdaea71e1a2 100644 --- a/airbyte-integrations/connectors/destination-databricks/Dockerfile +++ b/airbyte-integrations/connectors/destination-databricks/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-databricks COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.3.0 +LABEL io.airbyte.version=0.3.1 LABEL io.airbyte.name=airbyte/destination-databricks diff --git a/docs/integrations/destinations/databricks.md b/docs/integrations/destinations/databricks.md index 414755acdb3b..fc6454c399a6 100644 --- a/docs/integrations/destinations/databricks.md +++ b/docs/integrations/destinations/databricks.md @@ -121,7 +121,8 @@ Suppose you are interested in learning more about the Databricks connector or de | Version | Date | Pull Request | Subject | |:--------|:-----------|:--------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------| -| 0.3.0 | 2022-08-04 | [\#15329](https://github.com/airbytehq/airbyte/pull/15329) | Add support for Azure storage. | +| 0.3.1 | 2022-10-15 | [\#18032](https://github.com/airbytehq/airbyte/pull/18032) | Add `SSL=1` to the JDBC URL to ensure SSL connection. | +| 0.3.0 | 2022-10-14 | [\#15329](https://github.com/airbytehq/airbyte/pull/15329) | Add support for Azure storage. | | | 2022-09-01 | [\#16243](https://github.com/airbytehq/airbyte/pull/16243) | Fix Json to Avro conversion when there is field name clash from combined restrictions (`anyOf`, `oneOf`, `allOf` fields) | | 0.2.6 | 2022-08-05 | [\#14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiply log bindings | | 0.2.5 | 2022-07-15 | [\#14494](https://github.com/airbytehq/airbyte/pull/14494) | Make S3 output filename configurable. | From aa1372c3323bcff8e8631eeb119aee871a7a8f97 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Sun, 16 Oct 2022 02:46:18 +0200 Subject: [PATCH 126/498] include webhook configs in operations db persistence layer (#18030) * include webhook configs in operations db persistence layer * add unit tests for operations persistence --- .../DatabaseConfigPersistence.java | 4 ++++ .../airbyte/config/persistence/MockData.java | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index 725815ea62e8..cc70e6c3ba9d 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -36,6 +36,7 @@ import io.airbyte.config.DestinationOAuthParameter; import io.airbyte.config.OperatorDbt; import io.airbyte.config.OperatorNormalization; +import io.airbyte.config.OperatorWebhook; import io.airbyte.config.SourceConnection; import io.airbyte.config.SourceOAuthParameter; import io.airbyte.config.StandardDestinationDefinition; @@ -580,6 +581,7 @@ private StandardSyncOperation buildStandardSyncOperation(final Record record) { .withOperatorType(Enums.toEnum(record.get(OPERATION.OPERATOR_TYPE, String.class), OperatorType.class).orElseThrow()) .withOperatorNormalization(Jsons.deserialize(record.get(OPERATION.OPERATOR_NORMALIZATION).data(), OperatorNormalization.class)) .withOperatorDbt(Jsons.deserialize(record.get(OPERATION.OPERATOR_DBT).data(), OperatorDbt.class)) + .withOperatorWebhook(Jsons.deserialize(record.get(OPERATION.OPERATOR_WEBHOOK).data(), OperatorWebhook.class)) .withTombstone(record.get(OPERATION.TOMBSTONE)); } @@ -1033,6 +1035,7 @@ private void writeStandardSyncOperation(final List config io.airbyte.db.instance.configs.jooq.generated.enums.OperatorType.class).orElseThrow()) .set(OPERATION.OPERATOR_NORMALIZATION, JSONB.valueOf(Jsons.serialize(standardSyncOperation.getOperatorNormalization()))) .set(OPERATION.OPERATOR_DBT, JSONB.valueOf(Jsons.serialize(standardSyncOperation.getOperatorDbt()))) + .set(OPERATION.OPERATOR_WEBHOOK, JSONB.valueOf(Jsons.serialize(standardSyncOperation.getOperatorWebhook()))) .set(OPERATION.TOMBSTONE, standardSyncOperation.getTombstone() != null && standardSyncOperation.getTombstone()) .set(OPERATION.UPDATED_AT, timestamp) .where(OPERATION.ID.eq(standardSyncOperation.getOperationId())) @@ -1047,6 +1050,7 @@ private void writeStandardSyncOperation(final List config io.airbyte.db.instance.configs.jooq.generated.enums.OperatorType.class).orElseThrow()) .set(OPERATION.OPERATOR_NORMALIZATION, JSONB.valueOf(Jsons.serialize(standardSyncOperation.getOperatorNormalization()))) .set(OPERATION.OPERATOR_DBT, JSONB.valueOf(Jsons.serialize(standardSyncOperation.getOperatorDbt()))) + .set(OPERATION.OPERATOR_WEBHOOK, JSONB.valueOf(Jsons.serialize(standardSyncOperation.getOperatorWebhook()))) .set(OPERATION.TOMBSTONE, standardSyncOperation.getTombstone() != null && standardSyncOperation.getTombstone()) .set(OPERATION.CREATED_AT, timestamp) .set(OPERATION.UPDATED_AT, timestamp) diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java index bd0fc2499c37..fd49148530bb 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java @@ -19,6 +19,7 @@ import io.airbyte.config.OperatorDbt; import io.airbyte.config.OperatorNormalization; import io.airbyte.config.OperatorNormalization.Option; +import io.airbyte.config.OperatorWebhook; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.Schedule; import io.airbyte.config.Schedule.TimeUnit; @@ -128,6 +129,10 @@ public class MockData { private static final Instant NOW = Instant.parse("2021-12-15T20:30:40.00Z"); private static final String CONNECTION_SPECIFICATION = "'{\"name\":\"John\", \"age\":30, \"car\":null}'"; + private static final UUID OPERATION_ID_4 = UUID.randomUUID(); + private static final UUID WEBHOOK_CONFIG_ID = UUID.randomUUID(); + private static final String WEBHOOK_OPERATION_EXECUTION_URL = "test-webhook-url"; + private static final String WEBHOOK_OPERATION_EXECUTION_BODY = "test-webhook-body"; public static List standardWorkspaces() { final Notification notification = new Notification() @@ -429,7 +434,20 @@ public static List standardSyncOperations() { .withOperatorDbt(null) .withOperatorNormalization(new OperatorNormalization().withOption(Option.BASIC)) .withOperatorType(OperatorType.NORMALIZATION); - return Arrays.asList(standardSyncOperation1, standardSyncOperation2, standardSyncOperation3); + final StandardSyncOperation standardSyncOperation4 = new StandardSyncOperation() + .withName("webhook-operation") + .withTombstone(false) + .withOperationId(OPERATION_ID_4) + .withWorkspaceId(WORKSPACE_ID_1) + .withOperatorType(OperatorType.WEBHOOK) + .withOperatorDbt(null) + .withOperatorNormalization(null) + .withOperatorWebhook( + new OperatorWebhook() + .withWebhookConfigId(WEBHOOK_CONFIG_ID) + .withExecutionUrl(WEBHOOK_OPERATION_EXECUTION_URL) + .withExecutionBody(WEBHOOK_OPERATION_EXECUTION_BODY)); + return Arrays.asList(standardSyncOperation1, standardSyncOperation2, standardSyncOperation3, standardSyncOperation4); } public static List standardSyncs() { From e079761509306f25132ebe9f30f41b04d3468348 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Mon, 17 Oct 2022 01:23:29 +0200 Subject: [PATCH 127/498] ensure workspace webhook configs can be persisted correctly (#18034) * ensure workspace webhook configs can be correctly passed between API and persistence layers * remove unnecessary logging * add unit tests to workspace webhook config handling * additional testing and style cleanup around workspace webhook config handling --- airbyte-api/src/main/openapi/config.yaml | 4 ++ .../config/persistence/DbConverter.java | 4 +- .../airbyte/config/persistence/MockData.java | 6 ++- .../WorkspaceWebhookConfigsConverter.java | 52 +++++++++++++++---- .../server/handlers/WorkspacesHandler.java | 30 +++++------ .../handlers/WorkspacesHandlerTest.java | 34 +++++++++--- .../api/generated-api-html/index.html | 1 + 7 files changed, 96 insertions(+), 35 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index 01b632095d57..ab7ac43ab5bb 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -2454,6 +2454,10 @@ components: $ref: "#/components/schemas/Notification" defaultGeography: $ref: "#/components/schemas/Geography" + webhookConfigs: + type: array + items: + $ref: "#/components/schemas/WebhookConfigWrite" WorkspaceGiveFeedback: type: object required: diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index 44805357c6d1..20426a93734e 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -99,7 +99,9 @@ public static StandardWorkspace buildStandardWorkspace(final Record record) { .withFirstCompletedSync(record.get(WORKSPACE.FIRST_SYNC_COMPLETE)) .withFeedbackDone(record.get(WORKSPACE.FEEDBACK_COMPLETE)) .withDefaultGeography( - Enums.toEnum(record.get(WORKSPACE.GEOGRAPHY, String.class), Geography.class).orElseThrow()); + Enums.toEnum(record.get(WORKSPACE.GEOGRAPHY, String.class), Geography.class).orElseThrow()) + .withWebhookOperationConfigs(record.get(WORKSPACE.WEBHOOK_OPERATION_CONFIGS) == null ? null + : Jsons.deserialize(record.get(WORKSPACE.WEBHOOK_OPERATION_CONFIGS).data())); } public static SourceConnection buildSourceConnection(final Record record) { diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java index fd49148530bb..06716e1a0b88 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java @@ -36,6 +36,8 @@ import io.airbyte.config.StandardSyncState; import io.airbyte.config.StandardWorkspace; import io.airbyte.config.State; +import io.airbyte.config.WebhookConfig; +import io.airbyte.config.WebhookOperationConfigs; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.protocol.models.AirbyteCatalog; import io.airbyte.protocol.models.AuthSpecification; @@ -156,7 +158,9 @@ public static List standardWorkspaces() { .withNotifications(Collections.singletonList(notification)) .withFirstCompletedSync(true) .withFeedbackDone(true) - .withDefaultGeography(Geography.AUTO); + .withDefaultGeography(Geography.AUTO) + .withWebhookOperationConfigs(Jsons.jsonNode( + new WebhookOperationConfigs().withWebhookConfigs(List.of(new WebhookConfig().withId(WEBHOOK_CONFIG_ID).withName("name"))))); final StandardWorkspace workspace2 = new StandardWorkspace() .withWorkspaceId(WORKSPACE_ID_2) diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java b/airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java index f8ff8195865f..a8a86b838d2b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/WorkspaceWebhookConfigsConverter.java @@ -10,42 +10,74 @@ import io.airbyte.commons.json.Jsons; import io.airbyte.config.WebhookConfig; import io.airbyte.config.WebhookOperationConfigs; +import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Collectors; +// NOTE: we suppress this warning because PMD thinks it can be a foreach loop in toApiReads but the +// compiler disagrees. +@SuppressWarnings("PMD.ForLoopCanBeForeach") public class WorkspaceWebhookConfigsConverter { - public static JsonNode toPersistenceWrite(List apiWebhookConfigs) { + public static JsonNode toPersistenceWrite(List apiWebhookConfigs, Supplier uuidSupplier) { if (apiWebhookConfigs == null) { return Jsons.emptyObject(); } final WebhookOperationConfigs configs = new WebhookOperationConfigs() - .withWebhookConfigs(apiWebhookConfigs.stream().map(WorkspaceWebhookConfigsConverter::toPersistenceConfig).collect(Collectors.toList())); + .withWebhookConfigs(apiWebhookConfigs.stream().map((item) -> toPersistenceConfig(uuidSupplier, item)).collect(Collectors.toList())); return Jsons.jsonNode(configs); } - public static List toApiReads(List persistenceConfig) { - if (persistenceConfig.isEmpty()) { + /** + * Extract the read-only properties from a set of persisted webhook operation configs. + *

    + * Specifically, returns the id and name but excludes the secret auth token. Note that we "manually" + * deserialize the JSON tree instead of deserializing to our internal schema -- + * WebhookOperationConfigs -- because the persisted JSON doesn't conform to that schema until we + * hydrate the secrets. Since we don't want to unnecessarily hydrate the secrets to read from the + * API, we do this instead. + *

    + * TODO(mfsiega-airbyte): try find a cleaner way to handle this situation. + * + * @param persistedWebhookConfig - The JsonNode of the persisted webhook configs + * @return a list of (webhook id, name) pairs + */ + public static List toApiReads(final JsonNode persistedWebhookConfig) { + if (persistedWebhookConfig == null) { return Collections.emptyList(); } - return persistenceConfig.stream().map(WorkspaceWebhookConfigsConverter::toApiRead).collect(Collectors.toList()); + + // NOTE: we deserialize it "by hand" because the secrets aren't hydrated, so we can't deserialize it + // into the usual shape. + // TODO(mfsiega-airbyte): find a cleaner way to handle this situation. + List configReads = new ArrayList<>(); + + final JsonNode configArray = persistedWebhookConfig.findPath("webhookConfigs"); + Iterator it = configArray.elements(); + while (it.hasNext()) { + JsonNode webhookConfig = it.next(); + configReads.add(toApiRead(webhookConfig)); + } + return configReads; } - private static WebhookConfig toPersistenceConfig(final WebhookConfigWrite input) { + private static WebhookConfig toPersistenceConfig(final Supplier uuidSupplier, final WebhookConfigWrite input) { return new WebhookConfig() - .withId(UUID.randomUUID()) + .withId(uuidSupplier.get()) .withName(input.getName()) .withAuthToken(input.getAuthToken()); } - private static WebhookConfigRead toApiRead(final WebhookConfig persistenceConfig) { + private static WebhookConfigRead toApiRead(final JsonNode configJson) { final var read = new WebhookConfigRead(); - read.setId(persistenceConfig.getId()); - read.setName(persistenceConfig.getName()); + read.setId(UUID.fromString(configJson.findValue("id").asText())); + read.setName(configJson.findValue("name").asText()); return read; } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java index 3b49e60539eb..3743d8382bbb 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WorkspacesHandler.java @@ -5,6 +5,7 @@ package io.airbyte.server.handlers; import com.github.slugify.Slugify; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import io.airbyte.analytics.TrackingClientSingleton; @@ -24,9 +25,7 @@ import io.airbyte.api.model.generated.WorkspaceUpdate; import io.airbyte.api.model.generated.WorkspaceUpdateName; import io.airbyte.commons.enums.Enums; -import io.airbyte.commons.json.Jsons; import io.airbyte.config.StandardWorkspace; -import io.airbyte.config.WebhookOperationConfigs; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.SecretsRepositoryWriter; @@ -39,7 +38,6 @@ import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -66,12 +64,13 @@ public WorkspacesHandler(final ConfigRepository configRepository, this(configRepository, secretsRepositoryWriter, connectionsHandler, destinationHandler, sourceHandler, UUID::randomUUID); } - public WorkspacesHandler(final ConfigRepository configRepository, - final SecretsRepositoryWriter secretsRepositoryWriter, - final ConnectionsHandler connectionsHandler, - final DestinationHandler destinationHandler, - final SourceHandler sourceHandler, - final Supplier uuidSupplier) { + @VisibleForTesting + WorkspacesHandler(final ConfigRepository configRepository, + final SecretsRepositoryWriter secretsRepositoryWriter, + final ConnectionsHandler connectionsHandler, + final DestinationHandler destinationHandler, + final SourceHandler sourceHandler, + final Supplier uuidSupplier) { this.configRepository = configRepository; this.secretsRepositoryWriter = secretsRepositoryWriter; this.connectionsHandler = connectionsHandler; @@ -108,7 +107,7 @@ public WorkspaceRead createWorkspace(final WorkspaceCreate workspaceCreate) .withTombstone(false) .withNotifications(NotificationConverter.toConfigList(workspaceCreate.getNotifications())) .withDefaultGeography(defaultGeography) - .withWebhookOperationConfigs(WorkspaceWebhookConfigsConverter.toPersistenceWrite(workspaceCreate.getWebhookConfigs())); + .withWebhookOperationConfigs(WorkspaceWebhookConfigsConverter.toPersistenceWrite(workspaceCreate.getWebhookConfigs(), uuidSupplier)); if (!Strings.isNullOrEmpty(email)) { workspace.withEmail(email); @@ -269,12 +268,8 @@ private static WorkspaceRead buildWorkspaceRead(final StandardWorkspace workspac .notifications(NotificationConverter.toApiList(workspace.getNotifications())) .defaultGeography(Enums.convertTo(workspace.getDefaultGeography(), Geography.class)); // Add read-only webhook configs. - final Optional persistedConfigs = Jsons.tryObject( - workspace.getWebhookOperationConfigs(), - WebhookOperationConfigs.class); - if (persistedConfigs.isPresent()) { - result.setWebhookConfigs(WorkspaceWebhookConfigsConverter.toApiReads( - persistedConfigs.get().getWebhookConfigs())); + if (workspace.getWebhookOperationConfigs() != null) { + result.setWebhookConfigs(WorkspaceWebhookConfigsConverter.toApiReads(workspace.getWebhookOperationConfigs())); } return result; } @@ -309,6 +304,9 @@ private void applyPatchToStandardWorkspace(final StandardWorkspace workspace, fi workspace.setDefaultGeography( Enums.convertTo(workspacePatch.getDefaultGeography(), io.airbyte.config.Geography.class)); } + if (workspacePatch.getWebhookConfigs() != null) { + workspace.setWebhookOperationConfigs(WorkspaceWebhookConfigsConverter.toPersistenceWrite(workspacePatch.getWebhookConfigs(), uuidSupplier)); + } } private WorkspaceRead persistStandardWorkspace(final StandardWorkspace workspace) throws JsonValidationException, IOException { diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java index 8220509c9eaa..b9f64d12c2c7 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WorkspacesHandlerTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; import io.airbyte.api.model.generated.ConnectionRead; import io.airbyte.api.model.generated.ConnectionReadList; @@ -22,6 +23,8 @@ import io.airbyte.api.model.generated.SlugRequestBody; import io.airbyte.api.model.generated.SourceRead; import io.airbyte.api.model.generated.SourceReadList; +import io.airbyte.api.model.generated.WebhookConfigRead; +import io.airbyte.api.model.generated.WebhookConfigWrite; import io.airbyte.api.model.generated.WorkspaceCreate; import io.airbyte.api.model.generated.WorkspaceGiveFeedback; import io.airbyte.api.model.generated.WorkspaceIdRequestBody; @@ -38,6 +41,7 @@ import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.SecretsRepositoryWriter; +import io.airbyte.config.persistence.split_secrets.SecretPersistence; import io.airbyte.server.converters.NotificationConverter; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; @@ -55,6 +59,11 @@ class WorkspacesHandlerTest { public static final String FAILURE_NOTIFICATION_WEBHOOK = "http://airbyte.notifications/failure"; public static final String NEW_WORKSPACE = "new workspace"; + public static final String TEST_NAME = "test-name"; + private static final UUID WEBHOOK_CONFIG_ID = UUID.randomUUID(); + private static final JsonNode PERSISTED_WEBHOOK_CONFIGS = Jsons.deserialize( + String.format("{\"webhookConfigs\": [{\"id\": \"%s\", \"name\": \"%s\", \"authToken\": {\"_secret\": \"a-secret_v1\"}}]}", + WEBHOOK_CONFIG_ID, TEST_NAME)); private ConfigRepository configRepository; private SecretsRepositoryWriter secretsRepositoryWriter; private ConnectionsHandler connectionsHandler; @@ -72,12 +81,14 @@ class WorkspacesHandlerTest { io.airbyte.api.model.generated.Geography.AUTO; private static final io.airbyte.api.model.generated.Geography GEOGRAPHY_US = io.airbyte.api.model.generated.Geography.US; + private SecretPersistence secretPersistence; @SuppressWarnings("unchecked") @BeforeEach void setUp() { configRepository = mock(ConfigRepository.class); - secretsRepositoryWriter = new SecretsRepositoryWriter(configRepository, Optional.empty(), Optional.empty()); + secretPersistence = mock(SecretPersistence.class); + secretsRepositoryWriter = new SecretsRepositoryWriter(configRepository, Optional.of(secretPersistence), Optional.empty()); connectionsHandler = mock(ConnectionsHandler.class); destinationHandler = mock(DestinationHandler.class); sourceHandler = mock(SourceHandler.class); @@ -121,6 +132,7 @@ private io.airbyte.api.model.generated.Notification generateApiNotification() { @Test void testCreateWorkspace() throws JsonValidationException, IOException, ConfigNotFoundException { + workspace.withWebhookOperationConfigs(PERSISTED_WEBHOOK_CONFIGS); when(configRepository.getStandardWorkspaceNoSecrets(any(), eq(false))).thenReturn(workspace); final UUID uuid = UUID.randomUUID(); @@ -135,7 +147,8 @@ void testCreateWorkspace() throws JsonValidationException, IOException, ConfigNo .anonymousDataCollection(false) .securityUpdates(false) .notifications(List.of(generateApiNotification())) - .defaultGeography(GEOGRAPHY_US); + .defaultGeography(GEOGRAPHY_US) + .webhookConfigs(List.of(new WebhookConfigWrite().name(TEST_NAME).authToken("test-auth-token"))); final WorkspaceRead actualRead = workspacesHandler.createWorkspace(workspaceCreate); final WorkspaceRead expectedRead = new WorkspaceRead() @@ -151,7 +164,7 @@ void testCreateWorkspace() throws JsonValidationException, IOException, ConfigNo .securityUpdates(false) .notifications(List.of(generateApiNotification())) .defaultGeography(GEOGRAPHY_US) - .webhookConfigs(Collections.emptyList()); + .webhookConfigs(List.of(new WebhookConfigRead().id(uuid).name(TEST_NAME))); assertEquals(expectedRead, actualRead); } @@ -275,6 +288,7 @@ void testListWorkspaces() throws JsonValidationException, IOException { @Test void testGetWorkspace() throws JsonValidationException, ConfigNotFoundException, IOException { + workspace.withWebhookOperationConfigs(PERSISTED_WEBHOOK_CONFIGS); when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)).thenReturn(workspace); final WorkspaceIdRequestBody workspaceIdRequestBody = new WorkspaceIdRequestBody().workspaceId(workspace.getWorkspaceId()); @@ -291,7 +305,8 @@ void testGetWorkspace() throws JsonValidationException, ConfigNotFoundException, .anonymousDataCollection(false) .securityUpdates(false) .notifications(List.of(generateApiNotification())) - .defaultGeography(GEOGRAPHY_AUTO); + .defaultGeography(GEOGRAPHY_AUTO) + .webhookConfigs(List.of(new WebhookConfigRead().id(WEBHOOK_CONFIG_ID).name(TEST_NAME))); assertEquals(workspaceRead, workspacesHandler.getWorkspace(workspaceIdRequestBody)); } @@ -330,7 +345,8 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .initialSetupComplete(true) .displaySetupWizard(false) .notifications(List.of(apiNotification)) - .defaultGeography(GEOGRAPHY_US); + .defaultGeography(GEOGRAPHY_US) + .webhookConfigs(List.of(new WebhookConfigWrite().name(TEST_NAME).authToken("test-auth-token"))); final Notification expectedNotification = generateNotification(); expectedNotification.getSlackConfiguration().withWebhook("updated"); @@ -347,7 +363,10 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .withDisplaySetupWizard(false) .withTombstone(false) .withNotifications(List.of(expectedNotification)) - .withDefaultGeography(Geography.US); + .withDefaultGeography(Geography.US) + .withWebhookOperationConfigs(PERSISTED_WEBHOOK_CONFIGS); + + when(uuidSupplier.get()).thenReturn(WEBHOOK_CONFIG_ID); when(configRepository.getStandardWorkspaceNoSecrets(workspace.getWorkspaceId(), false)) .thenReturn(workspace) @@ -369,7 +388,8 @@ void testUpdateWorkspace() throws JsonValidationException, ConfigNotFoundExcepti .anonymousDataCollection(true) .securityUpdates(false) .notifications(List.of(expectedNotificationRead)) - .defaultGeography(GEOGRAPHY_US); + .defaultGeography(GEOGRAPHY_US) + .webhookConfigs(List.of(new WebhookConfigRead().name(TEST_NAME).id(WEBHOOK_CONFIG_ID))); verify(configRepository).writeStandardWorkspaceNoSecrets(expectedWorkspace); diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 85f426ab24bf..210e5416f003 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -11578,6 +11578,7 @@

    WorkspaceUpdate - securityUpdates (optional)

    notifications (optional)
    defaultGeography (optional)
    +
    webhookConfigs (optional)
    From a28836e0c85b00e27322b0c0c5e9e75b2d3904fd Mon Sep 17 00:00:00 2001 From: Arsen Losenko <20901439+arsenlosenko@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:20:03 +0300 Subject: [PATCH 128/498] Source File: Handle UnicodeDecodeError in case of incorrect filetype (#17994) * Handle UnicodeDecodeError in case of incorrect filetype * Update changelog and Dockerfile * Update changelog with PR number * Move try/except close to the end of method * Add test and test files --- .../connectors/source-file-secure/Dockerfile | 4 +-- .../connectors/source-file/Dockerfile | 2 +- .../sample_files/archive_with_test_xlsx.zip | Bin 0 -> 4086 bytes .../integration_tests/sample_files/test.xlsx | Bin 0 -> 4749 bytes .../source-file/source_file/client.py | 29 ++++++++++-------- .../source-file/unit_tests/test_client.py | 14 +++++++++ docs/integrations/sources/file.md | 1 + 7 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 airbyte-integrations/connectors/source-file/integration_tests/sample_files/archive_with_test_xlsx.zip create mode 100644 airbyte-integrations/connectors/source-file/integration_tests/sample_files/test.xlsx diff --git a/airbyte-integrations/connectors/source-file-secure/Dockerfile b/airbyte-integrations/connectors/source-file-secure/Dockerfile index e9d710e01203..793605451fdb 100644 --- a/airbyte-integrations/connectors/source-file-secure/Dockerfile +++ b/airbyte-integrations/connectors/source-file-secure/Dockerfile @@ -1,4 +1,4 @@ -FROM airbyte/source-file:0.2.24 +FROM airbyte/source-file:0.2.25 WORKDIR /airbyte/integration_code COPY source_file_secure ./source_file_secure @@ -9,5 +9,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.24 +LABEL io.airbyte.version=0.2.25 LABEL io.airbyte.name=airbyte/source-file-secure diff --git a/airbyte-integrations/connectors/source-file/Dockerfile b/airbyte-integrations/connectors/source-file/Dockerfile index 5af1b98180a3..faf40d209f6e 100644 --- a/airbyte-integrations/connectors/source-file/Dockerfile +++ b/airbyte-integrations/connectors/source-file/Dockerfile @@ -17,5 +17,5 @@ COPY source_file ./source_file ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.24 +LABEL io.airbyte.version=0.2.25 LABEL io.airbyte.name=airbyte/source-file diff --git a/airbyte-integrations/connectors/source-file/integration_tests/sample_files/archive_with_test_xlsx.zip b/airbyte-integrations/connectors/source-file/integration_tests/sample_files/archive_with_test_xlsx.zip new file mode 100644 index 0000000000000000000000000000000000000000..ee0898d4fae9c9d35d896533712f66e8ea9c247c GIT binary patch literal 4086 zcmZ|S=Q|q=+XnC;iWaS1TdmmC-Zb_~5qncgN$pu9M%AYFjIFkyMs2N4trV$<#xA8s zZHni<-{;GFyvOlg*KwWam+K$+>FW>xsQ~~0F@V$3z_<;U0DwFM0Fo#H01^Nbz}Lyg zS11tX6KHHi0w8dt(0lRk`2{`z-~o3C0D%9ABvTUUxx2IB@8gn*ulJgrIA55yJ)%jn z7H<>yW)`Er08{aQ%F#96%dbA3x9N-kXE{zd>fGZhaqQUsahG}BwR{(_B{v~99aJQ9 z@U!5&Tf>$}c}f?or>95RKV%Q~-f+P6g<5g$DZsj!4K%0Wq3`8)*MFtWd&Id$qYOjw z1ZVTR1Kxg&63?l>@+&z3KB0DAyJ5M)#`Q~KypwOs1x%hA zTr?RWE$+|2{Rb4J>mSK_6D(IdrOqT@nUiG+9=P$yx9)iun+*j@wUIT1>Rrk|i}$y0 z%yZ8|0(^QeR1Y%c&~J3;B>HTMA?J6yLmaC+L$u1Wb}wJkNm80{1bagT6WnwnloIFt z6C94YOX_lJz9^f+^IJH0jAZi!z(VwYB6(!rn1^cYy!|3Ha^;w951nrGLv#9oc-w83 zaxKXE8e+BWYq;;X0G+O_AKNYBbb%iUdl1L&e` zDX*rMW)*;w^Xe`^+e7N1^K&BLmkH?F;cvQ3mGvr89=UnC)PVR9s8Jz?qmpGnPcJr$ zdvkrd0I7($XVkM50G*GWgV9VCG9m8hKP4b{)=GZ8j#VO?I54y6$UfAPJJvR@xt zUHa9Rum#T(EZ7?h9P@C11TVj(Pe>F+lgWV|k+lYpG&0RKXBqH9XX*$0k$%1oS0ase zVIty5FjSrcM&qZ^U;T<*gd<(HOHW~`PoqV{=Z+EX%I39Z2TrL!`+LUI0`z>*erVl{ zHbS6SPFTDr?t!SI$2&}KgGZ|$wFtxOQi-yM%Rg)~UE3lSPqJF))(fiQ~`4WPk7cO@RW z(RNXi_brxxOii`YUoUt(XwtW@V4<}9DZuiS|14NQP*r=>Rv036G*B;$EW44nnw6-? zN<4e+6=y48Qp;Z@$XmneO04RV$t|oe$zVIVvAmVM`4W!$ixAEFJuF4CUC(IjXa?Cn zU71g}e~MZYNSJ^Ia?CozxJ&}iWy>ZdM}@|oIS~tNt-d?sUuV25;!{~s;v2vata`U7 zPO*ff?0%D3yiaIIsjNt$58qol)Xa6N7co+tPMRPj6_Ssv^%xdg^CqtML0;iw>YV7y zy$V+E{~T;~<}Ef>DC*qFeFO54z&Cq#D9r?!;m&7RFe~MMf#!hoi}kZuy_(63&n>lQ z>ec04xtUQzrzEv0?-b^xJY_^XWxH55Er3y{8SbB?;eO1QmPavNnOV>`vnQQdzGb+V zU#_*3(0L>}C~lI@gJg*|`}cKpf@OuO$S87Y9%1$`p4aqX&AUOoZ8{X z=4tGjb*nZ;hSyqQ!{1PKuwK_{%Q_$J*Y8AdW~YHRw5Ii{UArhP)-;P@mYzNRo+6F` zQ)IcPSe=|KDK#QRCZYNseo1br7lpmD!I$n8wBy%sbyUv^k|BjvME+*RrDLLMPTXB{ znxJ)VW^tYI<7i2@2vLaQpR@t$nO}DXniZaTJ{aL}j>nXs)TzIs;qWNn^&P7RvUzud{WTQeLuG;{Hw!`2htZS}<%#5p)!R0XkLY}vUc^f#Th zLm}SJ)Y}AK;rGD=5vCQ_L4`riH)0Q0yHSg)MusX?`PF$Mv*Vu2-MlgA#@PK?|EINc}d$6y}D9yBe!VUn2jJ+iS}p z-C^I^V)>^WJ`<4smraAM8L#ML(P!GRBJc8R=VY5+q1b_qLi+Zn6|hR;&VaOG77lS@)2+PrBoaIRu% zaFUlw+ujFm{ANPoh5`+V0F7PkQwMCEU11+GVt)qCJXR=8^}OK9YbIbR^!h zzr6#r;r9?I<@MN1zo$-N8In?GTbW&aw(iv>LH3y|$?Q;|lELLKmn&aEe@eq%SWEvcH z#IzIQmMM=pHmQv_iU2?u+XM&ZRp&kE5RW(8%MA79iDt7-bb()_Q|p6iP-F0y-zvoY0xIb&Ii%X zn7p;ov?s+18>&_)kqxK)cV+6GdcwGm-VR?n$DKzS0{lL{Pio$oKMvdLI%zr|Tsi^@ zl&^<0=x%6P++&9N%K7)V>#giT=6aqgYfGA!FBEINNe^2hcoKAanWCSt`v?W}W+X2Q9ZkLda?IpBlY~Z1)u(C?Dx5sHEE36*70Dve)QPLr++c*1tYt zc|vFsl7bX5F#pT}N`DU`bW`K62zb{*|HOLrF*aBQ3(uC~Q>r#>)s*C6+akiQ)f9_I zoj~YBW+mUqmp1mSy?8r`0+@+P>~A<2#M+5gs;w|{3cAZD^ZiQlG-Ca@-)1pHG7vqg z8cIBA>dwA+UoU!Vt|!p!YspRY=|+YvMu}im31gdV>wGw|$fOXb;sH%I@Nt#XFA5tP zolWJdEAcnt-fOuD#G~;Ek-W$R@vrB2>C$(r5VCglf$JD2Dw4oT^ z^RdHk75Ow)-@KEM-|C`QgexYaPV0Y{G3rrDx{kUxJ=lxq8Sj6;4?{8uZ|8!6rqhA) z3p68dEw0Bi-?dpcg7e}6K+-}t#Z+;F61m+iHDPzWFpgxcFB+zQIxG`Vxm0c|cv?dV zmfg_;~X6CZPe2QIfdQ=XqZW$K5VtMSR=i zCd2r6_rN2^pVXu7z$GtU?fz-DXVSZWHezA-%sd_`I3UHS*fe6zBE@8;*y{N{879t? zKJ&PLNUa8fa{+bS=o*1b-L z@O!BsaI6|G)w)M!t}HZ}v^gZ)P4CAOS_Exp?b%szYD%V@^NK1Ir}{x-6w12j#og6G zO;zr=5V#$FjBUQGIir%fQCcqQc*i*YAmUH671MP5j&X_-DZ7g) zX6Fqpw5Aqs1M~CCH`?f?$pK$oTv><&%;)iTu|p8SE_+#LG+JRqAU1L-P_{GsowMvsGgy zAliLZe5sQh!(wds4hNQl+>QX{SVD}9q{AL8e7H$JVHuIZ zG>P}E0%vZxHAe_iXqOML96NVyJ*R#=JP*SZJMJ`bcj)vOnL7e~9Xxy%g8z@?kN=_i lf2Sql-~L}fC;Tta{}0afbqMeM`-u0?KL4jv|Is}F@E@l?so3gW0NI7fL@ZgyKxN*F&E(5%@}5 z&e5Ng#}%*o?c+<7SIyV+wx9rkrAj6WKbERBO9A?iZn*m=6+%X?p*yuK;@eT*tH82l zeGLUCR{X6BDj9Fbw86EnD4(5{qFo%UqMPH($DA!X)_dvJmEMvi&Clnk)BOK>_E30c zsRfV*m|fq4jfB?79^X>SBWsRMvJ7~l0w;p^;V z1*BW9`lOgkh1J`#7q}Bot@U-NsHton(G;X4Bvb$r68-;2rMH`hv!??B;pHiD`0<0v zp>b3?l%496$QwnzDJoCxkrR*LrX~bSld|z@=gUuK0F^KK-BdLqG+TwUCPx>(2kzl( zx_GF18{$sF^19R(shS1I@R%qKdX>PY<<;NU-hLGqiJ(_S@U#v&ulbwkex zL_zIc0xSo$tYuV66?7{w@GBjim_7_Fh#w41C7ZOSnDNm$5jRxfdMnArB}%WS@C>Y$ zYjFON$%)L^%xKBY+*|uqv&)7`q1`nTC2avg45lB#-S_VpvNuB;vY4t=qE{+_x0t3n zo^hr=yqEx+ONU5}a%fTb8a+VOD(?(}Y%2SLZ$DdaRa73jZV{wOX*nhHRrAtgHaoX~ zj)#Uko!!p9!&RlXQ%_H=+&UL
    g;w(!mQ+OlMYPMo4ynCI|RvToG zSP4|r0!z8LA!22GA^%fl2>fWWBb8z8fp9rmAEyt#$M)$QZ-oLWr$Ij9P=D;}un3tI ztx!}R&$WyB3@$u^Yp=%BW3#7Q=U?;%v{}nCNaHxe6wZic%~3!rdp>m2Gxbj1Xaul= z;ITNIpiLGvtg%>E#;!;HMn-@=xWb?{1v5p%IDC;(!25xVt{)q1zS3PaA7eJIi!M>N zVyy+F8_C^pasH}0^4H=I_9`r=^rGl>E$K$T%!c)oeylYUmZVJ-bl)3mY{MW~&s#=5 zj@kena8OoAfZ>Q4LWn&4b9aD#aNy;Da79QQz8^NmOVe@G1RroWWDhXY*fP>6T5nL9 zZo&&US*lr*u2{=!c|#`WxYxw)?;4B??8;7oYp@uJ+@buB1=>Ri8b7fOl&{S&v;UrmCaWz zWt(+gsm&1P8>1e_%i^k)V0TqI@m*re*u&=?s`s*G`0AAuu7`Fm$jy&fu z1+@CV7g8yh;hgac;0QvW)0DIr_hj9BTT($;$U$H0_E>pjL(vC4#qCsEI|{c9eA@00 zXdqZh1UyA=!5dVW2rkG`?uMxuWSb9CzJ8??J@P0te>pIy#&v26Lz3 zgO9m=$SfP+X6(2{h=SJGglG)NKdgO?pZ6)sGyt!+`wwx@eWdKElm1lLn?VpuBVlu9 zFCtu~!>O+wW7P<9EaediE(PtX%Ima*P1qzDLwAd!RCR>j1?8uX5w3$zI#-YOPHaD$ zNqXIh=UQS*Jea=B{bq>4-o$ATd%n8dD6J97 zWmPV^kx;A>B_N@+8E=#3yo7~!ubP3tMe|jlkftR| zyS4}~wK4Vr6OE3(Ko}w7yS!ps3;4!&E}fiq@b1jLlEBGL)8)P5$Nhu8TjU2h0+9{) zY+?r6#MeKI3EjVQ^zwB<941$7-14`KJZP_vDi*Hfo}Vd9-i$9B66<)PMcP3oSN(m1 zJ7#inSj&mINaf9o^mmQm>+av6a1EI?X#H*xDWNC8Lc41qeRxvX&>SyB~M=w_J^%&olW|lJnN3G0^F+`*6MWZ zje4C&#s*+?_o~RQrzpgKN^kH$zc-#%tEgkX)(pDiAT}n`z9abNhM4Dc&d7wIL$4ia z*{x|%my~lLvOJ|#e;OdOF2a{7w;<(BzQhzAGf+=SS6R9>7zC&1Om|#(cIQ0j?ZsK_ z`@zwqZ|ENJuk;rjhu;pcXlF%(Xzq6?6|8+LI<0;|)8oZRdA34$q?e^F0zbg*YG;aO zK&SFZ3Lz2s-THu0?Yj3KS9dOZZ%IxXG)f28&6Qwt6WX0OlrOzZ*Z5fNKdO;4>oO}F z1J8Y30~9dR4)j%7o>!RrU)8yRG78#Z4V%beveUO!_>;$UD zH$~I)Bjq2YVH*onVY`PlUwO8&VVF2r%KtcE8Gh8m!GN`Ob8|j4uNWPoc?I6p>sLJg z7HxN4EaKuD@fR({NIF(N@9Li3rMK(#8fY;1coQ*Qb!P@T;xa=(3Nzuy73%i*^cEb84NRv5)Z?gw&tZ%*+sJo&o_ zFtzGw9Rm+bOp1;9l&e+6$Y0WKyTiZu9n#6C`klkPx*b`Bdfg#=5UO1l9GldAQo$`33D~<`V_LVCMX**|y zG!yTRcU=BPClY~cGI?%Q_4Y&PW-OD0kFMlXw-%*S$JW6El8WcI0jZ_D#wz*kUD3Rz zcK`<}mIQ7wS|ajMOZF!fqaps{2VMWRe`@q0P#{G=(f(cEG<3I#$$BCw>_W%$s78bT zT!CXr>&l8@EKM@@+j?MrX|k^^!xtMqu}57V*Bn?rv~ZA)Y&Iy9&=|nQMJi8TYbUu` zEh?eVoq|$Veb&rmiw;uJ@}5&FbOCVn+fj;s=kfrIF;30g1Mt))_CZEbKSD| zDv61l{J-wcT*D3Jg+O^(8~eH=JS`8JwAu)-ORS0=vOWIKEV8IGe4%NZ&9l@O6B*2w zVRpk3zLO(UxJz)sV!q%ZtA?6RMQK8H!FndHof4EfEhZ`2nSJhYh+N>+$b0SUi@MB- zEzVC`?1VNi+N#v2o!(?Dnzq-w@8A))Wr%U=C%w;~jm)L*R$D;8d#If#bQ0xf*UF4r zkY9Wg&FLl6Rcz^;)1$)!pXZg6|K$)H@}fx)8#&|HdCd)HiC3L-ZBUVvKFcl&8e656 z6&~0qBFPzoG<<$3n6qZhlvN(9&OJ1CL;I2>zk?GHhnttnHIe@)JH;mv%m84_T49Em+-5|zNSOgT_dXWWr>V~4`q>*jG5%;dVh3VJy`F5jelD9 z^>uzlIl9^WilRgOUn0tpZRY2m`#Z|f-QZvg`WXnKnEZsPbpD{pn2=HIV>34*qBk911{EQmv9|(WjlHZRWRnmi*^)ot-|4qk# zCfM(XkBZ8H*!~P{+QY;DAicli9PQWx3HliU#B`1b((eb47TvGhBZ-dmf3&y04h7`_ P`f=hTjVOhahdKWXv+k9O literal 0 HcmV?d00001 diff --git a/airbyte-integrations/connectors/source-file/source_file/client.py b/airbyte-integrations/connectors/source-file/source_file/client.py index e6d2c9f0c724..d07ba892f93b 100644 --- a/airbyte-integrations/connectors/source-file/source_file/client.py +++ b/airbyte-integrations/connectors/source-file/source_file/client.py @@ -317,19 +317,24 @@ def load_dataframes(self, fp, skip_data=False) -> Iterable: logger.error(error_msg) raise ConfigurationError(error_msg) from err + reader_options = {**self._reader_options} - if self._reader_format == "csv": - reader_options["chunksize"] = self.CSV_CHUNK_SIZE - if skip_data: - reader_options["nrows"] = 0 - reader_options["index_col"] = 0 - - yield from reader(fp, **reader_options) - elif self._reader_options == "excel_binary": - reader_options["engine"] = "pyxlsb" - yield from reader(fp, **reader_options) - else: - yield reader(fp, **reader_options) + try: + if self._reader_format == "csv": + reader_options["chunksize"] = self.CSV_CHUNK_SIZE + if skip_data: + reader_options["nrows"] = 0 + reader_options["index_col"] = 0 + yield from reader(fp, **reader_options) + elif self._reader_options == "excel_binary": + reader_options["engine"] = "pyxlsb" + yield from reader(fp, **reader_options) + else: + yield reader(fp, **reader_options) + except UnicodeDecodeError as err: + error_msg = f"File {fp} can't be parsed with reader of chosen type ({self._reader_format})\n{traceback.format_exc()}" + logger.error(error_msg) + raise ConfigurationError(error_msg) from err @staticmethod def dtype_to_json_type(current_type: str, dtype) -> str: diff --git a/airbyte-integrations/connectors/source-file/unit_tests/test_client.py b/airbyte-integrations/connectors/source-file/unit_tests/test_client.py index d62c2b005760..72825ac664f5 100644 --- a/airbyte-integrations/connectors/source-file/unit_tests/test_client.py +++ b/airbyte-integrations/connectors/source-file/unit_tests/test_client.py @@ -17,6 +17,14 @@ def wrong_format_client(): format="wrong", ) +@pytest.fixture +def csv_format_client(): + return Client( + dataset_name="test_dataset", + url="scp://test_dataset", + provider={"provider": {"storage": "HTTPS", "reader_impl": "gcsfs", "user_agent": False}}, + format="csv", + ) @pytest.mark.parametrize( "storage, expected_scheme", @@ -50,6 +58,12 @@ def test_load_dataframes(client, wrong_format_client, absolute_path, test_files) next(client.load_dataframes(fp=f, skip_data=True)) +def test_raises_configuration_error_with_incorrect_file_type(csv_format_client, absolute_path, test_files): + f = f"{absolute_path}/{test_files}/archive_with_test_xlsx.zip" + with pytest.raises(ConfigurationError): + next(csv_format_client.load_dataframes(fp=f)) + + def test_load_dataframes_xlsb(config, absolute_path, test_files): config["format"] = "excel_binary" client = Client(**config) diff --git a/docs/integrations/sources/file.md b/docs/integrations/sources/file.md index 147daec75a63..f10136ec89f3 100644 --- a/docs/integrations/sources/file.md +++ b/docs/integrations/sources/file.md @@ -129,6 +129,7 @@ In order to read large files from a remote location, this connector uses the [sm | Version | Date | Pull Request | Subject | | ------- | ---------- | -------------------------------------------------------- | -------------------------------------------------------- | +| 0.2.25 | 2022-10-14 | [17994](https://github.com/airbytehq/airbyte/pull/17994) | Handle `UnicodeDecodeError` during discover step. | 0.2.24 | 2022-10-03 | [17504](https://github.com/airbytehq/airbyte/pull/17504) | Validate data for `HTTPS` while `check_connection` | | 0.2.23 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | | 0.2.22 | 2022-09-15 | [16772](https://github.com/airbytehq/airbyte/pull/16772) | Fix schema generation for JSON files containing arrays | From 8c50395e4971787f2a79f17cf2ad1cb1a85c1821 Mon Sep 17 00:00:00 2001 From: Arsen Losenko <20901439+arsenlosenko@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:21:16 +0300 Subject: [PATCH 129/498] Source File: change releaseStage to GA (#17665) --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 3975c20aa834..b1789a1f90b4 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -314,7 +314,7 @@ documentationUrl: https://docs.airbyte.com/integrations/sources/file icon: file.svg sourceType: file - releaseStage: beta + releaseStage: generally_available - name: Freshcaller sourceDefinitionId: 8a5d48f6-03bb-4038-a942-a8d3f175cca3 dockerRepository: airbyte/source-freshcaller From fa388503d86b40cc502d4ac68fa06fad21431192 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 17 Oct 2022 15:21:13 +0300 Subject: [PATCH 130/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=94=A7=20Refactore?= =?UTF-8?q?d=20=20=20(#17597)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove styled components from FormRoot * refactored WarningMessage component * refactored TestingConnectionSpinner component * fixes after merge * replace styled-components with css modules in ConnectorServiceTypeControl components * extend ModalBody to support style overwrite * refactor RequestConnectorModal using new headlessUi Modal component * - refactor FrequentlyUsedDestinations component - update tests - updates storybook * - refactor StartWithDestination component - update tests - updates storybook * renamed FrequentlyUsedDestinationsCard component * move ConnectorForm to RequestConnectorModal * remove spinner from FrequentlyUsedDestinations component * - refactor ConnectorServiceTypeControl component - move utility functions outside of component - move analytics tracking functions to separate component hook * - refactor ServiceForm component - update tests - remove test with checking serviceType * memoize selectProps in ConnectorServiceTypeControl * simplify getting the namespace type * show FormRoot conditionally - if selectedConnectorDefinitionSpecification is provided * - refactor ConnectorCard component * refactor DestinationForm component * remove styled components from FormRoot * refactored WarningMessage component * refactored TestingConnectionSpinner component * fixes after merge * replace styled-components with css modules in ConnectorServiceTypeControl components * extend ModalBody to support style overwrite * refactor RequestConnectorModal using new headlessUi Modal component * - refactor FrequentlyUsedDestinations component - update tests - updates storybook * - refactor StartWithDestination component - update tests - updates storybook * renamed FrequentlyUsedDestinationsCard component * move ConnectorForm to RequestConnectorModal * remove spinner from FrequentlyUsedDestinations component * - refactor ConnectorServiceTypeControl component - move utility functions outside of component - move analytics tracking functions to separate component hook * - refactor ServiceForm component - update tests - remove test with checking serviceType * memoize selectProps in ConnectorServiceTypeControl * simplify getting the namespace type * show FormRoot conditionally - if selectedConnectorDefinitionSpecification is provided * - refactor ConnectorCard component * refactor DestinationForm component * fixes after rebase * fixed optional onDestinationSelect callbacks * add analyticsTrackFunctions to FrequentlyUsedDestinations component * update tests * fix fullWidth Card issue * fix async/await issue * refactor analytics - Source/Destination connector selected * fix onboarding page - octavia's line * wrap analytics track functions with useCallback * move analytics track functions to separate useAnalyticsTrackFunctions file * rename filename to match the contained component name * extend SectionContainer - do not add margin-bottom if container doesn't have any childs * disable ConnectorServiceTypeControl is form is submitting * fix onbording create source/destination steps * hide form via css if selectedConnector is not present * remove comments * fix e2e tests * fix test * bring back the old design (don't split form on separate cards) * Update airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx Co-authored-by: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> * rename "additionalDropdownComponent" to "additionalSelectorComponent" * rename "formType" arg to "connectorType" in analytics track functions * fix components import * updated test snapshot * update RequestConnectorModal: move Formik inside the ModalBody * fix PR comments: rename utilityFunctions.tsx to utils.tsx * move StartWithDestination component to DestinationForm * remove hiding empty form functionality since we don't split it on multiple cards * update comment regarding the serviceType prop Co-authored-by: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> --- .../components/AttemptDetails.module.scss | 2 +- .../src/components/ui/Modal/ModalBody.tsx | 12 +- airbyte-webapp/src/locales/en.json | 2 +- ...sx => ConnectionCreateDestinationForm.tsx} | 0 ...orm.tsx => ConnectionCreateSourceForm.tsx} | 0 .../CreationFormPage/CreationFormPage.tsx | 16 +- .../components/DestinationForm.module.scss | 5 + .../components/DestinationForm.tsx | 67 +++--- .../components/DestinationStep.tsx | 11 +- .../OnboardingPage/components/SourceStep.tsx | 11 +- .../components/SourceForm.tsx | 24 +-- airbyte-webapp/src/theme.ts | 1 - .../ConnectorCard/ConnectorCard.module.scss | 9 + .../Connector/ConnectorCard/ConnectorCard.tsx | 82 ++++---- .../useAnalyticsTrackFunctions.tsx | 47 +++++ .../RequestConnectorModal.module.scss | 14 ++ .../RequestConnectorModal.tsx | 175 +++++++++++++--- .../components/ConnectorForm.module.scss | 3 - .../components/ConnectorForm.tsx | 156 -------------- .../ServiceForm/FormRoot.module.scss | 13 ++ .../views/Connector/ServiceForm/FormRoot.tsx | 36 +--- .../ServiceForm/ServiceForm.test.tsx | 2 - .../Connector/ServiceForm/ServiceForm.tsx | 132 ++---------- .../ConnectorServiceTypeControl.module.scss | 53 +++++ .../ConnectorServiceTypeControl.tsx | 196 ++++++------------ .../ConnectorServiceTypeControl/index.ts | 1 + .../useAnalyticsTrackFunctions.tsx | 40 ++++ .../ConnectorServiceTypeControl/utils.tsx | 58 ++++++ .../ServiceForm/components/CreateControls.tsx | 2 +- .../ServiceForm/components/EditControls.tsx | 2 +- .../FrequentlyUsedDestinations.tsx | 84 ++++---- ...requentlyUsedDestinationsCard.module.scss} | 7 - ...> FrequentlyUsedDestinationsCard.test.tsx} | 20 +- .../FrequentlyUsedDestinationsCard.tsx | 40 ++++ ...quentlyUsedDestinationsCard.test.tsx.snap} | 0 .../index.stories.tsx | 22 +- .../useAnalyticsTrackFunctions.tsx | 21 ++ .../Sections/SectionContainer.module.scss | 4 + .../StartWithDestination.tsx | 64 +++--- ...s => StartWithDestinationCard.module.scss} | 12 +- ....tsx => StartWithDestinationCard.test.tsx} | 13 +- .../StartWithDestinationCard.tsx | 42 ++++ .../StartWithDestination.test.tsx.snap | 65 ------ .../StartWithDestinationCard.test.tsx.snap | 61 ++++++ .../StartWithDestination/index.stories.tsx | 22 +- .../components/StartWithDestination/index.ts | 1 + .../TestingConnectionSpinner.module.scss | 13 +- .../components/TestingConnectionSpinner.tsx | 40 ++-- .../components/WarningMessage.module.scss | 19 ++ .../ServiceForm/components/WarningMessage.tsx | 53 ++--- .../src/views/Connector/ServiceForm/index.tsx | 3 + 51 files changed, 906 insertions(+), 872 deletions(-) rename airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/{DestinationForm.tsx => ConnectionCreateDestinationForm.tsx} (100%) rename airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/{SourceForm.tsx => ConnectionCreateSourceForm.tsx} (100%) create mode 100644 airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.module.scss create mode 100644 airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.module.scss create mode 100644 airbyte-webapp/src/views/Connector/ConnectorCard/useAnalyticsTrackFunctions.tsx create mode 100644 airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.module.scss delete mode 100644 airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.module.scss delete mode 100644 airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.tsx create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.module.scss create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.module.scss rename airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/{ => ConnectorServiceTypeControl}/ConnectorServiceTypeControl.tsx (52%) create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/index.ts create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/useAnalyticsTrackFunctions.tsx create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx rename airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/{FrequentlyUsedDestinations.module.scss => FrequentlyUsedDestinationsCard.module.scss} (76%) rename airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/{FrequentlyUsedDestinations.test.tsx => FrequentlyUsedDestinationsCard.test.tsx} (62%) create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.tsx rename airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/{FrequentlyUsedDestinations.test.tsx.snap => FrequentlyUsedDestinationsCard.test.tsx.snap} (100%) create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/useAnalyticsTrackFunctions.tsx rename airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/{StartWithDestination.module.scss => StartWithDestinationCard.module.scss} (50%) rename airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/{StartWithDestination.test.tsx => StartWithDestinationCard.test.tsx} (73%) create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.tsx delete mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestinationCard.test.tsx.snap create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.ts create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.module.scss diff --git a/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss b/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss index 2ef8d8b7e04b..4df5dbcf53ee 100644 --- a/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss +++ b/airbyte-webapp/src/components/JobItem/components/AttemptDetails.module.scss @@ -25,4 +25,4 @@ &.failed { color: colors.$red; } -} \ No newline at end of file +} diff --git a/airbyte-webapp/src/components/ui/Modal/ModalBody.tsx b/airbyte-webapp/src/components/ui/Modal/ModalBody.tsx index 608701bdc7f1..f368c2158e76 100644 --- a/airbyte-webapp/src/components/ui/Modal/ModalBody.tsx +++ b/airbyte-webapp/src/components/ui/Modal/ModalBody.tsx @@ -5,16 +5,22 @@ import styles from "./ModalBody.module.scss"; interface ModalBodyProps { maxHeight?: number | string; padded?: boolean; + className?: string; } export const ModalBody: React.FC> = ({ children, maxHeight, padded = true, + className, }) => { - const modalStyles = classnames(styles.modalBody, { - [styles.paddingNone]: !padded, - }); + const modalStyles = classnames( + styles.modalBody, + { + [styles.paddingNone]: !padded, + }, + className + ); return (
    {children} diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index f9883139c251..63560c4c801f 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -495,7 +495,7 @@ "settings.accountSettings.updateEmailSuccess": "Email updated", "settings.cookiePreferences": "Cookie Preferences", - "connector.requestConnectorBlock": "+ Request a new connector", + "connector.requestConnectorBlock": "Request a new connector", "connector.requestConnector": "Request a new connector", "connector.request": "Request", "connector.requested": "Requested", diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/DestinationForm.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ConnectionCreateDestinationForm.tsx similarity index 100% rename from airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/DestinationForm.tsx rename to airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ConnectionCreateDestinationForm.tsx diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/SourceForm.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ConnectionCreateSourceForm.tsx similarity index 100% rename from airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/SourceForm.tsx rename to airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ConnectionCreateSourceForm.tsx diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx index e97420b17c60..e3f56400758b 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx @@ -11,6 +11,12 @@ import HeadTitle from "components/HeadTitle"; import { PageHeader } from "components/ui/PageHeader"; import { StepsMenu } from "components/ui/StepsMenu"; +import { + DestinationDefinitionRead, + DestinationRead, + SourceDefinitionRead, + SourceRead, +} from "core/request/AirbyteClient"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; import { useFormChangeTrackerService } from "hooks/services/FormChangeTracker"; import { useGetDestination } from "hooks/services/useDestinationHook"; @@ -19,15 +25,9 @@ import { useDestinationDefinition } from "services/connector/DestinationDefiniti import { useSourceDefinition } from "services/connector/SourceDefinitionService"; import { ConnectorDocumentationWrapper } from "views/Connector/ConnectorDocumentationLayout"; -import { - DestinationDefinitionRead, - DestinationRead, - SourceDefinitionRead, - SourceRead, -} from "../../../../core/request/AirbyteClient"; -import { ConnectionCreateDestinationForm } from "./DestinationForm"; +import { ConnectionCreateDestinationForm } from "./ConnectionCreateDestinationForm"; +import { ConnectionCreateSourceForm } from "./ConnectionCreateSourceForm"; import ExistingEntityForm from "./ExistingEntityForm"; -import { ConnectionCreateSourceForm } from "./SourceForm"; export enum StepsTypes { CREATE_ENTITY = "createEntity", diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.module.scss b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.module.scss new file mode 100644 index 000000000000..273d64b51e27 --- /dev/null +++ b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.module.scss @@ -0,0 +1,5 @@ +@use "scss/variables" as vars; + +.startWithDestinationContainer { + margin-top: vars.$spacing-xl; +} diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx index 7820398759b0..414062b6e1c7 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx @@ -2,14 +2,15 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useLocation } from "react-router-dom"; -import { Action, Namespace } from "core/analytics"; import { ConnectionConfiguration } from "core/domain/connection"; import { DestinationDefinitionRead } from "core/request/AirbyteClient"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics"; import { useGetDestinationDefinitionSpecificationAsync } from "services/connector/DestinationDefinitionSpecificationService"; import { generateMessageFromError, FormError } from "utils/errorStatusMessage"; import { ConnectorCard } from "views/Connector/ConnectorCard"; +import { FrequentlyUsedDestinations, StartWithDestination } from "views/Connector/ServiceForm"; + +import styles from "./DestinationForm.module.scss"; interface DestinationFormProps { onSubmit: (values: { @@ -18,7 +19,6 @@ interface DestinationFormProps { destinationDefinitionId?: string; connectionConfiguration?: ConnectionConfiguration; }) => void; - afterSelectConnector?: () => void; destinationDefinitions: DestinationDefinitionRead[]; hasSuccess?: boolean; error?: FormError | null; @@ -37,10 +37,8 @@ export const DestinationForm: React.FC = ({ destinationDefinitions, error, hasSuccess, - afterSelectConnector, }) => { const location = useLocation(); - const analyticsService = useAnalyticsService(); const [destinationDefinitionId, setDestinationDefinitionId] = useState( hasDestinationDefinitionId(location.state) ? location.state.destinationDefinitionId : null @@ -52,24 +50,8 @@ export const DestinationForm: React.FC = ({ isLoading, } = useGetDestinationDefinitionSpecificationAsync(destinationDefinitionId); - const onDropDownSelect = ( - destinationDefinitionId: string, - trackParams?: { actionDescription: string; connector_destination_suggested: boolean } - ) => { + const onDropDownSelect = (destinationDefinitionId: string) => { setDestinationDefinitionId(destinationDefinitionId); - - const connector = destinationDefinitions.find((item) => item.destinationDefinitionId === destinationDefinitionId); - - if (afterSelectConnector) { - afterSelectConnector(); - } - - analyticsService.track(Namespace.DESTINATION, Action.SELECT, { - actionDescription: "Destination connector type selected", - connector_destination: connector?.name, - connector_destination_definition_id: destinationDefinitionId, - ...trackParams, - }); }; const onSubmitForm = async (values: { name: string; serviceType: string }) => { @@ -81,20 +63,33 @@ export const DestinationForm: React.FC = ({ const errorMessage = error ? generateMessageFromError(error) : null; + const frequentlyUsedDestinationsComponent = !isLoading && !destinationDefinitionId && ( + + ); + const startWithDestinationComponent = !isLoading && !destinationDefinitionId && ( +
    + +
    + ); + return ( - } - jobInfo={LogsRequestError.extractJobInfo(error)} - /> + <> + } + jobInfo={LogsRequestError.extractJobInfo(error)} + /> + {startWithDestinationComponent} + ); }; diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx index c300abe2fe8e..e5ed0eba21bb 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx @@ -1,8 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Action, Namespace } from "core/analytics"; import { ConnectionConfiguration } from "core/domain/connection"; -import { useAnalyticsService } from "hooks/services/Analytics"; import { useCreateDestination } from "hooks/services/useDestinationHook"; import { useDestinationDefinitionList } from "services/connector/DestinationDefinitionService"; import { useGetDestinationDefinitionSpecificationAsync } from "services/connector/DestinationDefinitionSpecificationService"; @@ -26,8 +24,6 @@ const DestinationStep: React.FC = ({ onNextStep, onSuccess }) => { const { mutateAsync: createDestination } = useCreateDestination(); - const analyticsService = useAnalyticsService(); - const getDestinationDefinitionById = (id: string) => destinationDefinitions.find((item) => item.destinationDefinitionId === id); @@ -68,12 +64,6 @@ const DestinationStep: React.FC = ({ onNextStep, onSuccess }) => { const destinationConnector = getDestinationDefinitionById(destinationDefinitionId); setDocumentationUrl(destinationConnector?.documentationUrl || ""); - analyticsService.track(Namespace.DESTINATION, Action.SELECT, { - actionDescription: "Destination connector type selected", - connector_destination: destinationConnector?.name, - connector_destination_definition_id: destinationConnector?.destinationDefinitionId, - }); - setError(null); setDestinationDefinitionId(destinationDefinitionId); }; @@ -97,6 +87,7 @@ const DestinationStep: React.FC = ({ onNextStep, onSuccess }) => { errorMessage={errorMessage} selectedConnectorDefinitionSpecification={destinationDefinitionSpecification} isLoading={isLoading} + formValues={destinationDefinitionId ? { serviceType: destinationDefinitionId } : undefined} /> ); }; diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx index 65810c7b3c08..8be5fefb446c 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx @@ -1,9 +1,7 @@ import React, { useEffect, useState } from "react"; -import { Action, Namespace } from "core/analytics"; import { ConnectionConfiguration } from "core/domain/connection"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics"; import { useCreateSource } from "hooks/services/useSourceHook"; import { useSourceDefinitionList } from "services/connector/SourceDefinitionService"; import { useGetSourceDefinitionSpecificationAsync } from "services/connector/SourceDefinitionSpecificationService"; @@ -25,8 +23,6 @@ const SourceStep: React.FC = ({ onNextStep, onSuccess }) => { const { setDocumentationUrl, setDocumentationPanelOpen } = useDocumentationPanelContext(); const { mutateAsync: createSource } = useCreateSource(); - const analyticsService = useAnalyticsService(); - const getSourceDefinitionById = (id: string) => sourceDefinitions.find((item) => item.sourceDefinitionId === id); const { data: sourceDefinitionSpecification, isLoading } = @@ -71,12 +67,6 @@ const SourceStep: React.FC = ({ onNextStep, onSuccess }) => { const sourceDefinition = getSourceDefinitionById(sourceId); setDocumentationUrl(sourceDefinition?.documentationUrl || ""); - analyticsService.track(Namespace.SOURCE, Action.SELECT, { - actionDescription: "Source connector type selected", - connector_source: sourceDefinition?.name, - connector_source_definition_id: sourceDefinition?.sourceDefinitionId, - }); - setError(null); setSourceDefinitionId(sourceId); }; @@ -100,6 +90,7 @@ const SourceStep: React.FC = ({ onNextStep, onSuccess }) => { errorMessage={errorMessage} selectedConnectorDefinitionSpecification={sourceDefinitionSpecification} isLoading={isLoading} + formValues={sourceDefinitionId ? { serviceType: sourceDefinitionId } : undefined} /> ); }; diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx index 3715618b0c1e..6e8d29bba328 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx @@ -2,10 +2,8 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useLocation } from "react-router-dom"; -import { Action, Namespace } from "core/analytics"; import { ConnectionConfiguration } from "core/domain/connection"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics"; import { SourceDefinitionReadWithLatestTag } from "services/connector/SourceDefinitionService"; import { useGetSourceDefinitionSpecificationAsync } from "services/connector/SourceDefinitionSpecificationService"; import { generateMessageFromError, FormError } from "utils/errorStatusMessage"; @@ -19,7 +17,6 @@ interface SourceFormProps { sourceDefinitionId?: string; connectionConfiguration?: ConnectionConfiguration; }) => void; - afterSelectConnector?: () => void; sourceDefinitions: SourceDefinitionReadWithLatestTag[]; hasSuccess?: boolean; error?: FormError | null; @@ -33,15 +30,8 @@ const hasSourceDefinitionId = (state: unknown): state is { sourceDefinitionId: s ); }; -export const SourceForm: React.FC = ({ - onSubmit, - sourceDefinitions, - error, - hasSuccess, - afterSelectConnector, -}) => { +export const SourceForm: React.FC = ({ onSubmit, sourceDefinitions, error, hasSuccess }) => { const location = useLocation(); - const analyticsService = useAnalyticsService(); const [sourceDefinitionId, setSourceDefinitionId] = useState( hasSourceDefinitionId(location.state) ? location.state.sourceDefinitionId : null @@ -55,18 +45,6 @@ export const SourceForm: React.FC = ({ const onDropDownSelect = (sourceDefinitionId: string) => { setSourceDefinitionId(sourceDefinitionId); - - const connector = sourceDefinitions.find((item) => item.sourceDefinitionId === sourceDefinitionId); - - if (afterSelectConnector) { - afterSelectConnector(); - } - - analyticsService.track(Namespace.SOURCE, Action.SELECT, { - actionDescription: "Source connector type selected", - connector_source: connector?.name, - connector_source_definition_id: sourceDefinitionId, - }); }; const onSubmitForm = (values: ServiceFormValues) => { diff --git a/airbyte-webapp/src/theme.ts b/airbyte-webapp/src/theme.ts index 6baa66c95ddf..9ab8a4885945 100644 --- a/airbyte-webapp/src/theme.ts +++ b/airbyte-webapp/src/theme.ts @@ -111,7 +111,6 @@ export const theme = { dangerColor: scss.red, dangerColor25: scss.red50, warningColor: scss.yellow, - warningBackgroundColor: scss.yellow100, lightDangerColor: scss.red50, dangerTransparentColor: scss.red50, successColor: scss.green, diff --git a/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.module.scss b/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.module.scss new file mode 100644 index 000000000000..9fd2925ea579 --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.module.scss @@ -0,0 +1,9 @@ +@use "../../../scss/variables" as vars; + +.cardForm { + padding: 22px 27px 23px 24px; +} + +.connectorSelectControl { + margin-bottom: vars.$spacing-xl; +} diff --git a/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx b/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx index bf39b6a41028..e18c7be085e6 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx @@ -4,15 +4,16 @@ import { FormattedMessage } from "react-intl"; import { JobItem } from "components/JobItem/JobItem"; import { Card } from "components/ui/Card"; -import { Action, Namespace } from "core/analytics"; -import { Connector, ConnectorT } from "core/domain/connector"; +import { Connector, ConnectorSpecification, ConnectorT } from "core/domain/connector"; import { SynchronousJobRead } from "core/request/AirbyteClient"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics"; import { useAdvancedModeSetting } from "hooks/services/useAdvancedModeSetting"; import { generateMessageFromError } from "utils/errorStatusMessage"; import { ServiceForm, ServiceFormProps, ServiceFormValues } from "views/Connector/ServiceForm"; +import { ConnectorServiceTypeControl } from "../ServiceForm/components/Controls/ConnectorServiceTypeControl"; +import styles from "./ConnectorCard.module.scss"; +import { useAnalyticsTrackFunctions } from "./useAnalyticsTrackFunctions"; import { useTestConnector } from "./useTestConnector"; type ConnectorCardProvidedProps = Omit< @@ -24,6 +25,7 @@ interface ConnectorCardBaseProps extends ConnectorCardProvidedProps { title?: React.ReactNode; full?: boolean; jobInfo?: SynchronousJobRead | null; + additionalSelectorComponent?: React.ReactNode; } interface ConnectorCardCreateProps extends ConnectorCardBaseProps { @@ -40,14 +42,17 @@ export const ConnectorCard: React.FC { const [saved, setSaved] = useState(false); const [errorStatusRequest, setErrorStatusRequest] = useState(null); - + const [isFormSubmitting, setIsFormSubmitting] = useState(false); const [advancedMode] = useAdvancedModeSetting(); const { testConnector, isTestConnectionInProgress, onStopTesting, error, reset } = useTestConnector(props); + const { trackTestConnectorFailure, trackTestConnectorSuccess, trackTestConnectorStarted } = + useAnalyticsTrackFunctions(props.formType); useEffect(() => { // Whenever the selected connector changed, reset the check connection call and other errors @@ -55,34 +60,19 @@ export const ConnectorCard: React.FC { setErrorStatusRequest(null); + setIsFormSubmitting(true); const connector = props.availableServices.find((item) => Connector.id(item) === values.serviceType); - const trackAction = (actionType: Action, actionDescription: string) => { - if (!connector) { - return; - } - - const namespace = props.formType === "source" ? Namespace.SOURCE : Namespace.DESTINATION; - - analyticsService.track(namespace, actionType, { - actionDescription, - connector: connector?.name, - connector_definition_id: Connector.id(connector), - }); - }; - const testConnectorWithTracking = async () => { - trackAction(Action.TEST, "Test a connector"); + trackTestConnectorStarted(connector); try { await testConnector(values); - trackAction(Action.SUCCESS, "Tested connector - success"); + trackTestConnectorSuccess(connector); } catch (e) { - trackAction(Action.FAILURE, "Tested connector - failure"); + trackTestConnectorFailure(connector); throw e; } }; @@ -93,26 +83,46 @@ export const ConnectorCard: React.FC - ) - } - /> - {/* Show the job log only if advanced mode is turned on or the actual job failed (not the check inside the job) */} - {job && (advancedMode || !job.succeeded) && } +
    +
    + +
    + {additionalSelectorComponent} +
    + ) + } + /> + {/* Show the job log only if advanced mode is turned on or the actual job failed (not the check inside the job) */} + {job && (advancedMode || !job.succeeded) && } +
    +
    ); }; diff --git a/airbyte-webapp/src/views/Connector/ConnectorCard/useAnalyticsTrackFunctions.tsx b/airbyte-webapp/src/views/Connector/ConnectorCard/useAnalyticsTrackFunctions.tsx new file mode 100644 index 000000000000..e5074bf93f6b --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ConnectorCard/useAnalyticsTrackFunctions.tsx @@ -0,0 +1,47 @@ +import { useCallback } from "react"; + +import { Action, Namespace } from "core/analytics"; +import { Connector, ConnectorDefinition } from "core/domain/connector"; +import { useAnalyticsService } from "hooks/services/Analytics"; + +export const useAnalyticsTrackFunctions = (connectorType: "source" | "destination") => { + const analytics = useAnalyticsService(); + + const namespaceType = connectorType === "source" ? Namespace.SOURCE : Namespace.DESTINATION; + + const trackAction = useCallback( + (connector: ConnectorDefinition | undefined, actionType: Action, actionDescription: string) => { + if (!connector) { + return; + } + analytics.track(namespaceType, actionType, { + actionDescription, + connector: connector?.name, + connector_definition_id: Connector.id(connector), + }); + }, + [analytics, namespaceType] + ); + + const trackTestConnectorStarted = useCallback( + (connector: ConnectorDefinition | undefined) => { + trackAction(connector, Action.TEST, "Test a connector"); + }, + [trackAction] + ); + + const trackTestConnectorSuccess = useCallback( + (connector: ConnectorDefinition | undefined) => { + trackAction(connector, Action.SUCCESS, "Tested connector - success"); + }, + [trackAction] + ); + + const trackTestConnectorFailure = useCallback( + (connector: ConnectorDefinition | undefined) => { + trackAction(connector, Action.FAILURE, "Tested connector - failure"); + }, + [trackAction] + ); + return { trackTestConnectorStarted, trackTestConnectorSuccess, trackTestConnectorFailure }; +}; diff --git a/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.module.scss b/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.module.scss new file mode 100644 index 000000000000..6a80fe3b575f --- /dev/null +++ b/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.module.scss @@ -0,0 +1,14 @@ +@use "../../../scss/variables" as vars; + +.modalBody { + overflow: unset; + min-width: 500px; +} + +.controlLabel { + margin-bottom: vars.$spacing-xl; +} + +.requestButton { + min-width: 105px; +} diff --git a/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.tsx b/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.tsx index b3320f88ea76..ce41bad36793 100644 --- a/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.tsx +++ b/airbyte-webapp/src/views/Connector/RequestConnectorModal/RequestConnectorModal.tsx @@ -1,29 +1,50 @@ -import React, { useState } from "react"; -import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; +import { Field, FieldProps, Form, Formik, FormikProps } from "formik"; +import React, { useRef, useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; +import * as yup from "yup"; -import { Modal } from "components/ui/Modal"; +import { ControlLabels } from "components/LabeledControl"; +import { Button } from "components/ui/Button"; +import { DropDown } from "components/ui/DropDown"; +import { Input } from "components/ui/Input"; +import { ModalBody, ModalFooter } from "components/ui/Modal"; import useRequestConnector from "hooks/services/useRequestConnector"; -import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; -import ConnectorForm from "./components/ConnectorForm"; +import styles from "./RequestConnectorModal.module.scss"; import { Values } from "./types"; interface RequestConnectorModalProps { onClose: () => void; connectorType: "source" | "destination"; - initialName?: string; + workspaceEmail?: string; + searchedConnectorName?: string; } -const Content = styled.div` - width: 492px; - padding: 22px 34px 36px 32px; -`; -const RequestConnectorModal: React.FC = ({ onClose, connectorType, initialName }) => { +const requestConnectorValidationSchema = yup.object().shape({ + connectorType: yup.string().required("form.empty.error"), + name: yup.string().required("form.empty.error"), + additionalInfo: yup.string(), + email: yup.string().email("form.email.error").required("form.empty.error"), +}); + +const RequestConnectorModal: React.FC = ({ + onClose, + connectorType, + searchedConnectorName, + workspaceEmail, +}) => { const [hasFeedback, setHasFeedback] = useState(false); + const formRef = useRef>(null); const { requestConnector } = useRequestConnector(); - const workspace = useCurrentWorkspace(); + + // since we heed to handle onSubmit outside the context + const handleSubmit = () => { + if (formRef.current) { + formRef.current.handleSubmit(); + } + }; + const onSubmit = (values: Values) => { requestConnector(values); setHasFeedback(true); @@ -34,22 +55,124 @@ const RequestConnectorModal: React.FC = ({ onClose, }, 2000); }; + const { formatMessage } = useIntl(); + const dropdownData = [ + { value: "source", label: }, + { + value: "destination", + label: , + }, + ]; + return ( - } onClose={onClose}> - - + + - - + validateOnBlur + validateOnChange + validationSchema={requestConnectorValidationSchema} + onSubmit={onSubmit} + innerRef={formRef} + > + {({ setFieldValue }) => ( +
    + + {({ field, meta }: FieldProps) => ( + } + message={!!meta.error && meta.touched && } + > + { + setFieldValue("connectorType", item.value); + }} + /> + + )} + + + {({ field, meta, form }: FieldProps) => ( + + ) : ( + + ) + } + > + + + )} + + + {({ field, meta }: FieldProps) => ( + } + message={} + > + + + )} + + {!workspaceEmail && ( + + {({ field, meta }: FieldProps) => ( + } + message={!!meta.error && meta.touched && } + > + + + )} + + )} + + )} +
    + + + + + + + ); }; diff --git a/airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.module.scss b/airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.module.scss deleted file mode 100644 index e12dbd63b60c..000000000000 --- a/airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.requestButton { - min-width: 105px; -} diff --git a/airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.tsx b/airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.tsx deleted file mode 100644 index 6f641a334b77..000000000000 --- a/airbyte-webapp/src/views/Connector/RequestConnectorModal/components/ConnectorForm.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { Field, FieldProps, Form, Formik } from "formik"; -import React from "react"; -import { FormattedMessage, useIntl } from "react-intl"; -import styled from "styled-components"; -import * as yup from "yup"; - -import { ControlLabels } from "components/LabeledControl"; -import { Button } from "components/ui/Button"; -import { DropDown } from "components/ui/DropDown"; -import { Input } from "components/ui/Input"; - -import { Values } from "../types"; -import styles from "./ConnectorForm.module.scss"; - -const Buttons = styled.div` - width: 100%; - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; - - & > button { - margin-left: 5px; - } -`; - -const ControlLabelsWithMargin = styled(ControlLabels)` - margin-bottom: 29px; -`; - -interface ConnectorFormProps { - onSubmit: (values: Values) => void; - onCancel: () => void; - currentValues?: Values; - hasFeedback?: boolean; -} - -const requestConnectorValidationSchema = yup.object().shape({ - connectorType: yup.string().required("form.empty.error"), - name: yup.string().required("form.empty.error"), - additionalInfo: yup.string(), - email: yup.string().email("form.email.error").required("form.empty.error"), -}); - -const ConnectorForm: React.FC = ({ onSubmit, onCancel, currentValues, hasFeedback }) => { - const { formatMessage } = useIntl(); - const dropdownData = [ - { value: "source", label: }, - { - value: "destination", - label: , - }, - ]; - - return ( - - {({ setFieldValue }) => ( -
    - - {({ field, meta }: FieldProps) => ( - } - message={!!meta.error && meta.touched && } - > - { - setFieldValue("connectorType", item.value); - }} - /> - - )} - - - {({ field, meta, form }: FieldProps) => ( - - ) : ( - - ) - } - > - - - )} - - - {({ field, meta }: FieldProps) => ( - } - message={} - > - - - )} - - {!currentValues?.email && ( - - {({ field, meta }: FieldProps) => ( - } - message={!!meta.error && meta.touched && } - > - - - )} - - )} - - - - - - )} -
    - ); -}; - -export default ConnectorForm; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.module.scss new file mode 100644 index 000000000000..462908584ee3 --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.module.scss @@ -0,0 +1,13 @@ +@use "scss/variables" as vars; + +.loaderContainer { + display: flex; + justify-content: center; + align-items: center; + padding: 22px 0 23px; +} + +.loadingMessage { + margin-top: vars.$spacing-md; + margin-left: vars.$spacing-lg; +} diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.tsx index 26c8f6c43e3d..450f092bd3e0 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.tsx @@ -1,35 +1,19 @@ import { Form, useFormikContext } from "formik"; import React from "react"; -import styled from "styled-components"; import { Spinner } from "components/ui/Spinner"; +import { ConnectorDefinitionSpecification } from "core/domain/connector"; import { FormBlock } from "core/form/types"; -import { ConnectorDefinitionSpecification } from "../../../core/domain/connector"; import CreateControls from "./components/CreateControls"; import EditControls from "./components/EditControls"; import { FormSection } from "./components/Sections/FormSection"; import ShowLoadingMessage from "./components/ShowLoadingMessage"; +import styles from "./FormRoot.module.scss"; import { useServiceForm } from "./serviceFormContext"; import { ServiceFormValues } from "./types"; -const FormContainer = styled(Form)` - padding: 22px 27px 23px 24px; -`; - -const LoaderContainer = styled.div` - display: flex; - justify-content: center; - align-items: center; - padding: 22px 0 23px; -`; - -const LoadingMessage = styled.div` - margin-top: 10px; - margin-left: 15px; -`; - interface FormRootProps { formFields: FormBlock; hasSuccess?: boolean; @@ -42,7 +26,7 @@ interface FormRootProps { selectedConnector: ConnectorDefinitionSpecification | undefined; } -const FormRoot: React.FC = ({ +export const FormRoot: React.FC = ({ isTestConnectionInProgress = false, onRetest, formFields, @@ -57,15 +41,15 @@ const FormRoot: React.FC = ({ const { resetServiceForm, isLoadingSchema, selectedService, isEditMode, formType } = useServiceForm(); return ( - +
    {isLoadingSchema && ( - +
    - +
    - - +
    +
    )} {isEditMode ? ( @@ -97,8 +81,6 @@ const FormRoot: React.FC = ({ /> ) )} -
    + ); }; - -export { FormRoot }; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx index e4f71bcd80b6..146c6f87bfbf 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.test.tsx @@ -177,11 +177,9 @@ describe("Service Form", () => { it("should display general components: submit button, name and serviceType fields", () => { const name = container.querySelector("input[name='name']"); - const serviceType = container.querySelector("div[data-testid='serviceType']"); const submit = container.querySelector("button[type='submit']"); expect(name).toBeInTheDocument(); - expect(serviceType).toBeInTheDocument(); expect(submit).toBeInTheDocument(); }); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx index 4bbedf5f7dd6..fa8251f1b293 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx @@ -1,30 +1,23 @@ import { Formik, getIn, setIn, useFormikContext } from "formik"; import { JSONSchema7 } from "json-schema"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { useDeepCompareEffect, useToggle } from "react-use"; +import React, { useCallback, useEffect, useMemo } from "react"; +import { useDeepCompareEffect } from "react-use"; import { FormChangeTracker } from "components/FormChangeTracker"; import { ConnectorDefinition, ConnectorDefinitionSpecification } from "core/domain/connector"; -import { isDestinationDefinition, isDestinationDefinitionSpecification } from "core/domain/connector/destination"; +import { isDestinationDefinitionSpecification } from "core/domain/connector/destination"; import { isSourceDefinition, isSourceDefinitionSpecification } from "core/domain/connector/source"; import { FormBaseItem, FormComponentOverrideProps } from "core/form/types"; import { CheckConnectionRead } from "core/request/AirbyteClient"; -import { useExperiment } from "hooks/services/Experiment"; import { useFormChangeTrackerService, useUniqueFormId } from "hooks/services/FormChangeTracker"; -import { useDestinationList } from "hooks/services/useDestinationHook"; -import { DestinationDefinitionReadWithLatestTag } from "services/connector/DestinationDefinitionService"; import { isDefined } from "utils/common"; -import RequestConnectorModal from "views/Connector/RequestConnectorModal"; import { useDocumentationPanelContext } from "../ConnectorDocumentationLayout/DocumentationPanelContext"; import { ConnectorNameControl } from "./components/Controls/ConnectorNameControl"; -import { ConnectorServiceTypeControl } from "./components/Controls/ConnectorServiceTypeControl"; -import { FrequentlyUsedDestinations } from "./components/FrequentlyUsedDestinations"; -import { StartWithDestination } from "./components/StartWithDestination/StartWithDestination"; import { FormRoot } from "./FormRoot"; import { ServiceFormContextProvider, useServiceForm } from "./serviceFormContext"; -import { DestinationConnectorCard, ServiceFormValues } from "./types"; +import { ServiceFormValues } from "./types"; import { useBuildForm, useBuildInitialSchema, @@ -121,10 +114,7 @@ export interface ServiceFormProps { formId?: string; availableServices: ConnectorDefinition[]; selectedConnectorDefinitionSpecification?: ConnectorDefinitionSpecification; - onServiceSelect?: ( - id: string, - trackParams?: { actionDescription: string; connector_destination_suggested: boolean } - ) => void; + onServiceSelect?: (id: string) => void; onSubmit: (values: ServiceFormValues) => Promise | void; isLoading?: boolean; isEditMode?: boolean; @@ -139,18 +129,10 @@ export interface ServiceFormProps { testConnector?: (v?: ServiceFormValues) => Promise; } -const ServiceForm: React.FC = (props) => { +export const ServiceForm: React.FC = (props) => { const formId = useUniqueFormId(props.formId); const { clearFormChange } = useFormChangeTrackerService(); - const { destinations } = useDestinationList(); - const frequentlyUsedDestinationIds = useExperiment("connector.frequentlyUsedDestinationIds", [ - "22f6c74f-5699-40ff-833c-4a879ea40133", - "424892c4-daac-4491-b35d-c6688ba547ba", - ]); - const startWithDestinationId = useExperiment("connector.startWithDestinationId", ""); - - const [isOpenRequestModal, toggleOpenRequestModal] = useToggle(false); - const [initialRequestName, setInitialRequestName] = useState(); + const { formType, formValues, @@ -162,7 +144,7 @@ const ServiceForm: React.FC = (props) => { testConnector, selectedConnectorDefinitionSpecification, availableServices, - onServiceSelect, + errorMessage, } = props; const specifications = useBuildInitialSchema(selectedConnectorDefinitionSpecification); @@ -187,6 +169,7 @@ const ServiceForm: React.FC = (props) => { const { formFields, initialValues } = useBuildForm(jsonSchema, formValues); const { setDocumentationUrl, setDocumentationPanelOpen } = useDocumentationPanelContext(); + useEffect(() => { if (!selectedConnectorDefinitionSpecification) { return; @@ -210,35 +193,6 @@ const ServiceForm: React.FC = (props) => { setDocumentationPanelOpen(true); }, [availableServices, selectedConnectorDefinitionSpecification, setDocumentationPanelOpen, setDocumentationUrl]); - const frequentlyUsedDestinations: DestinationConnectorCard[] = useMemo( - () => - availableServices - .filter( - (service): service is DestinationDefinitionReadWithLatestTag => - isDestinationDefinition(service) && frequentlyUsedDestinationIds.includes(service.destinationDefinitionId) - ) - .map(({ destinationDefinitionId, name, icon, releaseStage }) => ({ - destinationDefinitionId, - name, - icon, - releaseStage, - })), - [availableServices, frequentlyUsedDestinationIds] - ); - - const startWithDestination = useMemo(() => { - const destination = availableServices.find( - (service): service is DestinationDefinitionReadWithLatestTag => - isDestinationDefinition(service) && service.destinationDefinitionId === startWithDestinationId - ); - if (!destination) { - return undefined; - } - const { destinationDefinitionId, name, icon, releaseStage } = destination; - - return { destinationDefinitionId, name, icon, releaseStage }; - }, [availableServices, startWithDestinationId]); - const uiOverrides = useMemo(() => { return { name: { @@ -247,44 +201,14 @@ const ServiceForm: React.FC = (props) => { ), }, serviceType: { - component: ({ path }: FormBaseItem, componentProps: FormComponentOverrideProps) => { - return ( - <> - { - setInitialRequestName(name); - toggleOpenRequestModal(); - }} - {...componentProps} - /> - {!isEditMode && formType === "destination" && !selectedConnectorDefinitionSpecification && ( - - )} - - ); - }, + /* since we use outside formik form + we need to keep the serviceType field in formik, but hide it. + serviceType prop will be removed in further PR + */ + component: () => null, }, }; - }, [ - formType, - onServiceSelect, - availableServices, - isEditMode, - selectedConnectorDefinitionSpecification, - frequentlyUsedDestinations, - isLoading, - toggleOpenRequestModal, - ]); + }, [formType]); const { uiWidgetsInfo, setUiWidgetsInfo, resetUiWidgetsInfo } = useBuildUiWidgetsContext( formFields, @@ -329,43 +253,27 @@ const ServiceForm: React.FC = (props) => { resetUiWidgetsInfo={resetUiWidgetsInfo} formType={formType} selectedConnector={selectedConnectorDefinitionSpecification} - availableServices={props.availableServices} - isEditMode={props.isEditMode} - isLoadingSchema={props.isLoading} + availableServices={availableServices} + isEditMode={isEditMode} + isLoadingSchema={isLoading} validationSchema={validationSchema} > - {!props.isEditMode && } + {!isEditMode && } onStopTesting() : undefined} onRetest={testConnector ? async () => await testConnector() : undefined} formFields={formFields} selectedConnector={selectedConnectorDefinitionSpecification} /> - {formType === "destination" && - !destinations.length && - !isEditMode && - !isLoading && - !selectedConnectorDefinitionSpecification && ( - - )} - {isOpenRequestModal && ( - - )} )} ); }; - -export { ServiceForm }; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.module.scss new file mode 100644 index 000000000000..a8f3738b326e --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.module.scss @@ -0,0 +1,53 @@ +@use "../../../../../../scss/colors"; +@use "../../../../../../scss/variables" as vars; + +.connectorListFooter { + width: 100%; + padding: vars.$spacing-md; + border-top: vars.$border-thin solid colors.$grey-50; + min-height: 36px; + position: relative; + + .requestNewConnectorBtn { + display: flex; + flex-direction: row; + align-items: center; + gap: vars.$spacing-md; + border: none; + cursor: pointer; + background: colors.$white; + color: colors.$dark-blue; + + &:hover { + color: colors.$blue; + } + } +} + +.connectorName { + display: flex; + flex-direction: row; + align-items: center; + gap: vars.$spacing-md; +} + +.stageLabel { + padding: 2px 6px; + height: 14px; + background: colors.$grey-100; + color: colors.$dark-blue; + text-transform: uppercase; + border-radius: 25px; + font-weight: 500; + font-size: 8px; + line-height: 10px; +} + +.singleValueContent { + width: 100%; + padding-right: 38px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.tsx similarity index 52% rename from airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx rename to airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.tsx index 9b8c3ebd7e16..30ed53462fc5 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/ConnectorServiceTypeControl.tsx @@ -1,11 +1,10 @@ -import { useField } from "formik"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { useCallback, useEffect, useMemo } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { components } from "react-select"; import { MenuListProps } from "react-select"; -import styled from "styled-components"; -import { ConnectorIcon } from "components/ConnectorIcon"; import { GAIcon } from "components/icons/GAIcon"; import { ControlLabels } from "components/LabeledControl"; import { @@ -17,97 +16,37 @@ import { SingleValueProps, SingleValueView, } from "components/ui/DropDown"; +import { Text } from "components/ui/Text"; -import { Action, Namespace } from "core/analytics"; import { Connector, ConnectorDefinition } from "core/domain/connector"; import { ReleaseStage } from "core/request/AirbyteClient"; import { useAvailableConnectorDefinitions } from "hooks/domain/connector/useAvailableConnectorDefinitions"; -import { useAnalyticsService } from "hooks/services/Analytics"; import { useExperiment } from "hooks/services/Experiment"; +import { useModalService } from "hooks/services/Modal"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; -import { naturalComparator } from "utils/objects"; import { useDocumentationPanelContext } from "views/Connector/ConnectorDocumentationLayout/DocumentationPanelContext"; +import RequestConnectorModal from "views/Connector/RequestConnectorModal"; -import { WarningMessage } from "../WarningMessage"; - -const BottomElement = styled.div` - background: ${({ theme }) => theme.greyColor0}; - padding: 6px 16px 8px; - width: 100%; - min-height: 34px; - border-top: 1px solid ${({ theme }) => theme.greyColor20}; - position: relative; - - cursor: pointer; - - color: ${({ theme }) => theme.textColor}; - &:hover { - color: ${({ theme }) => theme.primaryColor}; - } -`; - -const Text = styled.div` - display: flex; - flex-direction: row; - align-items: center; -`; - -const Label = styled.div` - margin-left: 13px; - font-weight: 500; - font-size: 14px; - line-height: 17px; -`; - -const Stage = styled.div` - padding: 2px 6px; - height: 14px; - background: ${({ theme }) => theme.greyColor20}; - border-radius: 25px; - text-transform: uppercase; - font-weight: 500; - font-size: 8px; - line-height: 10px; - color: ${({ theme }) => theme.textColor}; -`; - -const SingleValueContent = styled(components.SingleValue)` - width: 100%; - padding-right: 38px; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; -`; +import { WarningMessage } from "../../WarningMessage"; +import styles from "./ConnectorServiceTypeControl.module.scss"; +import { useAnalyticsTrackFunctions } from "./useAnalyticsTrackFunctions"; +import { getSortedDropdownDataUsingExperiment } from "./utils"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type MenuWithRequestButtonProps = MenuListProps & { selectProps: any }; -/** - * Returns the order for a specific release stage label. This will define - * in what order the different release stages are shown inside the select. - * They will be shown in an increasing order (i.e. 0 on top), unless not overwritten - * by ORDER_OVERWRITE above. - */ -function getOrderForReleaseStage(stage?: ReleaseStage): number { - switch (stage) { - case ReleaseStage.beta: - return 1; - case ReleaseStage.alpha: - return 2; - default: - return 0; - } -} - const ConnectorList: React.FC> = ({ children, ...props }) => ( <> {children} - props.selectProps.selectProps.onOpenRequestConnectorModal(props.selectProps.inputValue)} - > - - +
    + +
    ); @@ -121,9 +60,9 @@ const StageLabel: React.FC<{ releaseStage?: ReleaseStage }> = ({ releaseStage }) } return ( - +
    - +
    ); }; @@ -136,10 +75,10 @@ const Option: React.FC = (props) => { isDisabled={props.isDisabled} isFocused={props.isFocused} > - +
    {props.data.img || null} - - + {props.label} +
    @@ -152,102 +91,89 @@ const SingleValue: React.FC> = (props) => { {props.data.img && {props.data.img}}
    - + {props.data.label} - +
    ); }; interface ConnectorServiceTypeControlProps { - propertyPath: string; formType: "source" | "destination"; availableServices: ConnectorDefinition[]; isEditMode?: boolean; documentationUrl?: string; onChangeServiceType?: (id: string) => void; - onOpenRequestConnectorModal: (initialName: string) => void; disabled?: boolean; + selectedServiceId?: string; } const ConnectorServiceTypeControl: React.FC = ({ - propertyPath, formType, isEditMode, onChangeServiceType, availableServices, documentationUrl, - onOpenRequestConnectorModal, disabled, + selectedServiceId, }) => { const { formatMessage } = useIntl(); - const orderOverwrite = useExperiment("connector.orderOverwrite", {}); - const [field, fieldMeta, { setValue }] = useField(propertyPath); - const analytics = useAnalyticsService(); + const { openModal, closeModal } = useModalService(); + const { trackMenuOpen, trackNoOptionMessage, trackConnectorSelection } = useAnalyticsTrackFunctions(formType); + const workspace = useCurrentWorkspace(); + const orderOverwrite = useExperiment("connector.orderOverwrite", {}); const availableConnectorDefinitions = useAvailableConnectorDefinitions(availableServices, workspace); const sortedDropDownData = useMemo( - () => - availableConnectorDefinitions - .map((item) => ({ - label: item.name, - value: Connector.id(item), - img: , - releaseStage: item.releaseStage, - })) - .sort((a, b) => { - const priorityA = orderOverwrite[a.value] ?? 0; - const priorityB = orderOverwrite[b.value] ?? 0; - // If they have different priority use the higher priority first, otherwise use the label - if (priorityA !== priorityB) { - return priorityB - priorityA; - } else if (a.releaseStage !== b.releaseStage) { - return getOrderForReleaseStage(a.releaseStage) - getOrderForReleaseStage(b.releaseStage); - } - return naturalComparator(a.label, b.label); - }), + () => getSortedDropdownDataUsingExperiment(availableConnectorDefinitions, orderOverwrite), [availableConnectorDefinitions, orderOverwrite] ); const { setDocumentationUrl } = useDocumentationPanelContext(); - useEffect(() => setDocumentationUrl(documentationUrl ?? ""), [documentationUrl, setDocumentationUrl]); const getNoOptionsMessage = useCallback( ({ inputValue }: { inputValue: string }) => { - analytics.track(formType === "source" ? Namespace.SOURCE : Namespace.DESTINATION, Action.NO_MATCHING_CONNECTOR, { - actionDescription: "Connector query without results", - query: inputValue, - }); + trackNoOptionMessage(inputValue); return formatMessage({ id: "form.noConnectorFound" }); }, - [analytics, formType, formatMessage] + [formatMessage, trackNoOptionMessage] ); const selectedService = React.useMemo( - () => availableServices.find((s) => Connector.id(s) === field.value), - [field.value, availableServices] + () => availableServices.find((s) => Connector.id(s) === selectedServiceId), + [selectedServiceId, availableServices] ); const handleSelect = useCallback( (item: DropDownOptionDataItem | null) => { - if (item) { - setValue(item.value); - if (onChangeServiceType) { - onChangeServiceType(item.value); - } + if (item && onChangeServiceType) { + onChangeServiceType(item.value); + trackConnectorSelection(item.value, item.label || ""); } }, - [setValue, onChangeServiceType] + [onChangeServiceType, trackConnectorSelection] ); - const onMenuOpen = () => { - analytics.track(formType === "source" ? Namespace.SOURCE : Namespace.DESTINATION, Action.SELECTION_OPENED, { - actionDescription: "Opened connector type selection", - }); - }; + const selectProps = useMemo( + () => ({ + onOpenRequestConnectorModal: (input: string) => + openModal({ + title: formatMessage({ id: "connector.requestConnector" }), + content: () => ( + + ), + }), + }), + [closeModal, formType, formatMessage, openModal, workspace.email] + ); return ( <> @@ -257,14 +183,13 @@ const ConnectorServiceTypeControl: React.FC = })} > = })} options={sortedDropDownData} onChange={handleSelect} - onMenuOpen={onMenuOpen} + onMenuOpen={() => trackMenuOpen()} noOptionsMessage={getNoOptionsMessage} + data-testid="serviceType" /> {selectedService && diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/index.ts b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/index.ts new file mode 100644 index 000000000000..d53f65330a06 --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/index.ts @@ -0,0 +1 @@ +export { ConnectorServiceTypeControl } from "./ConnectorServiceTypeControl"; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/useAnalyticsTrackFunctions.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/useAnalyticsTrackFunctions.tsx new file mode 100644 index 000000000000..9f8e12429c1d --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/useAnalyticsTrackFunctions.tsx @@ -0,0 +1,40 @@ +import { capitalize } from "lodash"; +import { useCallback } from "react"; + +import { Action, Namespace } from "core/analytics"; +import { useAnalyticsService } from "hooks/services/Analytics"; + +export const useAnalyticsTrackFunctions = (connectorType: "source" | "destination") => { + const analytics = useAnalyticsService(); + + const namespaceType = connectorType === "source" ? Namespace.SOURCE : Namespace.DESTINATION; + + const trackMenuOpen = useCallback(() => { + analytics.track(namespaceType, Action.SELECTION_OPENED, { + actionDescription: "Opened connector type selection", + }); + }, [analytics, namespaceType]); + + const trackNoOptionMessage = useCallback( + (inputValue: string) => { + analytics.track(namespaceType, Action.NO_MATCHING_CONNECTOR, { + actionDescription: "Connector query without results", + query: inputValue, + }); + }, + [analytics, namespaceType] + ); + + const trackConnectorSelection = useCallback( + (connectorId: string, connectorName: string) => { + analytics.track(namespaceType, Action.SELECT, { + actionDescription: `${capitalize(connectorType)} connector type selected`, + [`connector_${connectorType}`]: connectorName, + [`connector_${connectorType}_definition_id`]: connectorId, + }); + }, + [analytics, connectorType, namespaceType] + ); + + return { trackMenuOpen, trackNoOptionMessage, trackConnectorSelection }; +}; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx new file mode 100644 index 000000000000..8da9b212581c --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx @@ -0,0 +1,58 @@ +import { ConnectorIcon } from "components/ConnectorIcon"; + +import { Connector, ConnectorDefinition } from "core/domain/connector"; +import { ReleaseStage } from "core/request/AirbyteClient"; +import { naturalComparator } from "utils/objects"; + +/** + * Returns the order for a specific release stage label. This will define + * in what order the different release stages are shown inside the select. + * They will be shown in an increasing order (i.e. 0 on top), unless not overwritten + * by ORDER_OVERWRITE above. + */ +const getOrderForReleaseStage = (stage?: ReleaseStage): number => { + switch (stage) { + case ReleaseStage.beta: + return 1; + case ReleaseStage.alpha: + return 2; + default: + return 0; + } +}; +interface ServiceDropdownOption { + label: string; + value: string; + img: JSX.Element; + releaseStage: ReleaseStage | undefined; +} +const transformConnectorDefinitionToDropdownOption = (item: ConnectorDefinition): ServiceDropdownOption => ({ + label: item.name, + value: Connector.id(item), + img: , + releaseStage: item.releaseStage, +}); + +const sortUsingOrderOverwrite = + (orderOverwrite: Record) => (a: ServiceDropdownOption, b: ServiceDropdownOption) => { + const priorityA = orderOverwrite[a.value] ?? 0; + const priorityB = orderOverwrite[b.value] ?? 0; + // If they have different priority use the higher priority first, otherwise use the label + if (priorityA !== priorityB) { + return priorityB - priorityA; + } else if (a.releaseStage !== b.releaseStage) { + return getOrderForReleaseStage(a.releaseStage) - getOrderForReleaseStage(b.releaseStage); + } + return naturalComparator(a.label, b.label); + }; + +/* + Returns sorted ServiceDropdownOption[] using overwritten experiment order + */ +export const getSortedDropdownDataUsingExperiment = ( + availableConnectorDefinitions: ConnectorDefinition[], + orderOverwrite: Record +) => + availableConnectorDefinitions + .map(transformConnectorDefinitionToDropdownOption) + .sort(sortUsingOrderOverwrite(orderOverwrite)); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/CreateControls.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/CreateControls.tsx index d0f60d62834b..be4a6267d4eb 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/CreateControls.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/CreateControls.tsx @@ -6,7 +6,7 @@ import { Button } from "components/ui/Button"; import styles from "./CreateControls.module.scss"; import { TestingConnectionError, FetchingConnectorError } from "./TestingConnectionError"; -import TestingConnectionSpinner from "./TestingConnectionSpinner"; +import { TestingConnectionSpinner } from "./TestingConnectionSpinner"; import TestingConnectionSuccess from "./TestingConnectionSuccess"; interface CreateControlProps { diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/EditControls.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/EditControls.tsx index 642a16f32a4e..d3506d0a31db 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/EditControls.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/EditControls.tsx @@ -7,7 +7,7 @@ import { Button } from "components/ui/Button"; import { useServiceForm } from "../serviceFormContext"; import styles from "./EditControls.module.scss"; import { TestingConnectionError } from "./TestingConnectionError"; -import TestingConnectionSpinner from "./TestingConnectionSpinner"; +import { TestingConnectionSpinner } from "./TestingConnectionSpinner"; import TestingConnectionSuccess from "./TestingConnectionSuccess"; const Controls = styled.div` diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.tsx index 07cf7b352ebf..46b0cf3d9c8c 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.tsx @@ -1,58 +1,54 @@ -import { useField } from "formik"; -import React from "react"; -import { useIntl } from "react-intl"; +import React, { useMemo } from "react"; -import { ConnectorCard } from "components"; -import { SlickSlider } from "components/ui/SlickSlider"; -import { Spinner } from "components/ui/Spinner"; +import { ConnectorDefinition } from "core/domain/connector"; +import { isDestinationDefinition } from "core/domain/connector/destination"; +import { useExperiment } from "hooks/services/Experiment"; +import { DestinationDefinitionReadWithLatestTag } from "services/connector/DestinationDefinitionService"; import { DestinationConnectorCard } from "../../types"; -import styles from "./FrequentlyUsedDestinations.module.scss"; +import { FrequentlyUsedDestinationsCard } from "./FrequentlyUsedDestinationsCard"; +import { useAnalyticsTrackFunctions } from "./useAnalyticsTrackFunctions"; -export interface FrequentlyUsedDestinationsProps { - propertyPath: string; - destinations: DestinationConnectorCard[]; - onDestinationSelect?: ( - id: string, - trackParams?: { actionDescription: string; connector_destination_suggested: boolean } - ) => void; - isLoading?: boolean; +interface FrequentlyUsedDestinationsProps { + availableServices: ConnectorDefinition[]; + onDestinationSelect: (id: string) => void; } export const FrequentlyUsedDestinations: React.FC = ({ - propertyPath, - destinations, + availableServices, onDestinationSelect, - isLoading, }) => { - const [, , { setValue }] = useField(propertyPath); - const { formatMessage } = useIntl(); + const frequentlyUsedDestinationIds = useExperiment("connector.frequentlyUsedDestinationIds", [ + "22f6c74f-5699-40ff-833c-4a879ea40133", + "424892c4-daac-4491-b35d-c6688ba547ba", + ]); + const { trackSelectedSuggestedDestination } = useAnalyticsTrackFunctions(); - if (!destinations?.length) { - return null; - } - const onSlideClick = (id: string) => { - setValue(id); - onDestinationSelect?.(id, { - actionDescription: "Suggested destination connector type selected", - connector_destination_suggested: true, - }); + const frequentlyUsedDestinations: DestinationConnectorCard[] = useMemo( + () => + availableServices + .filter( + (service): service is DestinationDefinitionReadWithLatestTag => + isDestinationDefinition(service) && frequentlyUsedDestinationIds.includes(service.destinationDefinitionId) + ) + .map(({ destinationDefinitionId, name, icon, releaseStage }) => ({ + destinationDefinitionId, + name, + icon, + releaseStage, + })), + [availableServices, frequentlyUsedDestinationIds] + ); + + const onDestinationCardClick = (id: string, connectorName: string) => { + onDestinationSelect(id); + trackSelectedSuggestedDestination(id, connectorName); }; + return ( -
    - {isLoading ? ( -
    - -
    - ) : ( - - {destinations.map(({ destinationDefinitionId, name, icon, releaseStage }, index) => ( - - ))} - - )} -
    + ); }; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.module.scss similarity index 76% rename from airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.module.scss rename to airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.module.scss index 9e8f43347fb6..a3c25af364df 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.module.scss +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.module.scss @@ -2,7 +2,6 @@ @use "../../../../../scss/variables"; .container { - padding-top: variables.$spacing-xl; border-radius: variables.$border-radius-sm; overflow: hidden; } @@ -19,9 +18,3 @@ background-color: colors.$grey-100; } } - -.spinnerContainer { - display: flex; - align-items: center; - justify-content: center; -} diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.test.tsx similarity index 62% rename from airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx rename to airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.test.tsx index c79973155b48..448acc3a61da 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.test.tsx @@ -1,19 +1,16 @@ import { fireEvent, render, waitFor } from "@testing-library/react"; -import { Formik } from "formik"; import { IntlProvider } from "react-intl"; import en from "locales/en.json"; import { mockData } from "../../../../../test-utils/mock-data/mockFrequentlyUsedDestinations"; -import { FrequentlyUsedDestinations, FrequentlyUsedDestinationsProps } from "./FrequentlyUsedDestinations"; +import { FrequentlyUsedDestinationsCard, FrequentlyUsedDestinationsCardProps } from "./FrequentlyUsedDestinationsCard"; -const renderFrequentlyUsedDestinationsComponent = (props: FrequentlyUsedDestinationsProps) => +const renderFrequentlyUsedDestinationsComponent = (props: FrequentlyUsedDestinationsCardProps) => render( - - - - - + + + ); describe("", () => { @@ -21,7 +18,6 @@ describe("", () => { const component = renderFrequentlyUsedDestinationsComponent({ destinations: mockData, onDestinationSelect: jest.fn(), - propertyPath: "serviceType", }); expect(component).toMatchSnapshot(); @@ -32,16 +28,12 @@ describe("", () => { const { getByText } = renderFrequentlyUsedDestinationsComponent({ destinations: mockData, onDestinationSelect: handler, - propertyPath: "serviceType", }); fireEvent.click(getByText("BigQuery")); await waitFor(() => { expect(handler).toHaveBeenCalledTimes(1); - expect(handler).toHaveBeenCalledWith("2", { - actionDescription: "Suggested destination connector type selected", - connector_destination_suggested: true, - }); + expect(handler).toHaveBeenCalledWith("2", "BigQuery"); }); }); }); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.tsx new file mode 100644 index 000000000000..98e9287084cb --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinationsCard.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import { ConnectorCard } from "components"; +import { SlickSlider } from "components/ui/SlickSlider"; + +import { DestinationConnectorCard } from "../../types"; +import styles from "./FrequentlyUsedDestinationsCard.module.scss"; + +export interface FrequentlyUsedDestinationsCardProps { + destinations: DestinationConnectorCard[]; + onDestinationSelect: (id: string, connectorName: string) => void; +} + +export const FrequentlyUsedDestinationsCard: React.FC = ({ + destinations, + onDestinationSelect, +}) => { + const { formatMessage } = useIntl(); + + if (!destinations?.length) { + return null; + } + + return ( +
    + + {destinations.map(({ destinationDefinitionId, name, icon, releaseStage }, index) => ( + + ))} + +
    + ); +}; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinations.test.tsx.snap b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinationsCard.test.tsx.snap similarity index 100% rename from airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinations.test.tsx.snap rename to airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinationsCard.test.tsx.snap diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/index.stories.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/index.stories.tsx index 5bf3c9c63f06..d9f6de88de21 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/index.stories.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/index.stories.tsx @@ -1,27 +1,19 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; -import { Formik } from "formik"; import { mockData } from "../../../../../test-utils/mock-data/mockFrequentlyUsedDestinations"; -import { FrequentlyUsedDestinations } from "./FrequentlyUsedDestinations"; +import { FrequentlyUsedDestinationsCard } from "./FrequentlyUsedDestinationsCard"; export default { title: "Views/FrequentlyUsedDestinations", - component: FrequentlyUsedDestinations, + component: FrequentlyUsedDestinationsCard, args: { destinations: mockData, propertyPath: "serviceType", }, -} as ComponentMeta; +} as ComponentMeta; -export const Template: ComponentStory = (args) => ( - { - return undefined; - }} - > -
    - -
    -
    +export const Template: ComponentStory = (args) => ( +
    + +
    ); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/useAnalyticsTrackFunctions.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/useAnalyticsTrackFunctions.tsx new file mode 100644 index 000000000000..7667b1fb9ddc --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/useAnalyticsTrackFunctions.tsx @@ -0,0 +1,21 @@ +import { useCallback } from "react"; + +import { Action, Namespace } from "core/analytics"; +import { useAnalyticsService } from "hooks/services/Analytics"; + +export const useAnalyticsTrackFunctions = () => { + const analytics = useAnalyticsService(); + + const trackSelectedSuggestedDestination = useCallback( + (destinationDefinitionId: string, connectorName: string) => { + analytics.track(Namespace.DESTINATION, Action.SELECT, { + actionDescription: "Suggested destination connector type selected", + connector_destination: connectorName, + connector_destination_definition_id: destinationDefinitionId, + connector_destination_suggested: true, + }); + }, + [analytics] + ); + return { trackSelectedSuggestedDestination }; +}; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/SectionContainer.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/SectionContainer.module.scss index 8df0d0cad13f..c16cc64fa569 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/SectionContainer.module.scss +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/SectionContainer.module.scss @@ -2,4 +2,8 @@ .container { margin-bottom: variables.$spacing-xl; + + &:empty { + margin-bottom: 0; + } } diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.tsx index bd1af75aa276..2921a49a0fe4 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.tsx @@ -1,48 +1,36 @@ -import { useField } from "formik"; -import React from "react"; -import { useIntl } from "react-intl"; +import React, { useMemo } from "react"; -import { ConnectorCard } from "components/ConnectorCard"; -import { Card } from "components/ui/Card"; +import { ConnectorDefinition } from "core/domain/connector"; +import { isDestinationDefinition } from "core/domain/connector/destination"; +import { useExperiment } from "hooks/services/Experiment"; +import { DestinationDefinitionReadWithLatestTag } from "services/connector/DestinationDefinitionService"; import { DestinationConnectorCard } from "../../types"; -import styles from "./StartWithDestination.module.scss"; +import { StartWithDestinationCard } from "./StartWithDestinationCard"; -export interface StartWithDestinationProps { - destination: DestinationConnectorCard | undefined; - onDestinationSelect?: (id: string) => void; +interface StartWithDestinationProps { + onDestinationSelect: (id: string) => void; + availableServices: ConnectorDefinition[]; } -export const StartWithDestination: React.FC = ({ destination, onDestinationSelect }) => { - // since we will use the component just in one place we can hardcode the useField() - const [, , { setValue }] = useField("serviceType"); - const { formatMessage } = useIntl(); +export const StartWithDestination: React.FC = ({ + onDestinationSelect, + availableServices, +}) => { + const startWithDestinationId = useExperiment("connector.startWithDestinationId", ""); - if (!destination) { - return null; - } - const { icon, releaseStage, name, destinationDefinitionId } = destination; + const startWithDestination = useMemo(() => { + const destination = availableServices.find( + (service): service is DestinationDefinitionReadWithLatestTag => + isDestinationDefinition(service) && service.destinationDefinitionId === startWithDestinationId + ); + if (!destination) { + return undefined; + } + const { destinationDefinitionId, name, icon, releaseStage } = destination; - const connectorCardClickHandler = () => { - setValue(destinationDefinitionId); - onDestinationSelect?.(destinationDefinitionId); - }; + return { destinationDefinitionId, name, icon, releaseStage }; + }, [availableServices, startWithDestinationId]); - return ( -
    - -
    - ); + return ; }; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.module.scss similarity index 50% rename from airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.module.scss rename to airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.module.scss index 7153de10ede1..d59a5905e63a 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.module.scss +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.module.scss @@ -1,25 +1,15 @@ @use "../../../../../scss/colors"; @use "../../../../../scss/variables" as vars; -.container { - background-color: colors.$white; - margin-top: -#{vars.$spacing-2xl}; - border-top: vars.$border-thin solid colors.$grey-100; -} - .button { cursor: pointer; border: none; padding: 0; margin: 0; width: 100%; + border-radius: 10px; } .connectorCardWrapper { padding: vars.$spacing-xl 35px; } - -.connectorCard { - border-top-left-radius: 0; - border-top-right-radius: 0; -} diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.test.tsx similarity index 73% rename from airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx rename to airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.test.tsx index 1985f8eba272..edc18166c26d 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestination.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.test.tsx @@ -1,22 +1,19 @@ import { fireEvent, render, waitFor } from "@testing-library/react"; -import { Formik } from "formik"; import { IntlProvider } from "react-intl"; import en from "locales/en.json"; import { mockData } from "../../../../../test-utils/mock-data/mockStartWithDestination"; -import { StartWithDestination, StartWithDestinationProps } from "./StartWithDestination"; +import { StartWithDestinationCard, StartWithDestinationProps } from "./StartWithDestinationCard"; const renderStartWithDestination = (props: StartWithDestinationProps) => render( - - - - - + + + ); -describe("", () => { +describe("", () => { it("should renders without crash with provided props", () => { const component = renderStartWithDestination({ destination: mockData, onDestinationSelect: jest.fn() }); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.tsx new file mode 100644 index 000000000000..fa48d10a703d --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/StartWithDestinationCard.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { useIntl } from "react-intl"; + +import { ConnectorCard } from "components"; +import { Card } from "components/ui/Card"; + +import { DestinationConnectorCard } from "../../types"; +import styles from "./StartWithDestinationCard.module.scss"; + +export interface StartWithDestinationProps { + destination: DestinationConnectorCard | undefined; + onDestinationSelect: (id: string) => void; +} + +export const StartWithDestinationCard: React.FC = ({ destination, onDestinationSelect }) => { + const { formatMessage } = useIntl(); + + if (!destination) { + return null; + } + const { icon, releaseStage, name, destinationDefinitionId } = destination; + + const connectorCardClickHandler = () => { + onDestinationSelect(destinationDefinitionId); + }; + + return ( + + ); +}; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap deleted file mode 100644 index 90c99f117a07..000000000000 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestination.test.tsx.snap +++ /dev/null @@ -1,65 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[` should renders without crash with provided props 1`] = ` - -
    -
    - -
    -
    - -`; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestinationCard.test.tsx.snap b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestinationCard.test.tsx.snap new file mode 100644 index 000000000000..7ed2232c68c7 --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/__snapshots__/StartWithDestinationCard.test.tsx.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should renders without crash with provided props 1`] = ` + +
    + +
    + +`; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.stories.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.stories.tsx index 009733356043..7b4bb0167638 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.stories.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.stories.tsx @@ -1,29 +1,21 @@ import { ComponentMeta, ComponentStory } from "@storybook/react"; -import { Formik } from "formik"; import { mockData } from "../../../../../test-utils/mock-data/mockStartWithDestination"; -import { StartWithDestination } from "./StartWithDestination"; +import { StartWithDestinationCard } from "./StartWithDestinationCard"; export default { title: "Views/StartWithDestination", - component: StartWithDestination, + component: StartWithDestinationCard, args: { destination: mockData, onDestinationSelect: () => { return undefined; }, }, -} as ComponentMeta; +} as ComponentMeta; -export const Template: ComponentStory = (args) => ( - { - return undefined; - }} - > -
    - -
    -
    +export const Template: ComponentStory = (args) => ( +
    + +
    ); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.ts b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.ts new file mode 100644 index 000000000000..1db176b3b1a6 --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/StartWithDestination/index.ts @@ -0,0 +1 @@ +export { StartWithDestination } from "./StartWithDestination"; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.module.scss index 99e0b592c91e..c6230db871ec 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.module.scss +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.module.scss @@ -1,3 +1,12 @@ -.styledButton { - margin-left: auto; +@use "../../../../scss/variables" as vars; + +.container { + margin: 34px 0 vars.$spacing-md; + display: flex; + align-items: center; + justify-content: center; +} + +.button { + margin-left: vars.$spacing-md; } diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.tsx index 634d30f1952b..d1811a7aba80 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSpinner.tsx @@ -1,19 +1,11 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; import { Button } from "components/ui/Button"; import { ProgressBar } from "components/ui/ProgressBar"; import styles from "./TestingConnectionSpinner.module.scss"; -const LoadingContainer = styled.div` - margin: 34px 0 9px; - display: flex; - align-items: center; - justify-content: center; -`; - // Progress Bar runs 2min for checking connections const PROGRESS_BAR_TIME = 60 * 2; @@ -22,22 +14,16 @@ interface TestingConnectionSpinnerProps { onCancelTesting?: () => void; } -const TestingConnectionSpinner: React.FC = (props) => { - return ( - - - {props.isCancellable && ( - - )} - - ); -}; - -export default TestingConnectionSpinner; +export const TestingConnectionSpinner: React.FC = ({ + isCancellable, + onCancelTesting, +}) => ( +
    + + {isCancellable && ( + + )} +
    +); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.module.scss new file mode 100644 index 000000000000..20f4e7cbddfb --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.module.scss @@ -0,0 +1,19 @@ +@use "../../../../scss/colors"; +@use "../../../../scss/variables" as vars; + +.container { + padding: vars.$spacing-md vars.$spacing-lg; + background: colors.$yellow-100; + border-radius: vars.$border-radius-md; + white-space: break-spaces; + margin-top: vars.$spacing-lg; +} + +.link { + color: colors.$dark-blue; + + &:hover, + &:focus { + color: colors.$blue-400; + } +} diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.tsx index 0653e1f9397b..fdadb8598cd2 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/WarningMessage.tsx @@ -1,48 +1,33 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; + +import { Text } from "components/ui/Text"; import { ReleaseStage } from "core/request/AirbyteClient"; import { links } from "utils/links"; -const Content = styled.div` - padding: 13px 16px; - background: ${({ theme }) => theme.warningBackgroundColor}; - border-radius: 8px; - font-size: 12px; - white-space: break-spaces; - margin-top: 16px; -`; - -const Link = styled.a` - color: ${({ theme }) => theme.darkPrimaryColor}; - - &:hover, - &:focus { - color: ${({ theme }) => theme.darkPrimaryColor60}; - } -`; +import styles from "./WarningMessage.module.scss"; interface WarningMessageProps { stage: typeof ReleaseStage.alpha | typeof ReleaseStage.beta; } -const WarningMessage: React.FC = ({ stage }) => { +export const WarningMessage: React.FC = ({ stage }) => { return ( - - {" "} - ( - - {node} - - ), - }} - /> - +
    + + {" "} + ( + + {node} + + ), + }} + /> + +
    ); }; - -export { WarningMessage }; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/index.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/index.tsx index d243baabb640..e14fe928e3bd 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/index.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/index.tsx @@ -1,2 +1,5 @@ export * from "./types"; export * from "./ServiceForm"; + +export { StartWithDestination } from "./components/StartWithDestination"; +export { FrequentlyUsedDestinations } from "./components/FrequentlyUsedDestinations"; From 3f2af7d8e1a544e0162352b2235fccf18359d079 Mon Sep 17 00:00:00 2001 From: Amin Date: Mon, 17 Oct 2022 05:37:47 -0700 Subject: [PATCH 131/498] Update 4-reading-data.md (#18037) --- .../config-based/tutorial/4-reading-data.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/connector-development/config-based/tutorial/4-reading-data.md b/docs/connector-development/config-based/tutorial/4-reading-data.md index 08feb35f3140..a39a8071f0ca 100644 --- a/docs/connector-development/config-based/tutorial/4-reading-data.md +++ b/docs/connector-development/config-based/tutorial/4-reading-data.md @@ -1,7 +1,7 @@ # Step 4: Reading data Now that we're able to authenticate to the source API, we'll want to select data from the HTTP responses. -Let's first add the stream to the configured catalog in `source-exchange_rates-tutorial/integration_tests/configured_catalog.json` +Let's first add the stream to the configured catalog in `source-exchange-rates-tutorial/integration_tests/configured_catalog.json` ```json { @@ -67,4 +67,4 @@ Next, we'll [enhance the connector to read data for a given date, which will ena ## More readings - [Record selector](../understanding-the-yaml-file/record-selector.md) -- [Catalog guide](https://docs.airbyte.io/understanding-airbyte/beginners-guide-to-catalog) \ No newline at end of file +- [Catalog guide](https://docs.airbyte.io/understanding-airbyte/beginners-guide-to-catalog) From df72bbdf2ced6e476ece9aa619b0486c559b8465 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Mon, 17 Oct 2022 05:47:56 -0700 Subject: [PATCH 132/498] Low-code: Pass stream_slice to read_records when reading from CheckStream (#17804) * Implement a test * Implement fix * rename * extract method * bump --- airbyte-cdk/python/CHANGELOG.md | 4 ++++ .../sources/declarative/checks/check_stream.py | 14 +++++++++++++- airbyte-cdk/python/setup.py | 2 +- .../declarative/checks/test_check_stream.py | 18 ++++++++++++------ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index c9832fb8fc90..2fbb4f21c01a 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.100 + +- Low-code: Pass stream_slice to read_records when reading from CheckStream + ## 0.1.99 - Low-code: Fix default stream schema loader diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/checks/check_stream.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/checks/check_stream.py index 3350a57fa3c7..5a6f7c9552ee 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/checks/check_stream.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/checks/check_stream.py @@ -36,10 +36,22 @@ def check_connection(self, source: Source, logger: logging.Logger, config: Mappi if stream_name in stream_name_to_stream.keys(): stream = stream_name_to_stream[stream_name] try: - records = stream.read_records(sync_mode=SyncMode.full_refresh) + # Some streams need a stream slice to read records (eg if they have a SubstreamSlicer) + stream_slice = self._get_stream_slice(stream) + records = stream.read_records(sync_mode=SyncMode.full_refresh, stream_slice=stream_slice) next(records) except Exception as error: return False, f"Unable to connect to stream {stream_name} - {error}" else: raise ValueError(f"{stream_name} is not part of the catalog. Expected one of {stream_name_to_stream.keys()}") return True, None + + def _get_stream_slice(self, stream): + slices = stream.stream_slices( + cursor_field=stream.cursor_field, + sync_mode=SyncMode.full_refresh, + ) + try: + return next(slices) + except StopIteration: + return {} diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index c13ab4ce12ff..c3f205f35843 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.99", + version="0.1.100", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/checks/test_check_stream.py b/airbyte-cdk/python/unit_tests/sources/declarative/checks/test_check_stream.py index 827b99ab6484..30fe0ea32855 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/checks/test_check_stream.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/checks/test_check_stream.py @@ -15,17 +15,19 @@ @pytest.mark.parametrize( - "test_name, record, streams_to_check, expectation", + "test_name, record, streams_to_check, stream_slice, expectation", [ - ("test success check", record, stream_names, (True, None)), - ("test fail check", None, stream_names, (True, None)), - ("test try to check invalid stream", record, ["invalid_stream_name"], None), + ("test_success_check", record, stream_names, {}, (True, None)), + ("test_success_check_stream_slice", record, stream_names, {"slice": "slice_value"}, (True, None)), + ("test_fail_check", None, stream_names, {}, (True, None)), + ("test_try_to_check_invalid stream", record, ["invalid_stream_name"], {}, None), ], ) -def test_check_stream(test_name, record, streams_to_check, expectation): +def test_check_stream(test_name, record, streams_to_check, stream_slice, expectation): stream = MagicMock() stream.name = "s1" - stream.read_records.return_value = iter([record]) + stream.stream_slices.return_value = iter([stream_slice]) + stream.read_records.side_effect = mock_read_records({frozenset(stream_slice): iter([record])}) source = MagicMock() source.streams.return_value = [stream] @@ -38,3 +40,7 @@ def test_check_stream(test_name, record, streams_to_check, expectation): else: with pytest.raises(ValueError): check_stream.check_connection(source, logger, config) + + +def mock_read_records(responses, default_response=None, **kwargs): + return lambda stream_slice, sync_mode: responses[frozenset(stream_slice)] if frozenset(stream_slice) in responses else default_response From 80128bea0275ea66a9a71788ba32c4707f158ef2 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 17 Oct 2022 16:08:45 +0300 Subject: [PATCH 133/498] trim namespace custom format before submit (#17986) --- .../src/views/Connection/ConnectionForm/formConfig.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx index a130831c81cc..d0b3ba445089 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx @@ -115,7 +115,7 @@ export const connectionValidationSchema = (mode: ConnectionFormMode) => .required("form.empty.error"), namespaceFormat: yup.string().when("namespaceDefinition", { is: NamespaceDefinitionType.customformat, - then: yup.string().required("form.empty.error"), + then: yup.string().trim().required("form.empty.error"), }), prefix: yup.string(), syncCatalog: yup.object({ From 19a7747e439ec752044d45b6b9316185d323283f Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Mon, 17 Oct 2022 10:53:21 -0400 Subject: [PATCH 134/498] =?UTF-8?q?=F0=9F=A7=B9remove=20YamlSeedConfigPers?= =?UTF-8?q?istence=20(#18018)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../init/YamlSeedConfigPersistence.java | 215 ------------------ .../init/YamlSeedConfigPersistenceTest.java | 87 ------- 2 files changed, 302 deletions(-) delete mode 100644 airbyte-config/init/src/main/java/io/airbyte/config/init/YamlSeedConfigPersistence.java delete mode 100644 airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java diff --git a/airbyte-config/init/src/main/java/io/airbyte/config/init/YamlSeedConfigPersistence.java b/airbyte-config/init/src/main/java/io/airbyte/config/init/YamlSeedConfigPersistence.java deleted file mode 100644 index 29be461dfec0..000000000000 --- a/airbyte-config/init/src/main/java/io/airbyte/config/init/YamlSeedConfigPersistence.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.init; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.BooleanNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.Resources; -import io.airbyte.commons.docker.DockerUtils; -import io.airbyte.commons.json.Jsons; -import io.airbyte.commons.util.MoreIterators; -import io.airbyte.commons.yaml.Yamls; -import io.airbyte.config.AirbyteConfig; -import io.airbyte.config.AirbyteConfigValidator; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.ConfigWithMetadata; -import io.airbyte.config.persistence.ConfigNotFoundException; -import io.airbyte.config.persistence.ConfigPersistence; -import io.airbyte.validation.json.JsonValidationException; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * This config persistence contains all seed definitions according to the yaml files. It is - * read-only. - */ -final public class YamlSeedConfigPersistence implements ConfigPersistence { - - private static final String PERSISTENCE_READ_ONLY_ERROR_MSG = "The seed config persistence is read only."; - public static final Class DEFAULT_SEED_DEFINITION_RESOURCE_CLASS = SeedType.class; - - private static final Map CONFIG_SCHEMA_MAP = Map.of( - ConfigSchema.STANDARD_SOURCE_DEFINITION, SeedType.STANDARD_SOURCE_DEFINITION, - ConfigSchema.STANDARD_DESTINATION_DEFINITION, SeedType.STANDARD_DESTINATION_DEFINITION); - - // A mapping from seed config type to config UUID to config. - private ImmutableMap> allSeedConfigs; - - // TODO inject via dependency injection framework - private final Class seedResourceClass; - - public YamlSeedConfigPersistence(final Class seedResourceClass) throws IOException { - this.seedResourceClass = seedResourceClass; - - // TODO remove this call once dependency injection framework manages object creation - initialize(); - } - - // TODO will be called automatically by the dependency injection framework on object creation - public void initialize() throws IOException { - final Map sourceDefinitionConfigs = getConfigs(this.seedResourceClass, SeedType.STANDARD_SOURCE_DEFINITION); - final Map sourceSpecConfigs = getConfigs(this.seedResourceClass, SeedType.SOURCE_SPEC); - final Map fullSourceDefinitionConfigs = sourceDefinitionConfigs.entrySet().stream() - .collect(Collectors.toMap(Entry::getKey, e -> { - final JsonNode withMissingFields = - addMissingCustomField( - addMissingPublicField( - addMissingTombstoneField(e.getValue()))); - final JsonNode output = mergeSpecIntoDefinition(withMissingFields, sourceSpecConfigs); - AirbyteConfigValidator.AIRBYTE_CONFIG_VALIDATOR.ensureAsRuntime(ConfigSchema.STANDARD_SOURCE_DEFINITION, output); - return output; - })); - - final Map destinationDefinitionConfigs = getConfigs(this.seedResourceClass, SeedType.STANDARD_DESTINATION_DEFINITION); - final Map destinationSpecConfigs = getConfigs(this.seedResourceClass, SeedType.DESTINATION_SPEC); - final Map fullDestinationDefinitionConfigs = destinationDefinitionConfigs.entrySet().stream() - .collect(Collectors.toMap(Entry::getKey, e -> { - final JsonNode withMissingFields = - addMissingCustomField( - addMissingPublicField( - addMissingTombstoneField(e.getValue()))); - final JsonNode output = mergeSpecIntoDefinition(withMissingFields, destinationSpecConfigs); - AirbyteConfigValidator.AIRBYTE_CONFIG_VALIDATOR.ensureAsRuntime(ConfigSchema.STANDARD_DESTINATION_DEFINITION, output); - return output; - })); - - this.allSeedConfigs = ImmutableMap.>builder() - .put(SeedType.STANDARD_SOURCE_DEFINITION, fullSourceDefinitionConfigs) - .put(SeedType.STANDARD_DESTINATION_DEFINITION, fullDestinationDefinitionConfigs).build(); - } - - /** - * Merges the corresponding spec JSON into the definition JSON. This is necessary because specs are - * stored in a separate resource file from definitions. - * - * @param definitionJson JSON of connector definition that is missing a spec - * @param specConfigs map of docker image to JSON of docker image/connector spec pair - * @return JSON of connector definition including the connector spec - */ - private JsonNode mergeSpecIntoDefinition(final JsonNode definitionJson, final Map specConfigs) { - final String dockerImage = DockerUtils.getTaggedImageName( - definitionJson.get("dockerRepository").asText(), - definitionJson.get("dockerImageTag").asText()); - final JsonNode specConfigJson = specConfigs.get(dockerImage); - if (specConfigJson == null || specConfigJson.get("spec") == null) { - throw new UnsupportedOperationException(String.format("There is no seed spec for docker image %s", dockerImage)); - } - ((ObjectNode) definitionJson).set("spec", specConfigJson.get("spec")); - return definitionJson; - } - - private JsonNode addMissingTombstoneField(final JsonNode definitionJson) { - final JsonNode currTombstone = definitionJson.get("tombstone"); - if (currTombstone == null || currTombstone.isNull()) { - ((ObjectNode) definitionJson).set("tombstone", BooleanNode.FALSE); - } - return definitionJson; - } - - private JsonNode addMissingPublicField(final JsonNode definitionJson) { - final JsonNode currPublic = definitionJson.get("public"); - if (currPublic == null || currPublic.isNull()) { - // definitions loaded from seed yamls are by definition public - ((ObjectNode) definitionJson).set("public", BooleanNode.TRUE); - } - return definitionJson; - } - - private JsonNode addMissingCustomField(final JsonNode definitionJson) { - final JsonNode currCustom = definitionJson.get("custom"); - if (currCustom == null || currCustom.isNull()) { - // definitions loaded from seed yamls are by definition not custom - ((ObjectNode) definitionJson).set("custom", BooleanNode.FALSE); - } - return definitionJson; - } - - @SuppressWarnings("UnstableApiUsage") - - private static Map getConfigs(final Class seedDefinitionsResourceClass, final SeedType seedType) throws IOException { - final URL url = Resources.getResource(seedDefinitionsResourceClass, seedType.getResourcePath()); - final String yamlString = Resources.toString(url, StandardCharsets.UTF_8); - final JsonNode configList = Yamls.deserialize(yamlString); - return MoreIterators.toList(configList.elements()).stream().collect(Collectors.toMap( - json -> json.get(seedType.getIdName()).asText(), - json -> json)); - } - - @Override - public T getConfig(final AirbyteConfig configType, final String configId, final Class clazz) - throws ConfigNotFoundException, JsonValidationException, IOException { - final Map configs = allSeedConfigs.get(CONFIG_SCHEMA_MAP.get(configType)); - if (configs == null) { - throw new UnsupportedOperationException("There is no seed for " + configType.name()); - } - final JsonNode config = configs.get(configId); - if (config == null) { - throw new ConfigNotFoundException(configType, configId); - } - return Jsons.object(config, clazz); - } - - @Override - public List listConfigs(final AirbyteConfig configType, final Class clazz) { - final Map configs = allSeedConfigs.get(CONFIG_SCHEMA_MAP.get(configType)); - if (configs == null) { - throw new UnsupportedOperationException("There is no seed for " + configType.name()); - } - return configs.values().stream().map(json -> Jsons.object(json, clazz)).collect(Collectors.toList()); - } - - @Override - public ConfigWithMetadata getConfigWithMetadata(final AirbyteConfig configType, final String configId, final Class clazz) - throws ConfigNotFoundException, JsonValidationException, IOException { - throw new UnsupportedOperationException("Yaml Seed Config doesn't support metadata"); - } - - @Override - public List> listConfigsWithMetadata(final AirbyteConfig configType, final Class clazz) - throws JsonValidationException, IOException { - throw new UnsupportedOperationException("Yaml Seed Config doesn't support metadata"); - } - - @Override - public void writeConfig(final AirbyteConfig configType, final String configId, final T config) { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - - @Override - public void writeConfigs(final AirbyteConfig configType, final Map configs) { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - - @Override - public void deleteConfig(final AirbyteConfig configType, final String configId) { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - - @Override - public void replaceAllConfigs(final Map> configs, final boolean dryRun) { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - - @Override - public Map> dumpConfigs() { - return allSeedConfigs.entrySet().stream().collect(Collectors.toMap( - e -> e.getKey().name(), - e -> e.getValue().values().stream())); - } - - @Override - public void loadData(final ConfigPersistence seedPersistence) throws IOException { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - -} diff --git a/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java b/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java deleted file mode 100644 index 4acc200e3ade..000000000000 --- a/airbyte-config/init/src/test/java/io/airbyte/config/init/YamlSeedConfigPersistenceTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.init; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.StandardDestinationDefinition; -import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.StandardSync; -import io.airbyte.config.StandardWorkspace; -import io.airbyte.config.persistence.ConfigNotFoundException; -import java.io.IOException; -import java.net.URI; -import java.util.Collections; -import java.util.Map; -import java.util.stream.Stream; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -class YamlSeedConfigPersistenceTest { - - private static YamlSeedConfigPersistence persistence; - - @BeforeAll - static void setup() throws IOException { - persistence = new YamlSeedConfigPersistence(YamlSeedConfigPersistence.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); - } - - @Test - void testGetConfig() throws Exception { - // source - final String mySqlSourceId = "435bb9a5-7887-4809-aa58-28c27df0d7ad"; - final StandardSourceDefinition mysqlSource = persistence - .getConfig(ConfigSchema.STANDARD_SOURCE_DEFINITION, mySqlSourceId, StandardSourceDefinition.class); - assertEquals(mySqlSourceId, mysqlSource.getSourceDefinitionId().toString()); - assertEquals("MySQL", mysqlSource.getName()); - assertEquals("airbyte/source-mysql", mysqlSource.getDockerRepository()); - assertEquals("https://docs.airbyte.com/integrations/sources/mysql", mysqlSource.getDocumentationUrl()); - assertEquals("mysql.svg", mysqlSource.getIcon()); - assertEquals(URI.create("https://docs.airbyte.com/integrations/sources/mysql"), mysqlSource.getSpec().getDocumentationUrl()); - assertEquals(true, mysqlSource.getPublic()); - assertEquals(false, mysqlSource.getCustom()); - - // destination - final String s3DestinationId = "4816b78f-1489-44c1-9060-4b19d5fa9362"; - final StandardDestinationDefinition s3Destination = persistence - .getConfig(ConfigSchema.STANDARD_DESTINATION_DEFINITION, s3DestinationId, StandardDestinationDefinition.class); - assertEquals(s3DestinationId, s3Destination.getDestinationDefinitionId().toString()); - assertEquals("S3", s3Destination.getName()); - assertEquals("airbyte/destination-s3", s3Destination.getDockerRepository()); - assertEquals("https://docs.airbyte.com/integrations/destinations/s3", s3Destination.getDocumentationUrl()); - assertEquals(URI.create("https://docs.airbyte.com/integrations/destinations/s3"), s3Destination.getSpec().getDocumentationUrl()); - assertEquals(true, s3Destination.getPublic()); - assertEquals(false, s3Destination.getCustom()); - } - - @Test - void testGetInvalidConfig() { - assertThrows( - UnsupportedOperationException.class, - () -> persistence.getConfig(ConfigSchema.STANDARD_SYNC, "invalid_id", StandardSync.class)); - assertThrows( - ConfigNotFoundException.class, - () -> persistence.getConfig(ConfigSchema.STANDARD_SOURCE_DEFINITION, "invalid_id", StandardWorkspace.class)); - } - - @Test - void testDumpConfigs() { - final Map> allSeedConfigs = persistence.dumpConfigs(); - assertEquals(2, allSeedConfigs.size()); - assertTrue(allSeedConfigs.get(ConfigSchema.STANDARD_SOURCE_DEFINITION.name()).findAny().isPresent()); - assertTrue(allSeedConfigs.get(ConfigSchema.STANDARD_DESTINATION_DEFINITION.name()).findAny().isPresent()); - } - - @Test - void testWriteMethods() { - assertThrows(UnsupportedOperationException.class, () -> persistence.writeConfig(ConfigSchema.STANDARD_SOURCE_DEFINITION, "id", new Object())); - assertThrows(UnsupportedOperationException.class, () -> persistence.replaceAllConfigs(Collections.emptyMap(), false)); - } - -} From e51756bb239573325285bf22b716bfaee75f4f31 Mon Sep 17 00:00:00 2001 From: terencecho <3916587+terencecho@users.noreply.github.com> Date: Mon, 17 Oct 2022 09:48:55 -0700 Subject: [PATCH 135/498] Skip psql stop in acceptance test for gke (#18023) --- .../io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java index 5f5bd4589fe0..cc6c260848cd 100644 --- a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java +++ b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java @@ -298,7 +298,9 @@ public void cleanup() { for (final UUID destinationId : destinationIds) { deleteDestination(destinationId); } - destinationPsql.stop(); + if (!isGke) { + destinationPsql.stop(); + } } catch (final Exception e) { LOGGER.error("Error tearing down test fixtures:", e); } From ef3e84ce3aa74d32a6ac9b5a292106512ce4dedf Mon Sep 17 00:00:00 2001 From: Ryan Fu Date: Mon, 17 Oct 2022 10:08:07 -0700 Subject: [PATCH 136/498] Checks for iterator hasNext element (#18041) * Checks for iterator hasNext element * Fix linter with newline --- .../io/airbyte/integrations/base/ssh/SshTunnel.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java index 1c694def8e9a..313dfe46d053 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java @@ -142,7 +142,7 @@ public SshTunnel(final JsonNode config, URL urlObject = null; try { urlObject = new URL(remoteServiceUrl); - } catch (MalformedURLException e) { + } catch (final MalformedURLException e) { AirbyteTraceMessageUtility.emitConfigErrorTrace(e, String.format("Provided value for remote service URL is not valid: %s", remoteServiceUrl)); } @@ -184,7 +184,8 @@ public JsonNode getConfigInTunnel() throws Exception { Jsons.replaceNestedInt(clone, portKey, tunnelLocalPort); } if (endPointKey != null) { - URL tunnelEndPointURL = new URL(remoteServiceProtocol, SshdSocketAddress.LOCALHOST_ADDRESS.getHostName(), tunnelLocalPort, remoteServicePath); + final URL tunnelEndPointURL = + new URL(remoteServiceProtocol, SshdSocketAddress.LOCALHOST_ADDRESS.getHostName(), tunnelLocalPort, remoteServicePath); Jsons.replaceNestedString(clone, Arrays.asList(endPointKey), tunnelEndPointURL.toString()); } return clone; @@ -306,7 +307,10 @@ KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException { .getKeyPairResourceParser() .loadKeyPairs(null, null, null, new StringReader(validatedKey)); - return (keyPairs == null) ? null : keyPairs.iterator().next(); + if (keyPairs != null && keyPairs.iterator().hasNext()) { + return keyPairs.iterator().next(); + } + return null; } private String validateKey() { From 5a80c765c2ef9e26c231f2d737682dfd4ad83c36 Mon Sep 17 00:00:00 2001 From: Jimmy Ma Date: Mon, 17 Oct 2022 10:26:41 -0700 Subject: [PATCH 137/498] Add Message Migration to Destination Connection Checks (#17954) * Add Message Migration to Destination Connection Checks * Fix test setup --- .../server/handlers/SchedulerHandler.java | 6 +++-- .../DefaultSynchronousSchedulerClient.java | 6 +++-- .../scheduler/SynchronousSchedulerClient.java | 4 ++- .../server/handlers/SchedulerHandlerTest.java | 27 ++++++++++++------- ...DefaultSynchronousSchedulerClientTest.java | 5 ++-- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java index cc88681305dd..3cfa7983d45b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java @@ -177,7 +177,8 @@ public CheckConnectionRead checkDestinationConnectionFromDestinationId(final Des final DestinationConnection destination = configRepository.getDestinationConnection(destinationIdRequestBody.getDestinationId()); final StandardDestinationDefinition destinationDef = configRepository.getStandardDestinationDefinition(destination.getDestinationDefinitionId()); final String imageName = DockerUtils.getTaggedImageName(destinationDef.getDockerRepository(), destinationDef.getDockerImageTag()); - return reportConnectionStatus(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, imageName)); + final Version protocolVersion = new Version(destinationDef.getProtocolVersion()); + return reportConnectionStatus(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, imageName, protocolVersion)); } public CheckConnectionRead checkDestinationConnectionFromDestinationCreate(final DestinationCoreConfig destinationConfig) @@ -195,7 +196,8 @@ public CheckConnectionRead checkDestinationConnectionFromDestinationCreate(final .withWorkspaceId(destinationConfig.getWorkspaceId()); final String imageName = DockerUtils.getTaggedImageName(destDef.getDockerRepository(), destDef.getDockerImageTag()); - return reportConnectionStatus(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, imageName)); + final Version protocolVersion = new Version(destDef.getProtocolVersion()); + return reportConnectionStatus(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, imageName, protocolVersion)); } public CheckConnectionRead checkDestinationConnectionFromDestinationIdForUpdate(final DestinationUpdate destinationUpdate) diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java index 493fea4dc120..d49a3efdd29b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java @@ -87,7 +87,8 @@ public SynchronousResponse createSourceCheckConne @Override public SynchronousResponse createDestinationCheckConnectionJob(final DestinationConnection destination, - final String dockerImage) + final String dockerImage, + final Version protocolVersion) throws IOException { final JsonNode destinationConfiguration = oAuthConfigSupplier.injectDestinationOAuthParameters( destination.getDestinationDefinitionId(), @@ -95,7 +96,8 @@ public SynchronousResponse createDestinationCheck destination.getConfiguration()); final JobCheckConnectionConfig jobCheckConnectionConfig = new JobCheckConnectionConfig() .withConnectionConfiguration(destinationConfiguration) - .withDockerImage(dockerImage); + .withDockerImage(dockerImage) + .withProtocolVersion(protocolVersion); final UUID jobId = UUID.randomUUID(); final ConnectorJobReportingContext jobReportingContext = new ConnectorJobReportingContext(jobId, dockerImage); diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java index 719a6b196729..02f9a3d7995f 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java @@ -23,7 +23,9 @@ SynchronousResponse createSourceCheckConnectionJo Version protocolVersion) throws IOException; - SynchronousResponse createDestinationCheckConnectionJob(DestinationConnection destination, String dockerImage) + SynchronousResponse createDestinationCheckConnectionJob(DestinationConnection destination, + String dockerImage, + Version protocolVersion) throws IOException; SynchronousResponse createDiscoverSchemaJob(SourceConnection source, String dockerImage, String connectorVersion) throws IOException; diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java index 98bdebd68d3b..d7997987a902 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java @@ -297,15 +297,18 @@ void testCheckDestinationConnectionFromDestinationId() throws IOException, JsonV .thenReturn(new StandardDestinationDefinition() .withDockerRepository(DESTINATION_DOCKER_REPO) .withDockerImageTag(DESTINATION_DOCKER_TAG) + .withProtocolVersion(DESTINATION_PROTOCOL_VERSION) .withDestinationDefinitionId(destination.getDestinationDefinitionId())); when(configRepository.getDestinationConnection(destination.getDestinationId())).thenReturn(destination); - when(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE)) - .thenReturn((SynchronousResponse) jobResponse); + when(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE, + new Version(DESTINATION_PROTOCOL_VERSION))) + .thenReturn((SynchronousResponse) jobResponse); schedulerHandler.checkDestinationConnectionFromDestinationId(request); verify(configRepository).getDestinationConnection(destination.getDestinationId()); - verify(synchronousSchedulerClient).createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE); + verify(synchronousSchedulerClient).createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE, + new Version(DESTINATION_PROTOCOL_VERSION)); } @Test @@ -322,16 +325,19 @@ void testCheckDestinationConnectionFromDestinationCreate() throws JsonValidation .thenReturn(new StandardDestinationDefinition() .withDockerRepository(DESTINATION_DOCKER_REPO) .withDockerImageTag(DESTINATION_DOCKER_TAG) + .withProtocolVersion(DESTINATION_PROTOCOL_VERSION) .withDestinationDefinitionId(destination.getDestinationDefinitionId())); - when(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE)) - .thenReturn((SynchronousResponse) jobResponse); + when(synchronousSchedulerClient.createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE, + new Version(DESTINATION_PROTOCOL_VERSION))) + .thenReturn((SynchronousResponse) jobResponse); when(secretsRepositoryWriter.statefulSplitEphemeralSecrets( eq(destination.getConfiguration()), any())).thenReturn(destination.getConfiguration()); schedulerHandler.checkDestinationConnectionFromDestinationCreate(destinationCoreConfig); - verify(synchronousSchedulerClient).createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE); + verify(synchronousSchedulerClient).createDestinationCheckConnectionJob(destination, DESTINATION_DOCKER_IMAGE, + new Version(DESTINATION_PROTOCOL_VERSION)); } @Test @@ -344,6 +350,7 @@ void testCheckDestinationConnectionFromUpdate() throws IOException, JsonValidati final StandardDestinationDefinition destinationDefinition = new StandardDestinationDefinition() .withDockerRepository(DESTINATION_DOCKER_REPO) .withDockerImageTag(DESTINATION_DOCKER_TAG) + .withProtocolVersion(DESTINATION_PROTOCOL_VERSION) .withDestinationDefinitionId(destination.getDestinationDefinitionId()) .withSpec(CONNECTOR_SPECIFICATION); when(configRepository.getStandardDestinationDefinition(destination.getDestinationDefinitionId())) @@ -354,15 +361,17 @@ void testCheckDestinationConnectionFromUpdate() throws IOException, JsonValidati final DestinationConnection submittedDestination = new DestinationConnection() .withDestinationDefinitionId(destination.getDestinationDefinitionId()) .withConfiguration(destination.getConfiguration()); - when(synchronousSchedulerClient.createDestinationCheckConnectionJob(submittedDestination, DESTINATION_DOCKER_IMAGE)) - .thenReturn((SynchronousResponse) jobResponse); + when(synchronousSchedulerClient.createDestinationCheckConnectionJob(submittedDestination, DESTINATION_DOCKER_IMAGE, + new Version(DESTINATION_PROTOCOL_VERSION))) + .thenReturn((SynchronousResponse) jobResponse); when(secretsRepositoryWriter.statefulSplitEphemeralSecrets( eq(destination.getConfiguration()), any())).thenReturn(destination.getConfiguration()); schedulerHandler.checkDestinationConnectionFromDestinationIdForUpdate(destinationUpdate); verify(jsonSchemaValidator).ensure(CONNECTOR_SPECIFICATION.getConnectionSpecification(), destination.getConfiguration()); - verify(synchronousSchedulerClient).createDestinationCheckConnectionJob(submittedDestination, DESTINATION_DOCKER_IMAGE); + verify(synchronousSchedulerClient).createDestinationCheckConnectionJob(submittedDestination, DESTINATION_DOCKER_IMAGE, + new Version(DESTINATION_PROTOCOL_VERSION)); } @Test diff --git a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java index 02c203a53318..85415dfa2065 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java @@ -219,14 +219,15 @@ void testCreateSourceCheckConnectionJob() throws IOException { void testCreateDestinationCheckConnectionJob() throws IOException { final JobCheckConnectionConfig jobCheckConnectionConfig = new JobCheckConnectionConfig() .withConnectionConfiguration(DESTINATION_CONNECTION.getConfiguration()) - .withDockerImage(DOCKER_IMAGE); + .withDockerImage(DOCKER_IMAGE) + .withProtocolVersion(PROTOCOL_VERSION); final StandardCheckConnectionOutput mockOutput = mock(StandardCheckConnectionOutput.class); final ConnectorJobOutput jobOutput = new ConnectorJobOutput().withCheckConnection(mockOutput); when(temporalClient.submitCheckConnection(any(UUID.class), eq(0), eq(jobCheckConnectionConfig))) .thenReturn(new TemporalResponse<>(jobOutput, createMetadata(true))); final SynchronousResponse response = - schedulerClient.createDestinationCheckConnectionJob(DESTINATION_CONNECTION, DOCKER_IMAGE); + schedulerClient.createDestinationCheckConnectionJob(DESTINATION_CONNECTION, DOCKER_IMAGE, PROTOCOL_VERSION); assertEquals(mockOutput, response.getOutput()); } From e4f8e754107a2f061ab8ed21424c0bfb817de6ec Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Mon, 17 Oct 2022 20:28:06 +0300 Subject: [PATCH 138/498] Update helm release workflow (#18048) * Update workflow * Update trigger rules * fix: Update release workflow with abillity to add tags * Update workflow --- .github/workflows/publish-helm-charts.yml | 50 ++++++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-helm-charts.yml b/.github/workflows/publish-helm-charts.yml index 6e72ede217d1..044d0db7908a 100644 --- a/.github/workflows/publish-helm-charts.yml +++ b/.github/workflows/publish-helm-charts.yml @@ -8,6 +8,8 @@ on: - 'master' paths: - 'charts/**' + - '!charts/**/Chart.yaml' + - '!charts/**/Chart.lock' workflow_dispatch: jobs: @@ -15,7 +17,8 @@ jobs: name: Generate semantic version runs-on: ubuntu-22.04 outputs: - next-version: ${{ steps.sem-ver.outputs.version }} + next-version: ${{ steps.ver.outputs.fragment }} + tag: ${{ steps.sem-ver.outputs.version_tag }} steps: - uses: actions/checkout@v2 with: @@ -30,11 +33,21 @@ jobs: format: "${major}.${minor}.${patch}" change_path: "./charts" bump_each_commit: true + namespace: helm + + - name: "Remove -helm from ver number" + shell: bash + env: + VERSION: ${{ steps.sem-ver.outputs.version }} + id: ver + run: echo "::set-output name=fragment::${VERSION%%-*}" + release-chart: name: Chart release runs-on: ubuntu-22.04 needs: ["generate-semantic-version"] + permissions: write-all steps: - uses: actions/checkout@v3 with: @@ -51,7 +64,9 @@ jobs: shell: bash working-directory: ./airbyte/charts run: | - sed -i "s/ version: placeholder/ version: ${{ needs.generate-semantic-version.outputs.next-version }}/g" airbyte/Chart.yaml + sed -i -E "s/ version: [[:digit:]].[[:digit:]].[[:digit:]]/ version: ${{ needs.generate-semantic-version.outputs.next-version }}/g" airbyte/Chart.yaml + sed -i -E 's/version: [0-9]+\.[0-9]+\.[0-9]+/version: ${{ needs.generate-semantic-version.outputs.next-version }}/' airbyte/Chart.yaml + - name: "Helm package" shell: bash @@ -59,6 +74,7 @@ jobs: declare -a StringArray=("airbyte-bootloader" "airbyte-server" "airbyte-temporal" "airbyte-webapp" "airbyte-pod-sweeper" "airbyte-worker" "airbyte-metrics") for val in ${StringArray[@]}; do cd ./airbyte/charts/${val} && helm dep update && cd $GITHUB_WORKSPACE + sed -i -E 's/version: \"[0-9]+\.[0-9]+\.[0-9]+\"/version: \"${{ needs.generate-semantic-version.outputs.next-version }}\"/' ./airbyte/charts/${val}/Chart.yaml helm package ./airbyte/charts/${val} -d airbyte-oss --version ${{ needs.generate-semantic-version.outputs.next-version }} done helm repo index airbyte-oss/ @@ -89,3 +105,33 @@ jobs: add: '.' cwd: './airbyte-oss/' + + - name: "Generate changelog" + shell: bash + id: changelog + run: | + cd ./airbyte/ + echo "::set-output name=changelog::$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no)" + + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v4 + with: + path: ./airbyte/ + branch: update-helm-chart-version-ref + branch-suffix: random + title: Bump helm chart version reference to ${{ needs.generate-semantic-version.outputs.next-version }} + body: | + ## What + Bump version reference in all Chart.yaml files to ${{ needs.generate-semantic-version.outputs.next-version }} + CHANGELOG: + ${{ steps.changelog.outputs.changelog }} + commit-message: Bump helm chart version reference to ${{ needs.generate-semantic-version.outputs.next-version }} + delete-branch: true + + - name: Create tag + shell: bash + run: | + cd ./airbyte/ + git tag ${{ needs.generate-semantic-version.outputs.tag }} + git push origin ${{ needs.generate-semantic-version.outputs.tag }} \ No newline at end of file From 792fa7ceadd5d58a4c2526e2ccc27c8e084776aa Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Mon, 17 Oct 2022 10:28:53 -0700 Subject: [PATCH 139/498] Remove unused `airbyte-cli` (#18009) --- airbyte-cli/.gitignore | 1 - airbyte-cli/Dockerfile | 13 ------------ airbyte-cli/LICENSE | 21 ------------------- airbyte-cli/build.gradle | 3 --- airbyte-cli/gradle.properties | 1 - airbyte-cli/readme.md | 3 --- settings.gradle | 1 - tools/bin/cli.sh | 38 ----------------------------------- 8 files changed, 81 deletions(-) delete mode 100644 airbyte-cli/.gitignore delete mode 100644 airbyte-cli/Dockerfile delete mode 100644 airbyte-cli/LICENSE delete mode 100644 airbyte-cli/build.gradle delete mode 100644 airbyte-cli/gradle.properties delete mode 100644 airbyte-cli/readme.md delete mode 100755 tools/bin/cli.sh diff --git a/airbyte-cli/.gitignore b/airbyte-cli/.gitignore deleted file mode 100644 index 4fefe73122ac..000000000000 --- a/airbyte-cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -restish.json diff --git a/airbyte-cli/Dockerfile b/airbyte-cli/Dockerfile deleted file mode 100644 index 0fe19f0ebdc0..000000000000 --- a/airbyte-cli/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -ARG ALPINE_IMAGE=alpine:3.13 -FROM ${ALPINE_IMAGE} - -RUN apk --no-cache add curl tar \ - && if [[ `uname -m` == "aarch64" ]] ; then ARCH=arm64 ; else ARCH=`uname -m` ; fi \ - && curl -OL https://github.com/danielgtaylor/restish/releases/download/v0.9.0/restish-0.9.0-linux-${ARCH}.tar.gz \ - && tar -C /usr/local/bin -xzf restish-0.9.0-linux-${ARCH}.tar.gz \ - && rm -rf restish-0.9.0-linux-${ARCH}.tar.gz - -ENTRYPOINT ["restish"] - -LABEL io.airbyte.version=0.1.0 -LABEL io.airbyte.name=airbyte/cli diff --git a/airbyte-cli/LICENSE b/airbyte-cli/LICENSE deleted file mode 100644 index ec45d182fcb9..000000000000 --- a/airbyte-cli/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Airbyte, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/airbyte-cli/build.gradle b/airbyte-cli/build.gradle deleted file mode 100644 index e94fc5bf50c1..000000000000 --- a/airbyte-cli/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -tasks.named("buildDockerImage") { - dependsOn copyDocker -} diff --git a/airbyte-cli/gradle.properties b/airbyte-cli/gradle.properties deleted file mode 100644 index 8cbcf90d343a..000000000000 --- a/airbyte-cli/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -dockerImageName=cli diff --git a/airbyte-cli/readme.md b/airbyte-cli/readme.md deleted file mode 100644 index c65791d7c440..000000000000 --- a/airbyte-cli/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# airbyte-cli - -Thin CLI over the Airbyte Configuration API to make it easier to interact with the API from the command line. diff --git a/settings.gradle b/settings.gradle index 8653fd11da50..c0233a62ce0f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -88,7 +88,6 @@ include ':airbyte-notification' // transitively used by airbyte-workers. // platform if (!System.getenv().containsKey("SUB_BUILD") || System.getenv().get("SUB_BUILD") == "PLATFORM") { include ':airbyte-bootloader' - include ':airbyte-cli' include ':airbyte-config:specs' include ':airbyte-container-orchestrator' include ':airbyte-cron' diff --git a/tools/bin/cli.sh b/tools/bin/cli.sh deleted file mode 100755 index e4c7ccb3cbcd..000000000000 --- a/tools/bin/cli.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash - -set -e - -LOCAL_RESTISH_PATH="$(pwd)"/airbyte-cli/restish.json -IMAGE_RESTISH_PATH=/root/.restish/apis.json - -DOWNLOADED_CONFIG_PATH=/tmp/downloaded-airbyte-api-config -IMAGE_CONFIG_PATH=/tmp/config.yaml - -if [ ! -f "$LOCAL_RESTISH_PATH" ]; then - API_URL="${API_URL:-http://localhost:8001}" - if ! curl -s "${API_URL}/api/v1/openapi" -o "$DOWNLOADED_CONFIG_PATH"; then - 2>&1 echo "ERROR: failed to download config file from ${API_URL}/api/v1/openapi" - 2>&1 echo " if the API is elsewhere you can specify it using:" - 2>&1 echo " API_URL=XXX $0" - exit 1 - fi - - cat > "$LOCAL_RESTISH_PATH" < Date: Mon, 17 Oct 2022 11:07:04 -0700 Subject: [PATCH 140/498] =?UTF-8?q?=F0=9F=90=9B=20=20[low-code]=20$options?= =?UTF-8?q?=20shouldn't=20overwrite=20values=20that=20are=20already=20defi?= =?UTF-8?q?ned=20(#18060)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix * Add missing test * remove prints * extract to method * rename * Add missing test * rename * bump --- airbyte-cdk/python/CHANGELOG.md | 4 ++ .../sources/declarative/create_partial.py | 16 +++-- airbyte-cdk/python/setup.py | 2 +- .../declarative/test_create_partial.py | 25 ++++++- .../sources/declarative/test_factory.py | 71 +++++++++++++------ 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index 2fbb4f21c01a..24aa5cca3699 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.101 + +- Low-code: $options do not overwrite parameters that are already set + ## 0.1.100 - Low-code: Pass stream_slice to read_records when reading from CheckStream diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/create_partial.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/create_partial.py index c941153f3f84..cc309c376dd2 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/create_partial.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/create_partial.py @@ -3,6 +3,7 @@ # import inspect +from typing import Any, Mapping OPTIONS_STR = "$options" @@ -23,6 +24,7 @@ def create(func, /, *args, **keywords): """ def newfunc(*fargs, **fkeywords): + all_keywords = {**keywords} all_keywords.update(fkeywords) @@ -39,8 +41,8 @@ def newfunc(*fargs, **fkeywords): if config is not None: all_keywords["config"] = config - kwargs_to_pass_down = _get_kwargs_to_pass_to_func(func, options) - all_keywords_to_pass_down = _get_kwargs_to_pass_to_func(func, all_keywords) + kwargs_to_pass_down = _get_kwargs_to_pass_to_func(func, options, all_keywords) + all_keywords_to_pass_down = _get_kwargs_to_pass_to_func(func, all_keywords, all_keywords) # options is required as part of creation of all declarative components dynamic_args = {**all_keywords_to_pass_down, **kwargs_to_pass_down} @@ -63,17 +65,23 @@ def newfunc(*fargs, **fkeywords): return newfunc -def _get_kwargs_to_pass_to_func(func, options): +def _get_kwargs_to_pass_to_func(func, options, existing_keyword_parameters): argspec = inspect.getfullargspec(func) kwargs_to_pass_down = set(argspec.kwonlyargs) args_to_pass_down = set(argspec.args) all_args = args_to_pass_down.union(kwargs_to_pass_down) - kwargs_to_pass_down = {k: v for k, v in options.items() if k in all_args} + kwargs_to_pass_down = { + k: v for k, v in options.items() if k in all_args and _key_is_unset_or_identical(k, v, existing_keyword_parameters) + } if "options" in all_args: kwargs_to_pass_down["options"] = options return kwargs_to_pass_down +def _key_is_unset_or_identical(key: str, value: Any, mapping: Mapping[str, Any]): + return key not in mapping or mapping[key] == value + + def _create_inner_objects(keywords, kwargs): fully_created = dict() for k, v in keywords.items(): diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index c3f205f35843..addef435c91f 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.100", + version="0.1.101", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/test_create_partial.py b/airbyte-cdk/python/unit_tests/sources/declarative/test_create_partial.py index 3ba79ab81e7d..48e07117d04c 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/test_create_partial.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/test_create_partial.py @@ -2,7 +2,8 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # -from airbyte_cdk.sources.declarative.create_partial import create +import pytest +from airbyte_cdk.sources.declarative.create_partial import _key_is_unset_or_identical, create from airbyte_cdk.sources.declarative.interpolation.interpolated_string import InterpolatedString @@ -33,6 +34,12 @@ def test_pass_parameter_to_create_function(): assert object.another_param == "B" +def test_parameter_not_overwritten_by_options(): + object = create(AClass, parameter="A", another_param="B", **{"$options": {"parameter": "C"}})() + assert object.parameter == "A" + assert object.another_param == "B" + + def test_overwrite_param(): object = create(AClass, parameter="A", another_param="B")(parameter="C") assert object.parameter == "C" @@ -46,7 +53,7 @@ def test_string_interpolation(): assert interpolated_string.string == s -def test_string_interpolation_through_kwargs(): +def test_string_interpolation_through_options(): s = "{{ options['name'] }}" options = {"name": "airbyte"} partial = create(InterpolatedString, string=s, **options) @@ -60,3 +67,17 @@ def test_string_interpolation_through_options_keyword(): partial = create(InterpolatedString, string=s, **options) interpolated_string = partial() assert interpolated_string.eval({}) == "airbyte" + + +@pytest.mark.parametrize( + "test_name, key, value, expected_result", + [ + ("test", "key", "value", True), + ("test", "key", "a_different_value", False), + ("test", "a_different_key", "value", True), + ], +) +def test_key_is_unset_or_identical(test_name, key, value, expected_result): + mapping = {"key": "value"} + result = _key_is_unset_or_identical(key, value, mapping) + assert expected_result == result diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py b/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py index 7cf63571b5ad..4adf401c481a 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py @@ -414,31 +414,31 @@ def test_create_record_selector(test_name, record_selector, expected_runtime_sel ( "test_option_in_selector", """ - extractor: - type: DpathExtractor - field_pointer: ["{{ options['name'] }}"] - selector: - class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector - $options: - name: "selector" - extractor: "*ref(extractor)" - """, + extractor: + type: DpathExtractor + field_pointer: ["{{ options['name'] }}"] + selector: + class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector + $options: + name: "selector" + extractor: "*ref(extractor)" + """, "selector", ), ( "test_option_in_extractor", """ - extractor: - type: DpathExtractor - $options: - name: "extractor" - field_pointer: ["{{ options['name'] }}"] - selector: - class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector - $options: - name: "selector" - extractor: "*ref(extractor)" - """, + extractor: + type: DpathExtractor + $options: + name: "extractor" + field_pointer: ["{{ options['name'] }}"] + selector: + class_name: airbyte_cdk.sources.declarative.extractors.record_selector.RecordSelector + $options: + name: "selector" + extractor: "*ref(extractor)" + """, "extractor", ), ], @@ -682,6 +682,37 @@ def test_add_fields(self): ] assert expected == component.transformations + def test_add_fields_path_in_options(self): + content = f""" + the_stream: + class_name: airbyte_cdk.sources.declarative.declarative_stream.DeclarativeStream + $options: + {self.base_options} + path: "/wrong_path" + transformations: + - type: AddFields + fields: + - path: ["field1"] + value: "static_value" + """ + config = parser.parse(content) + + factory.create_component(config["the_stream"], input_config, False) + + component = factory.create_component(config["the_stream"], input_config)() + assert isinstance(component, DeclarativeStream) + expected = [ + AddFields( + fields=[ + AddedFieldDefinition( + path=["field1"], value=InterpolatedString(string="static_value", default="static_value", options={}), options={} + ) + ], + options={}, + ) + ] + assert expected == component.transformations + def test_validation_wrong_input_type(): content = """ From e546ebd92d6fc20e3cb3eecddc043c42e0c7679b Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Mon, 17 Oct 2022 21:24:06 +0300 Subject: [PATCH 141/498] Update helm chart comments (#18072) --- charts/airbyte/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/charts/airbyte/values.yaml b/charts/airbyte/values.yaml index 705d8f8b414b..376bdeebce1e 100644 --- a/charts/airbyte/values.yaml +++ b/charts/airbyte/values.yaml @@ -1,3 +1,5 @@ +### TEST FOR RELEASE WORKFLOW + ## @section Global Parameters ## global -- Global params that are overwritten with umbrella chart From e40375b79d9b9355c499a13c454557a8e25bbe3d Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Mon, 17 Oct 2022 21:33:17 +0300 Subject: [PATCH 142/498] Update helm charts (#18073) * add test * fix chart.yaml --- charts/airbyte/Chart.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 1527de1be890..71a05725f97d 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -32,28 +32,28 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: placeholder + version: 0.40.18 From ee2e2b5b1fd935d2780fe609994a80a876186286 Mon Sep 17 00:00:00 2001 From: Yevhen Sukhomud Date: Tue, 18 Oct 2022 02:00:51 +0700 Subject: [PATCH 143/498] 16250 Destination Redis: Add SSH support (#17951) * 16250 Destination Redis: Add SSH support * 16250 Resolve port issue * 11679 Bump version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../seed/destination_definitions.yaml | 2 +- .../resources/seed/destination_specs.yaml | 103 +++++++++++++- .../base/ssh/SshBastionContainer.java | 4 + .../connectors/destination-redis/Dockerfile | 2 +- .../destination/redis/RedisDestination.java | 11 +- .../SshKeyRedisDestinationAcceptanceTest.java | 16 +++ ...asswordRedisDestinationAcceptanceTest.java | 16 +++ .../SshRedisDestinationAcceptanceTest.java | 126 ++++++++++++++++++ docs/integrations/destinations/redis.md | 8 +- 9 files changed, 283 insertions(+), 5 deletions(-) create mode 100644 airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshKeyRedisDestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshPasswordRedisDestinationAcceptanceTest.java create mode 100644 airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index ec71a03c143b..ae33fd671b27 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -231,7 +231,7 @@ - name: Redis destinationDefinitionId: d4d3fef9-e319-45c2-881a-bd02ce44cc9f dockerRepository: airbyte/destination-redis - dockerImageTag: 0.1.2 + dockerImageTag: 0.1.3 documentationUrl: https://docs.airbyte.com/integrations/destinations/redis icon: redis.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 23ac25f84604..166b39563111 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -4238,7 +4238,7 @@ supportsDBT: false supported_destination_sync_modes: - "append" -- dockerImage: "airbyte/destination-redis:0.1.2" +- dockerImage: "airbyte/destination-redis:0.1.3" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/redis" connectionSpecification: @@ -4287,6 +4287,107 @@ enum: - "hash" order: 5 + tunnel_method: + type: "object" + title: "SSH Tunnel Method" + description: "Whether to initiate an SSH tunnel before connecting to the\ + \ database, and if so, which kind of authentication to use." + oneOf: + - title: "No Tunnel" + required: + - "tunnel_method" + properties: + tunnel_method: + description: "No ssh tunnel needed to connect to database" + type: "string" + const: "NO_TUNNEL" + order: 0 + - title: "SSH Key Authentication" + required: + - "tunnel_method" + - "tunnel_host" + - "tunnel_port" + - "tunnel_user" + - "ssh_key" + properties: + tunnel_method: + description: "Connect through a jump server tunnel host using username\ + \ and ssh key" + type: "string" + const: "SSH_KEY_AUTH" + order: 0 + tunnel_host: + title: "SSH Tunnel Jump Server Host" + description: "Hostname of the jump server host that allows inbound\ + \ ssh tunnel." + type: "string" + order: 1 + tunnel_port: + title: "SSH Connection Port" + description: "Port on the proxy/jump server that accepts inbound ssh\ + \ connections." + type: "integer" + minimum: 0 + maximum: 65536 + default: 22 + examples: + - "22" + order: 2 + tunnel_user: + title: "SSH Login Username" + description: "OS-level username for logging into the jump server host." + type: "string" + order: 3 + ssh_key: + title: "SSH Private Key" + description: "OS-level user account ssh key credentials in RSA PEM\ + \ format ( created with ssh-keygen -t rsa -m PEM -f myuser_rsa )" + type: "string" + airbyte_secret: true + multiline: true + order: 4 + - title: "Password Authentication" + required: + - "tunnel_method" + - "tunnel_host" + - "tunnel_port" + - "tunnel_user" + - "tunnel_user_password" + properties: + tunnel_method: + description: "Connect through a jump server tunnel host using username\ + \ and password authentication" + type: "string" + const: "SSH_PASSWORD_AUTH" + order: 0 + tunnel_host: + title: "SSH Tunnel Jump Server Host" + description: "Hostname of the jump server host that allows inbound\ + \ ssh tunnel." + type: "string" + order: 1 + tunnel_port: + title: "SSH Connection Port" + description: "Port on the proxy/jump server that accepts inbound ssh\ + \ connections." + type: "integer" + minimum: 0 + maximum: 65536 + default: 22 + examples: + - "22" + order: 2 + tunnel_user: + title: "SSH Login Username" + description: "OS-level username for logging into the jump server host" + type: "string" + order: 3 + tunnel_user_password: + title: "Password" + description: "OS-level password for logging into the jump server host" + type: "string" + airbyte_secret: true + order: 4 supportsIncremental: true supportsNormalization: false supportsDBT: false diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshBastionContainer.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshBastionContainer.java index 62aa42d7712c..c232743c6bf9 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshBastionContainer.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshBastionContainer.java @@ -78,6 +78,10 @@ public void stopAndCloseContainers(final JdbcDatabaseContainer db) { db.close(); } + public void stopAndClose() { + bastion.close(); + } + public GenericContainer getContainer() { return bastion; } diff --git a/airbyte-integrations/connectors/destination-redis/Dockerfile b/airbyte-integrations/connectors/destination-redis/Dockerfile index c773173c33ab..82ce05383e61 100644 --- a/airbyte-integrations/connectors/destination-redis/Dockerfile +++ b/airbyte-integrations/connectors/destination-redis/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-redis COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.2 +LABEL io.airbyte.version=0.1.3 LABEL io.airbyte.name=airbyte/destination-redis diff --git a/airbyte-integrations/connectors/destination-redis/src/main/java/io/airbyte/integrations/destination/redis/RedisDestination.java b/airbyte-integrations/connectors/destination-redis/src/main/java/io/airbyte/integrations/destination/redis/RedisDestination.java index 5fe3f7ffe286..8e0716cc576c 100644 --- a/airbyte-integrations/connectors/destination-redis/src/main/java/io/airbyte/integrations/destination/redis/RedisDestination.java +++ b/airbyte-integrations/connectors/destination-redis/src/main/java/io/airbyte/integrations/destination/redis/RedisDestination.java @@ -9,9 +9,11 @@ import io.airbyte.integrations.base.AirbyteMessageConsumer; import io.airbyte.integrations.base.Destination; import io.airbyte.integrations.base.IntegrationRunner; +import io.airbyte.integrations.base.ssh.SshWrappedDestination; import io.airbyte.protocol.models.AirbyteConnectionStatus; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; +import java.util.List; import java.util.function.Consumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +23,14 @@ class RedisDestination extends BaseConnector implements Destination { private static final Logger LOGGER = LoggerFactory.getLogger(RedisDestination.class); public static void main(String[] args) throws Exception { - new IntegrationRunner(new RedisDestination()).run(args); + LOGGER.info("starting destination: {}", RedisDestination.class); + final Destination destination = RedisDestination.sshWrappedDestination(); + new IntegrationRunner(destination).run(args); + LOGGER.info("completed destination: {}", RedisDestination.class); + } + + public static Destination sshWrappedDestination() { + return new SshWrappedDestination(new RedisDestination(), List.of("host"), List.of("port")); } @Override diff --git a/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshKeyRedisDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshKeyRedisDestinationAcceptanceTest.java new file mode 100644 index 000000000000..55eda49ea627 --- /dev/null +++ b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshKeyRedisDestinationAcceptanceTest.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.redis; + +import io.airbyte.integrations.base.ssh.SshTunnel; + +public class SshKeyRedisDestinationAcceptanceTest extends SshRedisDestinationAcceptanceTest { + + @Override + public SshTunnel.TunnelMethod getTunnelMethod() { + return SshTunnel.TunnelMethod.SSH_KEY_AUTH; + } + +} diff --git a/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshPasswordRedisDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshPasswordRedisDestinationAcceptanceTest.java new file mode 100644 index 000000000000..e2d358fa9bbf --- /dev/null +++ b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshPasswordRedisDestinationAcceptanceTest.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.redis; + +import io.airbyte.integrations.base.ssh.SshTunnel; + +public class SshPasswordRedisDestinationAcceptanceTest extends SshRedisDestinationAcceptanceTest { + + @Override + public SshTunnel.TunnelMethod getTunnelMethod() { + return SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH; + } + +} diff --git a/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java new file mode 100644 index 000000000000..c358f78d6ff7 --- /dev/null +++ b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.redis; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.ImmutableMap; +import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.base.ssh.SshBastionContainer; +import io.airbyte.integrations.base.ssh.SshTunnel; +import io.airbyte.integrations.destination.redis.RedisContainerInitializr.RedisContainer; +import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; +import io.airbyte.integrations.standardtest.destination.comparator.AdvancedTestDataComparator; +import io.airbyte.integrations.standardtest.destination.comparator.TestDataComparator; +import io.airbyte.integrations.util.HostPortResolver; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.testcontainers.containers.Network; + +public abstract class SshRedisDestinationAcceptanceTest extends DestinationAcceptanceTest { + + private static final SshBastionContainer bastion = new SshBastionContainer(); + private static final Network network = Network.newNetwork(); + private static RedisContainerInitializr.RedisContainer redisContainer; + private JsonNode configJson; + private RedisCache redisCache; + private RedisNameTransformer redisNameTransformer; + + @BeforeAll + static void initContainers() { + redisContainer = new RedisContainer() + .withExposedPorts(6379) + .withNetwork(network); + redisContainer.start(); + bastion.initAndStartBastion(network); + } + + @AfterAll + static void stop() { + redisContainer.close(); + bastion.stopAndClose(); + } + + public abstract SshTunnel.TunnelMethod getTunnelMethod(); + + @Override + protected void setup(TestDestinationEnv testEnv) { + configJson = RedisDataFactory.jsonConfig( + redisContainer.getHost(), + redisContainer.getFirstMappedPort()); + var redisConfig = new RedisConfig(configJson); + redisCache = new RedisHCache(redisConfig); + redisNameTransformer = new RedisNameTransformer(); + } + + @Override + protected void tearDown(TestDestinationEnv testEnv) { + redisCache.flushAll(); + } + + @Override + protected String getImageName() { + return "airbyte/destination-redis:dev"; + } + + @Override + protected JsonNode getConfig() throws Exception { + return bastion.getTunnelConfig(getTunnelMethod(), ImmutableMap.builder() + .put("host", HostPortResolver.resolveIpAddress(redisContainer)) + .put("port", redisContainer.getExposedPorts().get(0)) + .put("username", configJson.get("username")) + .put("password", configJson.get("password")) + .put("cache_type", configJson.get("cache_type"))); + } + + @Override + protected JsonNode getFailCheckConfig() { + return RedisDataFactory.jsonConfig( + "127.0.0.9", + 8080); + } + + @Override + protected List retrieveRecords(TestDestinationEnv testEnv, + String streamName, + String namespace, + JsonNode streamSchema) { + var key = redisNameTransformer.keyName(namespace, streamName); + return redisCache.getAll(key).stream() + .sorted(Comparator.comparing(RedisRecord::getTimestamp)) + .map(RedisRecord::getData) + .map(Jsons::deserialize) + .collect(Collectors.toList()); + } + + + @Override + protected boolean implementsNamespaces() { + return true; + } + + @Override + protected TestDataComparator getTestDataComparator() { + return new AdvancedTestDataComparator(); + } + + @Override + protected boolean supportBasicDataTypeTest() { + return true; + } + + @Override + protected boolean supportArrayDataTypeTest() { + return true; + } + + @Override + protected boolean supportObjectDataTypeTest() { + return true; + } + +} diff --git a/docs/integrations/destinations/redis.md b/docs/integrations/destinations/redis.md index b427a7cb89e3..7d2064758412 100644 --- a/docs/integrations/destinations/redis.md +++ b/docs/integrations/destinations/redis.md @@ -51,4 +51,10 @@ save snapshots periodically on disk. ### Setup guide -######TODO: more info, screenshots?, etc... \ No newline at end of file +######TODO: more info, screenshots?, etc... + +## Changelog + +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:-----------------------------------------------------------|:------------------| +| 0.1.3 | 2022-10-18 | [\#17951](https://github.com/airbytehq/airbyte/pull/17951) | Add SSH support | From 6ebfe6b0b74368ea514788db3e638833f2c1f9d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:04:53 +0300 Subject: [PATCH 144/498] Bump helm chart version reference to 0.40.20 (#18074) * Bump helm chart version reference to 0.40.20 * remove binary Co-authored-by: xpuska513 Co-authored-by: Kyryl Skobylko --- charts/airbyte-bootloader/Chart.lock | 6 ++++++ charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-metrics/Chart.yaml | 2 +- charts/airbyte-pod-sweeper/Chart.lock | 6 ++++++ charts/airbyte-pod-sweeper/Chart.yaml | 2 +- charts/airbyte-server/Chart.lock | 6 ++++++ charts/airbyte-temporal/Chart.lock | 6 ++++++ charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.lock | 6 ++++++ charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.lock | 6 ++++++ charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 27 +++++++++++++++++++++++++++ charts/airbyte/Chart.yaml | 16 ++++++++-------- 14 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 charts/airbyte-bootloader/Chart.lock create mode 100644 charts/airbyte-pod-sweeper/Chart.lock create mode 100644 charts/airbyte-server/Chart.lock create mode 100644 charts/airbyte-temporal/Chart.lock create mode 100644 charts/airbyte-webapp/Chart.lock create mode 100644 charts/airbyte-worker/Chart.lock create mode 100644 charts/airbyte/Chart.lock diff --git a/charts/airbyte-bootloader/Chart.lock b/charts/airbyte-bootloader/Chart.lock new file mode 100644 index 000000000000..fb39f2dc7d08 --- /dev/null +++ b/charts/airbyte-bootloader/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +digest: sha256:dacc73770a5640c011e067ff8840ddf89631fc19016c8d0a9e5ea160e7da8690 +generated: "2022-10-17T18:35:11.238826963Z" diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index dea6c78c7393..42c306f2e19a 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.45.2" +version: "0.40.20" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index 7db9650f4b0f..b76fb5f83edd 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.45.2" +version: "0.40.20" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-pod-sweeper/Chart.lock b/charts/airbyte-pod-sweeper/Chart.lock new file mode 100644 index 000000000000..5fdb23f1cc51 --- /dev/null +++ b/charts/airbyte-pod-sweeper/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +digest: sha256:dacc73770a5640c011e067ff8840ddf89631fc19016c8d0a9e5ea160e7da8690 +generated: "2022-10-17T18:35:27.057672332Z" diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index 1b144454fda5..d33cd4ca1983 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.39.36" +version: "0.40.20" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-server/Chart.lock b/charts/airbyte-server/Chart.lock new file mode 100644 index 000000000000..746fb41b3a91 --- /dev/null +++ b/charts/airbyte-server/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +digest: sha256:dacc73770a5640c011e067ff8840ddf89631fc19016c8d0a9e5ea160e7da8690 +generated: "2022-10-17T18:35:15.123937677Z" diff --git a/charts/airbyte-temporal/Chart.lock b/charts/airbyte-temporal/Chart.lock new file mode 100644 index 000000000000..4deb970eece0 --- /dev/null +++ b/charts/airbyte-temporal/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +digest: sha256:dacc73770a5640c011e067ff8840ddf89631fc19016c8d0a9e5ea160e7da8690 +generated: "2022-10-17T18:35:19.234598793Z" diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index 385ad79b33f4..e0ffa5b1f94a 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.45.2" +version: "0.40.20" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-webapp/Chart.lock b/charts/airbyte-webapp/Chart.lock new file mode 100644 index 000000000000..6d8b135d7f94 --- /dev/null +++ b/charts/airbyte-webapp/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +digest: sha256:dacc73770a5640c011e067ff8840ddf89631fc19016c8d0a9e5ea160e7da8690 +generated: "2022-10-17T18:35:23.164183749Z" diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index da5f47a46c45..ff65eb39bd17 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.45.2" +version: "0.40.20" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-worker/Chart.lock b/charts/airbyte-worker/Chart.lock new file mode 100644 index 000000000000..7000cfe2217e --- /dev/null +++ b/charts/airbyte-worker/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +digest: sha256:dacc73770a5640c011e067ff8840ddf89631fc19016c8d0a9e5ea160e7da8690 +generated: "2022-10-17T18:35:31.130386898Z" diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 2947909cb2e8..4e0a0aa36230 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.45.2" +version: "0.40.20" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock new file mode 100644 index 000000000000..617b62982862 --- /dev/null +++ b/charts/airbyte/Chart.lock @@ -0,0 +1,27 @@ +dependencies: +- name: common + repository: https://charts.bitnami.com/bitnami + version: 1.17.1 +- name: airbyte-bootloader + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +- name: temporal + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +- name: webapp + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +- name: server + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +- name: worker + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +- name: pod-sweeper + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +- name: metrics + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.20 +digest: sha256:9b5cab655a1a12640c8752c40a6560a1774edee8262af7571df16a1cc189251b +generated: "2022-10-17T18:40:41.673881061Z" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 71a05725f97d..2404f7aa288a 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.45.3 +version: 0.40.20 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -32,28 +32,28 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.18 + version: 0.40.20 From 211b4beb4210227d974f0ac6cecaf9fd70e552ba Mon Sep 17 00:00:00 2001 From: Prasanth <72515998+sfc-gh-pkommini@users.noreply.github.com> Date: Mon, 17 Oct 2022 12:29:07 -0700 Subject: [PATCH 145/498] Helm Chart: Create service annotations for airbyte-server (#17932) * Support annotations for airbyte-server as well, update version and update docs. * Fix auto-indent. Co-authored-by: Kyryl Skobylko --- charts/airbyte-server/Chart.yaml | 2 +- charts/airbyte-server/README.md | 1 + charts/airbyte-server/templates/service.yaml | 4 ++++ charts/airbyte-server/values.yaml | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/charts/airbyte-server/Chart.yaml b/charts/airbyte-server/Chart.yaml index 13d0784422e7..d804d190414e 100644 --- a/charts/airbyte-server/Chart.yaml +++ b/charts/airbyte-server/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.45.2 +version: 0.45.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-server/README.md b/charts/airbyte-server/README.md index 09556b2cebc4..1cf47631d75e 100644 --- a/charts/airbyte-server/README.md +++ b/charts/airbyte-server/README.md @@ -70,6 +70,7 @@ Helm chart to deploy airbyte-server | resources.limits | object | `{}` | | | resources.requests | object | `{}` | | | secrets | object | `{}` | | +| service.annotations | object | `{}` | | | service.port | int | `8001` | | | service.type | string | `"ClusterIP"` | | | tolerations | list | `[]` | | diff --git a/charts/airbyte-server/templates/service.yaml b/charts/airbyte-server/templates/service.yaml index 7690b8e71675..5078c58de241 100644 --- a/charts/airbyte-server/templates/service.yaml +++ b/charts/airbyte-server/templates/service.yaml @@ -3,6 +3,10 @@ apiVersion: v1 kind: Service metadata: name: {{.Release.Name }}-airbyte-server-svc + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} labels: {{- include "airbyte.labels" . | nindent 4 }} spec: diff --git a/charts/airbyte-server/values.yaml b/charts/airbyte-server/values.yaml index d113d78e8cca..9305d91ebb27 100644 --- a/charts/airbyte-server/values.yaml +++ b/charts/airbyte-server/values.yaml @@ -139,6 +139,7 @@ resources: service: type: ClusterIP port: 8001 + annotations: {} ## nodeSelector [object] Node labels for pod assignment ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ From 7a71c55c1f4e3d3d7ff7c87605d94899cdda6b48 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Mon, 17 Oct 2022 12:35:04 -0700 Subject: [PATCH 146/498] Bmoric/remove dep server worker (#17894) * test [ci skip] * Autogenerated files * Add missing annotation * Remove unused json2Schema block from worker * Move tess * Missing deps and format * Fix test build * TMP * Add missing dependencies * PR comments * Tmp * [ci skip] Tmp * Fix acceptance test and add the seed dependency * Fix build * For diff * tmp * Build pass * make the worker to be on the platform only * fix setting.yaml * Fix pmd * Fix Cron * Add chart * Fix cron * Fix server build.gradle * Fix jar conflict * PR comments * Add cron micronaut environemnt --- .env | 4 +- .env.dev | 1 + airbyte-commons-temporal/build.gradle | 22 +- .../temporal/ConnectionManagerUtils.java | 196 +++- .../airbyte/commons}/temporal/ErrorCode.java | 2 +- .../commons}/temporal/JobMetadata.java | 2 +- .../temporal/StreamResetRecordsHelper.java | 4 +- .../commons/temporal/TemporalClient.java | 418 ++++++++- .../commons}/temporal/TemporalResponse.java | 2 +- .../commons/temporal/TemporalUtils.java | 23 + .../temporal/config/TemporalBeanFactory.java | 9 + .../commons/temporal}/config/WorkerMode.java | 2 +- .../exception/DeletedWorkflowException.java | 2 +- .../exception/RetryableException.java | 2 +- .../UnreachableWorkflowException.java | 2 +- .../scheduling}/CheckConnectionWorkflow.java | 2 +- .../scheduling/ConnectionManagerWorkflow.java | 3 + .../scheduling}/DiscoverCatalogWorkflow.java | 2 +- .../temporal/scheduling}/SpecWorkflow.java | 2 +- .../temporal/scheduling}/SyncWorkflow.java | 2 +- .../commons/temporal/TemporalClientTest.java | 801 ++++++++++++++++- airbyte-commons-worker/build.gradle | 20 +- .../java/io/airbyte/workers/WorkerUtils.java | 23 - .../workers/helper/ConnectionHelper.java | 2 +- .../workers/helper/ProtocolConverters.java | 0 .../workers/helper/StateConverter.java | 0 airbyte-container-orchestrator/build.gradle | 1 + .../ContainerOrchestratorApp.java | 4 +- .../DbtJobOrchestrator.java | 4 +- .../NormalizationJobOrchestrator.java | 4 +- .../ReplicationJobOrchestrator.java | 3 +- airbyte-cron/build.gradle | 1 + .../cron/config/DatabaseBeanFactory.java | 24 + .../io/airbyte/cron/selfhealing/Temporal.java | 3 +- .../src/main/resources/application.yml | 13 +- airbyte-server/build.gradle | 9 +- .../java/io/airbyte/server/ServerApp.java | 6 +- .../server/handlers/SchedulerHandler.java | 4 +- .../DefaultSynchronousSchedulerClient.java | 4 +- .../airbyte/server/scheduler/EventRunner.java | 2 +- .../scheduler/SynchronousJobMetadata.java | 2 +- .../server/scheduler/SynchronousResponse.java | 2 +- .../server/scheduler/TemporalEventRunner.java | 4 +- .../server/handlers/SchedulerHandlerTest.java | 4 +- .../WebBackendConnectionsHandlerTest.java | 2 +- ...DefaultSynchronousSchedulerClientTest.java | 6 +- airbyte-worker-models/build.gradle | 22 + .../IntegrationLauncherConfig.yaml | 0 .../workers_models/JobRunConfig.yaml | 0 airbyte-workers/build.gradle | 2 +- .../workers/ApplicationInitializer.java | 2 +- .../workers/config/ActivityBeanFactory.java | 1 + .../workers/config/ApiClientBeanFactory.java | 1 + .../config/ApplicationBeanFactory.java | 1 + .../workers/config/DatabaseBeanFactory.java | 1 + .../config/JobErrorReportingBeanFactory.java | 1 + .../config/ProcessFactoryBeanFactory.java | 1 + .../config/SecretPersistenceBeanFactory.java | 1 + .../workers/config/TemporalBeanFactory.java | 3 +- .../WorkerConfigurationBeanFactory.java | 1 + .../workers/run/TemporalWorkerRunFactory.java | 4 +- .../io/airbyte/workers/run/WorkerRun.java | 4 +- .../temporal/ConnectionManagerUtils.java | 247 ------ .../temporal/TemporalAttemptExecution.java | 3 +- .../workers/temporal/TemporalClient.java | 549 ------------ .../CheckConnectionActivityImpl.java | 2 +- .../CheckConnectionWorkflowImpl.java | 1 + .../catalog/DiscoverCatalogActivityImpl.java | 2 +- .../catalog/DiscoverCatalogWorkflowImpl.java | 1 + .../ConnectionManagerWorkflowImpl.java | 7 +- .../AutoDisableConnectionActivityImpl.java | 4 +- .../activities/ConfigFetchActivityImpl.java | 4 +- .../ConnectionDeletionActivityImpl.java | 4 +- .../activities/GenerateInputActivityImpl.java | 4 +- .../JobCreationAndStatusUpdateActivity.java | 2 +- ...obCreationAndStatusUpdateActivityImpl.java | 4 +- .../activities/RecordMetricActivityImpl.java | 2 +- .../activities/StreamResetActivityImpl.java | 4 +- .../WorkflowConfigActivityImpl.java | 2 +- .../temporal/spec/SpecActivityImpl.java | 2 +- .../temporal/spec/SpecWorkflowImpl.java | 1 + .../temporal/sync/SyncWorkflowImpl.java | 1 + .../run/TemporalWorkerRunFactoryTest.java | 4 +- .../StreamResetRecordsHelperTest.java | 1 + .../workers/temporal/TemporalClientTest.java | 839 ------------------ .../ConnectionManagerWorkflowTest.java | 2 +- .../ConnectionDeletionActivityTest.java | 2 +- ...obCreationAndStatusUpdateActivityTest.java | 2 +- .../activities/StreamResetActivityTest.java | 2 +- .../DbtFailureSyncWorkflow.java | 2 +- .../testsyncworkflow/EmptySyncWorkflow.java | 2 +- .../NormalizationFailureSyncWorkflow.java | 2 +- ...NormalizationTraceFailureSyncWorkflow.java | 2 +- .../PersistFailureSyncWorkflow.java | 2 +- .../ReplicateFailureSyncWorkflow.java | 2 +- .../SleepingSyncWorkflow.java | 2 +- ...urceAndDestinationFailureSyncWorkflow.java | 2 +- .../SyncWorkflowFailingOutputWorkflow.java | 2 +- ...owFailingWithHearbeatTimeoutException.java | 2 +- ...cWorkflowWithActivityFailureException.java | 2 +- .../temporal/stubs/ErrorTestWorkflowImpl.java | 2 +- .../stubs/InvalidTestWorkflowImpl.java | 2 +- .../TemporalActivityStubInterceptorTest.java | 2 +- .../temporal/sync/SyncWorkflowTest.java | 1 + charts/airbyte/templates/env-configmap.yaml | 1 + docker-compose.yaml | 1 + kube/overlays/dev-integration-test/.env | 3 + kube/overlays/dev/.env | 3 + .../overlays/stable-with-resource-limits/.env | 3 + kube/overlays/stable/.env | 3 + kube/resources/cron.yaml | 5 + settings.gradle | 1 + 112 files changed, 1622 insertions(+), 1820 deletions(-) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/ErrorCode.java (79%) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/JobMetadata.java (96%) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/StreamResetRecordsHelper.java (95%) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/TemporalResponse.java (97%) rename {airbyte-commons-worker/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal}/config/WorkerMode.java (90%) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/exception/DeletedWorkflowException.java (81%) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/exception/RetryableException.java (80%) rename {airbyte-workers/src/main/java/io/airbyte/workers => airbyte-commons-temporal/src/main/java/io/airbyte/commons}/temporal/exception/UnreachableWorkflowException.java (87%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection => airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling}/CheckConnectionWorkflow.java (92%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog => airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling}/DiscoverCatalogWorkflow.java (92%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec => airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling}/SpecWorkflow.java (91%) rename {airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync => airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling}/SyncWorkflow.java (94%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java (99%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/helper/ProtocolConverters.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/helper/StateConverter.java (100%) create mode 100644 airbyte-worker-models/build.gradle rename {airbyte-commons-worker => airbyte-worker-models}/src/main/resources/workers_models/IntegrationLauncherConfig.yaml (100%) rename {airbyte-commons-temporal => airbyte-worker-models}/src/main/resources/workers_models/JobRunConfig.yaml (100%) delete mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/ConnectionManagerUtils.java delete mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java delete mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalClientTest.java diff --git a/.env b/.env index 23e2c1b1402e..d8ab738b59fd 100644 --- a/.env +++ b/.env @@ -81,6 +81,8 @@ LOG_LEVEL=INFO ### APPLICATIONS ### # Worker # WORKERS_MICRONAUT_ENVIRONMENTS=control-plane +# Cron # +CRON_MICRONAUT_ENVIRONMENTS=control-plane # Relevant to scaling. MAX_SYNC_WORKERS=5 MAX_SPEC_WORKERS=5 @@ -102,4 +104,4 @@ METRIC_CLIENT= # Useful only when metric client is set to be otel. Must start with http:// or https://. OTEL_COLLECTOR_ENDPOINT="http://host.docker.internal:4317" -USE_STREAM_CAPABLE_STATE=true \ No newline at end of file +USE_STREAM_CAPABLE_STATE=true diff --git a/.env.dev b/.env.dev index 687fbe07b5dc..3fa990232cb8 100644 --- a/.env.dev +++ b/.env.dev @@ -26,6 +26,7 @@ INTERNAL_API_HOST=airbyte-server:8001 SYNC_JOB_MAX_ATTEMPTS=3 SYNC_JOB_MAX_TIMEOUT_DAYS=3 WORKERS_MICRONAUT_ENVIRONMENTS=control-plane +CRON_MICRONAUT_ENVIRONMENTS=control-plane # Sentry SENTRY_DSN="" diff --git a/airbyte-commons-temporal/build.gradle b/airbyte-commons-temporal/build.gradle index 04101720844c..3140b4272ba5 100644 --- a/airbyte-commons-temporal/build.gradle +++ b/airbyte-commons-temporal/build.gradle @@ -1,8 +1,5 @@ -import org.jsonschema2pojo.SourceType - plugins { id "java-library" - id 'com.github.eirnym.js2p' version '1.0' } dependencies { @@ -18,27 +15,16 @@ dependencies { testAnnotationProcessor platform(libs.micronaut.bom) testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor - implementation project(':airbyte-config:config-models') + implementation project(':airbyte-config:config-persistence') implementation project(':airbyte-metrics:metrics-lib') + implementation project(':airbyte-persistence:job-persistence') + implementation project(':airbyte-protocol:protocol-models') + implementation project(':airbyte-worker-models') testImplementation 'io.temporal:temporal-testing:1.8.1' // Needed to be able to mock final class testImplementation 'org.mockito:mockito-inline:4.7.0' } -jsonSchema2Pojo { - sourceType = SourceType.YAMLSCHEMA - source = files("${sourceSets.main.output.resourcesDir}/workers_models") - targetDirectory = new File(project.buildDir, 'generated/src/gen/java/') - removeOldOutput = true - - targetPackage = 'io.airbyte.persistence.job.models' - - useLongIntegers = true - generateBuilders = true - includeConstructors = false - includeSetters = true -} - Task publishArtifactsTask = getPublishArtifactsTask("$rootProject.ext.version", project) diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ConnectionManagerUtils.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ConnectionManagerUtils.java index 178c17151f15..47ccaa36ab1f 100644 --- a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ConnectionManagerUtils.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ConnectionManagerUtils.java @@ -4,11 +4,24 @@ package io.airbyte.commons.temporal; +import io.airbyte.commons.temporal.exception.DeletedWorkflowException; +import io.airbyte.commons.temporal.exception.UnreachableWorkflowException; import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; import io.airbyte.commons.temporal.scheduling.ConnectionUpdaterInput; +import io.airbyte.commons.temporal.scheduling.state.WorkflowState; +import io.temporal.api.common.v1.WorkflowExecution; +import io.temporal.api.enums.v1.WorkflowExecutionStatus; +import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest; +import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionResponse; +import io.temporal.client.BatchRequest; import io.temporal.client.WorkflowClient; +import io.temporal.workflow.Functions.Proc; +import io.temporal.workflow.Functions.Proc1; +import io.temporal.workflow.Functions.TemporalFunctionalInterfaceMarker; import jakarta.inject.Singleton; +import java.util.Optional; import java.util.UUID; +import java.util.function.Function; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,6 +30,105 @@ @Slf4j public class ConnectionManagerUtils { + /** + * Attempts to send a signal to the existing ConnectionManagerWorkflow for the provided connection. + * + * If the workflow is unreachable, this will restart the workflow and send the signal in a single + * batched request. Batching is used to avoid race conditions between starting the workflow and + * executing the signal. + * + * @param client the WorkflowClient for interacting with temporal + * @param connectionId the connection ID to execute this operation for + * @param signalMethod a function that takes in a connection manager workflow and executes a signal + * method on it, with no arguments + * @return the healthy connection manager workflow that was signaled + * @throws DeletedWorkflowException if the connection manager workflow was deleted + */ + public ConnectionManagerWorkflow signalWorkflowAndRepairIfNecessary(final WorkflowClient client, + final UUID connectionId, + final Function signalMethod) + throws DeletedWorkflowException { + return signalWorkflowAndRepairIfNecessary(client, connectionId, signalMethod, Optional.empty()); + } + + /** + * Attempts to send a signal to the existing ConnectionManagerWorkflow for the provided connection. + * + * If the workflow is unreachable, this will restart the workflow and send the signal in a single + * batched request. Batching is used to avoid race conditions between starting the workflow and + * executing the signal. + * + * @param client the WorkflowClient for interacting with temporal + * @param connectionId the connection ID to execute this operation for + * @param signalMethod a function that takes in a connection manager workflow and executes a signal + * method on it, with 1 argument + * @param signalArgument the single argument to be input to the signal + * @return the healthy connection manager workflow that was signaled + * @throws DeletedWorkflowException if the connection manager workflow was deleted + */ + public ConnectionManagerWorkflow signalWorkflowAndRepairIfNecessary(final WorkflowClient client, + final UUID connectionId, + final Function> signalMethod, + final T signalArgument) + throws DeletedWorkflowException { + return signalWorkflowAndRepairIfNecessary(client, connectionId, signalMethod, Optional.of(signalArgument)); + } + + // This method unifies the logic of the above two, by using the optional signalArgument parameter to + // indicate if an argument is being provided to the signal or not. + // Keeping this private and only exposing the above methods outside this class provides a strict + // type enforcement for external calls, and means this method can assume consistent type + // implementations for both cases. + private ConnectionManagerWorkflow signalWorkflowAndRepairIfNecessary(final WorkflowClient client, + final UUID connectionId, + final Function signalMethod, + final Optional signalArgument) + throws DeletedWorkflowException { + try { + final ConnectionManagerWorkflow connectionManagerWorkflow = getConnectionManagerWorkflow(client, connectionId); + log.info("Retrieved existing connection manager workflow for connection {}. Executing signal.", connectionId); + // retrieve the signal from the lambda + final TemporalFunctionalInterfaceMarker signal = signalMethod.apply(connectionManagerWorkflow); + // execute the signal + if (signalArgument.isPresent()) { + ((Proc1) signal).apply(signalArgument.get()); + } else { + ((Proc) signal).apply(); + } + return connectionManagerWorkflow; + } catch (final UnreachableWorkflowException e) { + log.error( + String.format( + "Failed to retrieve ConnectionManagerWorkflow for connection %s. Repairing state by creating new workflow and starting with the signal.", + connectionId), + e); + + // in case there is an existing workflow in a bad state, attempt to terminate it first before + // starting a new workflow + safeTerminateWorkflow(client, connectionId, "Terminating workflow in unreachable state before starting a new workflow for this connection"); + + final ConnectionManagerWorkflow connectionManagerWorkflow = newConnectionManagerWorkflowStub(client, connectionId); + final ConnectionUpdaterInput startWorkflowInput = TemporalWorkflowUtils.buildStartWorkflowInput(connectionId); + + final BatchRequest batchRequest = client.newSignalWithStartRequest(); + batchRequest.add(connectionManagerWorkflow::run, startWorkflowInput); + + // retrieve the signal from the lambda + final TemporalFunctionalInterfaceMarker signal = signalMethod.apply(connectionManagerWorkflow); + // add signal to batch request + if (signalArgument.isPresent()) { + batchRequest.add((Proc1) signal, signalArgument.get()); + } else { + batchRequest.add((Proc) signal); + } + + client.signalWithStart(batchRequest); + log.info("Connection manager workflow for connection {} has been started and signaled.", connectionId); + + return connectionManagerWorkflow; + } + } + void safeTerminateWorkflow(final WorkflowClient client, final String workflowId, final String reason) { log.info("Attempting to terminate existing workflow for workflowId {}.", workflowId); try { @@ -33,10 +145,6 @@ public void safeTerminateWorkflow(final WorkflowClient client, final UUID connec safeTerminateWorkflow(client, getConnectionManagerName(connectionId), reason); } - public String getConnectionManagerName(final UUID connectionId) { - return "connection_manager_" + connectionId; - } - public ConnectionManagerWorkflow startConnectionManagerNoSignal(final WorkflowClient client, final UUID connectionId) { final ConnectionManagerWorkflow connectionManagerWorkflow = newConnectionManagerWorkflowStub(client, connectionId); final ConnectionUpdaterInput input = TemporalWorkflowUtils.buildStartWorkflowInput(connectionId); @@ -45,9 +153,89 @@ public ConnectionManagerWorkflow startConnectionManagerNoSignal(final WorkflowCl return connectionManagerWorkflow; } + /** + * Attempts to retrieve the connection manager workflow for the provided connection. + * + * @param connectionId the ID of the connection whose workflow should be retrieved + * @return the healthy ConnectionManagerWorkflow + * @throws DeletedWorkflowException if the workflow was deleted, according to the workflow state + * @throws UnreachableWorkflowException if the workflow is in an unreachable state + */ + public ConnectionManagerWorkflow getConnectionManagerWorkflow(final WorkflowClient client, final UUID connectionId) + throws DeletedWorkflowException, UnreachableWorkflowException { + + final ConnectionManagerWorkflow connectionManagerWorkflow; + final WorkflowState workflowState; + final WorkflowExecutionStatus workflowExecutionStatus; + try { + connectionManagerWorkflow = client.newWorkflowStub(ConnectionManagerWorkflow.class, getConnectionManagerName(connectionId)); + workflowState = connectionManagerWorkflow.getState(); + workflowExecutionStatus = getConnectionManagerWorkflowStatus(client, connectionId); + } catch (final Exception e) { + throw new UnreachableWorkflowException( + String.format("Failed to retrieve ConnectionManagerWorkflow for connection %s due to the following error:", connectionId), + e); + } + + if (WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED.equals(workflowExecutionStatus)) { + if (workflowState.isDeleted()) { + throw new DeletedWorkflowException(String.format( + "The connection manager workflow for connection %s is deleted, so no further operations cannot be performed on it.", + connectionId)); + } + + // A non-deleted workflow being in a COMPLETED state is unexpected, and should be corrected + throw new UnreachableWorkflowException( + String.format("ConnectionManagerWorkflow for connection %s is unreachable due to having COMPLETED status.", connectionId)); + } + + if (workflowState.isQuarantined()) { + throw new UnreachableWorkflowException( + String.format("ConnectionManagerWorkflow for connection %s is unreachable due to being in a quarantined state.", connectionId)); + } + + return connectionManagerWorkflow; + } + + boolean isWorkflowStateRunning(final WorkflowClient client, final UUID connectionId) { + try { + final ConnectionManagerWorkflow connectionManagerWorkflow = client.newWorkflowStub(ConnectionManagerWorkflow.class, + getConnectionManagerName(connectionId)); + return connectionManagerWorkflow.getState().isRunning(); + } catch (final Exception e) { + return false; + } + } + + public WorkflowExecutionStatus getConnectionManagerWorkflowStatus(final WorkflowClient workflowClient, final UUID connectionId) { + final DescribeWorkflowExecutionRequest describeWorkflowExecutionRequest = DescribeWorkflowExecutionRequest.newBuilder() + .setExecution(WorkflowExecution.newBuilder() + .setWorkflowId(getConnectionManagerName(connectionId)) + .build()) + .setNamespace(workflowClient.getOptions().getNamespace()).build(); + + final DescribeWorkflowExecutionResponse describeWorkflowExecutionResponse = workflowClient.getWorkflowServiceStubs().blockingStub() + .describeWorkflowExecution(describeWorkflowExecutionRequest); + + return describeWorkflowExecutionResponse.getWorkflowExecutionInfo().getStatus(); + } + + public long getCurrentJobId(final WorkflowClient client, final UUID connectionId) { + try { + final ConnectionManagerWorkflow connectionManagerWorkflow = getConnectionManagerWorkflow(client, connectionId); + return connectionManagerWorkflow.getJobInformation().getJobId(); + } catch (final Exception e) { + return ConnectionManagerWorkflow.NON_RUNNING_JOB_ID; + } + } + public ConnectionManagerWorkflow newConnectionManagerWorkflowStub(final WorkflowClient client, final UUID connectionId) { return client.newWorkflowStub(ConnectionManagerWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.CONNECTION_UPDATER, getConnectionManagerName(connectionId))); } + public String getConnectionManagerName(final UUID connectionId) { + return "connection_manager_" + connectionId; + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/ErrorCode.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ErrorCode.java similarity index 79% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/ErrorCode.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ErrorCode.java index cd0a14b0c4c9..2d276320919c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/ErrorCode.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/ErrorCode.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal; +package io.airbyte.commons.temporal; public enum ErrorCode { UNKNOWN, diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/JobMetadata.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/JobMetadata.java similarity index 96% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/JobMetadata.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/JobMetadata.java index 612efc0eb1cc..9ee1328079c4 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/JobMetadata.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/JobMetadata.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal; +package io.airbyte.commons.temporal; import java.nio.file.Path; import java.util.Objects; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/StreamResetRecordsHelper.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/StreamResetRecordsHelper.java similarity index 95% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/StreamResetRecordsHelper.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/StreamResetRecordsHelper.java index e12149e80600..77c3e7d6ba54 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/StreamResetRecordsHelper.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/StreamResetRecordsHelper.java @@ -2,14 +2,14 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal; +package io.airbyte.commons.temporal; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.JobConfig.ConfigType; import io.airbyte.config.persistence.StreamResetPersistence; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.Job; import io.airbyte.protocol.models.StreamDescriptor; -import io.airbyte.workers.temporal.exception.RetryableException; import jakarta.inject.Singleton; import java.io.IOException; import java.util.List; diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java index 8f97e20c6308..433a22bdb902 100644 --- a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java @@ -4,9 +4,31 @@ package io.airbyte.commons.temporal; +import static io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow.NON_RUNNING_JOB_ID; + import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.ByteString; +import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.temporal.exception.DeletedWorkflowException; +import io.airbyte.commons.temporal.exception.UnreachableWorkflowException; +import io.airbyte.commons.temporal.scheduling.CheckConnectionWorkflow; import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; +import io.airbyte.commons.temporal.scheduling.DiscoverCatalogWorkflow; +import io.airbyte.commons.temporal.scheduling.SpecWorkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; +import io.airbyte.config.ConnectorJobOutput; +import io.airbyte.config.JobCheckConnectionConfig; +import io.airbyte.config.JobDiscoverCatalogConfig; +import io.airbyte.config.JobGetSpecConfig; +import io.airbyte.config.JobSyncConfig; +import io.airbyte.config.StandardCheckConnectionInput; +import io.airbyte.config.StandardDiscoverCatalogInput; +import io.airbyte.config.StandardSyncInput; +import io.airbyte.config.StandardSyncOutput; +import io.airbyte.config.persistence.StreamResetPersistence; +import io.airbyte.persistence.job.models.IntegrationLauncherConfig; +import io.airbyte.persistence.job.models.JobRunConfig; +import io.airbyte.protocol.models.StreamDescriptor; import io.micronaut.context.annotation.Requires; import io.temporal.api.common.v1.WorkflowType; import io.temporal.api.enums.v1.WorkflowExecutionStatus; @@ -16,32 +38,59 @@ import io.temporal.api.workflowservice.v1.ListOpenWorkflowExecutionsResponse; import io.temporal.client.WorkflowClient; import io.temporal.serviceclient.WorkflowServiceStubs; -import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.inject.Singleton; +import java.io.IOException; +import java.nio.file.Path; import java.util.HashSet; +import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; import java.util.stream.Collectors; -import lombok.AllArgsConstructor; -import lombok.NoArgsConstructor; +import lombok.Builder; +import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.StopWatch; -@AllArgsConstructor -@NoArgsConstructor @Slf4j @Singleton -@Requires(property = "airbyte.worker.plane", - notEquals = "DATA_PLANE") +@Requires(env = WorkerMode.CONTROL_PLANE) public class TemporalClient { - @Inject - private WorkflowClient client; - @Inject - private WorkflowServiceStubs service; - @Inject - private ConnectionManagerUtils connectionManagerUtils; + /** + * This is used to sleep between 2 temporal queries. The query is needed to ensure that the cancel + * and start manual sync methods wait before returning. Since temporal signals are async, we need to + * use the queries to make sure that we are in a state in which we want to continue with. + */ + private static final int DELAY_BETWEEN_QUERY_MS = 10; + + private final Path workspaceRoot; + private final WorkflowClient client; + private final WorkflowServiceStubs service; + private final StreamResetPersistence streamResetPersistence; + private final ConnectionManagerUtils connectionManagerUtils; + private final StreamResetRecordsHelper streamResetRecordsHelper; + + public TemporalClient(@Named("workspaceRootTemporal") final Path workspaceRoot, + final WorkflowClient client, + final WorkflowServiceStubs service, + final StreamResetPersistence streamResetPersistence, + final ConnectionManagerUtils connectionManagerUtils, + final StreamResetRecordsHelper streamResetRecordsHelper) { + this.workspaceRoot = workspaceRoot; + this.client = client; + this.service = service; + this.streamResetPersistence = streamResetPersistence; + this.connectionManagerUtils = connectionManagerUtils; + this.streamResetRecordsHelper = streamResetRecordsHelper; + } private final Set workflowNames = new HashSet<>(); @@ -132,4 +181,347 @@ Optional extractConnectionIdFromWorkflowId(final String workflowId) { stringUUID -> UUID.fromString(stringUUID)); } + @Value + @Builder + public static class ManualOperationResult { + + final Optional failingReason; + final Optional jobId; + final Optional errorCode; + + } + + public ManualOperationResult startNewManualSync(final UUID connectionId) { + log.info("Manual sync request"); + + if (connectionManagerUtils.isWorkflowStateRunning(client, connectionId)) { + // TODO Bmoric: Error is running + return new ManualOperationResult( + Optional.of("A sync is already running for: " + connectionId), + Optional.empty(), Optional.of(ErrorCode.WORKFLOW_RUNNING)); + } + + try { + connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::submitManualSync); + } catch (final DeletedWorkflowException e) { + log.error("Can't sync a deleted connection.", e); + return new ManualOperationResult( + Optional.of(e.getMessage()), + Optional.empty(), Optional.of(ErrorCode.WORKFLOW_DELETED)); + } + + do { + try { + Thread.sleep(DELAY_BETWEEN_QUERY_MS); + } catch (final InterruptedException e) { + return new ManualOperationResult( + Optional.of("Didn't managed to start a sync for: " + connectionId), + Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); + } + } while (!connectionManagerUtils.isWorkflowStateRunning(client, connectionId)); + + log.info("end of manual schedule"); + + final long jobId = connectionManagerUtils.getCurrentJobId(client, connectionId); + + return new ManualOperationResult( + Optional.empty(), + Optional.of(jobId), Optional.empty()); + } + + public ManualOperationResult startNewCancellation(final UUID connectionId) { + log.info("Manual cancellation request"); + + final long jobId = connectionManagerUtils.getCurrentJobId(client, connectionId); + + try { + connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::cancelJob); + } catch (final DeletedWorkflowException e) { + log.error("Can't cancel a deleted workflow", e); + return new ManualOperationResult( + Optional.of(e.getMessage()), + Optional.empty(), Optional.of(ErrorCode.WORKFLOW_DELETED)); + } + + do { + try { + Thread.sleep(DELAY_BETWEEN_QUERY_MS); + } catch (final InterruptedException e) { + return new ManualOperationResult( + Optional.of("Didn't manage to cancel a sync for: " + connectionId), + Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); + } + } while (connectionManagerUtils.isWorkflowStateRunning(client, connectionId)); + + streamResetRecordsHelper.deleteStreamResetRecordsForJob(jobId, connectionId); + + log.info("end of manual cancellation"); + + return new ManualOperationResult( + Optional.empty(), + Optional.of(jobId), Optional.empty()); + } + + public ManualOperationResult resetConnection(final UUID connectionId, + final List streamsToReset, + final boolean syncImmediatelyAfter) { + log.info("reset sync request"); + + try { + streamResetPersistence.createStreamResets(connectionId, streamsToReset); + } catch (final IOException e) { + log.error("Could not persist streams to reset.", e); + return new ManualOperationResult( + Optional.of(e.getMessage()), + Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); + } + + // get the job ID before the reset, defaulting to NON_RUNNING_JOB_ID if workflow is unreachable + final long oldJobId = connectionManagerUtils.getCurrentJobId(client, connectionId); + + try { + if (syncImmediatelyAfter) { + connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::resetConnectionAndSkipNextScheduling); + } else { + connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::resetConnection); + } + } catch (final DeletedWorkflowException e) { + log.error("Can't reset a deleted workflow", e); + return new ManualOperationResult( + Optional.of(e.getMessage()), + Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); + } + + Optional newJobId; + + do { + try { + Thread.sleep(DELAY_BETWEEN_QUERY_MS); + } catch (final InterruptedException e) { + return new ManualOperationResult( + Optional.of("Didn't manage to reset a sync for: " + connectionId), + Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); + } + newJobId = getNewJobId(connectionId, oldJobId); + } while (newJobId.isEmpty()); + + log.info("end of reset submission"); + + return new ManualOperationResult( + Optional.empty(), + newJobId, Optional.empty()); + } + + private Optional getNewJobId(final UUID connectionId, final long oldJobId) { + final long currentJobId = connectionManagerUtils.getCurrentJobId(client, connectionId); + if (currentJobId == NON_RUNNING_JOB_ID || currentJobId == oldJobId) { + return Optional.empty(); + } else { + return Optional.of(currentJobId); + } + } + + public TemporalResponse submitGetSpec(final UUID jobId, final int attempt, final JobGetSpecConfig config) { + final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); + + final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() + .withJobId(jobId.toString()) + .withAttemptId((long) attempt) + .withDockerImage(config.getDockerImage()); + return execute(jobRunConfig, + () -> getWorkflowStub(SpecWorkflow.class, TemporalJobType.GET_SPEC).run(jobRunConfig, launcherConfig)); + + } + + public TemporalResponse submitCheckConnection(final UUID jobId, + final int attempt, + final JobCheckConnectionConfig config) { + final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); + final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() + .withJobId(jobId.toString()) + .withAttemptId((long) attempt) + .withDockerImage(config.getDockerImage()) + .withProtocolVersion(config.getProtocolVersion()); + final StandardCheckConnectionInput input = new StandardCheckConnectionInput().withConnectionConfiguration(config.getConnectionConfiguration()); + + return execute(jobRunConfig, + () -> getWorkflowStub(CheckConnectionWorkflow.class, TemporalJobType.CHECK_CONNECTION).run(jobRunConfig, launcherConfig, input)); + } + + public TemporalResponse submitDiscoverSchema(final UUID jobId, + final int attempt, + final JobDiscoverCatalogConfig config) { + final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); + final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() + .withJobId(jobId.toString()) + .withAttemptId((long) attempt) + .withDockerImage(config.getDockerImage()); + final StandardDiscoverCatalogInput input = new StandardDiscoverCatalogInput().withConnectionConfiguration(config.getConnectionConfiguration()) + .withSourceId(config.getSourceId()).withConnectorVersion(config.getConnectorVersion()).withConfigHash(config.getConfigHash()); + + return execute(jobRunConfig, + () -> getWorkflowStub(DiscoverCatalogWorkflow.class, TemporalJobType.DISCOVER_SCHEMA).run(jobRunConfig, launcherConfig, input)); + } + + public TemporalResponse submitSync(final long jobId, final int attempt, final JobSyncConfig config, final UUID connectionId) { + final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); + + final IntegrationLauncherConfig sourceLauncherConfig = new IntegrationLauncherConfig() + .withJobId(String.valueOf(jobId)) + .withAttemptId((long) attempt) + .withDockerImage(config.getSourceDockerImage()); + + final IntegrationLauncherConfig destinationLauncherConfig = new IntegrationLauncherConfig() + .withJobId(String.valueOf(jobId)) + .withAttemptId((long) attempt) + .withDockerImage(config.getDestinationDockerImage()); + + final StandardSyncInput input = new StandardSyncInput() + .withNamespaceDefinition(config.getNamespaceDefinition()) + .withNamespaceFormat(config.getNamespaceFormat()) + .withPrefix(config.getPrefix()) + .withSourceConfiguration(config.getSourceConfiguration()) + .withDestinationConfiguration(config.getDestinationConfiguration()) + .withOperationSequence(config.getOperationSequence()) + .withCatalog(config.getConfiguredAirbyteCatalog()) + .withState(config.getState()) + .withResourceRequirements(config.getResourceRequirements()) + .withSourceResourceRequirements(config.getSourceResourceRequirements()) + .withDestinationResourceRequirements(config.getDestinationResourceRequirements()); + + return execute(jobRunConfig, + () -> getWorkflowStub(SyncWorkflow.class, TemporalJobType.SYNC).run( + jobRunConfig, + sourceLauncherConfig, + destinationLauncherConfig, + input, + connectionId)); + } + + public void migrateSyncIfNeeded(final Set connectionIds) { + final StopWatch globalMigrationWatch = new StopWatch(); + globalMigrationWatch.start(); + refreshRunningWorkflow(); + + connectionIds.forEach((connectionId) -> { + final StopWatch singleSyncMigrationWatch = new StopWatch(); + singleSyncMigrationWatch.start(); + if (!isInRunningWorkflowCache(connectionManagerUtils.getConnectionManagerName(connectionId))) { + log.info("Migrating: " + connectionId); + try { + submitConnectionUpdaterAsync(connectionId); + } catch (final Exception e) { + log.error("New workflow submission failed, retrying", e); + refreshRunningWorkflow(); + submitConnectionUpdaterAsync(connectionId); + } + } + singleSyncMigrationWatch.stop(); + log.info("Sync migration took: " + singleSyncMigrationWatch.formatTime()); + }); + globalMigrationWatch.stop(); + + log.info("The migration to the new scheduler took: " + globalMigrationWatch.formatTime()); + } + + @VisibleForTesting + TemporalResponse execute(final JobRunConfig jobRunConfig, final Supplier executor) { + final Path jobRoot = TemporalUtils.getJobRoot(workspaceRoot, jobRunConfig); + final Path logPath = TemporalUtils.getLogPath(jobRoot); + + T operationOutput = null; + RuntimeException exception = null; + + try { + operationOutput = executor.get(); + } catch (final RuntimeException e) { + exception = e; + } + + boolean succeeded = exception == null; + if (succeeded && operationOutput instanceof ConnectorJobOutput) { + succeeded = getConnectorJobSucceeded((ConnectorJobOutput) operationOutput); + } + + final JobMetadata metadata = new JobMetadata(succeeded, logPath); + return new TemporalResponse<>(operationOutput, metadata); + } + + private T getWorkflowStub(final Class workflowClass, final TemporalJobType jobType) { + return client.newWorkflowStub(workflowClass, TemporalWorkflowUtils.buildWorkflowOptions(jobType)); + } + + public ConnectionManagerWorkflow submitConnectionUpdaterAsync(final UUID connectionId) { + log.info("Starting the scheduler temporal wf"); + final ConnectionManagerWorkflow connectionManagerWorkflow = + connectionManagerUtils.startConnectionManagerNoSignal(client, connectionId); + try { + CompletableFuture.supplyAsync(() -> { + try { + do { + Thread.sleep(DELAY_BETWEEN_QUERY_MS); + } while (!isWorkflowReachable(connectionId)); + } catch (final InterruptedException e) {} + return null; + }).get(60, TimeUnit.SECONDS); + } catch (final InterruptedException | ExecutionException e) { + log.error("Failed to create a new connection manager workflow", e); + } catch (final TimeoutException e) { + log.error("Can't create a new connection manager workflow due to timeout", e); + } + + return connectionManagerWorkflow; + } + + public void deleteConnection(final UUID connectionId) { + try { + connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, + connectionManagerWorkflow -> connectionManagerWorkflow::deleteConnection); + } catch (final DeletedWorkflowException e) { + log.info("Connection {} has already been deleted.", connectionId); + } + } + + public void update(final UUID connectionId) { + final ConnectionManagerWorkflow connectionManagerWorkflow; + try { + connectionManagerWorkflow = connectionManagerUtils.getConnectionManagerWorkflow(client, connectionId); + } catch (final DeletedWorkflowException e) { + log.info("Connection {} is deleted, and therefore cannot be updated.", connectionId); + return; + } catch (final UnreachableWorkflowException e) { + log.error( + String.format("Failed to retrieve ConnectionManagerWorkflow for connection %s. Repairing state by creating new workflow.", connectionId), + e); + connectionManagerUtils.safeTerminateWorkflow(client, connectionId, + "Terminating workflow in unreachable state before starting a new workflow for this connection"); + submitConnectionUpdaterAsync(connectionId); + return; + } + + connectionManagerWorkflow.connectionUpdated(); + } + + private boolean getConnectorJobSucceeded(final ConnectorJobOutput output) { + return output.getFailureReason() == null; + } + + /** + * Check if a workflow is reachable for signal calls by attempting to query for current state. If + * the query succeeds, and the workflow is not marked as deleted, the workflow is reachable. + */ + @VisibleForTesting + boolean isWorkflowReachable(final UUID connectionId) { + try { + connectionManagerUtils.getConnectionManagerWorkflow(client, connectionId); + return true; + } catch (final Exception e) { + return false; + } + } + + boolean isInRunningWorkflowCache(final String workflowName) { + return workflowNames.contains(workflowName); + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalResponse.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalResponse.java similarity index 97% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalResponse.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalResponse.java index 238f027e85c4..2f659507cae2 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalResponse.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalResponse.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal; +package io.airbyte.commons.temporal; import java.util.Objects; import java.util.Optional; diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalUtils.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalUtils.java index e3824e1fdbb4..8c5700c748ca 100644 --- a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalUtils.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalUtils.java @@ -8,7 +8,9 @@ import com.uber.m3.tally.Scope; import com.uber.m3.tally.StatsReporter; import io.airbyte.commons.lang.Exceptions; +import io.airbyte.config.helpers.LogClientSingleton; import io.airbyte.metrics.lib.MetricClientFactory; +import io.airbyte.persistence.job.models.JobRunConfig; import io.micrometer.core.instrument.MeterRegistry; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Value; @@ -32,6 +34,7 @@ import java.io.InputStream; import java.io.Serializable; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.time.Duration; import java.util.UUID; import java.util.concurrent.Callable; @@ -315,4 +318,24 @@ public T withBackgroundHeartbeat(final AtomicReference afterCancel } } + // todo (cgardens) - there are 2 sources of truth for job path. we need to reduce this down to one, + // once we are fully on temporal. + public static Path getJobRoot(final Path workspaceRoot, final JobRunConfig jobRunConfig) { + return getJobRoot(workspaceRoot, jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); + } + + public static Path getLogPath(final Path jobRoot) { + return jobRoot.resolve(LogClientSingleton.LOG_FILENAME); + } + + public static Path getJobRoot(final Path workspaceRoot, final String jobId, final long attemptId) { + return getJobRoot(workspaceRoot, jobId, Math.toIntExact(attemptId)); + } + + public static Path getJobRoot(final Path workspaceRoot, final String jobId, final int attemptId) { + return workspaceRoot + .resolve(String.valueOf(jobId)) + .resolve(String.valueOf(attemptId)); + } + } diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/TemporalBeanFactory.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/TemporalBeanFactory.java index 2a86687ef532..24eede27ffba 100644 --- a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/TemporalBeanFactory.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/TemporalBeanFactory.java @@ -7,9 +7,12 @@ import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.commons.temporal.TemporalWorkflowUtils; import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Value; import io.temporal.client.WorkflowClient; import io.temporal.serviceclient.WorkflowServiceStubs; +import jakarta.inject.Named; import jakarta.inject.Singleton; +import java.nio.file.Path; /** * Micronaut bean factory for Temporal-related singletons. @@ -29,4 +32,10 @@ public WorkflowClient workflowClient( return TemporalWorkflowUtils.createWorkflowClient(temporalService, temporalUtils.getNamespace()); } + @Singleton + @Named("workspaceRootTemporal") + public Path workspaceRoot(@Value("${airbyte.workspace.root}") final String workspaceRoot) { + return Path.of(workspaceRoot); + } + } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/config/WorkerMode.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/WorkerMode.java similarity index 90% rename from airbyte-commons-worker/src/main/java/io/airbyte/workers/config/WorkerMode.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/WorkerMode.java index 66e164b2fbca..16960c51bb47 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/config/WorkerMode.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/config/WorkerMode.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.config; +package io.airbyte.commons.temporal.config; /** * Defines the different execution modes for the workers application. diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/DeletedWorkflowException.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/DeletedWorkflowException.java similarity index 81% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/DeletedWorkflowException.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/DeletedWorkflowException.java index 8488051066a0..4be081eb4d29 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/DeletedWorkflowException.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/DeletedWorkflowException.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.exception; +package io.airbyte.commons.temporal.exception; public class DeletedWorkflowException extends Exception { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/RetryableException.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/RetryableException.java similarity index 80% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/RetryableException.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/RetryableException.java index 7ef8b178ec85..870f9b4613c4 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/RetryableException.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/RetryableException.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.exception; +package io.airbyte.commons.temporal.exception; public class RetryableException extends RuntimeException { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/UnreachableWorkflowException.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/UnreachableWorkflowException.java similarity index 87% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/UnreachableWorkflowException.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/UnreachableWorkflowException.java index 91e8ed2bd5cb..e7daccf9260e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/exception/UnreachableWorkflowException.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/exception/UnreachableWorkflowException.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.exception; +package io.airbyte.commons.temporal.exception; public class UnreachableWorkflowException extends Exception { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflow.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/CheckConnectionWorkflow.java similarity index 92% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflow.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/CheckConnectionWorkflow.java index 13528fdfeaa6..cff77025e995 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflow.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/CheckConnectionWorkflow.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.check.connection; +package io.airbyte.commons.temporal.scheduling; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.StandardCheckConnectionInput; diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/ConnectionManagerWorkflow.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/ConnectionManagerWorkflow.java index 43597afd7c1c..dff392109b07 100644 --- a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/ConnectionManagerWorkflow.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/ConnectionManagerWorkflow.java @@ -17,6 +17,9 @@ @WorkflowInterface public interface ConnectionManagerWorkflow { + long NON_RUNNING_JOB_ID = -1; + int NON_RUNNING_ATTEMPT_ID = -1; + /** * Workflow method to launch a {@link ConnectionManagerWorkflow}. Launches a workflow responsible * for scheduling syncs. This workflow will run and then continue running until deleted. diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflow.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/DiscoverCatalogWorkflow.java similarity index 92% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflow.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/DiscoverCatalogWorkflow.java index 65a45e7602ee..27e99d0bf0bb 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflow.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/DiscoverCatalogWorkflow.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.discover.catalog; +package io.airbyte.commons.temporal.scheduling; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.StandardDiscoverCatalogInput; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflow.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/SpecWorkflow.java similarity index 91% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflow.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/SpecWorkflow.java index 8f42cbd29b22..025b23ba4acd 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflow.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/SpecWorkflow.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.spec; +package io.airbyte.commons.temporal.scheduling; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflow.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/SyncWorkflow.java similarity index 94% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflow.java rename to airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/SyncWorkflow.java index 6fa9efe3e47e..513974b95534 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflow.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/SyncWorkflow.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.sync; +package io.airbyte.commons.temporal.scheduling; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; diff --git a/airbyte-commons-temporal/src/test/java/io/airbyte/commons/temporal/TemporalClientTest.java b/airbyte-commons-temporal/src/test/java/io/airbyte/commons/temporal/TemporalClientTest.java index 0ddff18dc49b..82b161e5eb05 100644 --- a/airbyte-commons-temporal/src/test/java/io/airbyte/commons/temporal/TemporalClientTest.java +++ b/airbyte-commons-temporal/src/test/java/io/airbyte/commons/temporal/TemporalClientTest.java @@ -4,46 +4,125 @@ package io.airbyte.commons.temporal; +import static io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow.NON_RUNNING_JOB_ID; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.google.common.collect.Sets; +import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; +import io.airbyte.commons.temporal.scheduling.CheckConnectionWorkflow; import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; +import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow.JobInformation; +import io.airbyte.commons.temporal.scheduling.DiscoverCatalogWorkflow; +import io.airbyte.commons.temporal.scheduling.SpecWorkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; +import io.airbyte.commons.temporal.scheduling.state.WorkflowState; +import io.airbyte.config.ConnectorJobOutput; +import io.airbyte.config.FailureReason; +import io.airbyte.config.JobCheckConnectionConfig; +import io.airbyte.config.JobDiscoverCatalogConfig; +import io.airbyte.config.JobGetSpecConfig; +import io.airbyte.config.JobSyncConfig; +import io.airbyte.config.StandardCheckConnectionInput; +import io.airbyte.config.StandardDiscoverCatalogInput; +import io.airbyte.config.StandardSyncInput; +import io.airbyte.config.helpers.LogClientSingleton; +import io.airbyte.config.persistence.StreamResetPersistence; +import io.airbyte.persistence.job.models.IntegrationLauncherConfig; +import io.airbyte.persistence.job.models.JobRunConfig; +import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; +import io.airbyte.protocol.models.StreamDescriptor; import io.temporal.api.enums.v1.WorkflowExecutionStatus; import io.temporal.api.workflow.v1.WorkflowExecutionInfo; import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionResponse; -import io.temporal.api.workflowservice.v1.WorkflowServiceGrpc; +import io.temporal.api.workflowservice.v1.WorkflowServiceGrpc.WorkflowServiceBlockingStub; +import io.temporal.client.BatchRequest; import io.temporal.client.WorkflowClient; import io.temporal.client.WorkflowClientOptions; +import io.temporal.client.WorkflowOptions; +import io.temporal.client.WorkflowStub; import io.temporal.serviceclient.WorkflowServiceStubs; +import io.temporal.workflow.Functions.Proc; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; import java.util.Set; import java.util.UUID; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +@SuppressWarnings("PMD.JUnit5TestShouldBePackagePrivate") public class TemporalClientTest { + private static final UUID CONNECTION_ID = UUID.randomUUID(); + private static final UUID JOB_UUID = UUID.randomUUID(); + private static final long JOB_ID = 11L; + private static final int ATTEMPT_ID = 21; + private static final JobRunConfig JOB_RUN_CONFIG = new JobRunConfig() + .withJobId(String.valueOf(JOB_ID)) + .withAttemptId((long) ATTEMPT_ID); + private static final String IMAGE_NAME1 = "hms invincible"; + private static final String IMAGE_NAME2 = "hms defiant"; + private static final IntegrationLauncherConfig UUID_LAUNCHER_CONFIG = new IntegrationLauncherConfig() + .withJobId(String.valueOf(JOB_UUID)) + .withAttemptId((long) ATTEMPT_ID) + .withDockerImage(IMAGE_NAME1); + private static final IntegrationLauncherConfig LAUNCHER_CONFIG = new IntegrationLauncherConfig() + .withJobId(String.valueOf(JOB_ID)) + .withAttemptId((long) ATTEMPT_ID) + .withDockerImage(IMAGE_NAME1); private static final String NAMESPACE = "namespace"; + private static final StreamDescriptor STREAM_DESCRIPTOR = new StreamDescriptor().withName("name"); + private static final String UNCHECKED = "unchecked"; + private static final String EXCEPTION_MESSAGE = "Force state exception to simulate workflow not running"; private WorkflowClient workflowClient; private TemporalClient temporalClient; + private Path logPath; private WorkflowServiceStubs workflowServiceStubs; - private WorkflowServiceGrpc.WorkflowServiceBlockingStub workflowServiceBlockingStub; + private WorkflowServiceBlockingStub workflowServiceBlockingStub; + private StreamResetPersistence streamResetPersistence; + private ConnectionManagerUtils connectionManagerUtils; + private StreamResetRecordsHelper streamResetRecordsHelper; + private Path workspaceRoot; @BeforeEach - void setup() { + void setup() throws IOException { + workspaceRoot = Files.createTempDirectory(Path.of("/tmp"), "temporal_client_test"); + logPath = workspaceRoot.resolve(String.valueOf(JOB_ID)).resolve(String.valueOf(ATTEMPT_ID)).resolve(LogClientSingleton.LOG_FILENAME); workflowClient = mock(WorkflowClient.class); when(workflowClient.getOptions()).thenReturn(WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); - workflowServiceStubs = mock(WorkflowServiceStubs.class); - workflowServiceBlockingStub = mock(WorkflowServiceGrpc.WorkflowServiceBlockingStub.class); + when(workflowClient.getWorkflowServiceStubs()).thenReturn(workflowServiceStubs); + workflowServiceBlockingStub = mock(WorkflowServiceBlockingStub.class); when(workflowServiceStubs.blockingStub()).thenReturn(workflowServiceBlockingStub); + streamResetPersistence = mock(StreamResetPersistence.class); + mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_RUNNING); + connectionManagerUtils = spy(new ConnectionManagerUtils()); + streamResetRecordsHelper = mock(StreamResetRecordsHelper.class); + temporalClient = + spy(new TemporalClient(workspaceRoot, workflowClient, workflowServiceStubs, streamResetPersistence, connectionManagerUtils, + streamResetRecordsHelper)); } @Nested @@ -52,11 +131,12 @@ class RestartPerStatus { private ConnectionManagerUtils mConnectionManagerUtils; @BeforeEach - public void init() { + void init() { mConnectionManagerUtils = mock(ConnectionManagerUtils.class); temporalClient = spy( - new TemporalClient(workflowClient, workflowServiceStubs, mConnectionManagerUtils)); + new TemporalClient(workspaceRoot, workflowClient, workflowServiceStubs, streamResetPersistence, mConnectionManagerUtils, + streamResetRecordsHelper)); } @Test @@ -80,6 +160,713 @@ void testRestartFailed() { } + @Nested + @DisplayName("Test execute method.") + class ExecuteJob { + + @SuppressWarnings(UNCHECKED) + @Test + void testExecute() { + final Supplier supplier = mock(Supplier.class); + when(supplier.get()).thenReturn("hello"); + + final TemporalResponse response = temporalClient.execute(JOB_RUN_CONFIG, supplier); + + assertNotNull(response); + assertTrue(response.getOutput().isPresent()); + assertEquals("hello", response.getOutput().get()); + assertTrue(response.getMetadata().isSucceeded()); + assertEquals(logPath, response.getMetadata().getLogPath()); + } + + @SuppressWarnings(UNCHECKED) + @Test + void testExecuteWithException() { + final Supplier supplier = mock(Supplier.class); + when(supplier.get()).thenThrow(IllegalStateException.class); + + final TemporalResponse response = temporalClient.execute(JOB_RUN_CONFIG, supplier); + + assertNotNull(response); + assertFalse(response.getOutput().isPresent()); + assertFalse(response.getMetadata().isSucceeded()); + assertEquals(logPath, response.getMetadata().getLogPath()); + } + + @Test + void testExecuteWithConnectorJobFailure() { + final Supplier supplier = mock(Supplier.class); + final FailureReason mockFailureReason = mock(FailureReason.class); + final ConnectorJobOutput connectorJobOutput = new ConnectorJobOutput() + .withFailureReason(mockFailureReason); + when(supplier.get()).thenReturn(connectorJobOutput); + + final TemporalResponse response = temporalClient.execute(JOB_RUN_CONFIG, supplier); + + assertNotNull(response); + assertTrue(response.getOutput().isPresent()); + assertEquals(connectorJobOutput, response.getOutput().get()); + assertFalse(response.getMetadata().isSucceeded()); + assertEquals(logPath, response.getMetadata().getLogPath()); + } + + } + + @Nested + @DisplayName("Test job creation for each configuration type.") + class TestJobSubmission { + + @Test + void testSubmitGetSpec() { + final SpecWorkflow specWorkflow = mock(SpecWorkflow.class); + when(workflowClient.newWorkflowStub(SpecWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.GET_SPEC))) + .thenReturn(specWorkflow); + final JobGetSpecConfig getSpecConfig = new JobGetSpecConfig().withDockerImage(IMAGE_NAME1); + + temporalClient.submitGetSpec(JOB_UUID, ATTEMPT_ID, getSpecConfig); + specWorkflow.run(JOB_RUN_CONFIG, UUID_LAUNCHER_CONFIG); + verify(workflowClient).newWorkflowStub(SpecWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.GET_SPEC)); + } + + @Test + void testSubmitCheckConnection() { + final CheckConnectionWorkflow checkConnectionWorkflow = mock(CheckConnectionWorkflow.class); + when( + workflowClient.newWorkflowStub(CheckConnectionWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.CHECK_CONNECTION))) + .thenReturn(checkConnectionWorkflow); + final JobCheckConnectionConfig checkConnectionConfig = new JobCheckConnectionConfig() + .withDockerImage(IMAGE_NAME1) + .withConnectionConfiguration(Jsons.emptyObject()); + final StandardCheckConnectionInput input = new StandardCheckConnectionInput() + .withConnectionConfiguration(checkConnectionConfig.getConnectionConfiguration()); + + temporalClient.submitCheckConnection(JOB_UUID, ATTEMPT_ID, checkConnectionConfig); + checkConnectionWorkflow.run(JOB_RUN_CONFIG, UUID_LAUNCHER_CONFIG, input); + verify(workflowClient).newWorkflowStub(CheckConnectionWorkflow.class, + TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.CHECK_CONNECTION)); + } + + @Test + void testSubmitDiscoverSchema() { + final DiscoverCatalogWorkflow discoverCatalogWorkflow = mock(DiscoverCatalogWorkflow.class); + when(workflowClient.newWorkflowStub(DiscoverCatalogWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.DISCOVER_SCHEMA))) + .thenReturn(discoverCatalogWorkflow); + final JobDiscoverCatalogConfig checkConnectionConfig = new JobDiscoverCatalogConfig() + .withDockerImage(IMAGE_NAME1) + .withConnectionConfiguration(Jsons.emptyObject()); + final StandardDiscoverCatalogInput input = new StandardDiscoverCatalogInput() + .withConnectionConfiguration(checkConnectionConfig.getConnectionConfiguration()); + + temporalClient.submitDiscoverSchema(JOB_UUID, ATTEMPT_ID, checkConnectionConfig); + discoverCatalogWorkflow.run(JOB_RUN_CONFIG, UUID_LAUNCHER_CONFIG, input); + verify(workflowClient).newWorkflowStub(DiscoverCatalogWorkflow.class, + TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.DISCOVER_SCHEMA)); + } + + @Test + void testSubmitSync() { + final SyncWorkflow discoverCatalogWorkflow = mock(SyncWorkflow.class); + when(workflowClient.newWorkflowStub(SyncWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.SYNC))) + .thenReturn(discoverCatalogWorkflow); + final JobSyncConfig syncConfig = new JobSyncConfig() + .withSourceDockerImage(IMAGE_NAME1) + .withSourceDockerImage(IMAGE_NAME2) + .withSourceConfiguration(Jsons.emptyObject()) + .withDestinationConfiguration(Jsons.emptyObject()) + .withOperationSequence(List.of()) + .withConfiguredAirbyteCatalog(new ConfiguredAirbyteCatalog()); + final StandardSyncInput input = new StandardSyncInput() + .withNamespaceDefinition(syncConfig.getNamespaceDefinition()) + .withNamespaceFormat(syncConfig.getNamespaceFormat()) + .withPrefix(syncConfig.getPrefix()) + .withSourceConfiguration(syncConfig.getSourceConfiguration()) + .withDestinationConfiguration(syncConfig.getDestinationConfiguration()) + .withOperationSequence(syncConfig.getOperationSequence()) + .withCatalog(syncConfig.getConfiguredAirbyteCatalog()) + .withState(syncConfig.getState()); + + final IntegrationLauncherConfig destinationLauncherConfig = new IntegrationLauncherConfig() + .withJobId(String.valueOf(JOB_ID)) + .withAttemptId((long) ATTEMPT_ID) + .withDockerImage(IMAGE_NAME2); + + temporalClient.submitSync(JOB_ID, ATTEMPT_ID, syncConfig, CONNECTION_ID); + discoverCatalogWorkflow.run(JOB_RUN_CONFIG, LAUNCHER_CONFIG, destinationLauncherConfig, input, CONNECTION_ID); + verify(workflowClient).newWorkflowStub(SyncWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.SYNC)); + } + + } + + @Nested + @DisplayName("Test related to the migration to the new scheduler") + class TestMigration { + + @DisplayName("Test that the migration is properly done if needed") + @Test + void migrateCalled() { + final UUID nonMigratedId = UUID.randomUUID(); + final UUID migratedId = UUID.randomUUID(); + + when(temporalClient.isInRunningWorkflowCache(connectionManagerUtils.getConnectionManagerName(nonMigratedId))).thenReturn(false); + when(temporalClient.isInRunningWorkflowCache(connectionManagerUtils.getConnectionManagerName(migratedId))).thenReturn(true); + + doNothing() + .when(temporalClient).refreshRunningWorkflow(); + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + doReturn(mConnectionManagerWorkflow) + .when(temporalClient).submitConnectionUpdaterAsync(nonMigratedId); + + temporalClient.migrateSyncIfNeeded(Sets.newHashSet(nonMigratedId, migratedId)); + + verify(temporalClient, times(1)).submitConnectionUpdaterAsync(nonMigratedId); + verify(temporalClient, times(0)).submitConnectionUpdaterAsync(migratedId); + } + + } + + @Nested + @DisplayName("Test delete connection method.") + class DeleteConnection { + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test delete connection method when workflow is in a running state.") + void testDeleteConnection() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + + doReturn(true).when(temporalClient).isWorkflowReachable(any(UUID.class)); + when(workflowClient.newWorkflowStub(any(Class.class), anyString())).thenReturn(mConnectionManagerWorkflow); + + final JobSyncConfig syncConfig = new JobSyncConfig() + .withSourceDockerImage(IMAGE_NAME1) + .withSourceDockerImage(IMAGE_NAME2) + .withSourceConfiguration(Jsons.emptyObject()) + .withDestinationConfiguration(Jsons.emptyObject()) + .withOperationSequence(List.of()) + .withConfiguredAirbyteCatalog(new ConfiguredAirbyteCatalog()); + + temporalClient.submitSync(JOB_ID, ATTEMPT_ID, syncConfig, CONNECTION_ID); + temporalClient.deleteConnection(CONNECTION_ID); + + verify(workflowClient, Mockito.never()).newSignalWithStartRequest(); + verify(mConnectionManagerWorkflow).deleteConnection(); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test delete connection method when workflow is in an unexpected state") + void testDeleteConnectionInUnexpectedState() { + final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + when(mTerminatedConnectionManagerWorkflow.getState()) + .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); + when(workflowClient.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(mTerminatedConnectionManagerWorkflow); + + final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); + final BatchRequest mBatchRequest = mock(BatchRequest.class); + when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); + + temporalClient.deleteConnection(CONNECTION_ID); + verify(workflowClient).signalWithStart(mBatchRequest); + + // Verify that the deleteConnection signal was passed to the batch request by capturing the + // argument, + // executing the signal, and verifying that the desired signal was executed + final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); + verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); + final Proc signal = batchRequestAddArgCaptor.getValue(); + signal.apply(); + verify(mNewConnectionManagerWorkflow).deleteConnection(); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test delete connection method when workflow has already been deleted") + void testDeleteConnectionOnDeletedWorkflow() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(true); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); + + temporalClient.deleteConnection(CONNECTION_ID); + + verify(temporalClient).deleteConnection(CONNECTION_ID); + verifyNoMoreInteractions(temporalClient); + } + + } + + @Nested + @DisplayName("Test update connection behavior") + class UpdateConnection { + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test update connection when workflow is running") + void testUpdateConnection() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + + when(mWorkflowState.isRunning()).thenReturn(true); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(workflowClient.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(mConnectionManagerWorkflow); + + temporalClient.update(CONNECTION_ID); + + verify(mConnectionManagerWorkflow, Mockito.times(1)).connectionUpdated(); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test update connection method starts a new workflow when workflow is in an unexpected state") + void testUpdateConnectionInUnexpectedState() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + + when(mConnectionManagerWorkflow.getState()).thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); + when(workflowClient.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(mConnectionManagerWorkflow); + doReturn(mConnectionManagerWorkflow).when(temporalClient).submitConnectionUpdaterAsync(CONNECTION_ID); + + final WorkflowStub untypedWorkflowStub = mock(WorkflowStub.class); + when(workflowClient.newUntypedWorkflowStub(anyString())).thenReturn(untypedWorkflowStub); + + temporalClient.update(CONNECTION_ID); + + // this is only called when updating an existing workflow + verify(mConnectionManagerWorkflow, Mockito.never()).connectionUpdated(); + + verify(untypedWorkflowStub, Mockito.times(1)).terminate(anyString()); + verify(temporalClient, Mockito.times(1)).submitConnectionUpdaterAsync(CONNECTION_ID); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test update connection method does nothing when connection is deleted") + void testUpdateConnectionDeletedWorkflow() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(true); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); + + temporalClient.update(CONNECTION_ID); + + // this is only called when updating an existing workflow + verify(mConnectionManagerWorkflow, Mockito.never()).connectionUpdated(); + verify(temporalClient).update(CONNECTION_ID); + verifyNoMoreInteractions(temporalClient); + } + + } + + @Nested + @DisplayName("Test manual sync behavior") + class ManualSync { + + @Test + @DisplayName("Test startNewManualSync successful") + void testStartNewManualSyncSuccess() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(false).thenReturn(true); + when(mConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + + final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); + + assertTrue(result.getJobId().isPresent()); + assertEquals(JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow).submitManualSync(); + } + + @Test + @DisplayName("Test startNewManualSync fails if job is already running") + void testStartNewManualSyncAlreadyRunning() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(true); + when(mConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + + final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); + + assertFalse(result.getJobId().isPresent()); + assertTrue(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow, times(0)).submitManualSync(); + } + + @Test + @DisplayName("Test startNewManualSync repairs the workflow if it is in a bad state") + void testStartNewManualSyncRepairsBadWorkflowState() { + final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + when(mTerminatedConnectionManagerWorkflow.getState()) + .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); + when(mTerminatedConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + + final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mNewConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(false).thenReturn(true); + when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); + final BatchRequest mBatchRequest = mock(BatchRequest.class); + when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); + + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mTerminatedConnectionManagerWorkflow, mTerminatedConnectionManagerWorkflow, + mNewConnectionManagerWorkflow); + + final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); + + assertTrue(result.getJobId().isPresent()); + assertEquals(JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(workflowClient).signalWithStart(mBatchRequest); + + // Verify that the submitManualSync signal was passed to the batch request by capturing the + // argument, + // executing the signal, and verifying that the desired signal was executed + final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); + verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); + final Proc signal = batchRequestAddArgCaptor.getValue(); + signal.apply(); + verify(mNewConnectionManagerWorkflow).submitManualSync(); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test startNewManualSync returns a failure reason when connection is deleted") + void testStartNewManualSyncDeletedWorkflow() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(true); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); + + final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); + + // this is only called when updating an existing workflow + assertFalse(result.getJobId().isPresent()); + assertTrue(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow, times(0)).submitManualSync(); + } + + } + + @Nested + @DisplayName("Test cancellation behavior") + class Cancellation { + + @Test + @DisplayName("Test startNewCancellation successful") + void testStartNewCancellationSuccess() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(true).thenReturn(false); + when(mConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + + final ManualOperationResult result = temporalClient.startNewCancellation(CONNECTION_ID); + + assertTrue(result.getJobId().isPresent()); + assertEquals(JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow).cancelJob(); + verify(streamResetRecordsHelper).deleteStreamResetRecordsForJob(JOB_ID, CONNECTION_ID); + } + + @Test + @DisplayName("Test startNewCancellation repairs the workflow if it is in a bad state") + void testStartNewCancellationRepairsBadWorkflowState() { + final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + when(mTerminatedConnectionManagerWorkflow.getState()) + .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); + when(mTerminatedConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + + final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mNewConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(true).thenReturn(false); + when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); + final BatchRequest mBatchRequest = mock(BatchRequest.class); + when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); + + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mTerminatedConnectionManagerWorkflow, mTerminatedConnectionManagerWorkflow, + mNewConnectionManagerWorkflow); + + final ManualOperationResult result = temporalClient.startNewCancellation(CONNECTION_ID); + + assertTrue(result.getJobId().isPresent()); + assertEquals(NON_RUNNING_JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(workflowClient).signalWithStart(mBatchRequest); + + // Verify that the cancelJob signal was passed to the batch request by capturing the argument, + // executing the signal, and verifying that the desired signal was executed + final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); + verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); + final Proc signal = batchRequestAddArgCaptor.getValue(); + signal.apply(); + verify(mNewConnectionManagerWorkflow).cancelJob(); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test startNewCancellation returns a failure reason when connection is deleted") + void testStartNewCancellationDeletedWorkflow() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(true); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); + + final ManualOperationResult result = temporalClient.startNewCancellation(CONNECTION_ID); + + // this is only called when updating an existing workflow + assertFalse(result.getJobId().isPresent()); + assertTrue(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow, times(0)).cancelJob(); + } + + } + + @Nested + @DisplayName("Test reset connection behavior") + class ResetConnection { + + @Test + @DisplayName("Test resetConnection successful") + void testResetConnectionSuccess() throws IOException { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(false); + final long jobId1 = 1; + final long jobId2 = 2; + when(mConnectionManagerWorkflow.getJobInformation()).thenReturn( + new JobInformation(jobId1, 0), + new JobInformation(jobId1, 0), + new JobInformation(NON_RUNNING_JOB_ID, 0), + new JobInformation(NON_RUNNING_JOB_ID, 0), + new JobInformation(jobId2, 0), + new JobInformation(jobId2, 0)); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + + final List streamsToReset = List.of(STREAM_DESCRIPTOR); + final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, false); + + verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); + + assertTrue(result.getJobId().isPresent()); + assertEquals(jobId2, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow).resetConnection(); + } + + @Test + @DisplayName("Test resetConnection successful") + void testResetConnectionSuccessAndContinue() throws IOException { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(false); + final long jobId1 = 1; + final long jobId2 = 2; + when(mConnectionManagerWorkflow.getJobInformation()).thenReturn( + new JobInformation(jobId1, 0), + new JobInformation(jobId1, 0), + new JobInformation(NON_RUNNING_JOB_ID, 0), + new JobInformation(NON_RUNNING_JOB_ID, 0), + new JobInformation(jobId2, 0), + new JobInformation(jobId2, 0)); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + + final List streamsToReset = List.of(STREAM_DESCRIPTOR); + final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, true); + + verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); + + assertTrue(result.getJobId().isPresent()); + assertEquals(jobId2, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow).resetConnectionAndSkipNextScheduling(); + } + + @Test + @DisplayName("Test resetConnection repairs the workflow if it is in a bad state") + void testResetConnectionRepairsBadWorkflowState() throws IOException { + final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + when(mTerminatedConnectionManagerWorkflow.getState()) + .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); + when(mTerminatedConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + + final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mNewConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(mWorkflowState.isRunning()).thenReturn(false); + when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn( + new JobInformation(NON_RUNNING_JOB_ID, 0), + new JobInformation(NON_RUNNING_JOB_ID, 0), + new JobInformation(JOB_ID, 0), + new JobInformation(JOB_ID, 0)); + when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); + final BatchRequest mBatchRequest = mock(BatchRequest.class); + when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); + + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mTerminatedConnectionManagerWorkflow, mTerminatedConnectionManagerWorkflow, + mNewConnectionManagerWorkflow); + + final List streamsToReset = List.of(STREAM_DESCRIPTOR); + final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, false); + + verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); + + assertTrue(result.getJobId().isPresent()); + assertEquals(JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(workflowClient).signalWithStart(mBatchRequest); + + // Verify that the resetConnection signal was passed to the batch request by capturing the argument, + // executing the signal, and verifying that the desired signal was executed + final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); + verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); + final Proc signal = batchRequestAddArgCaptor.getValue(); + signal.apply(); + verify(mNewConnectionManagerWorkflow).resetConnection(); + } + + @Test + @SuppressWarnings(UNCHECKED) + @DisplayName("Test resetConnection returns a failure reason when connection is deleted") + void testResetConnectionDeletedWorkflow() throws IOException { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isDeleted()).thenReturn(true); + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); + mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); + + final List streamsToReset = List.of(STREAM_DESCRIPTOR); + final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, false); + + verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); + + // this is only called when updating an existing workflow + assertFalse(result.getJobId().isPresent()); + assertTrue(result.getFailingReason().isPresent()); + verify(mConnectionManagerWorkflow, times(0)).resetConnection(); + } + + } + + @Test + @DisplayName("Test manual operation on quarantined workflow causes a restart") + void testManualOperationOnQuarantinedWorkflow() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isQuarantined()).thenReturn(true); + + final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mNewWorkflowState = mock(WorkflowState.class); + when(mNewConnectionManagerWorkflow.getState()).thenReturn(mNewWorkflowState); + when(mNewWorkflowState.isRunning()).thenReturn(false).thenReturn(true); + when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); + final BatchRequest mBatchRequest = mock(BatchRequest.class); + when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); + + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow, mConnectionManagerWorkflow, + mNewConnectionManagerWorkflow); + + final WorkflowStub mWorkflowStub = mock(WorkflowStub.class); + when(workflowClient.newUntypedWorkflowStub(anyString())).thenReturn(mWorkflowStub); + + final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); + + assertTrue(result.getJobId().isPresent()); + assertEquals(JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(workflowClient).signalWithStart(mBatchRequest); + verify(mWorkflowStub).terminate(anyString()); + + // Verify that the submitManualSync signal was passed to the batch request by capturing the + // argument, + // executing the signal, and verifying that the desired signal was executed + final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); + verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); + final Proc signal = batchRequestAddArgCaptor.getValue(); + signal.apply(); + verify(mNewConnectionManagerWorkflow).submitManualSync(); + } + + @Test + @DisplayName("Test manual operation on completed workflow causes a restart") + void testManualOperationOnCompletedWorkflow() { + final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mWorkflowState = mock(WorkflowState.class); + when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); + when(mWorkflowState.isQuarantined()).thenReturn(false); + when(mWorkflowState.isDeleted()).thenReturn(false); + when(workflowServiceBlockingStub.describeWorkflowExecution(any())) + .thenReturn(DescribeWorkflowExecutionResponse.newBuilder().setWorkflowExecutionInfo( + WorkflowExecutionInfo.newBuilder().setStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED).buildPartial()).build()) + .thenReturn(DescribeWorkflowExecutionResponse.newBuilder().setWorkflowExecutionInfo( + WorkflowExecutionInfo.newBuilder().setStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_RUNNING).buildPartial()).build()); + + final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); + final WorkflowState mNewWorkflowState = mock(WorkflowState.class); + when(mNewConnectionManagerWorkflow.getState()).thenReturn(mNewWorkflowState); + when(mNewWorkflowState.isRunning()).thenReturn(false).thenReturn(true); + when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); + when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); + final BatchRequest mBatchRequest = mock(BatchRequest.class); + when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); + + when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow, mConnectionManagerWorkflow, + mNewConnectionManagerWorkflow); + + final WorkflowStub mWorkflowStub = mock(WorkflowStub.class); + when(workflowClient.newUntypedWorkflowStub(anyString())).thenReturn(mWorkflowStub); + + final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); + + assertTrue(result.getJobId().isPresent()); + assertEquals(JOB_ID, result.getJobId().get()); + assertFalse(result.getFailingReason().isPresent()); + verify(workflowClient).signalWithStart(mBatchRequest); + verify(mWorkflowStub).terminate(anyString()); + + // Verify that the submitManualSync signal was passed to the batch request by capturing the + // argument, + // executing the signal, and verifying that the desired signal was executed + final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); + verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); + final Proc signal = batchRequestAddArgCaptor.getValue(); + signal.apply(); + verify(mNewConnectionManagerWorkflow).submitManualSync(); + } + private void mockWorkflowStatus(final WorkflowExecutionStatus status) { when(workflowServiceBlockingStub.describeWorkflowExecution(any())).thenReturn( DescribeWorkflowExecutionResponse.newBuilder().setWorkflowExecutionInfo( diff --git a/airbyte-commons-worker/build.gradle b/airbyte-commons-worker/build.gradle index b0fde55849bb..0cf067e0e1c4 100644 --- a/airbyte-commons-worker/build.gradle +++ b/airbyte-commons-worker/build.gradle @@ -1,8 +1,5 @@ -import org.jsonschema2pojo.SourceType - plugins { id "java-library" - id 'com.github.eirnym.js2p' version '1.0' } dependencies { @@ -17,13 +14,16 @@ dependencies { implementation 'org.apache.ant:ant:1.10.10' implementation 'org.apache.commons:commons-text:1.9' + implementation project(':airbyte-api') implementation project(':airbyte-commons-protocol') implementation project(':airbyte-commons-temporal') implementation project(':airbyte-config:config-models') + implementation project(':airbyte-config:config-persistence') implementation project(':airbyte-json-validation') implementation project(':airbyte-metrics:metrics-lib') implementation project(':airbyte-persistence:job-persistence') implementation project(':airbyte-protocol:protocol-models') + implementation project(':airbyte-worker-models') testAnnotationProcessor platform(libs.micronaut.bom) testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor @@ -38,18 +38,4 @@ dependencies { testImplementation project(':airbyte-commons-docker') } -jsonSchema2Pojo { - sourceType = SourceType.YAMLSCHEMA - source = files("${sourceSets.main.output.resourcesDir}/workers_models") - targetDirectory = new File(project.buildDir, 'generated/src/gen/java/') - removeOldOutput = true - - targetPackage = 'io.airbyte.persistence.job.models' - - useLongIntegers = true - generateBuilders = true - includeConstructors = false - includeSetters = true -} - Task publishArtifactsTask = getPublishArtifactsTask("$rootProject.ext.version", project) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerUtils.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerUtils.java index 5c580417bc20..81da1aab53ec 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerUtils.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/WorkerUtils.java @@ -11,15 +11,12 @@ import io.airbyte.config.StandardSyncInput; import io.airbyte.config.WorkerDestinationConfig; import io.airbyte.config.WorkerSourceConfig; -import io.airbyte.config.helpers.LogClientSingleton; -import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteMessage.Type; import io.airbyte.protocol.models.AirbyteTraceMessage; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.helper.FailureHelper; import io.airbyte.workers.helper.FailureHelper.ConnectorCommand; -import java.nio.file.Path; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -149,24 +146,4 @@ public static String streamNameWithNamespace(final @Nullable String namespace, f return Objects.toString(namespace, "").trim() + streamName.trim(); } - // todo (cgardens) - there are 2 sources of truth for job path. we need to reduce this down to one, - // once we are fully on temporal. - public static Path getJobRoot(final Path workspaceRoot, final JobRunConfig jobRunConfig) { - return getJobRoot(workspaceRoot, jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); - } - - public static Path getLogPath(final Path jobRoot) { - return jobRoot.resolve(LogClientSingleton.LOG_FILENAME); - } - - public static Path getJobRoot(final Path workspaceRoot, final String jobId, final long attemptId) { - return getJobRoot(workspaceRoot, jobId, Math.toIntExact(attemptId)); - } - - public static Path getJobRoot(final Path workspaceRoot, final String jobId, final int attemptId) { - return workspaceRoot - .resolve(String.valueOf(jobId)) - .resolve(String.valueOf(attemptId)); - } - } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java similarity index 99% rename from airbyte-workers/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java index 3232b25abbec..628b2b2f070f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ConnectionHelper.java @@ -7,6 +7,7 @@ import com.google.common.base.Preconditions; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.BasicSchedule; import io.airbyte.config.JobSyncConfig.NamespaceDefinitionType; import io.airbyte.config.Schedule; @@ -17,7 +18,6 @@ import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.persistence.job.WorkspaceHelper; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.config.WorkerMode; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.io.IOException; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/helper/ProtocolConverters.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ProtocolConverters.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/helper/ProtocolConverters.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/ProtocolConverters.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/helper/StateConverter.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/StateConverter.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/helper/StateConverter.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/StateConverter.java diff --git a/airbyte-container-orchestrator/build.gradle b/airbyte-container-orchestrator/build.gradle index 98c4b42bf3f1..e22f5217a038 100644 --- a/airbyte-container-orchestrator/build.gradle +++ b/airbyte-container-orchestrator/build.gradle @@ -21,6 +21,7 @@ dependencies { implementation project(':airbyte-protocol:protocol-models') implementation project(':airbyte-persistence:job-persistence') implementation project(':airbyte-metrics:metrics-lib') + implementation project(':airbyte-worker-models') testImplementation 'org.mockito:mockito-inline:2.13.0' testImplementation libs.postgresql diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java index 1d22e8af5f6b..22f82426b494 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ContainerOrchestratorApp.java @@ -8,13 +8,13 @@ import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.logging.LoggingHelper; import io.airbyte.commons.logging.MdcScope; +import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.commons.temporal.sync.OrchestratorConstants; import io.airbyte.config.Configs; import io.airbyte.config.EnvConfigs; import io.airbyte.config.helpers.LogClientSingleton; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.WorkerConfigs; -import io.airbyte.workers.WorkerUtils; import io.airbyte.workers.process.AsyncKubePodStatus; import io.airbyte.workers.process.AsyncOrchestratorPodProcess; import io.airbyte.workers.process.DockerProcessFactory; @@ -99,7 +99,7 @@ private void configureLogging() { logClient.setJobMdc( configs.getWorkerEnvironment(), configs.getLogConfigs(), - WorkerUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId())); + TemporalUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId())); } /** diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java index b8c659e566f2..589bbb23d5ff 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java @@ -4,12 +4,12 @@ package io.airbyte.container_orchestrator; +import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.Configs; import io.airbyte.config.OperatorDbtInput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.WorkerConfigs; -import io.airbyte.workers.WorkerUtils; import io.airbyte.workers.general.DbtTransformationRunner; import io.airbyte.workers.general.DbtTransformationWorker; import io.airbyte.workers.normalization.NormalizationRunnerFactory; @@ -64,7 +64,7 @@ public Optional runJob() throws Exception { NormalizationRunnerFactory.NORMALIZATION_VERSION))); log.info("Running dbt worker..."); - final Path jobRoot = WorkerUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); + final Path jobRoot = TemporalUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); worker.run(dbtInput, jobRoot); return Optional.empty(); diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java index bf3387a1a11c..34116b594601 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java @@ -5,12 +5,12 @@ package io.airbyte.container_orchestrator; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.Configs; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.WorkerUtils; import io.airbyte.workers.general.DefaultNormalizationWorker; import io.airbyte.workers.normalization.NormalizationRunnerFactory; import io.airbyte.workers.normalization.NormalizationWorker; @@ -62,7 +62,7 @@ public Optional runJob() throws Exception { configs.getWorkerEnvironment()); log.info("Running normalization worker..."); - final Path jobRoot = WorkerUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); + final Path jobRoot = TemporalUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); final NormalizationSummary normalizationSummary = normalizationWorker.run(normalizationInput, jobRoot); return Optional.of(Jsons.serialize(normalizationSummary)); diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java index 728851a740a0..4c71979210ee 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java @@ -6,6 +6,7 @@ import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.Configs; import io.airbyte.config.ReplicationOutput; import io.airbyte.config.StandardSyncInput; @@ -112,7 +113,7 @@ public Optional runJob() throws Exception { metricReporter); log.info("Running replication worker..."); - final Path jobRoot = WorkerUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); + final Path jobRoot = TemporalUtils.getJobRoot(configs.getWorkspaceRoot(), jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); final ReplicationOutput replicationOutput = replicationWorker.run(syncInput, jobRoot); log.info("Returning output..."); diff --git a/airbyte-cron/build.gradle b/airbyte-cron/build.gradle index c718f52bab29..548c5d13efb4 100644 --- a/airbyte-cron/build.gradle +++ b/airbyte-cron/build.gradle @@ -18,6 +18,7 @@ dependencies { implementation project(':airbyte-json-validation') implementation project(':airbyte-db:db-lib') implementation project(':airbyte-metrics:metrics-lib') + implementation project(':airbyte-persistence:job-persistence') annotationProcessor platform(libs.micronaut.bom) annotationProcessor libs.bundles.micronaut.annotation.processor diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java b/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java index e546dc6acde7..d3ecc3a2de9d 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java @@ -4,14 +4,19 @@ package io.airbyte.cron.config; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; +import io.airbyte.config.persistence.StreamResetPersistence; import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.Database; import io.airbyte.db.check.DatabaseMigrationCheck; import io.airbyte.db.factory.DatabaseCheckFactory; +import io.airbyte.persistence.job.DefaultJobPersistence; +import io.airbyte.persistence.job.JobPersistence; import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Requires; import io.micronaut.context.annotation.Value; import io.micronaut.flyway.FlywayConfigurationProperties; import jakarta.inject.Named; @@ -40,6 +45,13 @@ public Database configDatabase(@Named("config") final DSLContext dslContext) thr return new Database(dslContext); } + @Singleton + @Requires(env = WorkerMode.CONTROL_PLANE) + @Named("jobsDatabase") + public Database jobsDatabase(@Named("jobs") final DSLContext dslContext) throws IOException { + return new Database(dslContext); + } + @Singleton @Named("configFlyway") public Flyway configFlyway(@Named("config") final FlywayConfigurationProperties configFlywayConfigurationProperties, @@ -79,4 +91,16 @@ public DatabaseMigrationCheck configsDatabaseMigrationCheck(@Named("config") fin configsDatabaseInitializationTimeoutMs); } + @Singleton + @Requires(env = WorkerMode.CONTROL_PLANE) + public StreamResetPersistence streamResetPersistence(@Named("configDatabase") final Database configDatabase) { + return new StreamResetPersistence(configDatabase); + } + + @Singleton + @Requires(env = WorkerMode.CONTROL_PLANE) + public JobPersistence jobPersistence(@Named("jobsDatabase") final Database jobDatabase) { + return new DefaultJobPersistence(jobDatabase); + } + } diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java index 0b3848b0f26b..39a0bc87f2d8 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java @@ -7,7 +7,6 @@ import io.airbyte.commons.temporal.TemporalClient; import io.micronaut.scheduling.annotation.Scheduled; import io.temporal.api.enums.v1.WorkflowExecutionStatus; -import jakarta.inject.Named; import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; @@ -17,7 +16,7 @@ public class Temporal { private final TemporalClient temporalClient; - public Temporal(@Named("temporalClient") final TemporalClient temporalClient) { + public Temporal(final TemporalClient temporalClient) { log.debug("Creating temporal self-healing"); this.temporalClient = temporalClient; } diff --git a/airbyte-cron/src/main/resources/application.yml b/airbyte-cron/src/main/resources/application.yml index 3d18e233014e..f291a5e7dd38 100644 --- a/airbyte-cron/src/main/resources/application.yml +++ b/airbyte-cron/src/main/resources/application.yml @@ -15,7 +15,18 @@ datasources: connection-test-query: SELECT 1 connection-timeout: 30000 idle-timeout: 600000 - maximum-pool-size: 10 + maximum-pool-size: 5 + minimum-idle: 0 + url: ${DATABASE_URL} + driverClassName: org.postgresql.Driver + username: ${DATABASE_USER} + password: ${DATABASE_PASSWORD} + jobs: + connection-test-query: SELECT 1 + connection-timeout: 30000 + idle-timeout: 600000 + maximum-pool-size: 5 + minimum-idle: 0 url: ${DATABASE_URL} driverClassName: org.postgresql.Driver username: ${DATABASE_USER} diff --git a/airbyte-server/build.gradle b/airbyte-server/build.gradle index b39aad432696..b38e879fcb5b 100644 --- a/airbyte-server/build.gradle +++ b/airbyte-server/build.gradle @@ -4,6 +4,7 @@ plugins { configurations.all { exclude group: 'io.micronaut.jaxrs' + exclude group: 'io.micronaut.sql' } dependencies { @@ -22,14 +23,6 @@ dependencies { implementation project(':airbyte-oauth') implementation project(':airbyte-protocol:protocol-models') implementation project(':airbyte-persistence:job-persistence') - implementation(project(':airbyte-workers')) { - // Temporary hack to avoid dependency conflicts - exclude group: 'io.micronaut' - exclude group: 'io.micronaut.flyway' - exclude group: 'io.micronaut.jaxrs' - exclude group: 'io.micronaut.security' - exclude group: 'io.micronaut.sql' - } implementation libs.flyway.core implementation 'com.github.slugify:slugify:2.4' diff --git a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java index c50b443e203d..ee59c365d776 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java +++ b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java @@ -10,6 +10,9 @@ import io.airbyte.analytics.TrackingClientSingleton; import io.airbyte.commons.lang.CloseableShutdownHook; import io.airbyte.commons.resources.MoreResources; +import io.airbyte.commons.temporal.ConnectionManagerUtils; +import io.airbyte.commons.temporal.StreamResetRecordsHelper; +import io.airbyte.commons.temporal.TemporalClient; import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.commons.temporal.TemporalWorkflowUtils; import io.airbyte.commons.version.AirbyteVersion; @@ -56,9 +59,6 @@ import io.airbyte.server.scheduler.TemporalEventRunner; import io.airbyte.validation.json.JsonValidationException; import io.airbyte.workers.normalization.NormalizationRunnerFactory; -import io.airbyte.workers.temporal.ConnectionManagerUtils; -import io.airbyte.workers.temporal.StreamResetRecordsHelper; -import io.airbyte.workers.temporal.TemporalClient; import io.temporal.serviceclient.WorkflowServiceStubs; import java.io.IOException; import java.net.http.HttpClient; diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java index 3cfa7983d45b..c936e5768625 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java @@ -35,6 +35,8 @@ import io.airbyte.commons.docker.DockerUtils; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.ErrorCode; +import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; import io.airbyte.commons.version.Version; import io.airbyte.config.ActorCatalog; import io.airbyte.config.Configs.WorkerEnvironment; @@ -64,8 +66,6 @@ import io.airbyte.server.scheduler.SynchronousSchedulerClient; import io.airbyte.validation.json.JsonSchemaValidator; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.temporal.ErrorCode; -import io.airbyte.workers.temporal.TemporalClient.ManualOperationResult; import java.io.IOException; import java.util.ArrayList; import java.util.Optional; diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java index d49a3efdd29b..8ad8e23dda80 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java @@ -11,6 +11,8 @@ import com.google.common.hash.Hashing; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; +import io.airbyte.commons.temporal.TemporalClient; +import io.airbyte.commons.temporal.TemporalResponse; import io.airbyte.commons.version.Version; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.DestinationConnection; @@ -26,8 +28,6 @@ import io.airbyte.persistence.job.tracker.JobTracker; import io.airbyte.persistence.job.tracker.JobTracker.JobState; import io.airbyte.protocol.models.ConnectorSpecification; -import io.airbyte.workers.temporal.TemporalClient; -import io.airbyte.workers.temporal.TemporalResponse; import java.io.IOException; import java.time.Instant; import java.util.Optional; diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/EventRunner.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/EventRunner.java index 56d8fb8cae44..ca2b47bb6cc3 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/EventRunner.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/EventRunner.java @@ -4,8 +4,8 @@ package io.airbyte.server.scheduler; +import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; import io.airbyte.protocol.models.StreamDescriptor; -import io.airbyte.workers.temporal.TemporalClient.ManualOperationResult; import java.util.List; import java.util.Set; import java.util.UUID; diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousJobMetadata.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousJobMetadata.java index 196b00971151..37d862fb2ac9 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousJobMetadata.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousJobMetadata.java @@ -4,8 +4,8 @@ package io.airbyte.server.scheduler; +import io.airbyte.commons.temporal.JobMetadata; import io.airbyte.config.JobConfig.ConfigType; -import io.airbyte.workers.temporal.JobMetadata; import java.nio.file.Path; import java.time.Instant; import java.util.Objects; diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousResponse.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousResponse.java index 3905a3466fed..5227c5b51a84 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousResponse.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousResponse.java @@ -4,8 +4,8 @@ package io.airbyte.server.scheduler; +import io.airbyte.commons.temporal.TemporalResponse; import io.airbyte.config.JobConfig.ConfigType; -import io.airbyte.workers.temporal.TemporalResponse; import java.util.Objects; import java.util.UUID; diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/TemporalEventRunner.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/TemporalEventRunner.java index 1ff3cef84a01..79b54ce9a058 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/TemporalEventRunner.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/TemporalEventRunner.java @@ -4,9 +4,9 @@ package io.airbyte.server.scheduler; +import io.airbyte.commons.temporal.TemporalClient; +import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; import io.airbyte.protocol.models.StreamDescriptor; -import io.airbyte.workers.temporal.TemporalClient; -import io.airbyte.workers.temporal.TemporalClient.ManualOperationResult; import java.util.List; import java.util.Set; import java.util.UUID; diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java index d7997987a902..5e2953190af0 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java @@ -40,6 +40,8 @@ import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; +import io.airbyte.commons.temporal.ErrorCode; +import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; import io.airbyte.commons.version.Version; import io.airbyte.config.ActorCatalog; import io.airbyte.config.Configs.WorkerEnvironment; @@ -73,8 +75,6 @@ import io.airbyte.server.scheduler.SynchronousSchedulerClient; import io.airbyte.validation.json.JsonSchemaValidator; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.temporal.ErrorCode; -import io.airbyte.workers.temporal.TemporalClient.ManualOperationResult; import java.io.IOException; import java.net.URI; import java.util.HashMap; diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index bfd50d3863d9..72754b65c631 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -66,6 +66,7 @@ import io.airbyte.api.model.generated.WebBackendWorkspaceState; import io.airbyte.api.model.generated.WorkspaceIdRequestBody; import io.airbyte.commons.enums.Enums; +import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; @@ -87,7 +88,6 @@ import io.airbyte.server.helpers.SourceHelpers; import io.airbyte.server.scheduler.EventRunner; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.temporal.TemporalClient.ManualOperationResult; import java.io.IOException; import java.lang.reflect.Method; import java.time.Instant; diff --git a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java index 85415dfa2065..8cf86db022c0 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java @@ -20,6 +20,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.JobMetadata; +import io.airbyte.commons.temporal.TemporalClient; +import io.airbyte.commons.temporal.TemporalResponse; import io.airbyte.commons.version.Version; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.DestinationConnection; @@ -35,9 +38,6 @@ import io.airbyte.persistence.job.tracker.JobTracker; import io.airbyte.persistence.job.tracker.JobTracker.JobState; import io.airbyte.protocol.models.ConnectorSpecification; -import io.airbyte.workers.temporal.JobMetadata; -import io.airbyte.workers.temporal.TemporalClient; -import io.airbyte.workers.temporal.TemporalResponse; import java.io.IOException; import java.nio.file.Path; import java.util.UUID; diff --git a/airbyte-worker-models/build.gradle b/airbyte-worker-models/build.gradle new file mode 100644 index 000000000000..57796fd0246f --- /dev/null +++ b/airbyte-worker-models/build.gradle @@ -0,0 +1,22 @@ +import org.jsonschema2pojo.SourceType + +plugins { + id "java-library" + id 'com.github.eirnym.js2p' version '1.0' +} + +jsonSchema2Pojo { + sourceType = SourceType.YAMLSCHEMA + source = files("${sourceSets.main.output.resourcesDir}/workers_models") + targetDirectory = new File(project.buildDir, 'generated/src/gen/java/') + removeOldOutput = true + + targetPackage = 'io.airbyte.persistence.job.models' + + useLongIntegers = true + generateBuilders = true + includeConstructors = false + includeSetters = true +} + +Task publishArtifactsTask = getPublishArtifactsTask("$rootProject.ext.version", project) diff --git a/airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml b/airbyte-worker-models/src/main/resources/workers_models/IntegrationLauncherConfig.yaml similarity index 100% rename from airbyte-commons-worker/src/main/resources/workers_models/IntegrationLauncherConfig.yaml rename to airbyte-worker-models/src/main/resources/workers_models/IntegrationLauncherConfig.yaml diff --git a/airbyte-commons-temporal/src/main/resources/workers_models/JobRunConfig.yaml b/airbyte-worker-models/src/main/resources/workers_models/JobRunConfig.yaml similarity index 100% rename from airbyte-commons-temporal/src/main/resources/workers_models/JobRunConfig.yaml rename to airbyte-worker-models/src/main/resources/workers_models/JobRunConfig.yaml diff --git a/airbyte-workers/build.gradle b/airbyte-workers/build.gradle index 94626fdaab9f..885400e6363f 100644 --- a/airbyte-workers/build.gradle +++ b/airbyte-workers/build.gradle @@ -58,7 +58,7 @@ dependencies { exclude group: 'io.micronaut.security' exclude group: 'io.micronaut.sql' } - implementation project(':airbyte-api') + implementation project(':airbyte-worker-models') testAnnotationProcessor platform(libs.micronaut.bom) testAnnotationProcessor libs.bundles.micronaut.test.annotation.processor diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java b/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java index d183f4a3b816..bef334c310dc 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java @@ -7,6 +7,7 @@ import io.airbyte.commons.temporal.TemporalInitializationUtils; import io.airbyte.commons.temporal.TemporalJobType; import io.airbyte.commons.temporal.TemporalUtils; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.MaxWorkersConfig; import io.airbyte.config.helpers.LogClientSingleton; @@ -16,7 +17,6 @@ import io.airbyte.db.check.impl.JobsDatabaseAvailabilityCheck; import io.airbyte.metrics.lib.MetricClientFactory; import io.airbyte.metrics.lib.MetricEmittingApps; -import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.process.KubePortManagerSingleton; import io.airbyte.workers.temporal.check.connection.CheckConnectionWorkflowImpl; import io.airbyte.workers.temporal.discover.catalog.DiscoverCatalogWorkflowImpl; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java index 04f86556a501..946cd936e562 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java @@ -5,6 +5,7 @@ package io.airbyte.workers.config; import io.airbyte.commons.temporal.TemporalUtils; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.temporal.check.connection.CheckConnectionActivity; import io.airbyte.workers.temporal.discover.catalog.DiscoverCatalogActivity; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java index 4d7e319b0f4d..c30415dd468c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java @@ -9,6 +9,7 @@ import com.auth0.jwt.algorithms.Algorithm; import com.google.auth.oauth2.ServiceAccountCredentials; import io.airbyte.api.client.AirbyteApiClient; +import io.airbyte.commons.temporal.config.WorkerMode; import io.micronaut.context.BeanProvider; import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Prototype; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java index e0e7c650cacf..79efd27cf14e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApplicationBeanFactory.java @@ -7,6 +7,7 @@ import io.airbyte.analytics.TrackingClient; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.features.FeatureFlags; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.version.AirbyteVersion; import io.airbyte.config.AirbyteConfigValidator; import io.airbyte.config.Configs.DeploymentMode; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java index 9403a1c21014..6f91a8de5931 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java @@ -4,6 +4,7 @@ package io.airbyte.workers.config; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/JobErrorReportingBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/JobErrorReportingBeanFactory.java index f5ba3350d35a..f9f6c8aeb03d 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/JobErrorReportingBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/JobErrorReportingBeanFactory.java @@ -4,6 +4,7 @@ package io.airbyte.workers.config; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.DeploymentMode; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.persistence.job.WebUrlHelper; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ProcessFactoryBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ProcessFactoryBeanFactory.java index e3f499624fc4..dab9f21505c1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ProcessFactoryBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ProcessFactoryBeanFactory.java @@ -4,6 +4,7 @@ package io.airbyte.workers.config; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.workers.WorkerConfigs; import io.airbyte.workers.process.DockerProcessFactory; import io.airbyte.workers.process.KubeProcessFactory; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/SecretPersistenceBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/SecretPersistenceBeanFactory.java index 1b15ce5b2c4b..ab067f4de4b0 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/SecretPersistenceBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/SecretPersistenceBeanFactory.java @@ -4,6 +4,7 @@ package io.airbyte.workers.config; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.persistence.split_secrets.GoogleSecretManagerPersistence; import io.airbyte.config.persistence.split_secrets.LocalTestingSecretPersistence; import io.airbyte.config.persistence.split_secrets.RealSecretsHydrator; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java index 96aa20611129..22d2a01f6e15 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java @@ -8,7 +8,9 @@ import io.airbyte.analytics.TrackingClient; import io.airbyte.analytics.TrackingClientSingleton; import io.airbyte.commons.features.FeatureFlags; +import io.airbyte.commons.temporal.TemporalClient; import io.airbyte.commons.temporal.TemporalUtils; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.version.AirbyteVersion; import io.airbyte.config.Configs.DeploymentMode; import io.airbyte.config.Configs.TrackingStrategy; @@ -20,7 +22,6 @@ import io.airbyte.persistence.job.factory.OAuthConfigSupplier; import io.airbyte.persistence.job.factory.SyncJobFactory; import io.airbyte.workers.run.TemporalWorkerRunFactory; -import io.airbyte.workers.temporal.TemporalClient; import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Requires; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java index f418b84235a6..fbee2323e0f9 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/WorkerConfigurationBeanFactory.java @@ -7,6 +7,7 @@ import com.google.common.base.Splitter; import com.google.common.base.Strings; import io.airbyte.commons.map.MoreMaps; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.DeploymentMode; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.ResourceRequirements; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/run/TemporalWorkerRunFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/run/TemporalWorkerRunFactory.java index 44bcb8a1a400..046e06c21494 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/run/TemporalWorkerRunFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/run/TemporalWorkerRunFactory.java @@ -7,7 +7,9 @@ import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.functional.CheckedSupplier; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.TemporalClient; import io.airbyte.commons.temporal.TemporalJobType; +import io.airbyte.commons.temporal.TemporalResponse; import io.airbyte.config.JobConfig.ConfigType; import io.airbyte.config.JobOutput; import io.airbyte.config.JobResetConnectionConfig; @@ -18,8 +20,6 @@ import io.airbyte.workers.JobStatus; import io.airbyte.workers.OutputAndStatus; import io.airbyte.workers.WorkerConstants; -import io.airbyte.workers.temporal.TemporalClient; -import io.airbyte.workers.temporal.TemporalResponse; import java.nio.file.Path; import java.util.UUID; import lombok.AllArgsConstructor; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/run/WorkerRun.java b/airbyte-workers/src/main/java/io/airbyte/workers/run/WorkerRun.java index 33dd11a69306..2a9bda551898 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/run/WorkerRun.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/run/WorkerRun.java @@ -5,9 +5,9 @@ package io.airbyte.workers.run; import io.airbyte.commons.functional.CheckedSupplier; +import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.JobOutput; import io.airbyte.workers.OutputAndStatus; -import io.airbyte.workers.WorkerUtils; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.Callable; @@ -33,7 +33,7 @@ public static WorkerRun create(final Path workspaceRoot, final int attempt, final CheckedSupplier, Exception> workerRun, final String airbyteVersionOrWarnings) { - final Path jobRoot = WorkerUtils.getJobRoot(workspaceRoot, String.valueOf(jobId), attempt); + final Path jobRoot = TemporalUtils.getJobRoot(workspaceRoot, String.valueOf(jobId), attempt); return new WorkerRun(jobRoot, workerRun, airbyteVersionOrWarnings); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/ConnectionManagerUtils.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/ConnectionManagerUtils.java deleted file mode 100644 index 13b532d0044c..000000000000 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/ConnectionManagerUtils.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.workers.temporal; - -import io.airbyte.commons.temporal.TemporalJobType; -import io.airbyte.commons.temporal.TemporalWorkflowUtils; -import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; -import io.airbyte.commons.temporal.scheduling.ConnectionUpdaterInput; -import io.airbyte.commons.temporal.scheduling.state.WorkflowState; -import io.airbyte.workers.temporal.exception.DeletedWorkflowException; -import io.airbyte.workers.temporal.exception.UnreachableWorkflowException; -import io.airbyte.workers.temporal.scheduling.ConnectionManagerWorkflowImpl; -import io.temporal.api.common.v1.WorkflowExecution; -import io.temporal.api.enums.v1.WorkflowExecutionStatus; -import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionRequest; -import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionResponse; -import io.temporal.client.BatchRequest; -import io.temporal.client.WorkflowClient; -import io.temporal.workflow.Functions.Proc; -import io.temporal.workflow.Functions.Proc1; -import io.temporal.workflow.Functions.TemporalFunctionalInterfaceMarker; -import jakarta.inject.Singleton; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -/** - * Encapsulates logic specific to retrieving, starting, and signaling the ConnectionManagerWorkflow. - */ -@NoArgsConstructor -@Singleton -@Slf4j -public class ConnectionManagerUtils { - - /** - * Attempts to send a signal to the existing ConnectionManagerWorkflow for the provided connection. - * - * If the workflow is unreachable, this will restart the workflow and send the signal in a single - * batched request. Batching is used to avoid race conditions between starting the workflow and - * executing the signal. - * - * @param client the WorkflowClient for interacting with temporal - * @param connectionId the connection ID to execute this operation for - * @param signalMethod a function that takes in a connection manager workflow and executes a signal - * method on it, with no arguments - * @return the healthy connection manager workflow that was signaled - * @throws DeletedWorkflowException if the connection manager workflow was deleted - */ - public ConnectionManagerWorkflow signalWorkflowAndRepairIfNecessary(final WorkflowClient client, - final UUID connectionId, - final Function signalMethod) - throws DeletedWorkflowException { - return signalWorkflowAndRepairIfNecessary(client, connectionId, signalMethod, Optional.empty()); - } - - /** - * Attempts to send a signal to the existing ConnectionManagerWorkflow for the provided connection. - * - * If the workflow is unreachable, this will restart the workflow and send the signal in a single - * batched request. Batching is used to avoid race conditions between starting the workflow and - * executing the signal. - * - * @param client the WorkflowClient for interacting with temporal - * @param connectionId the connection ID to execute this operation for - * @param signalMethod a function that takes in a connection manager workflow and executes a signal - * method on it, with 1 argument - * @param signalArgument the single argument to be input to the signal - * @return the healthy connection manager workflow that was signaled - * @throws DeletedWorkflowException if the connection manager workflow was deleted - */ - public ConnectionManagerWorkflow signalWorkflowAndRepairIfNecessary(final WorkflowClient client, - final UUID connectionId, - final Function> signalMethod, - final T signalArgument) - throws DeletedWorkflowException { - return signalWorkflowAndRepairIfNecessary(client, connectionId, signalMethod, Optional.of(signalArgument)); - } - - // This method unifies the logic of the above two, by using the optional signalArgument parameter to - // indicate if an argument is being provided to the signal or not. - // Keeping this private and only exposing the above methods outside this class provides a strict - // type enforcement for external calls, and means this method can assume consistent type - // implementations for both cases. - private ConnectionManagerWorkflow signalWorkflowAndRepairIfNecessary(final WorkflowClient client, - final UUID connectionId, - final Function signalMethod, - final Optional signalArgument) - throws DeletedWorkflowException { - try { - final ConnectionManagerWorkflow connectionManagerWorkflow = getConnectionManagerWorkflow(client, connectionId); - log.info("Retrieved existing connection manager workflow for connection {}. Executing signal.", connectionId); - // retrieve the signal from the lambda - final TemporalFunctionalInterfaceMarker signal = signalMethod.apply(connectionManagerWorkflow); - // execute the signal - if (signalArgument.isPresent()) { - ((Proc1) signal).apply(signalArgument.get()); - } else { - ((Proc) signal).apply(); - } - return connectionManagerWorkflow; - } catch (final UnreachableWorkflowException e) { - log.error( - String.format( - "Failed to retrieve ConnectionManagerWorkflow for connection %s. Repairing state by creating new workflow and starting with the signal.", - connectionId), - e); - - // in case there is an existing workflow in a bad state, attempt to terminate it first before - // starting a new workflow - safeTerminateWorkflow(client, connectionId, "Terminating workflow in unreachable state before starting a new workflow for this connection"); - - final ConnectionManagerWorkflow connectionManagerWorkflow = newConnectionManagerWorkflowStub(client, connectionId); - final ConnectionUpdaterInput startWorkflowInput = TemporalWorkflowUtils.buildStartWorkflowInput(connectionId); - - final BatchRequest batchRequest = client.newSignalWithStartRequest(); - batchRequest.add(connectionManagerWorkflow::run, startWorkflowInput); - - // retrieve the signal from the lambda - final TemporalFunctionalInterfaceMarker signal = signalMethod.apply(connectionManagerWorkflow); - // add signal to batch request - if (signalArgument.isPresent()) { - batchRequest.add((Proc1) signal, signalArgument.get()); - } else { - batchRequest.add((Proc) signal); - } - - client.signalWithStart(batchRequest); - log.info("Connection manager workflow for connection {} has been started and signaled.", connectionId); - - return connectionManagerWorkflow; - } - } - - void safeTerminateWorkflow(final WorkflowClient client, final String workflowId, final String reason) { - log.info("Attempting to terminate existing workflow for workflowId {}.", workflowId); - try { - client.newUntypedWorkflowStub(workflowId).terminate(reason); - } catch (final Exception e) { - log.warn( - "Could not terminate temporal workflow due to the following error; " - + "this may be because there is currently no running workflow for this connection.", - e); - } - } - - public void safeTerminateWorkflow(final WorkflowClient client, final UUID connectionId, final String reason) { - safeTerminateWorkflow(client, getConnectionManagerName(connectionId), reason); - } - - public ConnectionManagerWorkflow startConnectionManagerNoSignal(final WorkflowClient client, final UUID connectionId) { - final ConnectionManagerWorkflow connectionManagerWorkflow = newConnectionManagerWorkflowStub(client, connectionId); - final ConnectionUpdaterInput input = TemporalWorkflowUtils.buildStartWorkflowInput(connectionId); - WorkflowClient.start(connectionManagerWorkflow::run, input); - - return connectionManagerWorkflow; - } - - /** - * Attempts to retrieve the connection manager workflow for the provided connection. - * - * @param connectionId the ID of the connection whose workflow should be retrieved - * @return the healthy ConnectionManagerWorkflow - * @throws DeletedWorkflowException if the workflow was deleted, according to the workflow state - * @throws UnreachableWorkflowException if the workflow is in an unreachable state - */ - public ConnectionManagerWorkflow getConnectionManagerWorkflow(final WorkflowClient client, final UUID connectionId) - throws DeletedWorkflowException, UnreachableWorkflowException { - - final ConnectionManagerWorkflow connectionManagerWorkflow; - final WorkflowState workflowState; - final WorkflowExecutionStatus workflowExecutionStatus; - try { - connectionManagerWorkflow = client.newWorkflowStub(ConnectionManagerWorkflow.class, getConnectionManagerName(connectionId)); - workflowState = connectionManagerWorkflow.getState(); - workflowExecutionStatus = getConnectionManagerWorkflowStatus(client, connectionId); - } catch (final Exception e) { - throw new UnreachableWorkflowException( - String.format("Failed to retrieve ConnectionManagerWorkflow for connection %s due to the following error:", connectionId), - e); - } - - if (WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED.equals(workflowExecutionStatus)) { - if (workflowState.isDeleted()) { - throw new DeletedWorkflowException(String.format( - "The connection manager workflow for connection %s is deleted, so no further operations cannot be performed on it.", - connectionId)); - } - - // A non-deleted workflow being in a COMPLETED state is unexpected, and should be corrected - throw new UnreachableWorkflowException( - String.format("ConnectionManagerWorkflow for connection %s is unreachable due to having COMPLETED status.", connectionId)); - } - - if (workflowState.isQuarantined()) { - throw new UnreachableWorkflowException( - String.format("ConnectionManagerWorkflow for connection %s is unreachable due to being in a quarantined state.", connectionId)); - } - - return connectionManagerWorkflow; - } - - boolean isWorkflowStateRunning(final WorkflowClient client, final UUID connectionId) { - try { - final ConnectionManagerWorkflow connectionManagerWorkflow = client.newWorkflowStub(ConnectionManagerWorkflow.class, - getConnectionManagerName(connectionId)); - return connectionManagerWorkflow.getState().isRunning(); - } catch (final Exception e) { - return false; - } - } - - public WorkflowExecutionStatus getConnectionManagerWorkflowStatus(final WorkflowClient workflowClient, final UUID connectionId) { - final DescribeWorkflowExecutionRequest describeWorkflowExecutionRequest = DescribeWorkflowExecutionRequest.newBuilder() - .setExecution(WorkflowExecution.newBuilder() - .setWorkflowId(getConnectionManagerName(connectionId)) - .build()) - .setNamespace(workflowClient.getOptions().getNamespace()).build(); - - final DescribeWorkflowExecutionResponse describeWorkflowExecutionResponse = workflowClient.getWorkflowServiceStubs().blockingStub() - .describeWorkflowExecution(describeWorkflowExecutionRequest); - - return describeWorkflowExecutionResponse.getWorkflowExecutionInfo().getStatus(); - } - - public long getCurrentJobId(final WorkflowClient client, final UUID connectionId) { - try { - final ConnectionManagerWorkflow connectionManagerWorkflow = getConnectionManagerWorkflow(client, connectionId); - return connectionManagerWorkflow.getJobInformation().getJobId(); - } catch (final Exception e) { - return ConnectionManagerWorkflowImpl.NON_RUNNING_JOB_ID; - } - } - - public ConnectionManagerWorkflow newConnectionManagerWorkflowStub(final WorkflowClient client, final UUID connectionId) { - return client.newWorkflowStub(ConnectionManagerWorkflow.class, - TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.CONNECTION_UPDATER, getConnectionManagerName(connectionId))); - } - - public String getConnectionManagerName(final UUID connectionId) { - return "connection_manager_" + connectionId; - } - -} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java index a15b109bbb3c..b463b8fc8ce5 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java @@ -16,7 +16,6 @@ import io.airbyte.config.helpers.LogConfigs; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.Worker; -import io.airbyte.workers.WorkerUtils; import io.temporal.activity.Activity; import io.temporal.activity.ActivityExecutionContext; import java.nio.file.Path; @@ -88,7 +87,7 @@ public TemporalAttemptExecution(final Path workspaceRoot, final String airbyteVersion) { this.jobRunConfig = jobRunConfig; - this.jobRoot = WorkerUtils.getJobRoot(workspaceRoot, jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); + this.jobRoot = TemporalUtils.getJobRoot(workspaceRoot, jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); this.workerSupplier = workerSupplier; this.inputSupplier = inputSupplier; this.mdcSetter = mdcSetter; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java deleted file mode 100644 index b85a32f79ba8..000000000000 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalClient.java +++ /dev/null @@ -1,549 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.workers.temporal; - -import static io.airbyte.workers.temporal.scheduling.ConnectionManagerWorkflowImpl.NON_RUNNING_JOB_ID; - -import com.google.common.annotations.VisibleForTesting; -import com.google.protobuf.ByteString; -import io.airbyte.commons.temporal.TemporalJobType; -import io.airbyte.commons.temporal.TemporalWorkflowUtils; -import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; -import io.airbyte.config.ConnectorJobOutput; -import io.airbyte.config.JobCheckConnectionConfig; -import io.airbyte.config.JobDiscoverCatalogConfig; -import io.airbyte.config.JobGetSpecConfig; -import io.airbyte.config.JobSyncConfig; -import io.airbyte.config.StandardCheckConnectionInput; -import io.airbyte.config.StandardDiscoverCatalogInput; -import io.airbyte.config.StandardSyncInput; -import io.airbyte.config.StandardSyncOutput; -import io.airbyte.config.persistence.StreamResetPersistence; -import io.airbyte.persistence.job.models.IntegrationLauncherConfig; -import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.protocol.models.StreamDescriptor; -import io.airbyte.workers.WorkerUtils; -import io.airbyte.workers.config.WorkerMode; -import io.airbyte.workers.temporal.check.connection.CheckConnectionWorkflow; -import io.airbyte.workers.temporal.discover.catalog.DiscoverCatalogWorkflow; -import io.airbyte.workers.temporal.exception.DeletedWorkflowException; -import io.airbyte.workers.temporal.exception.UnreachableWorkflowException; -import io.airbyte.workers.temporal.spec.SpecWorkflow; -import io.airbyte.workers.temporal.sync.SyncWorkflow; -import io.micronaut.context.annotation.Requires; -import io.temporal.api.common.v1.WorkflowType; -import io.temporal.api.enums.v1.WorkflowExecutionStatus; -import io.temporal.api.workflowservice.v1.ListClosedWorkflowExecutionsRequest; -import io.temporal.api.workflowservice.v1.ListClosedWorkflowExecutionsResponse; -import io.temporal.api.workflowservice.v1.ListOpenWorkflowExecutionsRequest; -import io.temporal.api.workflowservice.v1.ListOpenWorkflowExecutionsResponse; -import io.temporal.client.WorkflowClient; -import io.temporal.serviceclient.WorkflowServiceStubs; -import jakarta.inject.Named; -import jakarta.inject.Singleton; -import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; -import java.time.Instant; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import lombok.Builder; -import lombok.Value; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.time.StopWatch; - -@Slf4j -@Singleton -@Requires(env = WorkerMode.CONTROL_PLANE) -public class TemporalClient { - - /** - * This is used to sleep between 2 temporal queries. The query is needed to ensure that the cancel - * and start manual sync methods wait before returning. Since temporal signals are async, we need to - * use the queries to make sure that we are in a state in which we want to continue with. - */ - private static final int DELAY_BETWEEN_QUERY_MS = 10; - - private final Path workspaceRoot; - private final WorkflowClient client; - private final WorkflowServiceStubs service; - private final StreamResetPersistence streamResetPersistence; - private final ConnectionManagerUtils connectionManagerUtils; - private final StreamResetRecordsHelper streamResetRecordsHelper; - - public TemporalClient(@Named("workspaceRoot") final Path workspaceRoot, - final WorkflowClient client, - final WorkflowServiceStubs service, - final StreamResetPersistence streamResetPersistence, - final ConnectionManagerUtils connectionManagerUtils, - final StreamResetRecordsHelper streamResetRecordsHelper) { - this.workspaceRoot = workspaceRoot; - this.client = client; - this.service = service; - this.streamResetPersistence = streamResetPersistence; - this.connectionManagerUtils = connectionManagerUtils; - this.streamResetRecordsHelper = streamResetRecordsHelper; - } - - /** - * Direct termination of Temporal Workflows should generally be avoided. This method exists for some - * rare circumstances where this may be required. Originally added to facilitate Airbyte's migration - * to Temporal Cloud. TODO consider deleting this after Temporal Cloud migration - */ - public void dangerouslyTerminateWorkflow(final String workflowId, final String reason) { - this.client.newUntypedWorkflowStub(workflowId).terminate(reason); - } - - public TemporalResponse submitGetSpec(final UUID jobId, final int attempt, final JobGetSpecConfig config) { - final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); - - final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() - .withJobId(jobId.toString()) - .withAttemptId((long) attempt) - .withDockerImage(config.getDockerImage()); - return execute(jobRunConfig, - () -> getWorkflowStub(SpecWorkflow.class, TemporalJobType.GET_SPEC).run(jobRunConfig, launcherConfig)); - - } - - public TemporalResponse submitCheckConnection(final UUID jobId, - final int attempt, - final JobCheckConnectionConfig config) { - final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); - final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() - .withJobId(jobId.toString()) - .withAttemptId((long) attempt) - .withDockerImage(config.getDockerImage()) - .withProtocolVersion(config.getProtocolVersion()); - final StandardCheckConnectionInput input = new StandardCheckConnectionInput().withConnectionConfiguration(config.getConnectionConfiguration()); - - return execute(jobRunConfig, - () -> getWorkflowStub(CheckConnectionWorkflow.class, TemporalJobType.CHECK_CONNECTION).run(jobRunConfig, launcherConfig, input)); - } - - public TemporalResponse submitDiscoverSchema(final UUID jobId, - final int attempt, - final JobDiscoverCatalogConfig config) { - final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); - final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() - .withJobId(jobId.toString()) - .withAttemptId((long) attempt) - .withDockerImage(config.getDockerImage()); - final StandardDiscoverCatalogInput input = new StandardDiscoverCatalogInput().withConnectionConfiguration(config.getConnectionConfiguration()) - .withSourceId(config.getSourceId()).withConnectorVersion(config.getConnectorVersion()).withConfigHash(config.getConfigHash()); - - return execute(jobRunConfig, - () -> getWorkflowStub(DiscoverCatalogWorkflow.class, TemporalJobType.DISCOVER_SCHEMA).run(jobRunConfig, launcherConfig, input)); - } - - public TemporalResponse submitSync(final long jobId, final int attempt, final JobSyncConfig config, final UUID connectionId) { - final JobRunConfig jobRunConfig = TemporalWorkflowUtils.createJobRunConfig(jobId, attempt); - - final IntegrationLauncherConfig sourceLauncherConfig = new IntegrationLauncherConfig() - .withJobId(String.valueOf(jobId)) - .withAttemptId((long) attempt) - .withDockerImage(config.getSourceDockerImage()); - - final IntegrationLauncherConfig destinationLauncherConfig = new IntegrationLauncherConfig() - .withJobId(String.valueOf(jobId)) - .withAttemptId((long) attempt) - .withDockerImage(config.getDestinationDockerImage()); - - final StandardSyncInput input = new StandardSyncInput() - .withNamespaceDefinition(config.getNamespaceDefinition()) - .withNamespaceFormat(config.getNamespaceFormat()) - .withPrefix(config.getPrefix()) - .withSourceConfiguration(config.getSourceConfiguration()) - .withDestinationConfiguration(config.getDestinationConfiguration()) - .withOperationSequence(config.getOperationSequence()) - .withCatalog(config.getConfiguredAirbyteCatalog()) - .withState(config.getState()) - .withResourceRequirements(config.getResourceRequirements()) - .withSourceResourceRequirements(config.getSourceResourceRequirements()) - .withDestinationResourceRequirements(config.getDestinationResourceRequirements()); - - return execute(jobRunConfig, - () -> getWorkflowStub(SyncWorkflow.class, TemporalJobType.SYNC).run( - jobRunConfig, - sourceLauncherConfig, - destinationLauncherConfig, - input, - connectionId)); - } - - public void migrateSyncIfNeeded(final Set connectionIds) { - final StopWatch globalMigrationWatch = new StopWatch(); - globalMigrationWatch.start(); - refreshRunningWorkflow(); - - connectionIds.forEach((connectionId) -> { - final StopWatch singleSyncMigrationWatch = new StopWatch(); - singleSyncMigrationWatch.start(); - if (!isInRunningWorkflowCache(connectionManagerUtils.getConnectionManagerName(connectionId))) { - log.info("Migrating: " + connectionId); - try { - submitConnectionUpdaterAsync(connectionId); - } catch (final Exception e) { - log.error("New workflow submission failed, retrying", e); - refreshRunningWorkflow(); - submitConnectionUpdaterAsync(connectionId); - } - } - singleSyncMigrationWatch.stop(); - log.info("Sync migration took: " + singleSyncMigrationWatch.formatTime()); - }); - globalMigrationWatch.stop(); - - log.info("The migration to the new scheduler took: " + globalMigrationWatch.formatTime()); - } - - private final Set workflowNames = new HashSet<>(); - - boolean isInRunningWorkflowCache(final String workflowName) { - return workflowNames.contains(workflowName); - } - - @VisibleForTesting - void refreshRunningWorkflow() { - workflowNames.clear(); - ByteString token; - ListOpenWorkflowExecutionsRequest openWorkflowExecutionsRequest = - ListOpenWorkflowExecutionsRequest.newBuilder() - .setNamespace(client.getOptions().getNamespace()) - .build(); - do { - final ListOpenWorkflowExecutionsResponse listOpenWorkflowExecutionsRequest = - service.blockingStub().listOpenWorkflowExecutions(openWorkflowExecutionsRequest); - final Set workflowExecutionInfos = listOpenWorkflowExecutionsRequest.getExecutionsList().stream() - .map((workflowExecutionInfo -> workflowExecutionInfo.getExecution().getWorkflowId())) - .collect(Collectors.toSet()); - workflowNames.addAll(workflowExecutionInfos); - token = listOpenWorkflowExecutionsRequest.getNextPageToken(); - - openWorkflowExecutionsRequest = - ListOpenWorkflowExecutionsRequest.newBuilder() - .setNamespace(client.getOptions().getNamespace()) - .setNextPageToken(token) - .build(); - - } while (token != null && token.size() > 0); - } - - /** - * Refreshes the cache of running workflows, and returns their names. Currently called by the - * Temporal Cloud migrator to generate a list of workflows that should be migrated. After the - * Temporal Migration is complete, this could be removed, though it may be handy for a future use - * case. - */ - public Set getAllRunningWorkflows() { - final var startTime = Instant.now(); - refreshRunningWorkflow(); - final var endTime = Instant.now(); - log.info("getAllRunningWorkflows took {} milliseconds", Duration.between(startTime, endTime).toMillis()); - return workflowNames; - } - - public ConnectionManagerWorkflow submitConnectionUpdaterAsync(final UUID connectionId) { - log.info("Starting the scheduler temporal wf"); - final ConnectionManagerWorkflow connectionManagerWorkflow = - connectionManagerUtils.startConnectionManagerNoSignal(client, connectionId); - try { - CompletableFuture.supplyAsync(() -> { - try { - do { - Thread.sleep(DELAY_BETWEEN_QUERY_MS); - } while (!isWorkflowReachable(connectionId)); - } catch (final InterruptedException e) {} - return null; - }).get(60, TimeUnit.SECONDS); - } catch (final InterruptedException | ExecutionException e) { - log.error("Failed to create a new connection manager workflow", e); - } catch (final TimeoutException e) { - log.error("Can't create a new connection manager workflow due to timeout", e); - } - - return connectionManagerWorkflow; - } - - public void deleteConnection(final UUID connectionId) { - try { - connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, - connectionManagerWorkflow -> connectionManagerWorkflow::deleteConnection); - } catch (final DeletedWorkflowException e) { - log.info("Connection {} has already been deleted.", connectionId); - } - } - - public void update(final UUID connectionId) { - final ConnectionManagerWorkflow connectionManagerWorkflow; - try { - connectionManagerWorkflow = connectionManagerUtils.getConnectionManagerWorkflow(client, connectionId); - } catch (final DeletedWorkflowException e) { - log.info("Connection {} is deleted, and therefore cannot be updated.", connectionId); - return; - } catch (final UnreachableWorkflowException e) { - log.error( - String.format("Failed to retrieve ConnectionManagerWorkflow for connection %s. Repairing state by creating new workflow.", connectionId), - e); - connectionManagerUtils.safeTerminateWorkflow(client, connectionId, - "Terminating workflow in unreachable state before starting a new workflow for this connection"); - submitConnectionUpdaterAsync(connectionId); - return; - } - - connectionManagerWorkflow.connectionUpdated(); - } - - @Value - @Builder - public static class ManualOperationResult { - - final Optional failingReason; - final Optional jobId; - final Optional errorCode; - - } - - public ManualOperationResult startNewManualSync(final UUID connectionId) { - log.info("Manual sync request"); - - if (connectionManagerUtils.isWorkflowStateRunning(client, connectionId)) { - // TODO Bmoric: Error is running - return new ManualOperationResult( - Optional.of("A sync is already running for: " + connectionId), - Optional.empty(), Optional.of(ErrorCode.WORKFLOW_RUNNING)); - } - - try { - connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::submitManualSync); - } catch (final DeletedWorkflowException e) { - log.error("Can't sync a deleted connection.", e); - return new ManualOperationResult( - Optional.of(e.getMessage()), - Optional.empty(), Optional.of(ErrorCode.WORKFLOW_DELETED)); - } - - do { - try { - Thread.sleep(DELAY_BETWEEN_QUERY_MS); - } catch (final InterruptedException e) { - return new ManualOperationResult( - Optional.of("Didn't managed to start a sync for: " + connectionId), - Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); - } - } while (!connectionManagerUtils.isWorkflowStateRunning(client, connectionId)); - - log.info("end of manual schedule"); - - final long jobId = connectionManagerUtils.getCurrentJobId(client, connectionId); - - return new ManualOperationResult( - Optional.empty(), - Optional.of(jobId), Optional.empty()); - } - - public ManualOperationResult startNewCancellation(final UUID connectionId) { - log.info("Manual cancellation request"); - - final long jobId = connectionManagerUtils.getCurrentJobId(client, connectionId); - - try { - connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::cancelJob); - } catch (final DeletedWorkflowException e) { - log.error("Can't cancel a deleted workflow", e); - return new ManualOperationResult( - Optional.of(e.getMessage()), - Optional.empty(), Optional.of(ErrorCode.WORKFLOW_DELETED)); - } - - do { - try { - Thread.sleep(DELAY_BETWEEN_QUERY_MS); - } catch (final InterruptedException e) { - return new ManualOperationResult( - Optional.of("Didn't manage to cancel a sync for: " + connectionId), - Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); - } - } while (connectionManagerUtils.isWorkflowStateRunning(client, connectionId)); - - streamResetRecordsHelper.deleteStreamResetRecordsForJob(jobId, connectionId); - - log.info("end of manual cancellation"); - - return new ManualOperationResult( - Optional.empty(), - Optional.of(jobId), Optional.empty()); - } - - public ManualOperationResult resetConnection(final UUID connectionId, - final List streamsToReset, - final boolean syncImmediatelyAfter) { - log.info("reset sync request"); - - try { - streamResetPersistence.createStreamResets(connectionId, streamsToReset); - } catch (final IOException e) { - log.error("Could not persist streams to reset.", e); - return new ManualOperationResult( - Optional.of(e.getMessage()), - Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); - } - - // get the job ID before the reset, defaulting to NON_RUNNING_JOB_ID if workflow is unreachable - final long oldJobId = connectionManagerUtils.getCurrentJobId(client, connectionId); - - try { - if (syncImmediatelyAfter) { - connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::resetConnectionAndSkipNextScheduling); - } else { - connectionManagerUtils.signalWorkflowAndRepairIfNecessary(client, connectionId, workflow -> workflow::resetConnection); - } - } catch (final DeletedWorkflowException e) { - log.error("Can't reset a deleted workflow", e); - return new ManualOperationResult( - Optional.of(e.getMessage()), - Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); - } - - Optional newJobId; - - do { - try { - Thread.sleep(DELAY_BETWEEN_QUERY_MS); - } catch (final InterruptedException e) { - return new ManualOperationResult( - Optional.of("Didn't manage to reset a sync for: " + connectionId), - Optional.empty(), Optional.of(ErrorCode.UNKNOWN)); - } - newJobId = getNewJobId(connectionId, oldJobId); - } while (newJobId.isEmpty()); - - log.info("end of reset submission"); - - return new ManualOperationResult( - Optional.empty(), - newJobId, Optional.empty()); - } - - private Optional getNewJobId(final UUID connectionId, final long oldJobId) { - final long currentJobId = connectionManagerUtils.getCurrentJobId(client, connectionId); - if (currentJobId == NON_RUNNING_JOB_ID || currentJobId == oldJobId) { - return Optional.empty(); - } else { - return Optional.of(currentJobId); - } - } - - /** - * This should be in the class {@li} - * - * @param workflowId - * @return - */ - Optional extractConnectionIdFromWorkflowId(final String workflowId) { - if (!workflowId.startsWith("connection_manager_")) { - return Optional.empty(); - } - return Optional.ofNullable(StringUtils.removeStart(workflowId, "connection_manager_")) - .map( - stringUUID -> UUID.fromString(stringUUID)); - } - - Set fetchClosedWorkflowsByStatus(final WorkflowExecutionStatus executionStatus) { - ByteString token; - ListClosedWorkflowExecutionsRequest workflowExecutionsRequest = - ListClosedWorkflowExecutionsRequest.newBuilder() - .setNamespace(client.getOptions().getNamespace()) - .build(); - - final Set workflowExecutionInfos = new HashSet<>(); - do { - final ListClosedWorkflowExecutionsResponse listOpenWorkflowExecutionsRequest = - service.blockingStub().listClosedWorkflowExecutions(workflowExecutionsRequest); - final WorkflowType connectionManagerWorkflowType = WorkflowType.newBuilder().setName(ConnectionManagerWorkflow.class.getSimpleName()).build(); - workflowExecutionInfos.addAll(listOpenWorkflowExecutionsRequest.getExecutionsList().stream() - .filter(workflowExecutionInfo -> workflowExecutionInfo.getType() == connectionManagerWorkflowType || - workflowExecutionInfo.getStatus() == executionStatus) - .flatMap((workflowExecutionInfo -> extractConnectionIdFromWorkflowId(workflowExecutionInfo.getExecution().getWorkflowId()).stream())) - .collect(Collectors.toSet())); - token = listOpenWorkflowExecutionsRequest.getNextPageToken(); - - workflowExecutionsRequest = - ListClosedWorkflowExecutionsRequest.newBuilder() - .setNamespace(client.getOptions().getNamespace()) - .setNextPageToken(token) - .build(); - - } while (token != null && token.size() > 0); - - return workflowExecutionInfos; - } - - @VisibleForTesting - Set filterOutRunningWorkspaceId(final Set workflowIds) { - refreshRunningWorkflow(); - - final Set runningWorkflowByUUID = - workflowNames.stream().flatMap(name -> extractConnectionIdFromWorkflowId(name).stream()).collect(Collectors.toSet()); - - return workflowIds.stream().filter(workflowId -> !runningWorkflowByUUID.contains(workflowId)).collect(Collectors.toSet()); - } - - private T getWorkflowStub(final Class workflowClass, final TemporalJobType jobType) { - return client.newWorkflowStub(workflowClass, TemporalWorkflowUtils.buildWorkflowOptions(jobType)); - } - - private boolean getConnectorJobSucceeded(final ConnectorJobOutput output) { - return output.getFailureReason() == null; - } - - @VisibleForTesting - TemporalResponse execute(final JobRunConfig jobRunConfig, final Supplier executor) { - final Path jobRoot = WorkerUtils.getJobRoot(workspaceRoot, jobRunConfig); - final Path logPath = WorkerUtils.getLogPath(jobRoot); - - T operationOutput = null; - RuntimeException exception = null; - - try { - operationOutput = executor.get(); - } catch (final RuntimeException e) { - exception = e; - } - - boolean succeeded = exception == null; - if (succeeded && operationOutput instanceof ConnectorJobOutput) { - succeeded = getConnectorJobSucceeded((ConnectorJobOutput) operationOutput); - } - - final JobMetadata metadata = new JobMetadata(succeeded, logPath); - return new TemporalResponse<>(operationOutput, metadata); - } - - /** - * Check if a workflow is reachable for signal calls by attempting to query for current state. If - * the query succeeds, and the workflow is not marked as deleted, the workflow is reachable. - */ - @VisibleForTesting - boolean isWorkflowReachable(final UUID connectionId) { - try { - connectionManagerUtils.getConnectionManagerWorkflow(client, connectionId); - return true; - } catch (final Exception e) { - return false; - } - } - -} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java index 8448b1014aef..2ed068e633ab 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java @@ -10,6 +10,7 @@ import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; import io.airbyte.commons.protocol.AirbyteMessageVersionedMigratorFactory; import io.airbyte.commons.temporal.CancellationHandler; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.StandardCheckConnectionInput; @@ -20,7 +21,6 @@ import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.workers.Worker; import io.airbyte.workers.WorkerConfigs; -import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.general.DefaultCheckConnectionWorker; import io.airbyte.workers.internal.AirbyteStreamFactory; import io.airbyte.workers.internal.DefaultAirbyteStreamFactory; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java index 070a5b01758e..e8706c91a89b 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java @@ -4,6 +4,7 @@ package io.airbyte.workers.temporal.check.connection; +import io.airbyte.commons.temporal.scheduling.CheckConnectionWorkflow; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.ConnectorJobOutput.OutputType; import io.airbyte.config.StandardCheckConnectionInput; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java index 76a1f6235b6c..f631de372f4b 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java @@ -8,6 +8,7 @@ import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; import io.airbyte.commons.temporal.CancellationHandler; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.StandardDiscoverCatalogInput; @@ -18,7 +19,6 @@ import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.Worker; import io.airbyte.workers.WorkerConfigs; -import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.general.DefaultDiscoverCatalogWorker; import io.airbyte.workers.internal.AirbyteStreamFactory; import io.airbyte.workers.internal.DefaultAirbyteStreamFactory; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java index 273acfb210c2..b216dbc8f026 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java @@ -4,6 +4,7 @@ package io.airbyte.workers.temporal.discover.catalog; +import io.airbyte.commons.temporal.scheduling.DiscoverCatalogWorkflow; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.StandardDiscoverCatalogInput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java index d35f6c515ed4..4b3a2ca80f1e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java @@ -7,8 +7,10 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.temporal.TemporalJobType; import io.airbyte.commons.temporal.TemporalWorkflowUtils; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; import io.airbyte.commons.temporal.scheduling.ConnectionUpdaterInput; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.commons.temporal.scheduling.state.WorkflowInternalState; import io.airbyte.commons.temporal.scheduling.state.WorkflowState; import io.airbyte.commons.temporal.scheduling.state.listener.NoopStateListener; @@ -31,7 +33,6 @@ import io.airbyte.workers.temporal.annotations.TemporalActivityStub; import io.airbyte.workers.temporal.check.connection.CheckConnectionActivity; import io.airbyte.workers.temporal.check.connection.CheckConnectionActivity.CheckConnectionInput; -import io.airbyte.workers.temporal.exception.RetryableException; import io.airbyte.workers.temporal.scheduling.activities.AutoDisableConnectionActivity; import io.airbyte.workers.temporal.scheduling.activities.AutoDisableConnectionActivity.AutoDisableConnectionActivityInput; import io.airbyte.workers.temporal.scheduling.activities.AutoDisableConnectionActivity.AutoDisableConnectionOutput; @@ -68,7 +69,6 @@ import io.airbyte.workers.temporal.scheduling.activities.StreamResetActivity; import io.airbyte.workers.temporal.scheduling.activities.StreamResetActivity.DeleteStreamResetRecordsForJobInput; import io.airbyte.workers.temporal.scheduling.activities.WorkflowConfigActivity; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.ParentClosePolicy; import io.temporal.failure.ActivityFailure; import io.temporal.failure.CanceledFailure; @@ -89,9 +89,6 @@ @SuppressWarnings("PMD.AvoidDuplicateLiterals") public class ConnectionManagerWorkflowImpl implements ConnectionManagerWorkflow { - public static final long NON_RUNNING_JOB_ID = -1; - public static final int NON_RUNNING_ATTEMPT_ID = -1; - private static final int TASK_QUEUE_CHANGE_CURRENT_VERSION = 1; private static final int AUTO_DISABLE_FAILING_CONNECTION_CHANGE_CURRENT_VERSION = 1; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java index 67809505267f..ca361d079705 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java @@ -10,6 +10,8 @@ import static java.time.temporal.ChronoUnit.DAYS; import io.airbyte.commons.features.FeatureFlags; +import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSync.Status; import io.airbyte.config.persistence.ConfigRepository; @@ -19,8 +21,6 @@ import io.airbyte.persistence.job.models.JobStatus; import io.airbyte.persistence.job.models.JobWithStatusAndTimestamp; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.config.WorkerMode; -import io.airbyte.workers.temporal.exception.RetryableException; import io.micronaut.context.annotation.Requires; import io.micronaut.context.annotation.Value; import jakarta.inject.Singleton; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java index c6909a4c2875..88776de0bac0 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java @@ -4,6 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; +import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.Cron; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSync.ScheduleType; @@ -14,8 +16,6 @@ import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.Job; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.config.WorkerMode; -import io.airbyte.workers.temporal.exception.RetryableException; import io.micronaut.context.annotation.Requires; import io.micronaut.context.annotation.Value; import jakarta.inject.Named; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java index 5b138443b74d..9bde493d8a2c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.activities; +import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.validation.json.JsonValidationException; -import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.helper.ConnectionHelper; -import io.airbyte.workers.temporal.exception.RetryableException; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.io.IOException; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java index 503ba9bc8e02..74a622cf2749 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java @@ -6,6 +6,8 @@ import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalWorkflowUtils; +import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.JobConfig.ConfigType; import io.airbyte.config.JobResetConnectionConfig; import io.airbyte.config.JobSyncConfig; @@ -16,8 +18,6 @@ import io.airbyte.persistence.job.models.Job; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.WorkerConstants; -import io.airbyte.workers.config.WorkerMode; -import io.airbyte.workers.temporal.exception.RetryableException; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.util.List; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java index 6393d2643ff8..6d394b2dc5e5 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.scheduling.activities; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.AttemptFailureSummary; import io.airbyte.config.StandardSyncOutput; -import io.airbyte.workers.temporal.exception.RetryableException; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; import java.util.UUID; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java index 354b56363867..66533409192f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java @@ -7,6 +7,8 @@ import com.google.common.collect.Lists; import io.airbyte.commons.docker.DockerUtils; import io.airbyte.commons.enums.Enums; +import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.AttemptFailureSummary; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.DestinationConnection; @@ -41,11 +43,9 @@ import io.airbyte.protocol.models.StreamDescriptor; import io.airbyte.validation.json.JsonValidationException; import io.airbyte.workers.JobStatus; -import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.helper.FailureHelper; import io.airbyte.workers.run.TemporalWorkerRunFactory; import io.airbyte.workers.run.WorkerRun; -import io.airbyte.workers.temporal.exception.RetryableException; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.io.IOException; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java index 870a96b0e938..d65368b1877b 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.activities; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.temporal.scheduling.ConnectionUpdaterInput; import io.airbyte.metrics.lib.MetricAttribute; import io.airbyte.metrics.lib.MetricClient; import io.airbyte.metrics.lib.MetricTags; -import io.airbyte.workers.config.WorkerMode; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.util.ArrayList; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java index e4b2014dce23..1499fa160467 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; -import io.airbyte.workers.config.WorkerMode; -import io.airbyte.workers.temporal.StreamResetRecordsHelper; +import io.airbyte.commons.temporal.StreamResetRecordsHelper; +import io.airbyte.commons.temporal.config.WorkerMode; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java index 558868f04a4b..19d8fa1135ee 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java @@ -4,7 +4,7 @@ package io.airbyte.workers.temporal.scheduling.activities; -import io.airbyte.workers.config.WorkerMode; +import io.airbyte.commons.temporal.config.WorkerMode; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java index 5cdb899fdfba..3ad439a4c9bb 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java @@ -7,6 +7,7 @@ import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; import io.airbyte.commons.temporal.CancellationHandler; +import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.JobGetSpecConfig; @@ -15,7 +16,6 @@ import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.Worker; import io.airbyte.workers.WorkerConfigs; -import io.airbyte.workers.config.WorkerMode; import io.airbyte.workers.general.DefaultGetSpecWorker; import io.airbyte.workers.process.AirbyteIntegrationLauncher; import io.airbyte.workers.process.IntegrationLauncher; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java index 5766301fe816..24ddfe96951c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java @@ -4,6 +4,7 @@ package io.airbyte.workers.temporal.spec; +import io.airbyte.commons.temporal.scheduling.SpecWorkflow; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java index 5aa793994867..99d875968cf7 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java @@ -4,6 +4,7 @@ package io.airbyte.workers.temporal.sync; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; import io.airbyte.config.OperatorDbtInput; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/run/TemporalWorkerRunFactoryTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/run/TemporalWorkerRunFactoryTest.java index f199b60ac48f..b522070da341 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/run/TemporalWorkerRunFactoryTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/run/TemporalWorkerRunFactoryTest.java @@ -14,6 +14,8 @@ import com.google.common.collect.ImmutableMap; import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.temporal.TemporalClient; +import io.airbyte.commons.temporal.TemporalResponse; import io.airbyte.config.JobConfig.ConfigType; import io.airbyte.config.JobResetConnectionConfig; import io.airbyte.config.JobSyncConfig; @@ -22,8 +24,6 @@ import io.airbyte.persistence.job.models.Job; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.workers.WorkerConstants; -import io.airbyte.workers.temporal.TemporalClient; -import io.airbyte.workers.temporal.TemporalResponse; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/StreamResetRecordsHelperTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/StreamResetRecordsHelperTest.java index 7c839b2b0c4f..fb64e3ecf928 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/StreamResetRecordsHelperTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/StreamResetRecordsHelperTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; +import io.airbyte.commons.temporal.StreamResetRecordsHelper; import io.airbyte.config.JobConfig.ConfigType; import io.airbyte.config.persistence.StreamResetPersistence; import io.airbyte.persistence.job.JobPersistence; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalClientTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalClientTest.java deleted file mode 100644 index 59b1caa9ee74..000000000000 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalClientTest.java +++ /dev/null @@ -1,839 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.workers.temporal; - -import static io.airbyte.workers.temporal.scheduling.ConnectionManagerWorkflowImpl.NON_RUNNING_JOB_ID; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import com.google.common.collect.Sets; -import io.airbyte.commons.json.Jsons; -import io.airbyte.commons.temporal.TemporalJobType; -import io.airbyte.commons.temporal.TemporalWorkflowUtils; -import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; -import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow.JobInformation; -import io.airbyte.commons.temporal.scheduling.state.WorkflowState; -import io.airbyte.config.ConnectorJobOutput; -import io.airbyte.config.FailureReason; -import io.airbyte.config.JobCheckConnectionConfig; -import io.airbyte.config.JobDiscoverCatalogConfig; -import io.airbyte.config.JobGetSpecConfig; -import io.airbyte.config.JobSyncConfig; -import io.airbyte.config.StandardCheckConnectionInput; -import io.airbyte.config.StandardDiscoverCatalogInput; -import io.airbyte.config.StandardSyncInput; -import io.airbyte.config.helpers.LogClientSingleton; -import io.airbyte.config.persistence.StreamResetPersistence; -import io.airbyte.persistence.job.models.IntegrationLauncherConfig; -import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; -import io.airbyte.protocol.models.StreamDescriptor; -import io.airbyte.workers.temporal.TemporalClient.ManualOperationResult; -import io.airbyte.workers.temporal.check.connection.CheckConnectionWorkflow; -import io.airbyte.workers.temporal.discover.catalog.DiscoverCatalogWorkflow; -import io.airbyte.workers.temporal.spec.SpecWorkflow; -import io.airbyte.workers.temporal.sync.SyncWorkflow; -import io.temporal.api.enums.v1.WorkflowExecutionStatus; -import io.temporal.api.workflow.v1.WorkflowExecutionInfo; -import io.temporal.api.workflowservice.v1.DescribeWorkflowExecutionResponse; -import io.temporal.api.workflowservice.v1.WorkflowServiceGrpc.WorkflowServiceBlockingStub; -import io.temporal.client.BatchRequest; -import io.temporal.client.WorkflowClient; -import io.temporal.client.WorkflowClientOptions; -import io.temporal.client.WorkflowOptions; -import io.temporal.client.WorkflowStub; -import io.temporal.serviceclient.WorkflowServiceStubs; -import io.temporal.workflow.Functions.Proc; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.UUID; -import java.util.function.Supplier; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; - -class TemporalClientTest { - - private static final UUID CONNECTION_ID = UUID.randomUUID(); - private static final UUID JOB_UUID = UUID.randomUUID(); - private static final long JOB_ID = 11L; - private static final int ATTEMPT_ID = 21; - private static final JobRunConfig JOB_RUN_CONFIG = new JobRunConfig() - .withJobId(String.valueOf(JOB_ID)) - .withAttemptId((long) ATTEMPT_ID); - private static final String IMAGE_NAME1 = "hms invincible"; - private static final String IMAGE_NAME2 = "hms defiant"; - private static final IntegrationLauncherConfig UUID_LAUNCHER_CONFIG = new IntegrationLauncherConfig() - .withJobId(String.valueOf(JOB_UUID)) - .withAttemptId((long) ATTEMPT_ID) - .withDockerImage(IMAGE_NAME1); - private static final IntegrationLauncherConfig LAUNCHER_CONFIG = new IntegrationLauncherConfig() - .withJobId(String.valueOf(JOB_ID)) - .withAttemptId((long) ATTEMPT_ID) - .withDockerImage(IMAGE_NAME1); - private static final String NAMESPACE = "namespace"; - private static final StreamDescriptor STREAM_DESCRIPTOR = new StreamDescriptor().withName("name"); - private static final String UNCHECKED = "unchecked"; - private static final String EXCEPTION_MESSAGE = "Force state exception to simulate workflow not running"; - - private WorkflowClient workflowClient; - private TemporalClient temporalClient; - private Path logPath; - private WorkflowServiceStubs workflowServiceStubs; - private WorkflowServiceBlockingStub workflowServiceBlockingStub; - private StreamResetPersistence streamResetPersistence; - private ConnectionManagerUtils connectionManagerUtils; - private StreamResetRecordsHelper streamResetRecordsHelper; - - @BeforeEach - void setup() throws IOException { - final Path workspaceRoot = Files.createTempDirectory(Path.of("/tmp"), "temporal_client_test"); - logPath = workspaceRoot.resolve(String.valueOf(JOB_ID)).resolve(String.valueOf(ATTEMPT_ID)).resolve(LogClientSingleton.LOG_FILENAME); - workflowClient = mock(WorkflowClient.class); - when(workflowClient.getOptions()).thenReturn(WorkflowClientOptions.newBuilder().setNamespace(NAMESPACE).build()); - workflowServiceStubs = mock(WorkflowServiceStubs.class); - when(workflowClient.getWorkflowServiceStubs()).thenReturn(workflowServiceStubs); - workflowServiceBlockingStub = mock(WorkflowServiceBlockingStub.class); - when(workflowServiceStubs.blockingStub()).thenReturn(workflowServiceBlockingStub); - streamResetPersistence = mock(StreamResetPersistence.class); - mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_RUNNING); - connectionManagerUtils = spy(new ConnectionManagerUtils()); - streamResetRecordsHelper = mock(StreamResetRecordsHelper.class); - temporalClient = - spy(new TemporalClient(workspaceRoot, workflowClient, workflowServiceStubs, streamResetPersistence, connectionManagerUtils, - streamResetRecordsHelper)); - } - - @Nested - @DisplayName("Test execute method.") - class ExecuteJob { - - @SuppressWarnings(UNCHECKED) - @Test - void testExecute() { - final Supplier supplier = mock(Supplier.class); - when(supplier.get()).thenReturn("hello"); - - final TemporalResponse response = temporalClient.execute(JOB_RUN_CONFIG, supplier); - - assertNotNull(response); - assertTrue(response.getOutput().isPresent()); - assertEquals("hello", response.getOutput().get()); - assertTrue(response.getMetadata().isSucceeded()); - assertEquals(logPath, response.getMetadata().getLogPath()); - } - - @SuppressWarnings(UNCHECKED) - @Test - void testExecuteWithException() { - final Supplier supplier = mock(Supplier.class); - when(supplier.get()).thenThrow(IllegalStateException.class); - - final TemporalResponse response = temporalClient.execute(JOB_RUN_CONFIG, supplier); - - assertNotNull(response); - assertFalse(response.getOutput().isPresent()); - assertFalse(response.getMetadata().isSucceeded()); - assertEquals(logPath, response.getMetadata().getLogPath()); - } - - @Test - void testExecuteWithConnectorJobFailure() { - final Supplier supplier = mock(Supplier.class); - final FailureReason mockFailureReason = mock(FailureReason.class); - final ConnectorJobOutput connectorJobOutput = new ConnectorJobOutput() - .withFailureReason(mockFailureReason); - when(supplier.get()).thenReturn(connectorJobOutput); - - final TemporalResponse response = temporalClient.execute(JOB_RUN_CONFIG, supplier); - - assertNotNull(response); - assertTrue(response.getOutput().isPresent()); - assertEquals(connectorJobOutput, response.getOutput().get()); - assertFalse(response.getMetadata().isSucceeded()); - assertEquals(logPath, response.getMetadata().getLogPath()); - } - - } - - @Nested - @DisplayName("Test job creation for each configuration type.") - class TestJobSubmission { - - @Test - void testSubmitGetSpec() { - final SpecWorkflow specWorkflow = mock(SpecWorkflow.class); - when(workflowClient.newWorkflowStub(SpecWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.GET_SPEC))) - .thenReturn(specWorkflow); - final JobGetSpecConfig getSpecConfig = new JobGetSpecConfig().withDockerImage(IMAGE_NAME1); - - temporalClient.submitGetSpec(JOB_UUID, ATTEMPT_ID, getSpecConfig); - specWorkflow.run(JOB_RUN_CONFIG, UUID_LAUNCHER_CONFIG); - verify(workflowClient).newWorkflowStub(SpecWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.GET_SPEC)); - } - - @Test - void testSubmitCheckConnection() { - final CheckConnectionWorkflow checkConnectionWorkflow = mock(CheckConnectionWorkflow.class); - when( - workflowClient.newWorkflowStub(CheckConnectionWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.CHECK_CONNECTION))) - .thenReturn(checkConnectionWorkflow); - final JobCheckConnectionConfig checkConnectionConfig = new JobCheckConnectionConfig() - .withDockerImage(IMAGE_NAME1) - .withConnectionConfiguration(Jsons.emptyObject()); - final StandardCheckConnectionInput input = new StandardCheckConnectionInput() - .withConnectionConfiguration(checkConnectionConfig.getConnectionConfiguration()); - - temporalClient.submitCheckConnection(JOB_UUID, ATTEMPT_ID, checkConnectionConfig); - checkConnectionWorkflow.run(JOB_RUN_CONFIG, UUID_LAUNCHER_CONFIG, input); - verify(workflowClient).newWorkflowStub(CheckConnectionWorkflow.class, - TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.CHECK_CONNECTION)); - } - - @Test - void testSubmitDiscoverSchema() { - final DiscoverCatalogWorkflow discoverCatalogWorkflow = mock(DiscoverCatalogWorkflow.class); - when(workflowClient.newWorkflowStub(DiscoverCatalogWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.DISCOVER_SCHEMA))) - .thenReturn(discoverCatalogWorkflow); - final JobDiscoverCatalogConfig checkConnectionConfig = new JobDiscoverCatalogConfig() - .withDockerImage(IMAGE_NAME1) - .withConnectionConfiguration(Jsons.emptyObject()); - final StandardDiscoverCatalogInput input = new StandardDiscoverCatalogInput() - .withConnectionConfiguration(checkConnectionConfig.getConnectionConfiguration()); - - temporalClient.submitDiscoverSchema(JOB_UUID, ATTEMPT_ID, checkConnectionConfig); - discoverCatalogWorkflow.run(JOB_RUN_CONFIG, UUID_LAUNCHER_CONFIG, input); - verify(workflowClient).newWorkflowStub(DiscoverCatalogWorkflow.class, - TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.DISCOVER_SCHEMA)); - } - - @Test - void testSubmitSync() { - final SyncWorkflow discoverCatalogWorkflow = mock(SyncWorkflow.class); - when(workflowClient.newWorkflowStub(SyncWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.SYNC))) - .thenReturn(discoverCatalogWorkflow); - final JobSyncConfig syncConfig = new JobSyncConfig() - .withSourceDockerImage(IMAGE_NAME1) - .withSourceDockerImage(IMAGE_NAME2) - .withSourceConfiguration(Jsons.emptyObject()) - .withDestinationConfiguration(Jsons.emptyObject()) - .withOperationSequence(List.of()) - .withConfiguredAirbyteCatalog(new ConfiguredAirbyteCatalog()); - final StandardSyncInput input = new StandardSyncInput() - .withNamespaceDefinition(syncConfig.getNamespaceDefinition()) - .withNamespaceFormat(syncConfig.getNamespaceFormat()) - .withPrefix(syncConfig.getPrefix()) - .withSourceConfiguration(syncConfig.getSourceConfiguration()) - .withDestinationConfiguration(syncConfig.getDestinationConfiguration()) - .withOperationSequence(syncConfig.getOperationSequence()) - .withCatalog(syncConfig.getConfiguredAirbyteCatalog()) - .withState(syncConfig.getState()); - - final IntegrationLauncherConfig destinationLauncherConfig = new IntegrationLauncherConfig() - .withJobId(String.valueOf(JOB_ID)) - .withAttemptId((long) ATTEMPT_ID) - .withDockerImage(IMAGE_NAME2); - - temporalClient.submitSync(JOB_ID, ATTEMPT_ID, syncConfig, CONNECTION_ID); - discoverCatalogWorkflow.run(JOB_RUN_CONFIG, LAUNCHER_CONFIG, destinationLauncherConfig, input, CONNECTION_ID); - verify(workflowClient).newWorkflowStub(SyncWorkflow.class, TemporalWorkflowUtils.buildWorkflowOptions(TemporalJobType.SYNC)); - } - - } - - @Nested - @DisplayName("Test related to the migration to the new scheduler") - class TestMigration { - - @DisplayName("Test that the migration is properly done if needed") - @Test - void migrateCalled() { - final UUID nonMigratedId = UUID.randomUUID(); - final UUID migratedId = UUID.randomUUID(); - - when(temporalClient.isInRunningWorkflowCache(connectionManagerUtils.getConnectionManagerName(nonMigratedId))).thenReturn(false); - when(temporalClient.isInRunningWorkflowCache(connectionManagerUtils.getConnectionManagerName(migratedId))).thenReturn(true); - - doNothing() - .when(temporalClient).refreshRunningWorkflow(); - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - doReturn(mConnectionManagerWorkflow) - .when(temporalClient).submitConnectionUpdaterAsync(nonMigratedId); - - temporalClient.migrateSyncIfNeeded(Sets.newHashSet(nonMigratedId, migratedId)); - - verify(temporalClient, times(1)).submitConnectionUpdaterAsync(nonMigratedId); - verify(temporalClient, times(0)).submitConnectionUpdaterAsync(migratedId); - } - - } - - @Nested - @DisplayName("Test delete connection method.") - class DeleteConnection { - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test delete connection method when workflow is in a running state.") - void testDeleteConnection() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - - doReturn(true).when(temporalClient).isWorkflowReachable(any(UUID.class)); - when(workflowClient.newWorkflowStub(any(Class.class), anyString())).thenReturn(mConnectionManagerWorkflow); - - final JobSyncConfig syncConfig = new JobSyncConfig() - .withSourceDockerImage(IMAGE_NAME1) - .withSourceDockerImage(IMAGE_NAME2) - .withSourceConfiguration(Jsons.emptyObject()) - .withDestinationConfiguration(Jsons.emptyObject()) - .withOperationSequence(List.of()) - .withConfiguredAirbyteCatalog(new ConfiguredAirbyteCatalog()); - - temporalClient.submitSync(JOB_ID, ATTEMPT_ID, syncConfig, CONNECTION_ID); - temporalClient.deleteConnection(CONNECTION_ID); - - verify(workflowClient, Mockito.never()).newSignalWithStartRequest(); - verify(mConnectionManagerWorkflow).deleteConnection(); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test delete connection method when workflow is in an unexpected state") - void testDeleteConnectionInUnexpectedState() { - final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - when(mTerminatedConnectionManagerWorkflow.getState()) - .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); - when(workflowClient.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(mTerminatedConnectionManagerWorkflow); - - final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); - final BatchRequest mBatchRequest = mock(BatchRequest.class); - when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); - - temporalClient.deleteConnection(CONNECTION_ID); - verify(workflowClient).signalWithStart(mBatchRequest); - - // Verify that the deleteConnection signal was passed to the batch request by capturing the - // argument, - // executing the signal, and verifying that the desired signal was executed - final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); - verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); - final Proc signal = batchRequestAddArgCaptor.getValue(); - signal.apply(); - verify(mNewConnectionManagerWorkflow).deleteConnection(); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test delete connection method when workflow has already been deleted") - void testDeleteConnectionOnDeletedWorkflow() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(true); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); - - temporalClient.deleteConnection(CONNECTION_ID); - - verify(temporalClient).deleteConnection(CONNECTION_ID); - verifyNoMoreInteractions(temporalClient); - } - - } - - @Nested - @DisplayName("Test update connection behavior") - class UpdateConnection { - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test update connection when workflow is running") - void testUpdateConnection() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - - when(mWorkflowState.isRunning()).thenReturn(true); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(workflowClient.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(mConnectionManagerWorkflow); - - temporalClient.update(CONNECTION_ID); - - verify(mConnectionManagerWorkflow, Mockito.times(1)).connectionUpdated(); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test update connection method starts a new workflow when workflow is in an unexpected state") - void testUpdateConnectionInUnexpectedState() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - - when(mConnectionManagerWorkflow.getState()).thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); - when(workflowClient.newWorkflowStub(any(Class.class), any(String.class))).thenReturn(mConnectionManagerWorkflow); - doReturn(mConnectionManagerWorkflow).when(temporalClient).submitConnectionUpdaterAsync(CONNECTION_ID); - - final WorkflowStub untypedWorkflowStub = mock(WorkflowStub.class); - when(workflowClient.newUntypedWorkflowStub(anyString())).thenReturn(untypedWorkflowStub); - - temporalClient.update(CONNECTION_ID); - - // this is only called when updating an existing workflow - verify(mConnectionManagerWorkflow, Mockito.never()).connectionUpdated(); - - verify(untypedWorkflowStub, Mockito.times(1)).terminate(anyString()); - verify(temporalClient, Mockito.times(1)).submitConnectionUpdaterAsync(CONNECTION_ID); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test update connection method does nothing when connection is deleted") - void testUpdateConnectionDeletedWorkflow() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(true); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); - - temporalClient.update(CONNECTION_ID); - - // this is only called when updating an existing workflow - verify(mConnectionManagerWorkflow, Mockito.never()).connectionUpdated(); - verify(temporalClient).update(CONNECTION_ID); - verifyNoMoreInteractions(temporalClient); - } - - } - - @Nested - @DisplayName("Test manual sync behavior") - class ManualSync { - - @Test - @DisplayName("Test startNewManualSync successful") - void testStartNewManualSyncSuccess() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(false).thenReturn(true); - when(mConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - - final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); - - assertTrue(result.getJobId().isPresent()); - assertEquals(JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow).submitManualSync(); - } - - @Test - @DisplayName("Test startNewManualSync fails if job is already running") - void testStartNewManualSyncAlreadyRunning() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(true); - when(mConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - - final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); - - assertFalse(result.getJobId().isPresent()); - assertTrue(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow, times(0)).submitManualSync(); - } - - @Test - @DisplayName("Test startNewManualSync repairs the workflow if it is in a bad state") - void testStartNewManualSyncRepairsBadWorkflowState() { - final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - when(mTerminatedConnectionManagerWorkflow.getState()) - .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); - when(mTerminatedConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - - final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mNewConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(false).thenReturn(true); - when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); - final BatchRequest mBatchRequest = mock(BatchRequest.class); - when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); - - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mTerminatedConnectionManagerWorkflow, mTerminatedConnectionManagerWorkflow, - mNewConnectionManagerWorkflow); - - final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); - - assertTrue(result.getJobId().isPresent()); - assertEquals(JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(workflowClient).signalWithStart(mBatchRequest); - - // Verify that the submitManualSync signal was passed to the batch request by capturing the - // argument, - // executing the signal, and verifying that the desired signal was executed - final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); - verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); - final Proc signal = batchRequestAddArgCaptor.getValue(); - signal.apply(); - verify(mNewConnectionManagerWorkflow).submitManualSync(); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test startNewManualSync returns a failure reason when connection is deleted") - void testStartNewManualSyncDeletedWorkflow() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(true); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); - - final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); - - // this is only called when updating an existing workflow - assertFalse(result.getJobId().isPresent()); - assertTrue(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow, times(0)).submitManualSync(); - } - - } - - @Nested - @DisplayName("Test cancellation behavior") - class Cancellation { - - @Test - @DisplayName("Test startNewCancellation successful") - void testStartNewCancellationSuccess() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(true).thenReturn(false); - when(mConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - - final ManualOperationResult result = temporalClient.startNewCancellation(CONNECTION_ID); - - assertTrue(result.getJobId().isPresent()); - assertEquals(JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow).cancelJob(); - verify(streamResetRecordsHelper).deleteStreamResetRecordsForJob(JOB_ID, CONNECTION_ID); - } - - @Test - @DisplayName("Test startNewCancellation repairs the workflow if it is in a bad state") - void testStartNewCancellationRepairsBadWorkflowState() { - final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - when(mTerminatedConnectionManagerWorkflow.getState()) - .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); - when(mTerminatedConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - - final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mNewConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(true).thenReturn(false); - when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); - final BatchRequest mBatchRequest = mock(BatchRequest.class); - when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); - - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mTerminatedConnectionManagerWorkflow, mTerminatedConnectionManagerWorkflow, - mNewConnectionManagerWorkflow); - - final ManualOperationResult result = temporalClient.startNewCancellation(CONNECTION_ID); - - assertTrue(result.getJobId().isPresent()); - assertEquals(NON_RUNNING_JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(workflowClient).signalWithStart(mBatchRequest); - - // Verify that the cancelJob signal was passed to the batch request by capturing the argument, - // executing the signal, and verifying that the desired signal was executed - final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); - verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); - final Proc signal = batchRequestAddArgCaptor.getValue(); - signal.apply(); - verify(mNewConnectionManagerWorkflow).cancelJob(); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test startNewCancellation returns a failure reason when connection is deleted") - void testStartNewCancellationDeletedWorkflow() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(true); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); - - final ManualOperationResult result = temporalClient.startNewCancellation(CONNECTION_ID); - - // this is only called when updating an existing workflow - assertFalse(result.getJobId().isPresent()); - assertTrue(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow, times(0)).cancelJob(); - } - - } - - @Nested - @DisplayName("Test reset connection behavior") - class ResetConnection { - - @Test - @DisplayName("Test resetConnection successful") - void testResetConnectionSuccess() throws IOException { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(false); - final long jobId1 = 1; - final long jobId2 = 2; - when(mConnectionManagerWorkflow.getJobInformation()).thenReturn( - new JobInformation(jobId1, 0), - new JobInformation(jobId1, 0), - new JobInformation(NON_RUNNING_JOB_ID, 0), - new JobInformation(NON_RUNNING_JOB_ID, 0), - new JobInformation(jobId2, 0), - new JobInformation(jobId2, 0)); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - - final List streamsToReset = List.of(STREAM_DESCRIPTOR); - final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, false); - - verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); - - assertTrue(result.getJobId().isPresent()); - assertEquals(jobId2, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow).resetConnection(); - } - - @Test - @DisplayName("Test resetConnection successful") - void testResetConnectionSuccessAndContinue() throws IOException { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(false); - final long jobId1 = 1; - final long jobId2 = 2; - when(mConnectionManagerWorkflow.getJobInformation()).thenReturn( - new JobInformation(jobId1, 0), - new JobInformation(jobId1, 0), - new JobInformation(NON_RUNNING_JOB_ID, 0), - new JobInformation(NON_RUNNING_JOB_ID, 0), - new JobInformation(jobId2, 0), - new JobInformation(jobId2, 0)); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - - final List streamsToReset = List.of(STREAM_DESCRIPTOR); - final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, true); - - verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); - - assertTrue(result.getJobId().isPresent()); - assertEquals(jobId2, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow).resetConnectionAndSkipNextScheduling(); - } - - @Test - @DisplayName("Test resetConnection repairs the workflow if it is in a bad state") - void testResetConnectionRepairsBadWorkflowState() throws IOException { - final ConnectionManagerWorkflow mTerminatedConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - when(mTerminatedConnectionManagerWorkflow.getState()) - .thenThrow(new IllegalStateException(EXCEPTION_MESSAGE)); - when(mTerminatedConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - - final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mNewConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(mWorkflowState.isRunning()).thenReturn(false); - when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn( - new JobInformation(NON_RUNNING_JOB_ID, 0), - new JobInformation(NON_RUNNING_JOB_ID, 0), - new JobInformation(JOB_ID, 0), - new JobInformation(JOB_ID, 0)); - when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); - final BatchRequest mBatchRequest = mock(BatchRequest.class); - when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); - - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mTerminatedConnectionManagerWorkflow, mTerminatedConnectionManagerWorkflow, - mNewConnectionManagerWorkflow); - - final List streamsToReset = List.of(STREAM_DESCRIPTOR); - final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, false); - - verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); - - assertTrue(result.getJobId().isPresent()); - assertEquals(JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(workflowClient).signalWithStart(mBatchRequest); - - // Verify that the resetConnection signal was passed to the batch request by capturing the argument, - // executing the signal, and verifying that the desired signal was executed - final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); - verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); - final Proc signal = batchRequestAddArgCaptor.getValue(); - signal.apply(); - verify(mNewConnectionManagerWorkflow).resetConnection(); - } - - @Test - @SuppressWarnings(UNCHECKED) - @DisplayName("Test resetConnection returns a failure reason when connection is deleted") - void testResetConnectionDeletedWorkflow() throws IOException { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isDeleted()).thenReturn(true); - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow); - mockWorkflowStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED); - - final List streamsToReset = List.of(STREAM_DESCRIPTOR); - final ManualOperationResult result = temporalClient.resetConnection(CONNECTION_ID, streamsToReset, false); - - verify(streamResetPersistence).createStreamResets(CONNECTION_ID, streamsToReset); - - // this is only called when updating an existing workflow - assertFalse(result.getJobId().isPresent()); - assertTrue(result.getFailingReason().isPresent()); - verify(mConnectionManagerWorkflow, times(0)).resetConnection(); - } - - } - - @Test - @DisplayName("Test manual operation on quarantined workflow causes a restart") - void testManualOperationOnQuarantinedWorkflow() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isQuarantined()).thenReturn(true); - - final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mNewWorkflowState = mock(WorkflowState.class); - when(mNewConnectionManagerWorkflow.getState()).thenReturn(mNewWorkflowState); - when(mNewWorkflowState.isRunning()).thenReturn(false).thenReturn(true); - when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); - final BatchRequest mBatchRequest = mock(BatchRequest.class); - when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); - - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow, mConnectionManagerWorkflow, - mNewConnectionManagerWorkflow); - - final WorkflowStub mWorkflowStub = mock(WorkflowStub.class); - when(workflowClient.newUntypedWorkflowStub(anyString())).thenReturn(mWorkflowStub); - - final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); - - assertTrue(result.getJobId().isPresent()); - assertEquals(JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(workflowClient).signalWithStart(mBatchRequest); - verify(mWorkflowStub).terminate(anyString()); - - // Verify that the submitManualSync signal was passed to the batch request by capturing the - // argument, - // executing the signal, and verifying that the desired signal was executed - final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); - verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); - final Proc signal = batchRequestAddArgCaptor.getValue(); - signal.apply(); - verify(mNewConnectionManagerWorkflow).submitManualSync(); - } - - @Test - @DisplayName("Test manual operation on completed workflow causes a restart") - void testManualOperationOnCompletedWorkflow() { - final ConnectionManagerWorkflow mConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mWorkflowState = mock(WorkflowState.class); - when(mConnectionManagerWorkflow.getState()).thenReturn(mWorkflowState); - when(mWorkflowState.isQuarantined()).thenReturn(false); - when(mWorkflowState.isDeleted()).thenReturn(false); - when(workflowServiceBlockingStub.describeWorkflowExecution(any())) - .thenReturn(DescribeWorkflowExecutionResponse.newBuilder().setWorkflowExecutionInfo( - WorkflowExecutionInfo.newBuilder().setStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_COMPLETED).buildPartial()).build()) - .thenReturn(DescribeWorkflowExecutionResponse.newBuilder().setWorkflowExecutionInfo( - WorkflowExecutionInfo.newBuilder().setStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_RUNNING).buildPartial()).build()); - - final ConnectionManagerWorkflow mNewConnectionManagerWorkflow = mock(ConnectionManagerWorkflow.class); - final WorkflowState mNewWorkflowState = mock(WorkflowState.class); - when(mNewConnectionManagerWorkflow.getState()).thenReturn(mNewWorkflowState); - when(mNewWorkflowState.isRunning()).thenReturn(false).thenReturn(true); - when(mNewConnectionManagerWorkflow.getJobInformation()).thenReturn(new JobInformation(JOB_ID, ATTEMPT_ID)); - when(workflowClient.newWorkflowStub(any(Class.class), any(WorkflowOptions.class))).thenReturn(mNewConnectionManagerWorkflow); - final BatchRequest mBatchRequest = mock(BatchRequest.class); - when(workflowClient.newSignalWithStartRequest()).thenReturn(mBatchRequest); - - when(workflowClient.newWorkflowStub(any(), anyString())).thenReturn(mConnectionManagerWorkflow, mConnectionManagerWorkflow, - mNewConnectionManagerWorkflow); - - final WorkflowStub mWorkflowStub = mock(WorkflowStub.class); - when(workflowClient.newUntypedWorkflowStub(anyString())).thenReturn(mWorkflowStub); - - final ManualOperationResult result = temporalClient.startNewManualSync(CONNECTION_ID); - - assertTrue(result.getJobId().isPresent()); - assertEquals(JOB_ID, result.getJobId().get()); - assertFalse(result.getFailingReason().isPresent()); - verify(workflowClient).signalWithStart(mBatchRequest); - verify(mWorkflowStub).terminate(anyString()); - - // Verify that the submitManualSync signal was passed to the batch request by capturing the - // argument, - // executing the signal, and verifying that the desired signal was executed - final ArgumentCaptor batchRequestAddArgCaptor = ArgumentCaptor.forClass(Proc.class); - verify(mBatchRequest).add(batchRequestAddArgCaptor.capture()); - final Proc signal = batchRequestAddArgCaptor.getValue(); - signal.apply(); - verify(mNewConnectionManagerWorkflow).submitManualSync(); - } - - private void mockWorkflowStatus(final WorkflowExecutionStatus status) { - when(workflowServiceBlockingStub.describeWorkflowExecution(any())).thenReturn( - DescribeWorkflowExecutionResponse.newBuilder().setWorkflowExecutionInfo( - WorkflowExecutionInfo.newBuilder().setStatus(status).buildPartial()).build()); - } - -} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java index 8c91948b05f2..a75e9275459d 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java @@ -11,6 +11,7 @@ import io.airbyte.commons.temporal.TemporalJobType; import io.airbyte.commons.temporal.scheduling.ConnectionManagerWorkflow; import io.airbyte.commons.temporal.scheduling.ConnectionUpdaterInput; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.commons.temporal.scheduling.state.WorkflowState; import io.airbyte.commons.temporal.scheduling.state.listener.TestStateListener; import io.airbyte.commons.temporal.scheduling.state.listener.WorkflowStateChangedListener.ChangedStateEvent; @@ -57,7 +58,6 @@ import io.airbyte.workers.temporal.scheduling.testsyncworkflow.SleepingSyncWorkflow; import io.airbyte.workers.temporal.scheduling.testsyncworkflow.SourceAndDestinationFailureSyncWorkflow; import io.airbyte.workers.temporal.support.TemporalProxyHelper; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.micronaut.context.BeanRegistration; import io.micronaut.inject.BeanIdentifier; import io.temporal.activity.ActivityOptions; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityTest.java index b38f5bd1157d..326b79447fd4 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityTest.java @@ -4,10 +4,10 @@ package io.airbyte.workers.temporal.scheduling.activities; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.validation.json.JsonValidationException; import io.airbyte.workers.helper.ConnectionHelper; -import io.airbyte.workers.temporal.exception.RetryableException; import io.airbyte.workers.temporal.scheduling.activities.ConnectionDeletionActivity.ConnectionDeletionInput; import java.io.IOException; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java index 876bafbdf219..57d856a111d3 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java @@ -10,6 +10,7 @@ import static org.mockito.Mockito.verify; import io.airbyte.commons.docker.DockerUtils; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.AttemptFailureSummary; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.DestinationConnection; @@ -45,7 +46,6 @@ import io.airbyte.validation.json.JsonValidationException; import io.airbyte.workers.run.TemporalWorkerRunFactory; import io.airbyte.workers.run.WorkerRun; -import io.airbyte.workers.temporal.exception.RetryableException; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.AttemptCreationInput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.AttemptCreationOutput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.AttemptFailureInput; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityTest.java index d2fc66d76184..15808733acdb 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityTest.java @@ -7,7 +7,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -import io.airbyte.workers.temporal.StreamResetRecordsHelper; +import io.airbyte.commons.temporal.StreamResetRecordsHelper; import io.airbyte.workers.temporal.scheduling.activities.StreamResetActivity.DeleteStreamResetRecordsForJobInput; import java.util.UUID; import org.junit.jupiter.api.Test; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/DbtFailureSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/DbtFailureSyncWorkflow.java index e91a60d53238..677740d2f796 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/DbtFailureSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/DbtFailureSyncWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.RetryState; import io.temporal.failure.ActivityFailure; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/EmptySyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/EmptySyncWorkflow.java index 27bb55555063..3f93c6ba87d2 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/EmptySyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/EmptySyncWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import java.util.UUID; public class EmptySyncWorkflow implements SyncWorkflow { diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationFailureSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationFailureSyncWorkflow.java index 8f6e9d0ae69e..99e52c941c20 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationFailureSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationFailureSyncWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.RetryState; import io.temporal.failure.ActivityFailure; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationTraceFailureSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationTraceFailureSyncWorkflow.java index 4df2b66e92b2..1845f0ac0e45 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationTraceFailureSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/NormalizationTraceFailureSyncWorkflow.java @@ -5,6 +5,7 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; import com.google.common.annotations.VisibleForTesting; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.FailureReason; import io.airbyte.config.FailureReason.FailureOrigin; import io.airbyte.config.NormalizationSummary; @@ -12,7 +13,6 @@ import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import java.util.List; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/PersistFailureSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/PersistFailureSyncWorkflow.java index 54a97b3f2988..e8b704a6512c 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/PersistFailureSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/PersistFailureSyncWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.RetryState; import io.temporal.failure.ActivityFailure; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/ReplicateFailureSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/ReplicateFailureSyncWorkflow.java index 7706b860e1f2..d4c64152e2cc 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/ReplicateFailureSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/ReplicateFailureSyncWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.RetryState; import io.temporal.failure.ActivityFailure; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SleepingSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SleepingSyncWorkflow.java index 59dba751228d..c2f24529de12 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SleepingSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SleepingSyncWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.workflow.Workflow; import java.time.Duration; import java.util.UUID; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SourceAndDestinationFailureSyncWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SourceAndDestinationFailureSyncWorkflow.java index 7f4d1664d4c7..19a8bdabbe1c 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SourceAndDestinationFailureSyncWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SourceAndDestinationFailureSyncWorkflow.java @@ -5,6 +5,7 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; import com.google.common.annotations.VisibleForTesting; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.FailureReason; import io.airbyte.config.FailureReason.FailureOrigin; import io.airbyte.config.StandardSyncInput; @@ -14,7 +15,6 @@ import io.airbyte.config.SyncStats; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import java.util.Set; import java.util.UUID; import org.assertj.core.util.Sets; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingOutputWorkflow.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingOutputWorkflow.java index 87b4c18f2a16..07925086dcc7 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingOutputWorkflow.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingOutputWorkflow.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import java.util.UUID; public class SyncWorkflowFailingOutputWorkflow implements SyncWorkflow { diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingWithHearbeatTimeoutException.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingWithHearbeatTimeoutException.java index c854bbb621b2..b09a20163e95 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingWithHearbeatTimeoutException.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowFailingWithHearbeatTimeoutException.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.TimeoutType; import io.temporal.failure.TimeoutFailure; import io.temporal.workflow.Workflow; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowWithActivityFailureException.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowWithActivityFailureException.java index 35e53678a99b..2a897d73dabd 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowWithActivityFailureException.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/testsyncworkflow/SyncWorkflowWithActivityFailureException.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.testsyncworkflow; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOutput; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; -import io.airbyte.workers.temporal.sync.SyncWorkflow; import io.temporal.api.enums.v1.RetryState; import io.temporal.failure.ActivityFailure; import io.temporal.workflow.Workflow; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/ErrorTestWorkflowImpl.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/ErrorTestWorkflowImpl.java index ed419ba481f9..71b97086ede5 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/ErrorTestWorkflowImpl.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/ErrorTestWorkflowImpl.java @@ -4,7 +4,7 @@ package io.airbyte.workers.temporal.stubs; -import io.airbyte.workers.temporal.exception.RetryableException; +import io.airbyte.commons.temporal.exception.RetryableException; public class ErrorTestWorkflowImpl implements TestWorkflow { diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/InvalidTestWorkflowImpl.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/InvalidTestWorkflowImpl.java index 2a749650d91c..afc43d56ce53 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/InvalidTestWorkflowImpl.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/stubs/InvalidTestWorkflowImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.stubs; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.workers.temporal.annotations.TemporalActivityStub; -import io.airbyte.workers.temporal.exception.RetryableException; @SuppressWarnings("PMD.UnusedPrivateField") public class InvalidTestWorkflowImpl implements TestWorkflow { diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/support/TemporalActivityStubInterceptorTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/support/TemporalActivityStubInterceptorTest.java index bbf5c75f017a..6b4a2f33e6cf 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/support/TemporalActivityStubInterceptorTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/support/TemporalActivityStubInterceptorTest.java @@ -7,7 +7,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.airbyte.workers.temporal.exception.RetryableException; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.workers.temporal.stubs.ErrorTestWorkflowImpl; import io.airbyte.workers.temporal.stubs.InvalidTestWorkflowImpl; import io.airbyte.workers.temporal.stubs.TestActivity; diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java index 9c062c38c55b..d0d692645f9b 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java @@ -16,6 +16,7 @@ import static org.mockito.Mockito.when; import io.airbyte.commons.temporal.TemporalUtils; +import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; import io.airbyte.config.OperatorDbtInput; diff --git a/charts/airbyte/templates/env-configmap.yaml b/charts/airbyte/templates/env-configmap.yaml index b9945de80ed2..19006ff3043e 100644 --- a/charts/airbyte/templates/env-configmap.yaml +++ b/charts/airbyte/templates/env-configmap.yaml @@ -64,6 +64,7 @@ data: CONTAINER_ORCHESTRATOR_ENABLED: {{ .Values.worker.containerOrchestrator.enabled | quote }} CONTAINER_ORCHESTRATOR_IMAGE: {{ .Values.worker.containerOrchestrator.image | quote }} WORKERS_MICRONAUT_ENVIRONMENTS: "control-plane" + CRON_MICRONAUT_ENVIRONMENTS: "control-plane" WORKER_LOGS_STORAGE_TYPE: {{ .Values.global.logs.storage.type | quote }} WORKER_STATE_STORAGE_TYPE: {{ .Values.global.state.storage.type | quote }} {{- end }} diff --git a/docker-compose.yaml b/docker-compose.yaml index 44408bdec362..b8d18220030f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -194,6 +194,7 @@ services: - TEMPORAL_HISTORY_RETENTION_IN_DAYS=${TEMPORAL_HISTORY_RETENTION_IN_DAYS} - UPDATE_DEFINITIONS_CRON_ENABLED=${UPDATE_DEFINITIONS_CRON_ENABLED} - WORKSPACE_ROOT=${WORKSPACE_ROOT} + - MICRONAUT_ENVIRONMENTS=${CRON_MICRONAUT_ENVIRONMENTS} volumes: - workspace:${WORKSPACE_ROOT} volumes: diff --git a/kube/overlays/dev-integration-test/.env b/kube/overlays/dev-integration-test/.env index 4f3226e4eb0f..2b3674e89f11 100644 --- a/kube/overlays/dev-integration-test/.env +++ b/kube/overlays/dev-integration-test/.env @@ -60,6 +60,9 @@ NORMALIZATION_JOB_MAIN_CONTAINER_CPU_REQUEST= # Worker # WORKERS_MICRONAUT_ENVIRONMENTS=control-plane +# Cron # +CRON_MICRONAUT_ENVIRONMENTS=control-plane + # Worker pod tolerations, annotations and node selectors JOB_KUBE_TOLERATIONS= JOB_KUBE_ANNOTATIONS= diff --git a/kube/overlays/dev/.env b/kube/overlays/dev/.env index 0ddf0bb2800b..839afbaa47b1 100644 --- a/kube/overlays/dev/.env +++ b/kube/overlays/dev/.env @@ -62,6 +62,9 @@ NORMALIZATION_JOB_MAIN_CONTAINER_CPU_REQUEST= # Worker # WORKERS_MICRONAUT_ENVIRONMENTS=control-plane +# Cron # +CRON_MICRONAUT_ENVIRONMENTS=control-plane + # Worker pod tolerations, annotations and node selectors JOB_KUBE_TOLERATIONS= JOB_KUBE_ANNOTATIONS= diff --git a/kube/overlays/stable-with-resource-limits/.env b/kube/overlays/stable-with-resource-limits/.env index e69f951c1c20..375c93d34d8a 100644 --- a/kube/overlays/stable-with-resource-limits/.env +++ b/kube/overlays/stable-with-resource-limits/.env @@ -62,6 +62,9 @@ NORMALIZATION_JOB_MAIN_CONTAINER_CPU_REQUEST= # Worker # WORKERS_MICRONAUT_ENVIRONMENTS=control-plane +# Cron # +CRON_MICRONAUT_ENVIRONMENTS=control-plane + # Worker pod tolerations, annotations and node selectors JOB_KUBE_TOLERATIONS= JOB_KUBE_ANNOTATIONS= diff --git a/kube/overlays/stable/.env b/kube/overlays/stable/.env index 24ab591f212a..5daa65701e11 100644 --- a/kube/overlays/stable/.env +++ b/kube/overlays/stable/.env @@ -62,6 +62,9 @@ NORMALIZATION_JOB_MAIN_CONTAINER_CPU_REQUEST= # Worker # WORKERS_MICRONAUT_ENVIRONMENTS=control-plane +# Cron # +CRON_MICRONAUT_ENVIRONMENTS=control-plane + # Worker pod tolerations, annotations and node selectors JOB_KUBE_TOLERATIONS= JOB_KUBE_ANNOTATIONS= diff --git a/kube/resources/cron.yaml b/kube/resources/cron.yaml index 76ba09d37759..47ce4853d710 100644 --- a/kube/resources/cron.yaml +++ b/kube/resources/cron.yaml @@ -56,6 +56,11 @@ spec: configMapKeyRef: name: airbyte-env key: WORKSPACE_ROOT + - name: MICRONAUT_ENVIRONMENTS + valueFrom: + configMapKeyRef: + name: airbyte-env + key: CRON_MICRONAUT_ENVIRONMENTS volumeMounts: - name: airbyte-volume-configs mountPath: /configs diff --git a/settings.gradle b/settings.gradle index c0233a62ce0f..4ba0c20041f8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -84,6 +84,7 @@ include ':airbyte-config:config-persistence' // transitively used by airbyte-wor include ':airbyte-persistence:job-persistence' // transitively used by airbyte-workers. include ':airbyte-db:jooq' // transitively used by airbyte-workers. include ':airbyte-notification' // transitively used by airbyte-workers. +include ':airbyte-worker-models' // platform if (!System.getenv().containsKey("SUB_BUILD") || System.getenv().get("SUB_BUILD") == "PLATFORM") { From 8560f190223a3ba061e948f76d8332c42a9766d2 Mon Sep 17 00:00:00 2001 From: Amruta Ranade <11484018+Amruta-Ranade@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:41:50 -0400 Subject: [PATCH 147/498] Updated connector catalog page (#18076) --- docs/integrations/README.md | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/integrations/README.md b/docs/integrations/README.md index fe4fae5cccc1..a331bcce19aa 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -18,9 +18,9 @@ For more information about the grading system, see [Product Release Stages](http |:--------------------------------------------------------------------------------------------| :------------------- | :------------------ | | [3PL Central](sources/tplcentral.md) | Alpha | No | | [Adjust](sources/adjust.md) | Alpha | No | -| [Airtable](sources/airtable.md) | Alpha | No | +| [Airtable](sources/airtable.md) | Alpha | Yes | | [AlloyDB](sources/alloydb.md) | Generally Available | Yes | -| [Amazon Ads](sources/amazon-ads.md) | Beta | Yes | +| [Amazon Ads](sources/amazon-ads.md) | Generally Available | Yes | | [Amazon Seller Partner](sources/amazon-seller-partner.md) | Alpha | Yes | | [Amazon SQS](sources/amazon-sqs.md) | Alpha | Yes | | [Amplitude](sources/amplitude.md) | Generally Available | Yes | @@ -29,14 +29,14 @@ For more information about the grading system, see [Product Release Stages](http | [Asana](sources/asana.md) | Alpha | No | | [AWS CloudTrail](sources/aws-cloudtrail.md) | Alpha | Yes | | [Azure Table Storage](sources/azure-table.md) | Alpha | Yes | -| [BambooHR](sources/bamboo-hr.md) | Alpha | No | +| [BambooHR](sources/bamboo-hr.md) | Generally Available | Yes | | [Baton](sources/hellobaton.md) | Alpha | No | | [BigCommerce](sources/bigcommerce.md) | Alpha | Yes | | [BigQuery](sources/bigquery.md) | Alpha | Yes | | [Bing Ads](sources/bing-ads.md) | Generally Available | Yes | | [Braintree](sources/braintree.md) | Alpha | Yes | | [Cart.com](sources/cart.md) | Alpha | No | -| [Chargebee](sources/chargebee.md) | Beta | Yes | +| [Chargebee](sources/chargebee.md) | Generally Available | Yes | | [Chargify](sources/chargify.md) | Alpha | No | | [Chartmogul](sources/chartmogul.md) | Alpha | Yes | | [ClickHouse](sources/clickhouse.md) | Alpha | Yes | @@ -61,7 +61,7 @@ For more information about the grading system, see [Product Release Stages](http | [File](sources/file.md) | Beta | Yes | | [Firebolt](sources/firebolt.md) | Alpha | Yes | | [Flexport](sources/flexport.md) | Alpha | No | -| [Freshdesk](sources/freshdesk.md) | Beta | Yes | +| [Freshdesk](sources/freshdesk.md) | Generally Available | Yes | | [Freshsales](sources/freshsales.md) | Alpha | No | | [Freshservice](sources/freshservice.md) | Alpha | No | | [GitHub](sources/github.md) | Generally Available | Yes | @@ -74,14 +74,14 @@ For more information about the grading system, see [Product Release Stages](http | [Google Search Console](sources/google-search-console.md) | Generally Available | Yes | | [Google Sheets](sources/google-sheets.md) | Generally Available | Yes | | [Google Workspace Admin Reports](sources/google-workspace-admin-reports.md) | Alpha | Yes | -| [Greenhouse](sources/greenhouse.md) | Beta | Yes | +| [Greenhouse](sources/greenhouse.md) | Generally Available | Yes | | [Harness](sources/harness.md) | Alpha | No | -| [Harvest](sources/harvest.md) | Generally Available | No | +| [Harvest](sources/harvest.md) | Generally Available | Yes | | [http-request](sources/http-request.md) | Alpha | No | | [HubSpot](sources/hubspot.md) | Generally Available | Yes | | [Instagram](sources/instagram.md) | Generally Available | Yes | | [Intercom](sources/intercom.md) | Generally Available | Yes | -| [Iterable](sources/iterable.md) | Beta | Yes | +| [Iterable](sources/iterable.md) | Generally Available | Yes | | [Jenkins](sources/jenkins.md) | Alpha | No | | [Jira](sources/jira.md) | Alpha | No | | [Kafka](sources/kafka.md) | Alpha | No | @@ -96,7 +96,7 @@ For more information about the grading system, see [Product Release Stages](http | [Looker](sources/looker.md) | Alpha | Yes | | [Magento](sources/magento.md) | Alpha | No | | [Mailchimp](sources/mailchimp.md) | Generally Available | Yes | -| [Marketo](sources/marketo.md) | Beta | Yes | +| [Marketo](sources/marketo.md) | Generally Available | Yes | | [Metabase](sources/metabase.md) | Alpha | Yes | | [Microsoft Dynamics AX](sources/microsoft-dynamics-ax.md) | Alpha | No | | [Microsoft Dynamics Customer Engagement](sources/microsoft-dynamics-customer-engagement.md) | Alpha | No | @@ -104,27 +104,27 @@ For more information about the grading system, see [Product Release Stages](http | [Microsoft Dynamics NAV](sources/microsoft-dynamics-nav.md) | Alpha | No | | [Microsoft SQL Server (MSSQL)](sources/mssql.md) | Alpha | Yes | | [Microsoft Teams](sources/microsoft-teams.md) | Alpha | Yes | -| [Mixpanel](sources/mixpanel.md) | Beta | Yes | +| [Mixpanel](sources/mixpanel.md) | Generally Available | Yes | | [Monday](sources/monday.md) | Alpha | Yes | | [Mongo DB](sources/mongodb-v2.md) | Alpha | Yes | | [My Hours](sources/my-hours.md) | Alpha | Yes | -| [MySQL](sources/mysql.md) | Alpha | Yes | -| [Notion](sources/notion.md) | Generally Available | No | +| [MySQL](sources/mysql.md) | Beta | Yes | +| [Notion](sources/notion.md) | Generally Available | Yes | | [Okta](sources/okta.md) | Alpha | Yes | | [OneSignal](sources/onesignal.md) | Alpha | No | | [OpenWeather](sources/openweather.md) | Alpha | No | | [Oracle DB](sources/oracle.md) | Alpha | Yes | -| [Oracle Netsuite](sources/netsuite.md) | Alpha | Yes | +| [Oracle Netsuite](sources/netsuite.md) | Generally Available | Yes | | [Oracle PeopleSoft](sources/oracle-peoplesoft.md) | Alpha | No | | [Oracle Siebel CRM](sources/oracle-siebel-crm.md) | Alpha | No | | [Orb](sources/orb.md) | Alpha | Yes | | [Orbit](sources/orbit.md) | Alpha | Yes | | [Outreach](./sources/outreach.md) | Alpha | No | | [PagerDuty](sources/pagerduty.md) | Alpha | No | -| [PayPal Transaction](sources/paypal-transaction.md) | Beta | Yes | +| [PayPal Transaction](sources/paypal-transaction.md) | Generally Available | Yes | | [Paystack](sources/paystack.md) | Alpha | No | | [PersistIq](sources/persistiq.md) | Alpha | Yes | -| [Pinterest](sources/pinterest.md) | Beta | Yes | +| [Pinterest](sources/pinterest.md) | Generally Available | Yes | | [Pipedrive](sources/pipedrive.md) | Alpha | No | | [Pivotal Tracker](sources/pivotal-tracker.md) | Alpha | No | | [Plaid](sources/plaid.md) | Alpha | No | @@ -134,23 +134,23 @@ For more information about the grading system, see [Product Release Stages](http | [PrestaShop](sources/presta-shop.md) | Alpha | Yes | | [Qualaroo](sources/qualaroo.md) | Alpha | Yes | | [QuickBooks](sources/quickbooks.md) | Alpha | No | -| [Recharge](sources/recharge.md) | Beta | Yes | +| [Recharge](sources/recharge.md) | Generally Available | Yes | | [Recurly](sources/recurly.md) | Alpha | Yes | | [Redshift](sources/redshift.md) | Alpha | Yes | | [Retently](sources/retently.md) | Alpha | Yes | -| [S3](sources/s3.md) | Generally Available | Yes | +| [S3](sources/s3.md) | Generally Available | No | | [Salesforce](sources/salesforce.md) | Generally Available | Yes | | [Salesloft](sources/salesloft.md) | Alpha | No | | [SAP Business One](sources/sap-business-one.md) | Alpha | No | | [SearchMetrics](./sources/search-metrics.md) | Alpha | No | | [Sendgrid](sources/sendgrid.md) | Beta | Yes | -| [Sentry](sources/sentry.md) | Beta | Yes | +| [Sentry](sources/sentry.md) | Generally Available | Yes | | [SFTP](sources/sftp.md) | Alpha | Yes | | [Shopify](sources/shopify.md) | Alpha | No | | [Short.io](sources/shortio.md) | Alpha | Yes | -| [Slack](sources/slack.md) | Beta | Yes | +| [Slack](sources/slack.md) | Generally Available | Yes | | [Smartsheets](sources/smartsheets.md) | Beta | Yes | -| [Snapchat Marketing](sources/snapchat-marketing.md) | Beta | Yes | +| [Snapchat Marketing](sources/snapchat-marketing.md) | Generally Available | Yes | | [Snowflake](sources/snowflake.md) | Alpha | Yes | | [Spree Commerce](sources/spree-commerce.md) | Alpha | No | | [Square](sources/square.md) | Alpha | Yes | @@ -162,7 +162,7 @@ For more information about the grading system, see [Product Release Stages](http | [TiDB](sources/tidb.md) | Alpha | No | | [TikTok Marketing](./sources/tiktok-marketing.md) | Generally Available | Yes | | [Trello](sources/trello.md) | Alpha | No | -| [Twilio](sources/twilio.md) | Beta | Yes | +| [Twilio](sources/twilio.md) | Generally Available | Yes | | [Typeform](sources/typeform.md) | Alpha | Yes | | [US Census](sources/us-census.md) | Alpha | Yes | | [VictorOps](sources/victorops.md) | Alpha | No | @@ -175,7 +175,7 @@ For more information about the grading system, see [Product Release Stages](http | [Zendesk Chat](sources/zendesk-chat.md) | Generally Available | Yes | | [Zendesk Sunshine](sources/zendesk-sunshine.md) | Alpha | Yes | | [Zendesk Support](sources/zendesk-support.md) | Generally Available | Yes | -| [Zendesk Talk](sources/zendesk-talk.md) | Beta | Yes | +| [Zendesk Talk](sources/zendesk-talk.md) | Generally Available | Yes | | [Zenloop](sources/zenloop.md) | Alpha | Yes | | [Zoho CRM](sources/zoho-crm.md) | Alpha | No | | [Zoom](sources/zoom.md) | Alpha | No | @@ -197,7 +197,7 @@ For more information about the grading system, see [Product Release Stages](http | [Elasticsearch](destinations/elasticsearch.md) | Alpha | No | | [End-to-End Testing](destinations/e2e-test.md) | Alpha | Yes | | [Firebolt](destinations/firebolt.md) | Alpha | Yes | -| [Google Cloud Storage (GCS)](destinations/gcs.md) | Beta | No | +| [Google Cloud Storage (GCS)](destinations/gcs.md) | Beta | Yes | | [Google Pubsub](destinations/pubsub.md) | Alpha | Yes | | [Google Sheets](destinations/google-sheets.md) | Alpha | Yes | | [Kafka](destinations/kafka.md) | Alpha | No | @@ -219,7 +219,7 @@ For more information about the grading system, see [Product Release Stages](http | [Redis](destinations/redis.md) | Alpha | No | | [Redshift](destinations/redshift.md) | Beta | Yes | | [Rockset](destinations/rockset.md) | Alpha | No | -| [S3](destinations/s3.md) | Generally Available | No | +| [S3](destinations/s3.md) | Generally Available | Yes | | [Scylla](destinations/scylla.md) | Alpha | No | | [SFTP JSON](destinations/sftp-json.md) | Alpha | Yes | | [Snowflake](destinations/snowflake.md) | Generally Available | Yes | From 260ab6bdeac9add1d1fcf67a5777980199521911 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Mon, 17 Oct 2022 12:59:28 -0700 Subject: [PATCH 148/498] Move the port forward outside of the main docker-compose (#17864) --- docker-compose.acceptance-test.yaml | 13 +++++++++++++ docker-compose.yaml | 2 -- tools/bin/acceptance_test.sh | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 docker-compose.acceptance-test.yaml diff --git a/docker-compose.acceptance-test.yaml b/docker-compose.acceptance-test.yaml new file mode 100644 index 000000000000..5366ea1ba112 --- /dev/null +++ b/docker-compose.acceptance-test.yaml @@ -0,0 +1,13 @@ +# Adds ports to the db and access to the temporal UI for debugging purposes. +# Expected to be used like this: +# VERSION=dev docker-compose -f docker-compose.yaml -f docker-compose.debug.yaml up +version: "3.7" +x-logging: &default-logging + options: + max-size: "100m" + max-file: "5" + driver: json-file +services: + airbyte-temporal: + ports: + - 7233:7233 diff --git a/docker-compose.yaml b/docker-compose.yaml index b8d18220030f..b137d983c819 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -165,8 +165,6 @@ services: logging: *default-logging container_name: airbyte-temporal restart: unless-stopped - ports: - - 7233:7233 environment: - DB=postgresql - DB_PORT=${DATABASE_PORT} diff --git a/tools/bin/acceptance_test.sh b/tools/bin/acceptance_test.sh index 3b3e192bec49..188a13c7156b 100755 --- a/tools/bin/acceptance_test.sh +++ b/tools/bin/acceptance_test.sh @@ -21,7 +21,7 @@ check_success() { echo "Starting app..." # Detach so we can run subsequent commands -VERSION=dev TRACKING_STRATEGY=logging USE_STREAM_CAPABLE_STATE=true docker-compose up -d +VERSION=dev TRACKING_STRATEGY=logging USE_STREAM_CAPABLE_STATE=true docker-compose -f docker-compose.yaml -f docker-compose.acceptance-test.yaml up -d # Sometimes source/dest containers using airbyte volumes survive shutdown, which need to be killed in order to shut down properly. shutdown_cmd="docker-compose down -v || docker kill \$(docker ps -a -f volume=airbyte_workspace -f volume=airbyte_data -f volume=airbyte_db -q) && docker-compose down -v" From a5a0409ad55192abda4b6fac028ac90080e51e43 Mon Sep 17 00:00:00 2001 From: Octavia Squidington III <90398440+octavia-squidington-iii@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:33:20 -0500 Subject: [PATCH 149/498] Bump Airbyte version from 0.40.14 to 0.40.15 (#17970) Co-authored-by: benmoriceau --- .bumpversion.cfg | 2 +- .env | 2 +- airbyte-bootloader/Dockerfile | 2 +- airbyte-container-orchestrator/Dockerfile | 2 +- airbyte-cron/Dockerfile | 2 +- airbyte-metrics/reporter/Dockerfile | 2 +- airbyte-server/Dockerfile | 2 +- airbyte-webapp/package-lock.json | 4 ++-- airbyte-webapp/package.json | 2 +- airbyte-workers/Dockerfile | 2 +- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-server/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.yaml | 2 +- charts/airbyte/README.md | 4 ++-- docs/operator-guides/upgrading-airbyte.md | 2 +- kube/overlays/stable-with-resource-limits/.env | 2 +- .../stable-with-resource-limits/kustomization.yaml | 12 ++++++------ kube/overlays/stable/.env | 2 +- kube/overlays/stable/kustomization.yaml | 12 ++++++------ octavia-cli/Dockerfile | 2 +- octavia-cli/README.md | 4 ++-- octavia-cli/install.sh | 2 +- octavia-cli/setup.py | 2 +- 26 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f01a4920ec12..0a09e042299e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.40.14 +current_version = 0.40.15 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-[a-z]+)? diff --git a/.env b/.env index d8ab738b59fd..96ca2dc1ab9a 100644 --- a/.env +++ b/.env @@ -10,7 +10,7 @@ ### SHARED ### -VERSION=0.40.14 +VERSION=0.40.15 # When using the airbyte-db via default docker image CONFIG_ROOT=/data diff --git a/airbyte-bootloader/Dockerfile b/airbyte-bootloader/Dockerfile index a8847e1d90cb..a063f7ea0ebf 100644 --- a/airbyte-bootloader/Dockerfile +++ b/airbyte-bootloader/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} -ARG VERSION=0.40.14 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-bootloader ENV VERSION ${VERSION} diff --git a/airbyte-container-orchestrator/Dockerfile b/airbyte-container-orchestrator/Dockerfile index 4cdf5b13e0e4..86e187079e6a 100644 --- a/airbyte-container-orchestrator/Dockerfile +++ b/airbyte-container-orchestrator/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.14 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-container-orchestrator ENV VERSION=${VERSION} diff --git a/airbyte-cron/Dockerfile b/airbyte-cron/Dockerfile index bef9569442d5..afd1eb0a144d 100644 --- a/airbyte-cron/Dockerfile +++ b/airbyte-cron/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS cron -ARG VERSION=0.40.14 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-cron ENV VERSION ${VERSION} diff --git a/airbyte-metrics/reporter/Dockerfile b/airbyte-metrics/reporter/Dockerfile index ebf860125bf5..dcd697f66baf 100644 --- a/airbyte-metrics/reporter/Dockerfile +++ b/airbyte-metrics/reporter/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS metrics-reporter -ARG VERSION=0.40.14 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-metrics-reporter ENV VERSION ${VERSION} diff --git a/airbyte-server/Dockerfile b/airbyte-server/Dockerfile index c26a1a991db1..ca65443b10ea 100644 --- a/airbyte-server/Dockerfile +++ b/airbyte-server/Dockerfile @@ -3,7 +3,7 @@ FROM ${JDK_IMAGE} AS server EXPOSE 8000 -ARG VERSION=0.40.14 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-server ENV VERSION ${VERSION} diff --git a/airbyte-webapp/package-lock.json b/airbyte-webapp/package-lock.json index 12dbb3e0b9ac..e301769fc9dc 100644 --- a/airbyte-webapp/package-lock.json +++ b/airbyte-webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "airbyte-webapp", - "version": "0.40.14", + "version": "0.40.15", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "airbyte-webapp", - "version": "0.40.14", + "version": "0.40.15", "dependencies": { "@datadog/browser-rum": "^4.21.2", "@floating-ui/react-dom": "^1.0.0", diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index a6fa2a1a9db4..35c62a58cb74 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -1,6 +1,6 @@ { "name": "airbyte-webapp", - "version": "0.40.14", + "version": "0.40.15", "private": true, "engines": { "node": ">=16.0.0" diff --git a/airbyte-workers/Dockerfile b/airbyte-workers/Dockerfile index 7c594da87481..090dee9fcac9 100644 --- a/airbyte-workers/Dockerfile +++ b/airbyte-workers/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.14 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-workers ENV VERSION ${VERSION} diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 42c306f2e19a..9680b1bee965 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -21,7 +21,7 @@ version: "0.40.20" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.14" +appVersion: "0.40.15" dependencies: - name: common diff --git a/charts/airbyte-server/Chart.yaml b/charts/airbyte-server/Chart.yaml index d804d190414e..a590b6431ab3 100644 --- a/charts/airbyte-server/Chart.yaml +++ b/charts/airbyte-server/Chart.yaml @@ -21,7 +21,7 @@ version: 0.45.3 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.14" +appVersion: "0.40.15" dependencies: - name: common diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index e0ffa5b1f94a..83ce147085e9 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -21,7 +21,7 @@ version: "0.40.20" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.14" +appVersion: "0.40.15" dependencies: - name: common diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index ff65eb39bd17..eb27913563b5 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -21,7 +21,7 @@ version: "0.40.20" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.14" +appVersion: "0.40.15" dependencies: - name: common diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 4e0a0aa36230..c0145c7bfddb 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -21,7 +21,7 @@ version: "0.40.20" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.14" +appVersion: "0.40.15" dependencies: - name: common diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 2404f7aa288a..468fff07eece 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -21,7 +21,7 @@ version: 0.40.20 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.14" +appVersion: "0.40.15" dependencies: - name: common diff --git a/charts/airbyte/README.md b/charts/airbyte/README.md index 7863221df99d..08b3cedfca28 100644 --- a/charts/airbyte/README.md +++ b/charts/airbyte/README.md @@ -1,6 +1,6 @@ # airbyte -![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.14](https://img.shields.io/badge/AppVersion-0.40.14-informational?style=flat-square) +![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.15](https://img.shields.io/badge/AppVersion-0.40.15-informational?style=flat-square) Helm chart to deploy airbyte @@ -248,7 +248,7 @@ Helm chart to deploy airbyte | worker.hpa.enabled | bool | `false` | | | worker.image.pullPolicy | string | `"IfNotPresent"` | | | worker.image.repository | string | `"airbyte/worker"` | | -| worker.image.tag | string | `"0.40.14"` | | +| worker.image.tag | string | `"0.40.15"` | | | worker.livenessProbe.enabled | bool | `true` | | | worker.livenessProbe.failureThreshold | int | `3` | | | worker.livenessProbe.initialDelaySeconds | int | `30` | | diff --git a/docs/operator-guides/upgrading-airbyte.md b/docs/operator-guides/upgrading-airbyte.md index c96a14cea1a4..4c7bfd2a88db 100644 --- a/docs/operator-guides/upgrading-airbyte.md +++ b/docs/operator-guides/upgrading-airbyte.md @@ -103,7 +103,7 @@ If you are upgrading from (i.e. your current version of Airbyte is) Airbyte vers Here's an example of what it might look like with the values filled in. It assumes that the downloaded `airbyte_archive.tar.gz` is in `/tmp`. ```bash - docker run --rm -v /tmp:/config airbyte/migration:0.40.14 --\ + docker run --rm -v /tmp:/config airbyte/migration:0.40.15 --\ --input /config/airbyte_archive.tar.gz\ --output /config/airbyte_archive_migrated.tar.gz ``` diff --git a/kube/overlays/stable-with-resource-limits/.env b/kube/overlays/stable-with-resource-limits/.env index 375c93d34d8a..0503f6ba669b 100644 --- a/kube/overlays/stable-with-resource-limits/.env +++ b/kube/overlays/stable-with-resource-limits/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.14 +AIRBYTE_VERSION=0.40.15 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable-with-resource-limits/kustomization.yaml b/kube/overlays/stable-with-resource-limits/kustomization.yaml index d8eaf03c1a36..5110d70a151e 100644 --- a/kube/overlays/stable-with-resource-limits/kustomization.yaml +++ b/kube/overlays/stable-with-resource-limits/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/bootloader - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/server - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/webapp - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/worker - newTag: 0.40.14 + newTag: 0.40.15 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.14 + newTag: 0.40.15 configMapGenerator: - name: airbyte-env diff --git a/kube/overlays/stable/.env b/kube/overlays/stable/.env index 5daa65701e11..3931170e9451 100644 --- a/kube/overlays/stable/.env +++ b/kube/overlays/stable/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.14 +AIRBYTE_VERSION=0.40.15 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable/kustomization.yaml b/kube/overlays/stable/kustomization.yaml index c69ea8925be6..ccdd46b6a73d 100644 --- a/kube/overlays/stable/kustomization.yaml +++ b/kube/overlays/stable/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/bootloader - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/server - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/webapp - newTag: 0.40.14 + newTag: 0.40.15 - name: airbyte/worker - newTag: 0.40.14 + newTag: 0.40.15 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.14 + newTag: 0.40.15 configMapGenerator: - name: airbyte-env diff --git a/octavia-cli/Dockerfile b/octavia-cli/Dockerfile index 99b3f5dd061b..dfff65c6f22c 100644 --- a/octavia-cli/Dockerfile +++ b/octavia-cli/Dockerfile @@ -14,5 +14,5 @@ USER octavia-cli WORKDIR /home/octavia-project ENTRYPOINT ["octavia"] -LABEL io.airbyte.version=0.40.14 +LABEL io.airbyte.version=0.40.15 LABEL io.airbyte.name=airbyte/octavia-cli diff --git a/octavia-cli/README.md b/octavia-cli/README.md index 7852439d1be8..a53ea31c5d9d 100644 --- a/octavia-cli/README.md +++ b/octavia-cli/README.md @@ -104,7 +104,7 @@ This script: ```bash touch ~/.octavia # Create a file to store env variables that will be mapped the octavia-cli container mkdir my_octavia_project_directory # Create your octavia project directory where YAML configurations will be stored. -docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.14 +docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.15 ``` ### Using `docker-compose` @@ -709,7 +709,7 @@ You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment | Version | Date | Description | PR | | ------- | ---------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| 0.40.14 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | +| 0.40.15 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | | 0.39.33 | 2022-07-05 | Add `octavia import all` command | [#14374](https://github.com/airbytehq/airbyte/pull/14374) | | 0.39.32 | 2022-06-30 | Create import command to import and manage existing Airbyte resource from octavia-cli | [#14137](https://github.com/airbytehq/airbyte/pull/14137) | | 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | diff --git a/octavia-cli/install.sh b/octavia-cli/install.sh index 23112c3788bd..ea7c8f245a0f 100755 --- a/octavia-cli/install.sh +++ b/octavia-cli/install.sh @@ -3,7 +3,7 @@ # This install scripts currently only works for ZSH and Bash profiles. # It creates an octavia alias in your profile bound to a docker run command and your current user. -VERSION=0.40.14 +VERSION=0.40.15 OCTAVIA_ENV_FILE=${HOME}/.octavia detect_profile() { diff --git a/octavia-cli/setup.py b/octavia-cli/setup.py index a77f09d71104..158f2bb34b30 100644 --- a/octavia-cli/setup.py +++ b/octavia-cli/setup.py @@ -15,7 +15,7 @@ setup( name="octavia-cli", - version="0.40.14", + version="0.40.15", description="A command line interface to manage Airbyte configurations", long_description=README, author="Airbyte", From fc8be13d3352f4b81385e259f91e997715d9c6fc Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Mon, 17 Oct 2022 23:01:10 +0200 Subject: [PATCH 150/498] =?UTF-8?q?=F0=9F=8E=89=20Source=20Shopify:=20Add?= =?UTF-8?q?=20metafield=20streams=20(#17962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎉 Source Shopify: Add metafield streams * Source Shopify: fix unittest * Source Shopify: docs update * Source Shopify: fix backward compatibility test * Source Shopify: fix schemas * Source Shopify: fix state filter * Source Shopify: refactor & optimize * Source Shopify: fix test privileges * Source Shopify: fix stream filter * Source Shopify: fix streams * Source Shopify: update abnormal state * Source Shopify: fix abnormal state streams * Source Shopify: fix streams * updated methods, formated code * Source Shopify: typo fix * auto-bump connector version Co-authored-by: Oleksandr Bazarnov Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-shopify/Dockerfile | 2 +- .../source-shopify/acceptance-test-config.yml | 6 + .../integration_tests/abnormal_state.json | 59 ++++- .../integration_tests/configured_catalog.json | 220 +++++++++++++++- .../integration_tests/integration_test.py | 8 - .../source_shopify/schemas/articles.json | 53 ++++ .../source_shopify/schemas/blogs.json | 44 ++++ .../source_shopify/schemas/collections.json | 46 ++++ .../schemas/metafield_articles.json | 46 ++++ .../{metafields.json => metafield_blogs.json} | 0 .../schemas/metafield_collections.json | 43 ++++ .../schemas/metafield_customers.json | 46 ++++ .../schemas/metafield_draft_orders.json | 46 ++++ .../schemas/metafield_locations.json | 46 ++++ .../schemas/metafield_orders.json | 46 ++++ .../schemas/metafield_pages.json | 46 ++++ .../schemas/metafield_product_images.json | 46 ++++ .../schemas/metafield_product_variants.json | 46 ++++ .../schemas/metafield_products.json | 46 ++++ .../schemas/metafield_shops.json | 46 ++++ .../schemas/metafield_smart_collections.json | 46 ++++ .../schemas/product_images.json | 37 +++ .../schemas/product_variants.json | 112 +++++++++ .../schemas/smart_collections.json | 49 ++++ .../source-shopify/source_shopify/source.py | 236 ++++++++++++++++-- .../source-shopify/source_shopify/utils.py | 35 ++- .../source-shopify/unit_tests/unit_test.py | 17 +- docs/integrations/sources/shopify.md | 83 +++--- 30 files changed, 1473 insertions(+), 87 deletions(-) delete mode 100644 airbyte-integrations/connectors/source-shopify/integration_tests/integration_test.py create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json rename airbyte-integrations/connectors/source-shopify/source_shopify/schemas/{metafields.json => metafield_blogs.json} (100%) create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json create mode 100644 airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index b1789a1f90b4..65f274bb60d4 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -955,7 +955,7 @@ - name: Shopify sourceDefinitionId: 9da77001-af33-4bcd-be46-6252bf9342b9 dockerRepository: airbyte/source-shopify - dockerImageTag: 0.1.38 + dockerImageTag: 0.1.39 documentationUrl: https://docs.airbyte.com/integrations/sources/shopify icon: shopify.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index e027db5e0214..bded7c7e5672 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -10005,7 +10005,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-shopify:0.1.38" +- dockerImage: "airbyte/source-shopify:0.1.39" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/shopify" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-shopify/Dockerfile b/airbyte-integrations/connectors/source-shopify/Dockerfile index 1e7a6898795c..62bbff5455f6 100644 --- a/airbyte-integrations/connectors/source-shopify/Dockerfile +++ b/airbyte-integrations/connectors/source-shopify/Dockerfile @@ -28,5 +28,5 @@ COPY source_shopify ./source_shopify ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.38 +LABEL io.airbyte.version=0.1.39 LABEL io.airbyte.name=airbyte/source-shopify diff --git a/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml b/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml index cda71978b962..5c542bfe96e0 100644 --- a/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml @@ -17,8 +17,14 @@ tests: status: "failed" discovery: - config_path: "secrets/config.json" + backward_compatibility_tests_config: + disable_for_version: "0.1.38" - config_path: "secrets/config_old.json" + backward_compatibility_tests_config: + disable_for_version: "0.1.38" - config_path: "secrets/config_oauth.json" + backward_compatibility_tests_config: + disable_for_version: "0.1.38" basic_read: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-shopify/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-shopify/integration_tests/abnormal_state.json index e9e8facca510..73502ba27e01 100644 --- a/airbyte-integrations/connectors/source-shopify/integration_tests/abnormal_state.json +++ b/airbyte-integrations/connectors/source-shopify/integration_tests/abnormal_state.json @@ -1,7 +1,22 @@ { + "articles": { + "id": 99999999999999 + }, + "metafield_articles": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "blogs": { + "id": 99999999999999 + }, + "metafield_blogs": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, "customers": { "updated_at": "2025-07-08T05:40:38-07:00" }, + "metafield_customers": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, "orders": { "updated_at": "2025-07-08T05:40:38-07:00" }, @@ -14,15 +29,48 @@ "abandoned_checkouts": { "updated_at": "2025-07-08T05:40:38-07:00" }, - "metafields": { + "metafield_draft_orders": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "metafield_orders": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "product_images": { + "id": 99999999999999 + }, + "metafield_product_images": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "product_variants": { + "id": 99999999999999 + }, + "metafield_product_variants": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "metafield_products": { "updated_at": "2025-07-08T05:40:38-07:00" }, "collects": { "id": 29427031703741 }, + "collections": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "metafield_collections": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "smart_collections": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, + "metafield_smart_collections": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, "custom_collections": { "updated_at": "2025-07-08T05:40:38-07:00" }, + "metafield_custom_collections": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, "order_refunds": { "created_at": "2025-03-03T03:47:46-08:00", "orders": { @@ -35,6 +83,9 @@ "updated_at": "2025-02-22T00:37:28-08:00" } }, + "metafield_locations": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, "transactions": { "created_at": "2025-03-03T03:47:45-08:00", "orders": { @@ -47,6 +98,9 @@ "pages": { "updated_at": "2025-07-08T05:24:10-07:00" }, + "metafield_pages": { + "updated_at": "2025-07-08T05:40:38-07:00" + }, "price_rules": { "updated_at": "2025-09-10T06:48:10-07:00" }, @@ -80,5 +134,8 @@ }, "balance_transactions": { "id": 9999999999999 + }, + "metafield_shops": { + "updated_at": "2025-07-08T05:40:38-07:00" } } diff --git a/airbyte-integrations/connectors/source-shopify/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-shopify/integration_tests/configured_catalog.json index be0c8854c55c..0ea5a78e05e6 100644 --- a/airbyte-integrations/connectors/source-shopify/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-shopify/integration_tests/configured_catalog.json @@ -1,5 +1,53 @@ { "streams": [ + { + "stream": { + "name": "articles", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "metafield_articles", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "blogs", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "metafield_blogs", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "customers", @@ -12,6 +60,18 @@ "cursor_field": ["updated_at"], "destination_sync_mode": "append" }, + { + "stream": { + "name": "metafield_customers", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "orders", @@ -24,6 +84,18 @@ "cursor_field": ["updated_at"], "destination_sync_mode": "append" }, + { + "stream": { + "name": "metafield_orders", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "draft_orders", @@ -36,6 +108,18 @@ "cursor_field": ["updated_at"], "destination_sync_mode": "append" }, + { + "stream": { + "name": "metafield_draft_orders", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "products", @@ -50,7 +134,31 @@ }, { "stream": { - "name": "abandoned_checkouts", + "name": "metafield_products", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "product_images", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "metafield_product_images", "json_schema": {}, "supported_sync_modes": ["incremental", "full_refresh"], "source_defined_cursor": true, @@ -62,7 +170,31 @@ }, { "stream": { - "name": "metafields", + "name": "product_variants", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "metafield_product_variants", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "abandoned_checkouts", "json_schema": {}, "supported_sync_modes": ["incremental", "full_refresh"], "source_defined_cursor": true, @@ -84,6 +216,30 @@ "cursor_field": ["id"], "destination_sync_mode": "append" }, + { + "stream": { + "name": "collections", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "metafield_collections", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "custom_collections", @@ -96,6 +252,30 @@ "cursor_field": ["updated_at"], "destination_sync_mode": "append" }, + { + "stream": { + "name": "smart_collections", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, + { + "stream": { + "name": "metafield_smart_collections", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "order_refunds", @@ -156,6 +336,18 @@ "cursor_field": ["updated_at"], "destination_sync_mode": "append" }, + { + "stream": { + "name": "metafield_pages", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "price_rules", @@ -178,6 +370,18 @@ "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" }, + { + "stream": { + "name": "metafield_shops", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "discount_codes", @@ -202,6 +406,18 @@ "cursor_field": ["id"], "destination_sync_mode": "overwrite" }, + { + "stream": { + "name": "metafield_locations", + "json_schema": {}, + "supported_sync_modes": ["incremental", "full_refresh"], + "source_defined_cursor": true, + "default_cursor_field": ["updated_at"] + }, + "sync_mode": "incremental", + "cursor_field": ["updated_at"], + "destination_sync_mode": "append" + }, { "stream": { "name": "inventory_items", diff --git a/airbyte-integrations/connectors/source-shopify/integration_tests/integration_test.py b/airbyte-integrations/connectors/source-shopify/integration_tests/integration_test.py deleted file mode 100644 index 869f9c8fc10e..000000000000 --- a/airbyte-integrations/connectors/source-shopify/integration_tests/integration_test.py +++ /dev/null @@ -1,8 +0,0 @@ -# -# Copyright (c) 2022 Airbyte, Inc., all rights reserved. -# - - -def test_dummy_test(): - """this is the dummy test to pass integration tests step""" - pass diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json new file mode 100644 index 000000000000..f1b09743f014 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json @@ -0,0 +1,53 @@ +{ + "type": "object", + "properties": { + "id": { + "type": ["null", "integer"] + }, + "title": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "body_html": { + "type": ["null", "string"] + }, + "blog_id": { + "type": ["null", "integer"] + }, + "author": { + "type": ["null", "string"] + }, + "user_id": { + "type": ["null", "string"] + }, + "published_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "summary_html": { + "type": ["null", "string"] + }, + "template_suffix": { + "type": ["null", "string"] + }, + "handle": { + "type": ["null", "string"] + }, + "tags": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json new file mode 100644 index 000000000000..003a867b5672 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json @@ -0,0 +1,44 @@ +{ + "type": "object", + "properties": { + "commentable": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "feedburner": { + "type": ["null", "string"], + "format": "date-time" + }, + "feedburner_location": { + "type": ["null", "integer"] + }, + "handle": { + "type": ["null", "string"] + }, + "id": { + "type": ["null", "integer"] + }, + "tags": { + "type": ["null", "string"] + }, + "template_suffix": { + "type": ["null", "string"] + }, + "title": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json new file mode 100644 index 000000000000..d2503f842f83 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json @@ -0,0 +1,46 @@ +{ + "type": "object", + "properties": { + "id": { + "type": ["null", "integer"] + }, + "handle": { + "type": ["null", "string"] + }, + "title": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "body_html": { + "type": ["null", "string"] + }, + "published_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "sort_order": { + "type": ["null", "string"] + }, + "template_suffix": { + "type": ["null", "string"] + }, + "products_count": { + "type": ["null", "integer"] + }, + "collection_type": { + "type": ["null", "string"] + }, + "published_scope": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + } +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json new file mode 100644 index 000000000000..1e91c726368f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafields.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_blogs.json similarity index 100% rename from airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafields.json rename to airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_blogs.json diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json new file mode 100644 index 000000000000..3a4a7bc1fcbc --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json @@ -0,0 +1,43 @@ +{ + "properties": { + "owner_id": { + "type": ["null", "integer"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "owner_resource": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json new file mode 100644 index 000000000000..1e91c726368f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json new file mode 100644 index 000000000000..1e91c726368f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json new file mode 100644 index 000000000000..bf74f22f5d1f --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json @@ -0,0 +1,46 @@ +{ + "properties": { + "id": { + "type": ["null", "integer"] + }, + "namespace": { + "type": ["null", "string"] + }, + "key": { + "type": ["null", "string"] + }, + "value": { + "type": ["null", "string"] + }, + "value_type": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "owner_id": { + "type": ["null", "integer"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "owner_resource": { + "type": ["null", "string"] + }, + "type": { + "type": ["null", "string"] + }, + "admin_graphql_api_id": { + "type": ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + }, + "type": ["null", "object"] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json new file mode 100644 index 000000000000..1b9299c3976e --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json @@ -0,0 +1,37 @@ +{ + "type": ["null", "object"], + "properties": { + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "id": { + "type": ["null", "integer"] + }, + "position": { + "type": ["null", "integer"] + }, + "product_id": { + "type": ["null", "integer"] + }, + "variant_ids": { + "type": ["null", "array"], + "items": { + "type": ["null", "integer"] + } + }, + "src": { + "type": ["null", "string"] + }, + "width": { + "type": ["null", "integer"] + }, + "height": { + "type": ["null", "integer"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json new file mode 100644 index 000000000000..c5cac4d7e253 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json @@ -0,0 +1,112 @@ +{ + "type": ["null", "object"], + "properties": { + "id": { + "type": ["null", "integer"] + }, + "product_id": { + "type": ["null", "integer"] + }, + "title": { + "type": ["null", "string"] + }, + "price": { + "type": ["null", "number"], + "format": "float" + }, + "sku": { + "type": ["null", "string"] + }, + "position": { + "type": ["null", "integer"] + }, + "inventory_policy": { + "type": ["null", "string"] + }, + "compare_at_price": { + "type": ["null", "string"] + }, + "fulfillment_service": { + "type": ["null", "string"] + }, + "inventory_management": { + "type": ["null", "string"] + }, + "option1": { + "type": ["null", "string"] + }, + "option2": { + "type": ["null", "string"] + }, + "option3": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "taxable": { + "type": ["null", "boolean"] + }, + "barcode": { + "type": ["null", "string"] + }, + "grams": { + "type": ["null", "integer"] + }, + "image_id": { + "type": ["null", "integer"] + }, + "weight": { + "type": ["null", "number"], + "format": "float" + }, + "weight_unit": { + "type": ["null", "string"] + }, + "inventory_item_id": { + "type": ["null", "integer"] + }, + "inventory_quantity": { + "type": ["null", "integer"] + }, + "old_inventory_quantity": { + "type": ["null", "integer"] + }, + "presentment_prices": { + "type": ["null", "array"], + "items": { + "type": ["null", "object"], + "properties": { + "price": { + "type": ["null", "object"], + "properties": { + "amount": { + "type": ["null", "number"] + }, + "currency_code": { + "type": ["null", "string"] + } + } + }, + "compare_at_price": { + "type": ["null", "number"] + } + } + } + }, + "requires_shipping" : { + "type" : ["null", "boolean"] + }, + "admin_graphql_api_id" : { + "type" : ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + } +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json new file mode 100644 index 000000000000..01930de03b54 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json @@ -0,0 +1,49 @@ +{ + "type": ["null", "object"], + "properties": { + "id": { + "type": ["null", "integer"] + }, + "handle": { + "type": ["null", "string"] + }, + "title": { + "type": ["null", "string"] + }, + "updated_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "body_html": { + "type": ["null", "string"] + }, + "published_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "sort_order": { + "type": ["null", "string"] + }, + "template_suffix": { + "type": ["null", "string"] + }, + "disjunctive": { + "type": ["null", "boolean"] + }, + "rules" : { + "type" : ["null", "array"], + "items" : { + "type" : ["null", "string"] + } + }, + "published_scope" : { + "type" : ["null", "string"] + }, + "admin_graphql_api_id" : { + "type" : ["null", "string"] + }, + "shop_url": { + "type": ["null", "string"] + } + } +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/source.py b/airbyte-integrations/connectors/source-shopify/source_shopify/source.py index 2e8dbe03958c..700d9113bd1b 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/source.py +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/source.py @@ -67,7 +67,7 @@ def request_params(self, next_page_token: Mapping[str, Any] = None, **kwargs) -> @limiter.balance_rate_limit() def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: - json_response = response.json() + json_response = response.json() or {} records = json_response.get(self.data_field, []) if self.data_field is not None else json_response # transform method was implemented according to issue 4841 # Shopify API returns price fields as a string and it should be converted to number @@ -88,6 +88,7 @@ def should_retry(self, response: requests.Response) -> bool: if response.status_code == 404: self.logger.warn(f"Stream `{self.name}` is not available, skipping.") setattr(self, "raise_on_http_errors", False) + return False return super().should_retry(response) @property @@ -136,14 +137,23 @@ def request_params(self, stream_state: Mapping[str, Any] = None, next_page_token def filter_records_newer_than_state(self, stream_state: Mapping[str, Any] = None, records_slice: Iterable[Mapping] = None) -> Iterable: # Getting records >= state if stream_state: + state_value = stream_state.get(self.cursor_field) for record in records_slice: if self.cursor_field in record: - if record.get(self.cursor_field, self.default_state_comparison_value) >= stream_state.get(self.cursor_field): + record_value = record.get(self.cursor_field, self.default_state_comparison_value) + if record_value: + if record_value >= state_value: + yield record + else: + # old entities could have cursor field in place, but set to null + self.logger.warn( + f"Stream `{self.name}`, Record ID: `{record.get(self.primary_key)}` cursor value is: {record_value}, record is emitted without state comparison" + ) yield record else: # old entities could miss the cursor field self.logger.warn( - f"Stream `{self.name}`, Record ID: `{record.get(self.primary_key)}` missing cursor: {self.cursor_field}" + f"Stream `{self.name}`, Record ID: `{record.get(self.primary_key)}` missing cursor field: {self.cursor_field}, record is emitted without state comparison" ) yield record else: @@ -171,6 +181,7 @@ class ShopifySubstream(IncrementalShopifyStream): nested_record: str = "id" nested_record_field_name: str = None nested_substream = None + nested_substream_list_field_id = None @property def parent_stream(self) -> object: @@ -232,7 +243,20 @@ def stream_slices(self, stream_state: Mapping[str, Any] = None, **kwargs) -> Ite # to limit the number of API Calls and reduce the time of data fetch, # we can pull the ready data for child_substream, if nested data is present, # and corresponds to the data of child_substream we need. - if self.nested_substream: + if self.nested_substream and self.nested_substream_list_field_id: + if record.get(self.nested_substream): + sorted_substream_slices.extend( + [ + { + self.slice_key: sub_record[self.nested_substream_list_field_id], + self.cursor_field: record[self.nested_substream][0].get( + self.cursor_field, self.default_state_comparison_value + ), + } + for sub_record in record[self.nested_record] + ] + ) + elif self.nested_substream: if record.get(self.nested_substream): sorted_substream_slices.append( { @@ -270,6 +294,45 @@ def read_records( yield from self.filter_records_newer_than_state(stream_state=stream_state, records_slice=records) +class MetafieldShopifySubstream(ShopifySubstream): + slice_key = "id" + data_field = "metafields" + + parent_stream_class: object = None + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + object_id = stream_slice[self.slice_key] + return f"{self.parent_stream_class.data_field}/{object_id}/{self.data_field}.json" + + +class Articles(IncrementalShopifyStream): + data_field = "articles" + cursor_field = "id" + order_field = "id" + filter_field = "since_id" + + def path(self, **kwargs) -> str: + return f"{self.data_field}.json" + + +class MetafieldArticles(MetafieldShopifySubstream): + parent_stream_class: object = Articles + + +class Blogs(IncrementalShopifyStream): + cursor_field = "id" + order_field = "id" + data_field = "blogs" + filter_field = "since_id" + + def path(self, **kwargs) -> str: + return f"{self.data_field}.json" + + +class MetafieldBlogs(MetafieldShopifySubstream): + parent_stream_class: object = Blogs + + class Customers(IncrementalShopifyStream): data_field = "customers" @@ -277,6 +340,10 @@ def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class MetafieldCustomers(MetafieldShopifySubstream): + parent_stream_class: object = Customers + + class Orders(IncrementalShopifyStream): data_field = "orders" @@ -292,6 +359,10 @@ def request_params( return params +class MetafieldOrders(MetafieldShopifySubstream): + parent_stream_class: object = Orders + + class DraftOrders(IncrementalShopifyStream): data_field = "draft_orders" @@ -299,13 +370,72 @@ def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class MetafieldDraftOrders(MetafieldShopifySubstream): + parent_stream_class: object = DraftOrders + + class Products(IncrementalShopifyStream): + use_cache = True data_field = "products" def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class MetafieldProducts(MetafieldShopifySubstream): + parent_stream_class: object = Products + + +class ProductImages(ShopifySubstream): + parent_stream_class: object = Products + cursor_field = "id" + slice_key = "product_id" + data_field = "images" + nested_substream = "images" + filter_field = "since_id" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + product_id = stream_slice[self.slice_key] + return f"products/{product_id}/{self.data_field}.json" + + +class MetafieldProductImages(MetafieldShopifySubstream): + parent_stream_class: object = Products + nested_record = "images" + slice_key = "images" + nested_substream = "images" + nested_substream_list_field_id = "id" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + image_id = stream_slice[self.slice_key] + return f"product_images/{image_id}/{self.data_field}.json" + + +class ProductVariants(ShopifySubstream): + parent_stream_class: object = Products + cursor_field = "id" + slice_key = "product_id" + data_field = "variants" + nested_substream = "variants" + filter_field = "since_id" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + product_id = stream_slice[self.slice_key] + return f"products/{product_id}/{self.data_field}.json" + + +class MetafieldProductVariants(MetafieldShopifySubstream): + parent_stream_class: object = Products + nested_record = "variants" + slice_key = "variants" + nested_substream = "variants" + nested_substream_list_field_id = "id" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + variant_id = stream_slice[self.slice_key] + return f"variants/{variant_id}/{self.data_field}.json" + + class AbandonedCheckouts(IncrementalShopifyStream): data_field = "checkouts" @@ -322,20 +452,24 @@ def request_params( return params -class Metafields(IncrementalShopifyStream): - data_field = "metafields" +class CustomCollections(IncrementalShopifyStream): + data_field = "custom_collections" def path(self, **kwargs) -> str: return f"{self.data_field}.json" -class CustomCollections(IncrementalShopifyStream): - data_field = "custom_collections" +class SmartCollections(IncrementalShopifyStream): + data_field = "smart_collections" def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class MetafieldSmartCollections(MetafieldShopifySubstream): + parent_stream_class: object = SmartCollections + + class Collects(IncrementalShopifyStream): """ Collects stream does not support Incremental Refresh based on datetime fields, only `since_id` is supported: @@ -355,6 +489,27 @@ def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class Collections(ShopifySubstream): + parent_stream_class: object = Collects + nested_record = "collection_id" + slice_key = "collection_id" + data_field = "collection" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + collection_id = stream_slice[self.slice_key] + return f"collections/{collection_id}.json" + + +class MetafieldCollections(MetafieldShopifySubstream): + parent_stream_class: object = Collects + slice_key = "collection_id" + nested_record = "collection_id" + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + object_id = stream_slice[self.slice_key] + return f"collections/{object_id}/{self.data_field}.json" + + class BalanceTransactions(IncrementalShopifyStream): """ @@ -422,6 +577,10 @@ def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class MetafieldPages(MetafieldShopifySubstream): + parent_stream_class: object = Pages + + class PriceRules(IncrementalShopifyStream): data_field = "price_rules" @@ -453,6 +612,10 @@ def path(self, **kwargs): return f"{self.data_field}.json" +class MetafieldLocations(MetafieldShopifySubstream): + parent_stream_class: object = Locations + + class InventoryLevels(ShopifySubstream): parent_stream_class: object = Locations slice_key = "location_id" @@ -513,6 +676,13 @@ def path(self, **kwargs) -> str: return f"{self.data_field}.json" +class MetafieldShops(IncrementalShopifyStream): + data_field = "metafields" + + def path(self, **kwargs) -> str: + return f"{self.data_field}.json" + + class SourceShopify(AbstractSource): def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> Tuple[bool, any]: """ @@ -535,7 +705,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: """ config["authenticator"] = ShopifyAuthenticator(config) user_scopes = self.get_user_scopes(config) - always_permitted_streams = ["Metafields", "Shop"] + always_permitted_streams = ["MetafieldShops", "Shop"] permitted_streams = [ stream for user_scope in user_scopes @@ -545,28 +715,46 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: # before adding stream to stream_instances list, please add it to SCOPES_MAPPING stream_instances = [ - Customers(config), - Orders(config), - DraftOrders(config), - Products(config), AbandonedCheckouts(config), - Metafields(config), - CustomCollections(config), + Articles(config), + BalanceTransactions(config), + Blogs(config), + Collections(config), Collects(config), + CustomCollections(config), + Customers(config), + DiscountCodes(config), + DraftOrders(config), + FulfillmentOrders(config), + Fulfillments(config), + InventoryItems(config), + InventoryLevels(config), + Locations(config), + MetafieldArticles(config), + MetafieldBlogs(config), + MetafieldCollections(config), + MetafieldCustomers(config), + MetafieldDraftOrders(config), + MetafieldLocations(config), + MetafieldOrders(config), + MetafieldPages(config), + MetafieldProductImages(config), + MetafieldProducts(config), + MetafieldProductVariants(config), + MetafieldShops(config), + MetafieldSmartCollections(config), OrderRefunds(config), OrderRisks(config), - TenderTransactions(config), - Transactions(config), - BalanceTransactions(config), + Orders(config), Pages(config), PriceRules(config), - DiscountCodes(config), - Locations(config), - InventoryItems(config), - InventoryLevels(config), - FulfillmentOrders(config), - Fulfillments(config), + ProductImages(config), + Products(config), + ProductVariants(config), Shop(config), + SmartCollections(config), + TenderTransactions(config), + Transactions(config), ] return [stream_instance for stream_instance in stream_instances if self.format_name(stream_instance.name) in permitted_streams] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/utils.py b/airbyte-integrations/connectors/source-shopify/source_shopify/utils.py index 8e3eab27d401..aa349f3e7a07 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/utils.py +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/utils.py @@ -10,17 +10,40 @@ import requests SCOPES_MAPPING = { - "read_customers": ["Customers"], - "read_orders": ["Orders", "AbandonedCheckouts", "TenderTransactions", "Transactions", "Fulfillments", "OrderRefunds", "OrderRisks"], - "read_draft_orders": ["DraftOrders"], - "read_products": ["Products", "CustomCollections", "Collects"], - "read_content": ["Pages"], + "read_customers": ["Customers", "MetafieldCustomers"], + "read_orders": [ + "Orders", + "AbandonedCheckouts", + "TenderTransactions", + "Transactions", + "Fulfillments", + "OrderRefunds", + "OrderRisks", + "MetafieldOrders", + ], + "read_draft_orders": ["DraftOrders", "MetafieldDraftOrders"], + "read_products": [ + "Products", + "MetafieldProducts", + "ProductImages", + "MetafieldProductImages", + "MetafieldProductVariants", + "CustomCollections", + "Collects", + "Collections", + "ProductVariants", + "MetafieldCollections", + "SmartCollections", + "MetafieldSmartCollections", + ], + "read_content": ["Pages", "MetafieldPages"], "read_price_rules": ["PriceRules"], "read_discounts": ["DiscountCodes"], - "read_locations": ["Locations"], + "read_locations": ["Locations", "MetafieldLocations"], "read_inventory": ["InventoryItems", "InventoryLevels"], "read_merchant_managed_fulfillment_orders": ["FulfillmentOrders"], "read_shopify_payments_payouts": ["BalanceTransactions"], + "read_online_store_pages": ["Articles", "MetafieldArticles", "Blogs", "MetafieldBlogs"], } diff --git a/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py index 84684c28a032..2c223d2117eb 100644 --- a/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-shopify/unit_tests/unit_test.py @@ -32,15 +32,16 @@ def test_privileges_validation(requests_mock, basic_config): source = SourceShopify() expected = [ - "orders", "abandoned_checkouts", - "metafields", + "fulfillments", + "metafield_orders", + "metafield_shops", "order_refunds", "order_risks", + "orders", + "shop", "tender_transactions", "transactions", - "fulfillments", - "shop", ] assert [stream.name for stream in source.streams(basic_config)] == expected @@ -66,9 +67,15 @@ def test_filter_records_newer_than_state(basic_config): {"id": 1, "updated_at": "2022-01-01T01:01:01-07:00"}, # missing cursor, record should be present {"id": 2}, + # cursor is set to null + {"id": 3, "updated_at": "null"}, + # cursor is set to null + {"id": 4, "updated_at": None}, ] state = {"updated_at": "2022-02-01T00:00:00-07:00"} - expected = [{"id": 2}] + # we expect records with: id = 2, 3, 4. We output them `As Is`, + # because we cannot compare them to the STATE and SKIPPING them leads to data loss, + expected = [{"id": 2}, {"id": 3, "updated_at": "null"}, {"id": 4, "updated_at": None}] result = list(stream.filter_records_newer_than_state(state, records_slice)) assert result == expected diff --git a/docs/integrations/sources/shopify.md b/docs/integrations/sources/shopify.md index a54b834d66a1..da8df47995ec 100644 --- a/docs/integrations/sources/shopify.md +++ b/docs/integrations/sources/shopify.md @@ -94,9 +94,13 @@ This connector support both: `OAuth 2.0` and `API PASSWORD` (for private applica This Source is capable of syncing the following core Streams: +* [Articles](https://shopify.dev/api/admin-rest/2022-01/resources/article) +* [Blogs](https://shopify.dev/api/admin-rest/2022-01/resources/blog) * [Abandoned Checkouts](https://shopify.dev/api/admin-rest/2022-01/resources/abandoned-checkouts#top) * [Collects](https://shopify.dev/api/admin-rest/2022-01/resources/collect#top) +* [Collections](https://shopify.dev/api/admin-rest/2022-01/resources/collection) * [Custom Collections](https://shopify.dev/api/admin-rest/2022-01/resources/customcollection#top) +* [Smart Collections](https://shopify.dev/api/admin-rest/2022-01/resources/smartcollection) * [Customers](https://shopify.dev/api/admin-rest/2022-01/resources/customer#top) * [Draft Orders](https://shopify.dev/api/admin-rest/2022-01/resources/draftorder#top) * [Discount Codes](https://shopify.dev/api/admin-rest/2022-01/resources/discountcode#top) @@ -105,6 +109,8 @@ This Source is capable of syncing the following core Streams: * [Orders Refunds](https://shopify.dev/api/admin-rest/2022-01/resources/refund#top) * [Orders Risks](https://shopify.dev/api/admin-rest/2022-01/resources/order-risk#top) * [Products](https://shopify.dev/api/admin-rest/2022-01/resources/product#top) +* [Product Images](https://shopify.dev/api/admin-rest/2022-01/resources/product-image) +* [Product Variants](https://shopify.dev/api/admin-rest/2022-01/resources/product-variant) * [Transactions](https://shopify.dev/api/admin-rest/2022-01/resources/transaction#top) * [Tender Transactions](https://shopify.dev/api/admin-rest/2022-01/resources/tendertransaction) * [Pages](https://shopify.dev/api/admin-rest/2022-01/resources/page#top) @@ -137,41 +143,42 @@ This is expected when the connector hits the 429 - Rate Limit Exceeded HTTP Erro ## Changelog -| Version | Date | Pull Request | Subject | -|:--------| :--- | :--- | :--- | -| 0.1.38 | 2022-10-10 | [17777](https://github.com/airbytehq/airbyte/pull/17777) | Fixed `404` for configured streams, fix missing `cursor` error for old records -| 0.1.37 | 2022-04-30 | [12500](https://github.com/airbytehq/airbyte/pull/12500) | Improve input configuration copy | -| 0.1.36 | 2022-03-22 | [9850](https://github.com/airbytehq/airbyte/pull/9850) | Added `BalanceTransactions` stream | -| 0.1.35 | 2022-03-07 | [10915](https://github.com/airbytehq/airbyte/pull/10915) | Fix a bug which caused `full-refresh` syncs of child REST entities configured for `incremental` | -| 0.1.34 | 2022-03-02 | [10794](https://github.com/airbytehq/airbyte/pull/10794) | Minor specification re-order, fixed links in documentation | -| 0.1.33 | 2022-02-17 | [10419](https://github.com/airbytehq/airbyte/pull/10419) | Fixed wrong field type for tax_exemptions for `Abandoned_checkouts` stream | -| 0.1.32 | 2022-02-18 | [10449](https://github.com/airbytehq/airbyte/pull/10449) | Added `tender_transactions` stream | -| 0.1.31 | 2022-02-08 | [10175](https://github.com/airbytehq/airbyte/pull/10175) | Fixed compatibility issues for legacy user config | -| 0.1.30 | 2022-01-24 | [9648](https://github.com/airbytehq/airbyte/pull/9648) | Added permission validation before sync | -| 0.1.29 | 2022-01-20 | [9049](https://github.com/airbytehq/airbyte/pull/9248) | Added `shop_url` to the record for all streams | -| 0.1.28 | 2022-01-19 | [9591](https://github.com/airbytehq/airbyte/pull/9591) | Implemented `OAuth2.0` authentication method for Airbyte Cloud | -| 0.1.27 | 2021-12-22 | [9049](https://github.com/airbytehq/airbyte/pull/9049) | Update connector fields title/description | -| 0.1.26 | 2021-12-14 | [8597](https://github.com/airbytehq/airbyte/pull/8597) | Fix `mismatched number of tables` for base-normalization, increased performance of `order_refunds` stream | -| 0.1.25 | 2021-12-02 | [8297](https://github.com/airbytehq/airbyte/pull/8297) | Added Shop stream | -| 0.1.24 | 2021-11-30 | [7783](https://github.com/airbytehq/airbyte/pull/7783) | Reviewed and corrected schemas for all streams | -| 0.1.23 | 2021-11-15 | [7973](https://github.com/airbytehq/airbyte/pull/7973) | Added `InventoryItems` | -| 0.1.22 | 2021-10-18 | [7101](https://github.com/airbytehq/airbyte/pull/7107) | Added FulfillmentOrders, Fulfillments streams | -| 0.1.21 | 2021-10-14 | [7382](https://github.com/airbytehq/airbyte/pull/7382) | Fixed `InventoryLevels` primary key | -| 0.1.20 | 2021-10-14 | [7063](https://github.com/airbytehq/airbyte/pull/7063) | Added `Location` and `InventoryLevels` as streams | -| 0.1.19 | 2021-10-11 | [6951](https://github.com/airbytehq/airbyte/pull/6951) | Added support of `OAuth 2.0` authorisation option | -| 0.1.18 | 2021-09-21 | [6056](https://github.com/airbytehq/airbyte/pull/6056) | Added `pre_tax_price` to the `orders/line_items` schema | -| 0.1.17 | 2021-09-17 | [5244](https://github.com/airbytehq/airbyte/pull/5244) | Created data type enforcer for converting prices into numbers | -| 0.1.16 | 2021-09-09 | [5965](https://github.com/airbytehq/airbyte/pull/5945) | Fixed the connector's performance for `Incremental refresh` | -| 0.1.15 | 2021-09-02 | [5853](https://github.com/airbytehq/airbyte/pull/5853) | Fixed `amount` type in `order_refund` schema | -| 0.1.14 | 2021-09-02 | [5801](https://github.com/airbytehq/airbyte/pull/5801) | Fixed `line_items/discount allocations` & `duties` parts of `orders` schema | -| 0.1.13 | 2021-08-17 | [5470](https://github.com/airbytehq/airbyte/pull/5470) | Fixed rate limits throttling | -| 0.1.12 | 2021-08-09 | [5276](https://github.com/airbytehq/airbyte/pull/5276) | Add status property to product schema | -| 0.1.11 | 2021-07-23 | [4943](https://github.com/airbytehq/airbyte/pull/4943) | Fix products schema up to API 2021-07 | -| 0.1.10 | 2021-07-19 | [4830](https://github.com/airbytehq/airbyte/pull/4830) | Fix for streams json schemas, upgrade to API version 2021-07 | -| 0.1.9 | 2021-07-04 | [4472](https://github.com/airbytehq/airbyte/pull/4472) | Incremental sync is now using updated\_at instead of since\_id by default | -| 0.1.8 | 2021-06-29 | [4121](https://github.com/airbytehq/airbyte/pull/4121) | Add draft orders stream | -| 0.1.7 | 2021-06-26 | [4290](https://github.com/airbytehq/airbyte/pull/4290) | Fixed the bug when limiting output records to 1 caused infinity loop | -| 0.1.6 | 2021-06-24 | [4009](https://github.com/airbytehq/airbyte/pull/4009) | Add pages, price rules and discount codes streams | -| 0.1.5 | 2021-06-10 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | -| 0.1.4 | 2021-06-09 | [3926](https://github.com/airbytehq/airbyte/pull/3926) | New attributes to Orders schema | -| 0.1.3 | 2021-06-08 | [3787](https://github.com/airbytehq/airbyte/pull/3787) | Add Native Shopify Source Connector | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------| +| 0.1.39 | 2022-10-13 | [17962](https://github.com/airbytehq/airbyte/pull/17962) | Add metafield streams; support for nested list streams | +| 0.1.38 | 2022-10-10 | [17777](https://github.com/airbytehq/airbyte/pull/17777) | Fixed `404` for configured streams, fix missing `cursor` error for old records | +| 0.1.37 | 2022-04-30 | [12500](https://github.com/airbytehq/airbyte/pull/12500) | Improve input configuration copy | +| 0.1.36 | 2022-03-22 | [9850](https://github.com/airbytehq/airbyte/pull/9850) | Added `BalanceTransactions` stream | +| 0.1.35 | 2022-03-07 | [10915](https://github.com/airbytehq/airbyte/pull/10915) | Fix a bug which caused `full-refresh` syncs of child REST entities configured for `incremental` | +| 0.1.34 | 2022-03-02 | [10794](https://github.com/airbytehq/airbyte/pull/10794) | Minor specification re-order, fixed links in documentation | +| 0.1.33 | 2022-02-17 | [10419](https://github.com/airbytehq/airbyte/pull/10419) | Fixed wrong field type for tax_exemptions for `Abandoned_checkouts` stream | +| 0.1.32 | 2022-02-18 | [10449](https://github.com/airbytehq/airbyte/pull/10449) | Added `tender_transactions` stream | +| 0.1.31 | 2022-02-08 | [10175](https://github.com/airbytehq/airbyte/pull/10175) | Fixed compatibility issues for legacy user config | +| 0.1.30 | 2022-01-24 | [9648](https://github.com/airbytehq/airbyte/pull/9648) | Added permission validation before sync | +| 0.1.29 | 2022-01-20 | [9049](https://github.com/airbytehq/airbyte/pull/9248) | Added `shop_url` to the record for all streams | +| 0.1.28 | 2022-01-19 | [9591](https://github.com/airbytehq/airbyte/pull/9591) | Implemented `OAuth2.0` authentication method for Airbyte Cloud | +| 0.1.27 | 2021-12-22 | [9049](https://github.com/airbytehq/airbyte/pull/9049) | Update connector fields title/description | +| 0.1.26 | 2021-12-14 | [8597](https://github.com/airbytehq/airbyte/pull/8597) | Fix `mismatched number of tables` for base-normalization, increased performance of `order_refunds` stream | +| 0.1.25 | 2021-12-02 | [8297](https://github.com/airbytehq/airbyte/pull/8297) | Added Shop stream | +| 0.1.24 | 2021-11-30 | [7783](https://github.com/airbytehq/airbyte/pull/7783) | Reviewed and corrected schemas for all streams | +| 0.1.23 | 2021-11-15 | [7973](https://github.com/airbytehq/airbyte/pull/7973) | Added `InventoryItems` | +| 0.1.22 | 2021-10-18 | [7101](https://github.com/airbytehq/airbyte/pull/7107) | Added FulfillmentOrders, Fulfillments streams | +| 0.1.21 | 2021-10-14 | [7382](https://github.com/airbytehq/airbyte/pull/7382) | Fixed `InventoryLevels` primary key | +| 0.1.20 | 2021-10-14 | [7063](https://github.com/airbytehq/airbyte/pull/7063) | Added `Location` and `InventoryLevels` as streams | +| 0.1.19 | 2021-10-11 | [6951](https://github.com/airbytehq/airbyte/pull/6951) | Added support of `OAuth 2.0` authorisation option | +| 0.1.18 | 2021-09-21 | [6056](https://github.com/airbytehq/airbyte/pull/6056) | Added `pre_tax_price` to the `orders/line_items` schema | +| 0.1.17 | 2021-09-17 | [5244](https://github.com/airbytehq/airbyte/pull/5244) | Created data type enforcer for converting prices into numbers | +| 0.1.16 | 2021-09-09 | [5965](https://github.com/airbytehq/airbyte/pull/5945) | Fixed the connector's performance for `Incremental refresh` | +| 0.1.15 | 2021-09-02 | [5853](https://github.com/airbytehq/airbyte/pull/5853) | Fixed `amount` type in `order_refund` schema | +| 0.1.14 | 2021-09-02 | [5801](https://github.com/airbytehq/airbyte/pull/5801) | Fixed `line_items/discount allocations` & `duties` parts of `orders` schema | +| 0.1.13 | 2021-08-17 | [5470](https://github.com/airbytehq/airbyte/pull/5470) | Fixed rate limits throttling | +| 0.1.12 | 2021-08-09 | [5276](https://github.com/airbytehq/airbyte/pull/5276) | Add status property to product schema | +| 0.1.11 | 2021-07-23 | [4943](https://github.com/airbytehq/airbyte/pull/4943) | Fix products schema up to API 2021-07 | +| 0.1.10 | 2021-07-19 | [4830](https://github.com/airbytehq/airbyte/pull/4830) | Fix for streams json schemas, upgrade to API version 2021-07 | +| 0.1.9 | 2021-07-04 | [4472](https://github.com/airbytehq/airbyte/pull/4472) | Incremental sync is now using updated\_at instead of since\_id by default | +| 0.1.8 | 2021-06-29 | [4121](https://github.com/airbytehq/airbyte/pull/4121) | Add draft orders stream | +| 0.1.7 | 2021-06-26 | [4290](https://github.com/airbytehq/airbyte/pull/4290) | Fixed the bug when limiting output records to 1 caused infinity loop | +| 0.1.6 | 2021-06-24 | [4009](https://github.com/airbytehq/airbyte/pull/4009) | Add pages, price rules and discount codes streams | +| 0.1.5 | 2021-06-10 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | +| 0.1.4 | 2021-06-09 | [3926](https://github.com/airbytehq/airbyte/pull/3926) | New attributes to Orders schema | +| 0.1.3 | 2021-06-08 | [3787](https://github.com/airbytehq/airbyte/pull/3787) | Add Native Shopify Source Connector | From 4714a1c004a378d4c2a819480b1dd133447dfc18 Mon Sep 17 00:00:00 2001 From: Brian Lai <51336873+brianjlai@users.noreply.github.com> Date: Mon, 17 Oct 2022 17:11:11 -0400 Subject: [PATCH 151/498] fix check for streams that do not use a stream slicer (#18080) * fix check for streams that do not use a stream slicer * increment version and changelog before publish --- airbyte-cdk/python/CHANGELOG.md | 4 ++++ .../sources/declarative/stream_slicers/single_slice.py | 2 +- airbyte-cdk/python/setup.py | 2 +- .../sources/declarative/iterators/test_only_once.py | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index 24aa5cca3699..456cc44c730b 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.102 + +- Low-code: Fix check for streams that do not define a stream slicer + ## 0.1.101 - Low-code: $options do not overwrite parameters that are already set diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/stream_slicers/single_slice.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/stream_slicers/single_slice.py index 532982de9d08..d8c50a7ece36 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/stream_slicers/single_slice.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/stream_slicers/single_slice.py @@ -56,4 +56,4 @@ def get_request_body_json( return {} def stream_slices(self, sync_mode: SyncMode, stream_state: Mapping[str, Any]) -> Iterable[StreamSlice]: - return [dict()] + yield dict() diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index addef435c91f..ef278c81b2f1 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.101", + version="0.1.102", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/iterators/test_only_once.py b/airbyte-cdk/python/unit_tests/sources/declarative/iterators/test_only_once.py index d51ca23b04e3..616d557ca02a 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/iterators/test_only_once.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/iterators/test_only_once.py @@ -10,4 +10,5 @@ def test(): iterator = SingleSlice(options={}) stream_slices = iterator.stream_slices(SyncMode.incremental, None) - assert stream_slices == [dict()] + next_slice = next(stream_slices) + assert next_slice == dict() From 70cbdd8e505cd1fe8c395cf8d9c44731f9a46f6b Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Tue, 18 Oct 2022 00:38:05 +0200 Subject: [PATCH 152/498] tolerate database nulls in webhook operation configs (#18084) --- .../airbyte/config/persistence/DatabaseConfigPersistence.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index cc70e6c3ba9d..11014b91b022 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -581,7 +581,8 @@ private StandardSyncOperation buildStandardSyncOperation(final Record record) { .withOperatorType(Enums.toEnum(record.get(OPERATION.OPERATOR_TYPE, String.class), OperatorType.class).orElseThrow()) .withOperatorNormalization(Jsons.deserialize(record.get(OPERATION.OPERATOR_NORMALIZATION).data(), OperatorNormalization.class)) .withOperatorDbt(Jsons.deserialize(record.get(OPERATION.OPERATOR_DBT).data(), OperatorDbt.class)) - .withOperatorWebhook(Jsons.deserialize(record.get(OPERATION.OPERATOR_WEBHOOK).data(), OperatorWebhook.class)) + .withOperatorWebhook(record.get(OPERATION.OPERATOR_WEBHOOK) == null ? null + : Jsons.deserialize(record.get(OPERATION.OPERATOR_WEBHOOK).data(), OperatorWebhook.class)) .withTombstone(record.get(OPERATION.TOMBSTONE)); } From 71967885e4d60624868c1c844a2bb5f32a8ffa95 Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Tue, 18 Oct 2022 03:36:53 +0200 Subject: [PATCH 153/498] Implement webhook operation in the sync workflow (#18022) Implements the webhook operation as part of the sync workflow. - Introduces the new activity implementation - Updates the various interfaces that pass input to get the relevant configs to the sync workflow - Hooks the new activity into the sync workflow - Passes the webhook configs along into the sync workflow job --- .../types/JobResetConnectionConfig.yaml | 4 + .../main/resources/types/JobSyncConfig.yaml | 4 + .../resources/types/OperatorWebhookInput.yaml | 24 ++++ .../resources/types/StandardSyncInput.yaml | 4 + .../resources/types/StandardSyncOutput.yaml | 2 + .../types/WebhookOperationConfigs.yaml | 1 - .../types/WebhookOperationSummary.yaml | 24 ++++ .../persistence/job/DefaultJobCreator.java | 3 + .../airbyte/persistence/job/JobCreator.java | 2 + .../job/factory/DefaultSyncJobFactory.java | 10 +- .../job/DefaultJobCreatorTest.java | 19 +++- .../factory/DefaultSyncJobFactoryTest.java | 26 ++++- .../test/acceptance/BasicAcceptanceTests.java | 107 +++++++++++++----- .../workers/config/ActivityBeanFactory.java | 7 +- .../workers/config/ApiClientBeanFactory.java | 5 + .../workers/config/TemporalBeanFactory.java | 5 +- .../activities/GenerateInputActivityImpl.java | 1 + .../temporal/sync/SyncWorkflowImpl.java | 25 ++++ .../sync/WebhookOperationActivity.java | 17 +++ .../sync/WebhookOperationActivityImpl.java | 76 +++++++++++++ .../temporal/sync/SyncWorkflowTest.java | 34 +++++- .../sync/WebhookOperationActivityTest.java | 59 ++++++++++ 22 files changed, 418 insertions(+), 41 deletions(-) create mode 100644 airbyte-config/config-models/src/main/resources/types/OperatorWebhookInput.yaml create mode 100644 airbyte-config/config-models/src/main/resources/types/WebhookOperationSummary.yaml create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivity.java create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityTest.java diff --git a/airbyte-config/config-models/src/main/resources/types/JobResetConnectionConfig.yaml b/airbyte-config/config-models/src/main/resources/types/JobResetConnectionConfig.yaml index 32696ab6afb6..62e15dfe3b72 100644 --- a/airbyte-config/config-models/src/main/resources/types/JobResetConnectionConfig.yaml +++ b/airbyte-config/config-models/src/main/resources/types/JobResetConnectionConfig.yaml @@ -35,6 +35,10 @@ properties: type: array items: "$ref": StandardSyncOperation.yaml + webhookOperationConfigs: + description: The webhook operation configs belonging to this workspace. Must conform to WebhookOperationConfigs.yaml. + type: object + existingJavaType: com.fasterxml.jackson.databind.JsonNode resourceRequirements: type: object description: optional resource requirements to run sync workers diff --git a/airbyte-config/config-models/src/main/resources/types/JobSyncConfig.yaml b/airbyte-config/config-models/src/main/resources/types/JobSyncConfig.yaml index ea6535954378..317ad0a89394 100644 --- a/airbyte-config/config-models/src/main/resources/types/JobSyncConfig.yaml +++ b/airbyte-config/config-models/src/main/resources/types/JobSyncConfig.yaml @@ -52,6 +52,10 @@ properties: type: array items: "$ref": StandardSyncOperation.yaml + webhookOperationConfigs: + description: The webhook operation configs belonging to this workspace. Must conform to WebhookOperationConfigs.yaml. + type: object + existingJavaType: com.fasterxml.jackson.databind.JsonNode state: description: optional state of the previous run. this object is defined per integration. "$ref": State.yaml diff --git a/airbyte-config/config-models/src/main/resources/types/OperatorWebhookInput.yaml b/airbyte-config/config-models/src/main/resources/types/OperatorWebhookInput.yaml new file mode 100644 index 000000000000..e0a0016b527b --- /dev/null +++ b/airbyte-config/config-models/src/main/resources/types/OperatorWebhookInput.yaml @@ -0,0 +1,24 @@ +--- +"$schema": http://json-schema.org/draft-07/schema# +"$id": https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/resources/types/OperatorWebhookInput.yaml +title: OperatorWebhookInput +description: Execution input for a webhook operation +type: object +required: + - executionUrl + - webhookConfigId +properties: + executionUrl: + description: URL to invoke the webhook via POST. + type: string + executionBody: + description: Message body to be POSTed. + type: string + webhookConfigId: + description: An id used to index into the workspaceWebhookConfigs, which has a list of webhook configs. + type: string + format: uuid + workspaceWebhookConfigs: + description: Webhook configs for this workspace. Must conform to WebhookOperationConfigs.yaml; any secrets must be hydrated before use. + type: object + existingJavaType: com.fasterxml.jackson.databind.JsonNode diff --git a/airbyte-config/config-models/src/main/resources/types/StandardSyncInput.yaml b/airbyte-config/config-models/src/main/resources/types/StandardSyncInput.yaml index a41da2982aa9..ea84cecce3d4 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardSyncInput.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardSyncInput.yaml @@ -32,6 +32,10 @@ properties: type: array items: "$ref": StandardSyncOperation.yaml + webhookOperationConfigs: + description: The webhook operation configs belonging to this workspace. See webhookOperationConfigs in StandardWorkspace.yaml. + type: object + existingJavaType: com.fasterxml.jackson.databind.JsonNode catalog: description: the configured airbyte catalog type: object diff --git a/airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml b/airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml index 28fc46a3bfd1..ba0a14a340fd 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardSyncOutput.yaml @@ -14,6 +14,8 @@ properties: "$ref": StandardSyncSummary.yaml normalizationSummary: "$ref": NormalizationSummary.yaml + webhookOperationSummary: + "$ref": WebhookOperationSummary.yaml state: "$ref": State.yaml output_catalog: diff --git a/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml b/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml index 1aca08ee2e7c..66ca12c2461e 100644 --- a/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml +++ b/airbyte-config/config-models/src/main/resources/types/WebhookOperationConfigs.yaml @@ -15,7 +15,6 @@ properties: required: - id - name - - authToken properties: id: type: string diff --git a/airbyte-config/config-models/src/main/resources/types/WebhookOperationSummary.yaml b/airbyte-config/config-models/src/main/resources/types/WebhookOperationSummary.yaml new file mode 100644 index 000000000000..29b42020ecbb --- /dev/null +++ b/airbyte-config/config-models/src/main/resources/types/WebhookOperationSummary.yaml @@ -0,0 +1,24 @@ +--- +"$schema": http://json-schema.org/draft-07/schema# +"$id": https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/resources/types/WebhookOperationSummary.yaml +title: WebhookOperationSummary +description: information output by syncs for which at least one webhook invocation step was performed +type: object +required: + - startTime + - endTime +additionalProperties: false +properties: + successes: + type: array + description: List of webhook config ids that were successfully executed. + items: + type: string + format: uuid + failures: + type: array + # TODO(mfsiega-airbyte): include failure reason once possible. + description: List of webhook config ids that failed. + items: + type: string + format: uuid diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobCreator.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobCreator.java index 092b4b93530b..7a85300ab0c1 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobCreator.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobCreator.java @@ -4,6 +4,7 @@ package io.airbyte.persistence.job; +import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.config.ActorDefinitionResourceRequirements; import io.airbyte.config.DestinationConnection; import io.airbyte.config.JobConfig; @@ -53,6 +54,7 @@ public Optional createSyncJob(final SourceConnection source, final String sourceDockerImageName, final String destinationDockerImageName, final List standardSyncOperations, + @Nullable final JsonNode webhookOperationConfigs, @Nullable final ActorDefinitionResourceRequirements sourceResourceReqs, @Nullable final ActorDefinitionResourceRequirements destinationResourceReqs) throws IOException { @@ -81,6 +83,7 @@ public Optional createSyncJob(final SourceConnection source, .withDestinationDockerImage(destinationDockerImageName) .withDestinationConfiguration(destination.getConfiguration()) .withOperationSequence(standardSyncOperations) + .withWebhookOperationConfigs(webhookOperationConfigs) .withConfiguredAirbyteCatalog(standardSync.getCatalog()) .withState(null) .withResourceRequirements(mergedOrchestratorResourceReq) diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobCreator.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobCreator.java index 7f70f842b69f..16011e80ff9e 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobCreator.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobCreator.java @@ -4,6 +4,7 @@ package io.airbyte.persistence.job; +import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.config.ActorDefinitionResourceRequirements; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; @@ -33,6 +34,7 @@ Optional createSyncJob(SourceConnection source, String sourceDockerImage, String destinationDockerImage, List standardSyncOperations, + @Nullable JsonNode webhookOperationConfigs, @Nullable ActorDefinitionResourceRequirements sourceResourceReqs, @Nullable ActorDefinitionResourceRequirements destinationResourceReqs) throws IOException; diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactory.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactory.java index cbb2c03c8afb..d5ed60d9c24a 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactory.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactory.java @@ -14,9 +14,11 @@ import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncOperation; +import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.persistence.job.DefaultJobCreator; +import io.airbyte.persistence.job.WorkspaceHelper; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.List; @@ -28,21 +30,26 @@ public class DefaultSyncJobFactory implements SyncJobFactory { private final DefaultJobCreator jobCreator; private final ConfigRepository configRepository; private final OAuthConfigSupplier oAuthConfigSupplier; + private final WorkspaceHelper workspaceHelper; public DefaultSyncJobFactory(final boolean connectorSpecificResourceDefaultsEnabled, final DefaultJobCreator jobCreator, final ConfigRepository configRepository, - final OAuthConfigSupplier oAuthConfigSupplier) { + final OAuthConfigSupplier oAuthConfigSupplier, + final WorkspaceHelper workspaceHelper) { this.connectorSpecificResourceDefaultsEnabled = connectorSpecificResourceDefaultsEnabled; this.jobCreator = jobCreator; this.configRepository = configRepository; this.oAuthConfigSupplier = oAuthConfigSupplier; + this.workspaceHelper = workspaceHelper; } @Override public Long create(final UUID connectionId) { try { final StandardSync standardSync = configRepository.getStandardSync(connectionId); + final UUID workspaceId = workspaceHelper.getWorkspaceForSourceId(standardSync.getSourceId()); + final StandardWorkspace workspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, true); final SourceConnection sourceConnection = configRepository.getSourceConnection(standardSync.getSourceId()); final DestinationConnection destinationConnection = configRepository.getDestinationConnection(standardSync.getDestinationId()); final JsonNode sourceConfiguration = oAuthConfigSupplier.injectSourceOAuthParameters( @@ -86,6 +93,7 @@ public Long create(final UUID connectionId) { sourceImageName, destinationImageName, standardSyncOperations, + workspace.getWebhookOperationConfigs(), sourceResourceRequirements, destinationResourceRequirements) .orElseThrow(() -> new IllegalStateException("We shouldn't be trying to create a new sync job if there is one running already.")); diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobCreatorTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobCreatorTest.java index 18815794d912..deeaaf0ce4ef 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobCreatorTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobCreatorTest.java @@ -73,10 +73,17 @@ class DefaultJobCreatorTest { private JobCreator jobCreator; private ResourceRequirements workerResourceRequirements; + private static final JsonNode PERSISTED_WEBHOOK_CONFIGS; + + private static final UUID WEBHOOK_CONFIG_ID; + private static final String WEBHOOK_NAME; + static { final UUID workspaceId = UUID.randomUUID(); final UUID sourceId = UUID.randomUUID(); final UUID sourceDefinitionId = UUID.randomUUID(); + WEBHOOK_CONFIG_ID = UUID.randomUUID(); + WEBHOOK_NAME = "test-name"; final JsonNode implementationJson = Jsons.jsonNode(ImmutableMap.builder() .put("apiKey", "123-abc") @@ -129,6 +136,10 @@ class DefaultJobCreatorTest { .withTombstone(false) .withOperatorType(OperatorType.NORMALIZATION) .withOperatorNormalization(new OperatorNormalization().withOption(Option.BASIC)); + + PERSISTED_WEBHOOK_CONFIGS = Jsons.deserialize( + String.format("{\"webhookConfigs\": [{\"id\": \"%s\", \"name\": \"%s\", \"authToken\": {\"_secret\": \"a-secret_v1\"}}]}", + WEBHOOK_CONFIG_ID, WEBHOOK_NAME)); } @BeforeEach @@ -157,7 +168,8 @@ void testCreateSyncJob() throws IOException { .withOperationSequence(List.of(STANDARD_SYNC_OPERATION)) .withResourceRequirements(workerResourceRequirements) .withSourceResourceRequirements(workerResourceRequirements) - .withDestinationResourceRequirements(workerResourceRequirements); + .withDestinationResourceRequirements(workerResourceRequirements) + .withWebhookOperationConfigs(PERSISTED_WEBHOOK_CONFIGS); final JobConfig jobConfig = new JobConfig() .withConfigType(JobConfig.ConfigType.SYNC) @@ -173,6 +185,7 @@ void testCreateSyncJob() throws IOException { SOURCE_IMAGE_NAME, DESTINATION_IMAGE_NAME, List.of(STANDARD_SYNC_OPERATION), + PERSISTED_WEBHOOK_CONFIGS, null, null).orElseThrow(); assertEquals(JOB_ID, jobId); @@ -207,6 +220,7 @@ void testCreateSyncJobEnsureNoQueuing() throws IOException { DESTINATION_IMAGE_NAME, List.of(STANDARD_SYNC_OPERATION), null, + null, null).isEmpty()); } @@ -220,6 +234,7 @@ void testCreateSyncJobDefaultWorkerResourceReqs() throws IOException { DESTINATION_IMAGE_NAME, List.of(STANDARD_SYNC_OPERATION), null, + null, null); final JobSyncConfig expectedJobSyncConfig = new JobSyncConfig() @@ -262,6 +277,7 @@ void testCreateSyncJobConnectionResourceReqs() throws IOException { DESTINATION_IMAGE_NAME, List.of(STANDARD_SYNC_OPERATION), null, + null, null); final JobSyncConfig expectedJobSyncConfig = new JobSyncConfig() @@ -307,6 +323,7 @@ void testCreateSyncJobSourceAndDestinationResourceReqs() throws IOException { SOURCE_IMAGE_NAME, DESTINATION_IMAGE_NAME, List.of(STANDARD_SYNC_OPERATION), + null, new ActorDefinitionResourceRequirements().withDefault(sourceResourceRequirements), new ActorDefinitionResourceRequirements().withJobSpecific(List.of( new JobTypeResourceLimit().withJobType(JobType.SYNC).withResourceRequirements(destResourceRequirements)))); diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactoryTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactoryTest.java index ca1cfb3bb603..12295be841ac 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactoryTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/factory/DefaultSyncJobFactoryTest.java @@ -5,20 +5,26 @@ package io.airbyte.persistence.job.factory; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.docker.DockerUtils; +import io.airbyte.commons.json.Jsons; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncOperation; +import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.persistence.job.DefaultJobCreator; +import io.airbyte.persistence.job.WorkspaceHelper; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.List; @@ -36,8 +42,14 @@ void createSyncJobFromConnectionId() throws JsonValidationException, ConfigNotFo final UUID sourceId = UUID.randomUUID(); final UUID destinationId = UUID.randomUUID(); final UUID operationId = UUID.randomUUID(); + final UUID workspaceWebhookConfigId = UUID.randomUUID(); + final String workspaceWebhookName = "test-webhook-name"; + final JsonNode persistedWebhookConfigs = Jsons.deserialize( + String.format("{\"webhookConfigs\": [{\"id\": \"%s\", \"name\": \"%s\", \"authToken\": {\"_secret\": \"a-secret_v1\"}}]}", + workspaceWebhookConfigId, workspaceWebhookName)); final DefaultJobCreator jobCreator = mock(DefaultJobCreator.class); final ConfigRepository configRepository = mock(ConfigRepository.class); + final WorkspaceHelper workspaceHelper = mock(WorkspaceHelper.class); final long jobId = 11L; final StandardSyncOperation operation = new StandardSyncOperation().withOperationId(operationId); @@ -62,8 +74,10 @@ void createSyncJobFromConnectionId() throws JsonValidationException, ConfigNotFo when(configRepository.getSourceConnection(sourceId)).thenReturn(sourceConnection); when(configRepository.getDestinationConnection(destinationId)).thenReturn(destinationConnection); when(configRepository.getStandardSyncOperation(operationId)).thenReturn(operation); - when(jobCreator.createSyncJob(sourceConnection, destinationConnection, standardSync, srcDockerImage, dstDockerImage, operations, null, null)) - .thenReturn(Optional.of(jobId)); + when( + jobCreator.createSyncJob(sourceConnection, destinationConnection, standardSync, srcDockerImage, dstDockerImage, operations, + persistedWebhookConfigs, null, null)) + .thenReturn(Optional.of(jobId)); when(configRepository.getStandardSourceDefinition(sourceDefinitionId)) .thenReturn(new StandardSourceDefinition().withSourceDefinitionId(sourceDefinitionId).withDockerRepository(srcDockerRepo) .withDockerImageTag(srcDockerTag)); @@ -72,12 +86,16 @@ void createSyncJobFromConnectionId() throws JsonValidationException, ConfigNotFo .thenReturn(new StandardDestinationDefinition().withDestinationDefinitionId(destinationDefinitionId).withDockerRepository(dstDockerRepo) .withDockerImageTag(dstDockerTag)); - final SyncJobFactory factory = new DefaultSyncJobFactory(true, jobCreator, configRepository, mock(OAuthConfigSupplier.class)); + when(configRepository.getStandardWorkspaceNoSecrets(any(), eq(true))).thenReturn( + new StandardWorkspace().withWebhookOperationConfigs(persistedWebhookConfigs)); + + final SyncJobFactory factory = new DefaultSyncJobFactory(true, jobCreator, configRepository, mock(OAuthConfigSupplier.class), workspaceHelper); final long actualJobId = factory.create(connectionId); assertEquals(jobId, actualJobId); verify(jobCreator) - .createSyncJob(sourceConnection, destinationConnection, standardSync, srcDockerImage, dstDockerImage, operations, null, null); + .createSyncJob(sourceConnection, destinationConnection, standardSync, srcDockerImage, dstDockerImage, operations, persistedWebhookConfigs, + null, null); } } diff --git a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java index 4560736b2938..6bed007bbe68 100644 --- a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java +++ b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java @@ -60,7 +60,11 @@ import io.airbyte.api.client.model.generated.JobRead; import io.airbyte.api.client.model.generated.JobStatus; import io.airbyte.api.client.model.generated.JobWithAttemptsRead; +import io.airbyte.api.client.model.generated.OperationCreate; import io.airbyte.api.client.model.generated.OperationRead; +import io.airbyte.api.client.model.generated.OperatorConfiguration; +import io.airbyte.api.client.model.generated.OperatorType; +import io.airbyte.api.client.model.generated.OperatorWebhook; import io.airbyte.api.client.model.generated.SourceDefinitionIdRequestBody; import io.airbyte.api.client.model.generated.SourceDefinitionIdWithWorkspaceId; import io.airbyte.api.client.model.generated.SourceDefinitionRead; @@ -71,6 +75,9 @@ import io.airbyte.api.client.model.generated.StreamState; import io.airbyte.api.client.model.generated.SyncMode; import io.airbyte.api.client.model.generated.WebBackendConnectionUpdate; +import io.airbyte.api.client.model.generated.WebhookConfigWrite; +import io.airbyte.api.client.model.generated.WorkspaceRead; +import io.airbyte.api.client.model.generated.WorkspaceUpdate; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.scheduling.state.WorkflowState; import io.airbyte.db.Database; @@ -409,21 +416,7 @@ void testScheduledSync() throws Exception { testHarness.createConnection(TEST_CONNECTION, sourceId, destinationId, List.of(operationId), catalog, ConnectionScheduleType.BASIC, BASIC_SCHEDULE_DATA).getConnectionId(); - // Retry for up to 5 minutes. - int i; - for (i = 0; i < MAX_SCHEDULED_JOB_RETRIES; i++) { - sleep(Duration.ofSeconds(30).toMillis()); - try { - final JobRead jobInfo = testHarness.getMostRecentSyncJobId(connectionId); - waitForSuccessfulJob(apiClient.getJobsApi(), jobInfo); - break; - } catch (Exception e) { - LOGGER.info("Something went wrong querying jobs API, retrying..."); - } - } - if (i == MAX_SCHEDULED_JOB_RETRIES) { - LOGGER.error("Sync job did not complete within 5 minutes"); - } + waitForSuccessfulJobWithRetries(connectionId, MAX_SCHEDULED_JOB_RETRIES); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); } @@ -447,21 +440,7 @@ void testCronSync() throws Exception { testHarness.createConnection(TEST_CONNECTION, sourceId, destinationId, List.of(operationId), catalog, ConnectionScheduleType.CRON, connectionScheduleData).getConnectionId(); - int i; - for (i = 0; i < MAX_SCHEDULED_JOB_RETRIES; i++) { - sleep(Duration.ofSeconds(30).toMillis()); - try { - final JobRead jobInfo = testHarness.getMostRecentSyncJobId(connectionId); - waitForSuccessfulJob(apiClient.getJobsApi(), jobInfo); - break; - } catch (Exception e) { - LOGGER.info("Something went wrong querying jobs API, retrying..."); - } - } - - if (i == MAX_SCHEDULED_JOB_RETRIES) { - LOGGER.error("Sync job did not complete within 5 minutes"); - } + waitForSuccessfulJobWithRetries(connectionId, MAX_SCHEDULED_JOB_RETRIES); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); apiClient.getConnectionApi().deleteConnection(new ConnectionIdRequestBody().connectionId(connectionId)); @@ -470,6 +449,49 @@ void testCronSync() throws Exception { testHarness.removeConnection(connectionId); } + @Test + @Order(19) + void testWebhookOperationExecutesSuccessfully() throws Exception { + // create workspace webhook config + final WorkspaceRead workspaceRead = apiClient.getWorkspaceApi().updateWorkspace( + new WorkspaceUpdate().workspaceId(workspaceId).addWebhookConfigsItem( + new WebhookConfigWrite().name("reqres test"))); + // create a webhook operation + final OperationRead operationRead = apiClient.getOperationApi().createOperation(new OperationCreate() + .workspaceId(workspaceId) + .name("reqres test") + .operatorConfiguration(new OperatorConfiguration() + .operatorType(OperatorType.WEBHOOK) + .webhook(new OperatorWebhook() + .webhookConfigId(workspaceRead.getWebhookConfigs().get(0).getId()) + // NOTE: reqres.in is free service that hosts a REST API intended for testing frontend/client code. + // We use it here as an endpoint that will accept an HTTP POST. + .executionUrl("https://reqres.in/api/users") + .executionBody("{\"name\": \"morpheus\", \"job\": \"leader\"}")))); + // create a connection with the new operation. + final UUID sourceId = testHarness.createPostgresSource().getSourceId(); + final UUID destinationId = testHarness.createPostgresDestination().getDestinationId(); + // NOTE: this is a normalization operation. + final UUID operationId = testHarness.createOperation().getOperationId(); + final AirbyteCatalog catalog = testHarness.discoverSourceSchema(sourceId); + final SyncMode syncMode = SyncMode.FULL_REFRESH; + final DestinationSyncMode destinationSyncMode = DestinationSyncMode.OVERWRITE; + catalog.getStreams().forEach(s -> s.getConfig().syncMode(syncMode).destinationSyncMode(destinationSyncMode)); + final UUID connectionId = + testHarness.createConnection( + TEST_CONNECTION, sourceId, destinationId, List.of(operationId, operationRead.getOperationId()), catalog, ConnectionScheduleType.MANUAL, + null) + .getConnectionId(); + // run the sync + apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); + waitForSuccessfulJobWithRetries(connectionId, MAX_SCHEDULED_JOB_RETRIES); + testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); + apiClient.getConnectionApi().deleteConnection(new ConnectionIdRequestBody().connectionId(connectionId)); + // remove connection to avoid exception during tear down + testHarness.removeConnection(connectionId); + // TODO(mfsiega-airbyte): add webhook info to the jobs api to verify the webhook execution status. + } + @Test @Order(10) void testMultipleSchemasAndTablesSync() throws Exception { @@ -1412,6 +1434,31 @@ private JobRead waitUntilTheNextJobIsStarted(final UUID connectionId) throws Exc return mostRecentSyncJob; } + /** + * Waits for the given connection to finish, waiting at 30s intervals, until maxRetries is reached. + * + * @param connectionId the connection to wait for + * @param maxRetries the number of times to retry + * @throws InterruptedException + */ + private void waitForSuccessfulJobWithRetries(final UUID connectionId, int maxRetries) throws InterruptedException { + int i; + for (i = 0; i < maxRetries; i++) { + try { + final JobRead jobInfo = testHarness.getMostRecentSyncJobId(connectionId); + waitForSuccessfulJob(apiClient.getJobsApi(), jobInfo); + break; + } catch (Exception e) { + LOGGER.info("Something went wrong querying jobs API, retrying..."); + } + sleep(Duration.ofSeconds(30).toMillis()); + } + + if (i == maxRetries) { + LOGGER.error("Sync job did not complete within 5 minutes"); + } + } + // This test is disabled because it takes a couple of minutes to run, as it is testing timeouts. // It should be re-enabled when the @SlowIntegrationTest can be applied to it. // See relevant issue: https://github.com/airbytehq/airbyte/issues/8397 diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java index 946cd936e562..a9fa39c4e5b2 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ActivityBeanFactory.java @@ -24,6 +24,7 @@ import io.airbyte.workers.temporal.sync.NormalizationSummaryCheckActivity; import io.airbyte.workers.temporal.sync.PersistStateActivity; import io.airbyte.workers.temporal.sync.ReplicationActivity; +import io.airbyte.workers.temporal.sync.WebhookOperationActivity; import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Requires; @@ -100,8 +101,10 @@ public List syncActivities( final NormalizationActivity normalizationActivity, final DbtTransformationActivity dbtTransformationActivity, final PersistStateActivity persistStateActivity, - final NormalizationSummaryCheckActivity normalizationSummaryCheckActivity) { - return List.of(replicationActivity, normalizationActivity, dbtTransformationActivity, persistStateActivity, normalizationSummaryCheckActivity); + final NormalizationSummaryCheckActivity normalizationSummaryCheckActivity, + final WebhookOperationActivity webhookOperationActivity) { + return List.of(replicationActivity, normalizationActivity, dbtTransformationActivity, persistStateActivity, normalizationSummaryCheckActivity, + webhookOperationActivity); } @Singleton diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java index c30415dd468c..19168b4678da 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ApiClientBeanFactory.java @@ -57,6 +57,11 @@ public AirbyteApiClient airbyteApiClient( })); } + @Singleton + public HttpClient httpClient() { + return HttpClient.newHttpClient(); + } + @Singleton @Named("internalApiScheme") public String internalApiScheme(final Environment environment) { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java index 22d2a01f6e15..37a1d760b568 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/TemporalBeanFactory.java @@ -18,6 +18,7 @@ import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.persistence.job.DefaultJobCreator; import io.airbyte.persistence.job.JobPersistence; +import io.airbyte.persistence.job.WorkspaceHelper; import io.airbyte.persistence.job.factory.DefaultSyncJobFactory; import io.airbyte.persistence.job.factory.OAuthConfigSupplier; import io.airbyte.persistence.job.factory.SyncJobFactory; @@ -66,6 +67,7 @@ public TrackingClient trackingClient(final Optional trackingSt @Requires(env = WorkerMode.CONTROL_PLANE) public SyncJobFactory jobFactory( final ConfigRepository configRepository, + final JobPersistence jobPersistence, @Property(name = "airbyte.connector.specific-resource-defaults-enabled", defaultValue = "false") final boolean connectorSpecificResourceDefaultsEnabled, final DefaultJobCreator jobCreator, @@ -74,7 +76,8 @@ public SyncJobFactory jobFactory( connectorSpecificResourceDefaultsEnabled, jobCreator, configRepository, - new OAuthConfigSupplier(configRepository, trackingClient)); + new OAuthConfigSupplier(configRepository, trackingClient), + new WorkspaceHelper(configRepository, jobPersistence)); } @Singleton diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java index 74a622cf2749..aa3d782b209b 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java @@ -87,6 +87,7 @@ public GeneratedJobInput getSyncWorkflowInput(final SyncInput input) { .withSourceConfiguration(config.getSourceConfiguration()) .withDestinationConfiguration(config.getDestinationConfiguration()) .withOperationSequence(config.getOperationSequence()) + .withWebhookOperationConfigs(config.getWebhookOperationConfigs()) .withCatalog(config.getConfiguredAirbyteCatalog()) .withState(config.getState()) .withResourceRequirements(config.getResourceRequirements()) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java index 99d875968cf7..fd604f1d878e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java @@ -8,10 +8,12 @@ import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; import io.airbyte.config.OperatorDbtInput; +import io.airbyte.config.OperatorWebhookInput; import io.airbyte.config.StandardSyncInput; import io.airbyte.config.StandardSyncOperation; import io.airbyte.config.StandardSyncOperation.OperatorType; import io.airbyte.config.StandardSyncOutput; +import io.airbyte.config.WebhookOperationSummary; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; @@ -43,6 +45,9 @@ public class SyncWorkflowImpl implements SyncWorkflow { @TemporalActivityStub(activityOptionsBeanName = "shortActivityOptions") private NormalizationSummaryCheckActivity normalizationSummaryCheckActivity; + @TemporalActivityStub(activityOptionsBeanName = "shortActivityOptions") + private WebhookOperationActivity webhookOperationActivity; + @Override public StandardSyncOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig sourceLauncherConfig, @@ -93,6 +98,26 @@ public StandardSyncOutput run(final JobRunConfig jobRunConfig, .withOperatorDbt(standardSyncOperation.getOperatorDbt()); dbtTransformationActivity.run(jobRunConfig, destinationLauncherConfig, syncInput.getResourceRequirements(), operatorDbtInput); + } else if (standardSyncOperation.getOperatorType() == OperatorType.WEBHOOK) { + LOGGER.info("running webhook operation"); + LOGGER.debug("webhook operation input: {}", standardSyncOperation); + boolean success = webhookOperationActivity + .invokeWebhook(new OperatorWebhookInput() + .withExecutionUrl(standardSyncOperation.getOperatorWebhook().getExecutionUrl()) + .withExecutionBody(standardSyncOperation.getOperatorWebhook().getExecutionBody()) + .withWebhookConfigId(standardSyncOperation.getOperatorWebhook().getWebhookConfigId()) + .withWorkspaceWebhookConfigs(syncInput.getWebhookOperationConfigs())); + LOGGER.info("webhook {} completed {}", standardSyncOperation.getOperatorWebhook().getWebhookConfigId(), + success ? "successfully" : "unsuccessfully"); + // TODO(mfsiega-airbyte): clean up this logic to be returned from the webhook invocation. + if (syncOutput.getWebhookOperationSummary() == null) { + syncOutput.withWebhookOperationSummary(new WebhookOperationSummary()); + } + if (success) { + syncOutput.getWebhookOperationSummary().getSuccesses().add(standardSyncOperation.getOperatorWebhook().getWebhookConfigId()); + } else { + syncOutput.getWebhookOperationSummary().getFailures().add(standardSyncOperation.getOperatorWebhook().getWebhookConfigId()); + } } else { final String message = String.format("Unsupported operation type: %s", standardSyncOperation.getOperatorType()); LOGGER.error(message); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivity.java new file mode 100644 index 000000000000..7e157530e18d --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivity.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.sync; + +import io.airbyte.config.OperatorWebhookInput; +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; + +@ActivityInterface +public interface WebhookOperationActivity { + + @ActivityMethod + boolean invokeWebhook(OperatorWebhookInput input); + +} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java new file mode 100644 index 000000000000..e5edb1d8bce1 --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.sync; + +import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.commons.json.Jsons; +import io.airbyte.config.OperatorWebhookInput; +import io.airbyte.config.WebhookConfig; +import io.airbyte.config.WebhookOperationConfigs; +import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import jakarta.inject.Singleton; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +public class WebhookOperationActivityImpl implements WebhookOperationActivity { + + private static final Logger LOGGER = LoggerFactory.getLogger(WebhookOperationActivityImpl.class); + + private final HttpClient httpClient; + + private final SecretsHydrator secretsHydrator; + + public WebhookOperationActivityImpl(final HttpClient httpClient, final SecretsHydrator secretsHydrator) { + this.httpClient = httpClient; + this.secretsHydrator = secretsHydrator; + + } + + @Override + public boolean invokeWebhook(OperatorWebhookInput input) { + LOGGER.debug("Webhook operation input: {}", input); + final JsonNode fullWebhookConfigJson = secretsHydrator.hydrate(input.getWorkspaceWebhookConfigs()); + final WebhookOperationConfigs webhookConfigs = Jsons.object(fullWebhookConfigJson, WebhookOperationConfigs.class); + final Optional webhookConfig = + webhookConfigs.getWebhookConfigs().stream().filter((config) -> config.getId().equals(input.getWebhookConfigId())).findFirst(); + if (webhookConfig.isEmpty()) { + throw new RuntimeException(String.format("Cannot find webhook config %s", input.getWebhookConfigId().toString())); + } + + LOGGER.info("Invoking webhook operation {}", webhookConfig.get().getName()); + + LOGGER.debug("Found webhook config: {}", webhookConfig.get()); + final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(input.getExecutionUrl())); + if (input.getExecutionBody() != null) { + requestBuilder.POST(HttpRequest.BodyPublishers.ofString(input.getExecutionBody())); + } + if (webhookConfig.get().getAuthToken() != null) { + requestBuilder + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + webhookConfig.get().getAuthToken()).build(); + } + try { + HttpResponse response = this.httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + LOGGER.debug("Webhook response: {}", response == null ? null : response.body()); + LOGGER.info("Webhook response status: {}", response == null ? "empty response" : response.statusCode()); + // Return true if the request was successful. + boolean isSuccessful = response != null && response.statusCode() >= 200 && response.statusCode() <= 300; + LOGGER.info("Webhook {} execution status {}", webhookConfig.get().getName(), isSuccessful ? "successful" : "failed"); + return isSuccessful; + } catch (IOException | InterruptedException e) { + LOGGER.error(e.getMessage()); + throw new RuntimeException(e); + } + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java index d0d692645f9b..cb419e3e0d51 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java @@ -15,14 +15,20 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; import io.airbyte.config.OperatorDbtInput; +import io.airbyte.config.OperatorWebhook; +import io.airbyte.config.OperatorWebhookInput; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSyncInput; +import io.airbyte.config.StandardSyncOperation; +import io.airbyte.config.StandardSyncOperation.OperatorType; import io.airbyte.config.StandardSyncOutput; import io.airbyte.config.StandardSyncSummary; import io.airbyte.config.StandardSyncSummary.ReplicationStatus; @@ -48,6 +54,7 @@ import java.io.IOException; import java.time.Duration; import java.util.List; +import java.util.UUID; import org.apache.commons.lang3.tuple.ImmutablePair; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -57,6 +64,10 @@ @SuppressWarnings({"PMD.UnusedPrivateField", "PMD.UnusedPrivateMethod"}) class SyncWorkflowTest { + private static final String WEBHOOK_URL = "http://example.com"; + private static final String WEBHOOK_BODY = "webhook-body"; + private static final UUID WEBHOOK_CONFIG_ID = UUID.randomUUID(); + // TEMPORAL private TestWorkflowEnvironment testEnv; @@ -67,6 +78,7 @@ class SyncWorkflowTest { private DbtTransformationActivityImpl dbtTransformationActivity; private PersistStateActivityImpl persistStateActivity; private NormalizationSummaryCheckActivityImpl normalizationSummaryCheckActivity; + private WebhookOperationActivityImpl webhookOperationActivity; private static final String SYNC_TASK_QUEUE = "SYNC_TASK_QUEUE"; @@ -133,6 +145,7 @@ void setUp() throws IOException { dbtTransformationActivity = mock(DbtTransformationActivityImpl.class); persistStateActivity = mock(PersistStateActivityImpl.class); normalizationSummaryCheckActivity = mock(NormalizationSummaryCheckActivityImpl.class); + webhookOperationActivity = mock(WebhookOperationActivityImpl.class); when(normalizationActivity.generateNormalizationInput(any(), any())).thenReturn(normalizationInput); when(normalizationSummaryCheckActivity.shouldRunNormalization(any(), any(), any())).thenReturn(true); @@ -177,7 +190,7 @@ public void tearDown() { // bundle up all the temporal worker setup / execution into one method. private StandardSyncOutput execute() { syncWorker.registerActivitiesImplementations(replicationActivity, normalizationActivity, dbtTransformationActivity, - persistStateActivity, normalizationSummaryCheckActivity); + persistStateActivity, normalizationSummaryCheckActivity, webhookOperationActivity); testEnv.start(); final SyncWorkflow workflow = client.newWorkflowStub(SyncWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(SYNC_TASK_QUEUE).build()); @@ -339,6 +352,25 @@ void testSkipNormalization() throws IOException { operatorDbtInput); } + @Test + void testWebhookOperation() { + when(replicationActivity.replicate(any(), any(), any(), any())).thenReturn(new StandardSyncOutput()); + final StandardSyncOperation webhookOperation = new StandardSyncOperation() + .withOperationId(UUID.randomUUID()) + .withOperatorType(OperatorType.WEBHOOK) + .withOperatorWebhook(new OperatorWebhook() + .withExecutionUrl(WEBHOOK_URL) + .withExecutionBody(WEBHOOK_BODY) + .withWebhookConfigId(WEBHOOK_CONFIG_ID)); + final JsonNode workspaceWebhookConfigs = Jsons.emptyObject(); + syncInput.withOperationSequence(List.of(webhookOperation)).withWebhookOperationConfigs(workspaceWebhookConfigs); + when(webhookOperationActivity.invokeWebhook( + new OperatorWebhookInput().withWebhookConfigId(WEBHOOK_CONFIG_ID).withExecutionUrl(WEBHOOK_URL).withExecutionBody(WEBHOOK_BODY) + .withWorkspaceWebhookConfigs(workspaceWebhookConfigs))).thenReturn(true); + final StandardSyncOutput actualOutput = execute(); + assertEquals(actualOutput.getWebhookOperationSummary().getSuccesses().get(0), WEBHOOK_CONFIG_ID); + } + @SuppressWarnings("ResultOfMethodCallIgnored") private void cancelWorkflow() { final WorkflowServiceBlockingStub temporalService = testEnv.getWorkflowService().blockingStub(); diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityTest.java new file mode 100644 index 000000000000..a005bf07692e --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.sync; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.airbyte.commons.json.Jsons; +import io.airbyte.config.OperatorWebhookInput; +import io.airbyte.config.WebhookConfig; +import io.airbyte.config.WebhookOperationConfigs; +import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import java.io.IOException; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class WebhookOperationActivityTest { + + private static final String WEBHOOK_EXECUTION_BODY = "fake-webhook-execution-body"; + private static final String WEBHOOK_EXECUTION_URL = "http://example.com"; + private WebhookOperationActivity webhookActivity; + private HttpClient httpClient; + private SecretsHydrator secretsHydrator; + private static final UUID WEBHOOK_ID = UUID.randomUUID(); + private static final String WEBHOOK_AUTH_TOKEN = "fake-auth-token"; + private static final WebhookOperationConfigs WORKSPACE_WEBHOOK_CONFIGS = new WebhookOperationConfigs().withWebhookConfigs(List.of( + new WebhookConfig().withId(WEBHOOK_ID).withAuthToken(WEBHOOK_AUTH_TOKEN))); + + @BeforeEach + void init() { + httpClient = mock(HttpClient.class); + secretsHydrator = mock(SecretsHydrator.class); + webhookActivity = new WebhookOperationActivityImpl(httpClient, secretsHydrator); + } + + @Test + void webhookActivityInvokesConfiguredWebhook() throws IOException, InterruptedException { + final HttpResponse mockHttpResponse = mock(HttpResponse.class); + when(mockHttpResponse.statusCode()).thenReturn(200).thenReturn(200); + when(secretsHydrator.hydrate(any())).thenReturn(Jsons.jsonNode(WORKSPACE_WEBHOOK_CONFIGS)); + final OperatorWebhookInput input = new OperatorWebhookInput() + .withExecutionBody(WEBHOOK_EXECUTION_BODY) + .withExecutionUrl(WEBHOOK_EXECUTION_URL) + .withWebhookConfigId(WEBHOOK_ID); + // TODO(mfsiega-airbyte): make these matchers more specific. + when(httpClient.send(any(), any())).thenReturn(mockHttpResponse); + boolean success = webhookActivity.invokeWebhook(input); + assertTrue(success); + } + +} From a4253b3d2646e04b6a583c35de4ab974232d7b4a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 09:15:47 +0300 Subject: [PATCH 154/498] Bump helm chart version reference to 0.40.22 (#18077) --- .gitignore | 3 +++ charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-metrics/Chart.yaml | 2 +- charts/airbyte-pod-sweeper/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 18 +++++++++--------- charts/airbyte/Chart.yaml | 16 ++++++++-------- 9 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 9cf1deb464ec..cef56133163d 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,6 @@ docs/SUMMARY.md # Files generated by unit tests **/specs_secrets_mask.yaml + +# Helm charts .tgz dependencies +charts/**/charts \ No newline at end of file diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 9680b1bee965..7a06c1514e8e 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.20" +version: "0.40.22" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index b76fb5f83edd..2a568e2d2b78 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.20" +version: "0.40.22" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index d33cd4ca1983..0c131d0c8a71 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.20" +version: "0.40.22" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index 83ce147085e9..d5702b111a42 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.20" +version: "0.40.22" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index eb27913563b5..dc7392ef52db 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.20" +version: "0.40.22" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index c0145c7bfddb..fee738c6764d 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.20" +version: "0.40.22" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index 617b62982862..4664987861a6 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -4,24 +4,24 @@ dependencies: version: 1.17.1 - name: airbyte-bootloader repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 + version: 0.40.22 - name: temporal repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 + version: 0.40.22 - name: webapp repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 + version: 0.40.22 - name: server repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 + version: 0.40.22 - name: worker repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 + version: 0.40.22 - name: pod-sweeper repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 + version: 0.40.22 - name: metrics repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.20 -digest: sha256:9b5cab655a1a12640c8752c40a6560a1774edee8262af7571df16a1cc189251b -generated: "2022-10-17T18:40:41.673881061Z" + version: 0.40.22 +digest: sha256:1361252c4daa8b47e6b961396b3631077696c846e2e2c140d69119a8f7692b03 +generated: "2022-10-17T19:36:16.613801078Z" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 468fff07eece..3139088b67cf 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.40.20 +version: 0.40.22 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -32,28 +32,28 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.20 + version: 0.40.22 From 26b6317198daaec0841493ff07ec463e36cf4705 Mon Sep 17 00:00:00 2001 From: Alexander Marquardt Date: Tue, 18 Oct 2022 08:19:55 +0200 Subject: [PATCH 155/498] =?UTF-8?q?Added=20new=20"filters"=20python=20file?= =?UTF-8?q?,=20along=20with=20a=20"hash"=20filter.=20This=20can=E2=80=A6?= =?UTF-8?q?=20(#18000)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added new "filters" python file, along with a "hash" filter. This can be extended to include other custom filters in the future. * Added additional comments * Moved usage of the hash_obj inside the conditional that confirms it exists * Moved the hash function call inside a condition to ensure that it exists * Fixed the application of the salt , so that it does not modify the hash unless it is actually passed in. * Added unit tests to validate new jinja hash functionality * Updated unit test to pass numeric value as a float instead of string * Removed unreferenced import to pytest * Updated version * format * format * format * format * format Co-authored-by: Alexandre Girard --- airbyte-cdk/python/CHANGELOG.md | 4 ++ .../declarative/interpolation/filters.py | 55 +++++++++++++++++++ .../declarative/interpolation/jinja.py | 2 + airbyte-cdk/python/setup.py | 2 +- .../declarative/interpolation/test_filters.py | 50 +++++++++++++++++ 5 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/filters.py create mode 100644 airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_filters.py diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index 456cc44c730b..6b4f48c6c082 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.103 + +- Low-code: added hash filter to jinja template + ## 0.1.102 - Low-code: Fix check for streams that do not define a stream slicer diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/filters.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/filters.py new file mode 100644 index 000000000000..f707f42469e7 --- /dev/null +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/filters.py @@ -0,0 +1,55 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import hashlib + + +def hash(value, hash_type="md5", salt=None): + """ + Implementation of a custom Jinja2 hash filter + Hash type defaults to 'md5' if one is not specified. + + If you are using this has function for GDPR compliance, then + you should probably also pass in a salt as discussed in: + https://security.stackexchange.com/questions/202022/hashing-email-addresses-for-gdpr-compliance + + This can be used in a low code connector definition under the AddFields transformation. + For example: + + rates_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "rates" + primary_key: "date" + path: "/exchangerates_data/latest" + transformations: + - type: AddFields + fields: + - path: ["some_new_path"] + value: "{{ record['rates']['CAD'] | hash('md5', 'mysalt') }}" + + + + :param value: value to be hashed + :param hash_type: valid hash type + :param salt: a salt that will be combined with the value to ensure that the hash created for a given value on this system + is different from the hash created for that value on other systems. + :return: computed hash as a hexadecimal string + """ + hash_func = getattr(hashlib, hash_type, None) + + if hash_func: + hash_obj = hash_func() + hash_obj.update(str(value).encode("utf-8")) + if salt: + hash_obj.update(str(salt).encode("utf-8")) + computed_hash = hash_obj.hexdigest() + else: + raise AttributeError("No hashing function named {hname}".format(hname=hash_type)) + + return computed_hash + + +_filters_list = [hash] +filters = {f.__name__: f for f in _filters_list} diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/jinja.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/jinja.py index 883118fb5fdf..7fac960fcb27 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/jinja.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/interpolation/jinja.py @@ -5,6 +5,7 @@ import ast from typing import Optional +from airbyte_cdk.sources.declarative.interpolation.filters import filters from airbyte_cdk.sources.declarative.interpolation.interpolation import Interpolation from airbyte_cdk.sources.declarative.interpolation.macros import macros from airbyte_cdk.sources.declarative.types import Config @@ -32,6 +33,7 @@ class JinjaInterpolation(Interpolation): def __init__(self): self._environment = Environment() + self._environment.filters.update(**filters) self._environment.globals.update(**macros) def eval(self, input_str: str, config: Config, default: Optional[str] = None, **additional_options): diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index ef278c81b2f1..9363d3ae5598 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.102", + version="0.1.103", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_filters.py b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_filters.py new file mode 100644 index 000000000000..96dc423f91da --- /dev/null +++ b/airbyte-cdk/python/unit_tests/sources/declarative/interpolation/test_filters.py @@ -0,0 +1,50 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import hashlib + +from airbyte_cdk.sources.declarative.interpolation.jinja import JinjaInterpolation + +interpolation = JinjaInterpolation() + + +def test_hash_md5_no_salt(): + input_string = "abcd" + s = "{{ '%s' | hash('md5') }}" % input_string + filter_hash = interpolation.eval(s, config={}) + + # compute expected hash calling hashlib directly + hash_obj = hashlib.md5() + hash_obj.update(str(input_string).encode("utf-8")) + hashlib_computed_hash = hash_obj.hexdigest() + + assert filter_hash == hashlib_computed_hash + + +def test_hash_md5_on_numeric_value(): + input_value = 123.456 + s = "{{ %f | hash('md5') }}" % input_value + filter_hash = interpolation.eval(s, config={}) + + # compute expected hash calling hashlib directly + hash_obj = hashlib.md5() + hash_obj.update(str(input_value).encode("utf-8")) + hashlib_computed_hash = hash_obj.hexdigest() + + assert filter_hash == hashlib_computed_hash + + +def test_hash_md5_with_salt(): + input_string = "test_input_string" + input_salt = "test_input_salt" + + s = "{{ '%s' | hash('md5', '%s' ) }}" % (input_string, input_salt) + filter_hash = interpolation.eval(s, config={}) + + # compute expected value calling hashlib directly + hash_obj = hashlib.md5() + hash_obj.update(str(input_string + input_salt).encode("utf-8")) + hashlib_computed_hash = hash_obj.hexdigest() + + assert filter_hash == hashlib_computed_hash From d4351ee3b4d025ecce1686df01b656b704f71aa7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 10:35:14 +0300 Subject: [PATCH 156/498] Bump helm chart version reference to 0.40.24 (#18081) * Bump helm chart version reference to 0.40.24 * Update .gitignore Co-authored-by: benmoriceau Co-authored-by: Kyryl Skobylko --- .github/workflows/publish-helm-charts.yml | 1 + .gitignore | 2 +- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-metrics/Chart.yaml | 2 +- charts/airbyte-pod-sweeper/.gitignore | 2 ++ charts/airbyte-pod-sweeper/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 18 +++++++++--------- charts/airbyte/Chart.yaml | 16 ++++++++-------- 11 files changed, 27 insertions(+), 24 deletions(-) create mode 100644 charts/airbyte-pod-sweeper/.gitignore diff --git a/.github/workflows/publish-helm-charts.yml b/.github/workflows/publish-helm-charts.yml index 044d0db7908a..df4ed73978e1 100644 --- a/.github/workflows/publish-helm-charts.yml +++ b/.github/workflows/publish-helm-charts.yml @@ -10,6 +10,7 @@ on: - 'charts/**' - '!charts/**/Chart.yaml' - '!charts/**/Chart.lock' + - '!charts/**/README.md' workflow_dispatch: jobs: diff --git a/.gitignore b/.gitignore index cef56133163d..f8e7ba1032cc 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,4 @@ docs/SUMMARY.md **/specs_secrets_mask.yaml # Helm charts .tgz dependencies -charts/**/charts \ No newline at end of file +charts/**/charts diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 7a06c1514e8e..48e97024970f 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.22" +version: "0.40.24" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index 2a568e2d2b78..72aa0ca25ecd 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.22" +version: "0.40.24" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-pod-sweeper/.gitignore b/charts/airbyte-pod-sweeper/.gitignore new file mode 100644 index 000000000000..88e91e8a8f34 --- /dev/null +++ b/charts/airbyte-pod-sweeper/.gitignore @@ -0,0 +1,2 @@ +# Charts are downloaded at install time with `helm dep build`. +charts diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index 0c131d0c8a71..157c98c8c4bf 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.22" +version: "0.40.24" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index d5702b111a42..abdc73e6e148 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.22" +version: "0.40.24" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index dc7392ef52db..58734d675826 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.22" +version: "0.40.24" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index fee738c6764d..9d4bc827a803 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.22" +version: "0.40.24" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index 4664987861a6..443c3a07825b 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -4,24 +4,24 @@ dependencies: version: 1.17.1 - name: airbyte-bootloader repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 + version: 0.40.24 - name: temporal repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 + version: 0.40.24 - name: webapp repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 + version: 0.40.24 - name: server repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 + version: 0.40.24 - name: worker repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 + version: 0.40.24 - name: pod-sweeper repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 + version: 0.40.24 - name: metrics repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.22 -digest: sha256:1361252c4daa8b47e6b961396b3631077696c846e2e2c140d69119a8f7692b03 -generated: "2022-10-17T19:36:16.613801078Z" + version: 0.40.24 +digest: sha256:34f736f80f353eac15537f4b2a619773a1f768b575c4c29adfac9de2e3db2e9b +generated: "2022-10-17T20:40:52.086435729Z" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 3139088b67cf..e652f82b2d94 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.40.22 +version: 0.40.24 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -32,28 +32,28 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.22 + version: 0.40.24 From 7a3815a7cd14b07a96dc331f1ab90f3503b158c7 Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Tue, 18 Oct 2022 11:34:50 +0300 Subject: [PATCH 157/498] SATs: allow new records in a sequential read for full refresh test (#17660) * SATs: allow new records in a sequential read for full refresh test * SATs: upd changelog * SATs: change the output when failing full refresh test * SATs: upd according to code review --- .../bases/source-acceptance-test/CHANGELOG.md | 3 + .../bases/source-acceptance-test/Dockerfile | 2 +- .../tests/test_full_refresh.py | 9 +- .../unit_tests/test_test_full_refresh.py | 207 +++++++++++++----- 4 files changed, 157 insertions(+), 64 deletions(-) diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index 3c6429a31587..71fc49d543dc 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.8 +Make full refresh tests tolerant to new records in a sequential read.[#17660](https://github.com/airbytehq/airbyte/pull/17660/) + ## 0.2.7 Fix a bug when a state is evaluated once before used in a loop of `test_read_sequential_slices` [#17757](https://github.com/airbytehq/airbyte/pull/17757/) diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index 778684b102f7..187f2a5c9138 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.7 +LABEL io.airbyte.version=0.2.8 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_full_refresh.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_full_refresh.py index c88514487da6..c27938723c72 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_full_refresh.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_full_refresh.py @@ -67,15 +67,14 @@ def test_sequential_reads( serializer = partial(make_hashable, exclude_fields=ignored_fields.get(stream)) stream_records_1 = records_by_stream_1.get(stream) stream_records_2 = records_by_stream_2.get(stream) - # Using - output_diff = set(map(serializer, stream_records_1)).symmetric_difference(set(map(serializer, stream_records_2))) - if output_diff: + if not set(map(serializer, stream_records_1)).issubset(set(map(serializer, stream_records_2))): + missing_records = set(map(serializer, stream_records_1)) - (set(map(serializer, stream_records_2))) msg = f"{stream}: the two sequential reads should produce either equal set of records or one of them is a strict subset of the other" detailed_logger.info(msg) detailed_logger.info("First read") detailed_logger.log_json_list(stream_records_1) detailed_logger.info("Second read") detailed_logger.log_json_list(stream_records_2) - detailed_logger.info("Difference") - detailed_logger.log_json_list(output_diff) + detailed_logger.info("Missing records") + detailed_logger.log_json_list(missing_records) pytest.fail(msg) diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py index 6fd1e310385e..54821dff87a3 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py @@ -2,8 +2,8 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from contextlib import nullcontext as does_not_raise from typing import Dict, List -from unittest.mock import MagicMock import pytest from _pytest.outcomes import Failed @@ -16,15 +16,51 @@ class ReadTestConfigWithIgnoreFields(ConnectionTestConfig): ignored_fields: Dict[str, List[str]] = {"test_stream": ["ignore_me", "ignore_me_too"]} -test_cases = [ - ( +def record_message_from_record(records: List[Dict]) -> List[AirbyteMessage]: + return [ + AirbyteMessage( + type=Type.RECORD, + record=AirbyteRecordMessage(stream="test_stream", data=record, emitted_at=111), + ) + for record in records + ] + + +def get_default_catalog(schema, **kwargs): + configured_catalog_kwargs = {"sync_mode": "full_refresh", "destination_sync_mode": "overwrite"} + primary_key = kwargs.get("primary_key") + if primary_key: + configured_catalog_kwargs["primary_key"] = primary_key + return ConfiguredAirbyteCatalog( + streams=[ + ConfiguredAirbyteStream( + stream=AirbyteStream( + name="test_stream", + json_schema=schema, + supported_sync_modes=["full_refresh"], + ), + **configured_catalog_kwargs, + ) + ] + ) + + +fail_context = pytest.raises( + Failed, + match="the two sequential reads should produce either equal set of records or one of them is a strict subset of the other", +) +no_fail_context = does_not_raise() + + +ignored_fields_test_cases = [ + pytest.param( {"type": "object", "properties": {"created": {"type": "string"}}}, {"created": "23"}, {"created": "23"}, - False, - "no_ignored_fields_present", + no_fail_context, + id="no_ignored_fields_present", ), - ( + pytest.param( { "type": "object", "properties": { @@ -34,10 +70,10 @@ class ReadTestConfigWithIgnoreFields(ConnectionTestConfig): }, {"created": "23"}, {"created": "23", "ignore_me": "23"}, - False, - "with_ignored_field", + no_fail_context, + id="with_ignored_field", ), - ( + pytest.param( { "type": "object", "required": ["created", "DONT_ignore_me"], @@ -49,76 +85,131 @@ class ReadTestConfigWithIgnoreFields(ConnectionTestConfig): }, {"created": "23"}, {"created": "23", "DONT_ignore_me": "23", "ignore_me": "hello"}, - True, - "ignore_field_present_but_a_required_is_not", + fail_context, + id="ignore_field_present_but_a_required_is_not", ), ] @pytest.mark.parametrize( - "schema, record, expected_record, should_fail, test_case_name", - test_cases, - ids=[test_case[-1] for test_case in test_cases], + "schema, record, expected_record, fail_context", + ignored_fields_test_cases, ) -def test_read_with_ignore_fields(schema, record, expected_record, should_fail, test_case_name): +def test_read_with_ignore_fields(mocker, schema, record, expected_record, fail_context): catalog = get_default_catalog(schema) input_config = ReadTestConfigWithIgnoreFields() - docker_runner_mock = MagicMock() - - def record_message_from_record(record_: Dict) -> List[AirbyteMessage]: - return [ - AirbyteMessage( - type=Type.RECORD, - record=AirbyteRecordMessage(stream="test_stream", data=record_, emitted_at=111), - ) - ] + docker_runner_mock = mocker.MagicMock() sequence_of_docker_callread_results = [record, expected_record] # Ignored fields should work both ways - for pair in ( + for first, second in ( sequence_of_docker_callread_results, list(reversed(sequence_of_docker_callread_results)), ): - docker_runner_mock.call_read.side_effect = [ - record_message_from_record(pair[0]), - record_message_from_record(pair[1]), - ] + docker_runner_mock.call_read.side_effect = [record_message_from_record([first]), record_message_from_record([second])] t = _TestFullRefresh() - if should_fail: - with pytest.raises( - Failed, - match="the two sequential reads should produce either equal set of records or one of them is a strict subset of the other", - ): - t.test_sequential_reads( - inputs=input_config, - connector_config=MagicMock(), - configured_catalog=catalog, - docker_runner=docker_runner_mock, - detailed_logger=MagicMock(), - ) - else: + with fail_context: t.test_sequential_reads( inputs=input_config, - connector_config=MagicMock(), + connector_config=mocker.MagicMock(), configured_catalog=catalog, docker_runner=docker_runner_mock, - detailed_logger=MagicMock(), + detailed_logger=mocker.MagicMock(), ) -def get_default_catalog(schema): - return ConfiguredAirbyteCatalog( - streams=[ - ConfiguredAirbyteStream( - stream=AirbyteStream( - name="test_stream", - json_schema=schema, - supported_sync_modes=["full_refresh"], - ), - sync_mode="full_refresh", - destination_sync_mode="overwrite", - ) - ] - ) +recordset_comparison_test_cases = [ + pytest.param( + [["id"]], + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + [{"id": 1, "first_name": "Albert", "last_name": "Einstein"}, {"id": 2, "first_name": "Joseph", "last_name": "Lagrange"}], + no_fail_context, + id="pk_sets_equal_success", + ), + pytest.param( + [["id"]], + [ + {"id": 1, "first_name": "Thomas", "last_name": "Edison"}, + ], + [{"id": 1, "first_name": "Albert", "last_name": "Einstein"}, {"id": 2, "first_name": "Joseph", "last_name": "Lagrange"}], + no_fail_context, + id="pk_first_is_subset_success", + ), + pytest.param( + [["id"]], + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + [{"id": 1, "first_name": "Albert", "last_name": "Einstein"}], + fail_context, + id="pk_second_is_subset_fail", + ), + pytest.param( + [["id"]], + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + [{"id": 2, "first_name": "Thomas", "last_name": "Edison"}, {"id": 3, "first_name": "Nicola", "last_name": "Tesla"}], + fail_context, + id="pk_no_subsets_fail", + ), + pytest.param( + None, + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + no_fail_context, + id="no_pk_sets_equal_success", + ), + pytest.param( + None, + [ + {"id": 1, "first_name": "Thomas", "last_name": "Edison"}, + ], + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + no_fail_context, + id="no_pk_first_is_subset_success", + ), + pytest.param( + None, + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + [ + {"id": 1, "first_name": "Thomas", "last_name": "Edison"}, + ], + fail_context, + id="no_pk_second_is_subset_fail", + ), + pytest.param( + None, + [{"id": 1, "first_name": "Thomas", "last_name": "Edison"}, {"id": 2, "first_name": "Nicola", "last_name": "Tesla"}], + [{"id": 2, "first_name": "Nicola", "last_name": "Tesla"}, {"id": 3, "first_name": "Albert", "last_name": "Einstein"}], + fail_context, + id="no_pk_no_subsets_fail", + ), +] + + +@pytest.mark.parametrize( + "primary_key, first_read_records, second_read_records, fail_context", + recordset_comparison_test_cases, +) +def test_recordset_comparison(mocker, primary_key, first_read_records, second_read_records, fail_context): + schema = { + "type": "object", + "properties": {"id": {"type": "integer"}, "first_name": {"type": "string"}, "last_name": {"type": "string"}}, + } + catalog = get_default_catalog(schema, primary_key=primary_key) + input_config = ReadTestConfigWithIgnoreFields() + docker_runner_mock = mocker.MagicMock() + + docker_runner_mock.call_read.side_effect = [ + record_message_from_record(first_read_records), + record_message_from_record(second_read_records), + ] + + t = _TestFullRefresh() + with fail_context: + t.test_sequential_reads( + inputs=input_config, + connector_config=mocker.MagicMock(), + configured_catalog=catalog, + docker_runner=docker_runner_mock, + detailed_logger=mocker.MagicMock(), + ) From 475df69a66468aee911bbed6aab098b979a04393 Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Tue, 18 Oct 2022 13:22:41 +0300 Subject: [PATCH 158/498] Source facebook-marketing: remove `pixel` from custom conversions stream (#18045) * #744 source facebook-marketing: rm pixel from custom conversions stream * #744 source fb marketing: upd changelog * #744 source facebook-marketing - add custom_conversions to the test catalog * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-facebook-marketing/Dockerfile | 2 +- .../integration_tests/configured_catalog.json | 11 +++++++++++ .../schemas/custom_conversions.json | 8 -------- docs/integrations/sources/facebook-marketing.md | 3 ++- 6 files changed, 16 insertions(+), 12 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 65f274bb60d4..febc17f8d376 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -279,7 +279,7 @@ - name: Facebook Marketing sourceDefinitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c dockerRepository: airbyte/source-facebook-marketing - dockerImageTag: 0.2.68 + dockerImageTag: 0.2.69 documentationUrl: https://docs.airbyte.com/integrations/sources/facebook-marketing icon: facebook.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index bded7c7e5672..28a3982d30a8 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2561,7 +2561,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-facebook-marketing:0.2.68" +- dockerImage: "airbyte/source-facebook-marketing:0.2.69" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" changelogUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" diff --git a/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile b/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile index aa2031915ea6..5c7b89d52ef8 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile +++ b/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile @@ -13,5 +13,5 @@ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.68 +LABEL io.airbyte.version=0.2.69 LABEL io.airbyte.name=airbyte/source-facebook-marketing diff --git a/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/configured_catalog.json index ec6bfe9efa6a..91060fa15d78 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-facebook-marketing/integration_tests/configured_catalog.json @@ -264,6 +264,17 @@ "cursor_field": null, "destination_sync_mode": "append", "primary_key": null + }, + { + "stream": { + "name": "custom_conversions", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"], + "source_defined_primary_key": [["id"]] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append", + "primary_key": [["id"]] } ] } diff --git a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/schemas/custom_conversions.json b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/schemas/custom_conversions.json index 8f699108a5d7..97693962366f 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/schemas/custom_conversions.json +++ b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/schemas/custom_conversions.json @@ -62,14 +62,6 @@ "offline_conversion_data_set": { "type": ["null", "string"] }, - "pixel": { - "type": "object", - "properties": { - "id": { - "type": "string" - } - } - }, "retention_days": { "type": ["null", "number"] }, diff --git a/docs/integrations/sources/facebook-marketing.md b/docs/integrations/sources/facebook-marketing.md index 5a935aaf20ec..9e7cac76388f 100644 --- a/docs/integrations/sources/facebook-marketing.md +++ b/docs/integrations/sources/facebook-marketing.md @@ -120,7 +120,8 @@ Please be informed that the connector uses the `lookback_window` parameter to pe ## Changelog | Version | Date | Pull Request | Subject | -| :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +|:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.2.69 | 2022-10-17 | [18045](https://github.com/airbytehq/airbyte/pull/18045) | Remove "pixel" field from the Custom Conversions stream schema | | 0.2.68 | 2022-10-12 | [17869](https://github.com/airbytehq/airbyte/pull/17869) | Remove "format" from optional datetime `end_date` field | | 0.2.67 | 2022-10-04 | [17551](https://github.com/airbytehq/airbyte/pull/17551) | Add `cursor_field` for custom_insights stream schema | | 0.2.65 | 2022-09-29 | [17371](https://github.com/airbytehq/airbyte/pull/17371) | Fix stream CustomConversions `enable_deleted=False` | From 9d23d48a014dbea0924a92c07d70b757b578b839 Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Tue, 18 Oct 2022 15:04:05 +0300 Subject: [PATCH 159/498] #17506 fix klaviyo & marketo expected_records (#18101) --- .../integration_tests/expected_records.txt | 864 +----------------- .../integration_tests/expected_records.txt | 2 +- 2 files changed, 6 insertions(+), 860 deletions(-) diff --git a/airbyte-integrations/connectors/source-klaviyo/integration_tests/expected_records.txt b/airbyte-integrations/connectors/source-klaviyo/integration_tests/expected_records.txt index ba7330a0a938..97f6dbf37ca1 100644 --- a/airbyte-integrations/connectors/source-klaviyo/integration_tests/expected_records.txt +++ b/airbyte-integrations/connectors/source-klaviyo/integration_tests/expected_records.txt @@ -1,864 +1,10 @@ {"stream": "campaigns", "data": {"object": "campaign", "id": "VFaYVy", "name": "Email Campaign 2021-05-16 19:17:45", "subject": "My Test subject", "from_email": "integration-test@airbyte.io", "from_name": "Airbyte", "lists": [{"object": "list", "id": "RnsiHB", "name": "Newsletter", "list_type": "list", "folder": null, "created": "2021-03-31T10:50:36+00:00", "updated": "2021-03-31T10:50:36+00:00", "person_count": 1}, {"object": "list", "id": "TaSce6", "name": "Preview List", "list_type": "list", "folder": null, "created": "2021-03-31T10:50:37+00:00", "updated": "2021-03-31T10:50:37+00:00", "person_count": 1}], "excluded_lists": [{"object": "list", "id": "Ukh37W", "name": "Unengaged (3 Months)", "list_type": "segment", "folder": null, "created": "2021-03-31T10:50:37+00:00", "updated": "2021-03-31T10:50:43+00:00", "person_count": 0}], "status": "sent", "status_id": 1, "status_label": "Sent", "sent_at": "2021-05-26T23:30:13+00:00", "send_time": "2021-05-26T23:30:00+00:00", "created": "2021-05-16T23:17:45+00:00", "updated": "2021-05-26T23:30:13+00:00", "num_recipients": 1, "campaign_type": "Batch", "is_segmented": true, "message_type": "email", "template_id": "VR2KEG"}, "emitted_at": 1663367156487} {"stream": "campaigns", "data": {"object": "campaign", "id": "T4hgvQ", "name": "Email Campaign 2021-05-12 16:45:46", "subject": "", "from_email": "integration-test@airbyte.io", "from_name": "Airbyte", "lists": [], "excluded_lists": [], "status": "draft", "status_id": 2, "status_label": "Draft", "sent_at": null, "send_time": null, "created": "2021-05-12T20:45:47+00:00", "updated": "2021-05-12T20:45:47+00:00", "num_recipients": 0, "campaign_type": "Regular", "is_segmented": false, "message_type": "email", "template_id": null}, "emitted_at": 1663367156491} -{"stream": "events", "data": {"object": "event", "id": "3qvdbYg3", "statistic_id": "VFFb4u", "timestamp": 1621295008, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295008"}, "datetime": "2021-05-17 23:43:28+00:00", "uuid": "adc8d000-b769-11eb-8001-28a6687f81c3", "person": {"object": "person", "id": "01F5YBDQE9W7WDSH9KK398CAYX", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.{seed}@airbyte.io", "$first_name": "", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.{seed}@airbyte.io", "first_name": "", "last_name": "", "created": "2021-05-17 23:43:50", "updated": "2021-05-17 23:43:50"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156953} -{"stream": "events", "data": {"object": "event", "id": "3qvdgpzF", "statistic_id": "VFFb4u", "timestamp": 1621295124, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295124"}, "datetime": "2021-05-17 23:45:24+00:00", "uuid": "f2ed0200-b769-11eb-8001-76152f6b1c82", "person": {"object": "person", "id": "01F5YBGKW1SQN453RM293PHH37", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 0", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.0@airbyte.io", "$first_name": "First Name 0", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.0@airbyte.io", "first_name": "First Name 0", "last_name": "Last Name 0", "created": "2021-05-17 23:45:24", "updated": "2021-05-17 23:45:25"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156954} -{"stream": "events", "data": {"object": "event", "id": "3qvdgr5Z", "statistic_id": "VFFb4u", "timestamp": 1621295124, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295124"}, "datetime": "2021-05-17 23:45:24+00:00", "uuid": "f2ed0200-b769-11eb-8001-b642ddab48ad", "person": {"object": "person", "id": "01F5YBGM7J4YD4P6EYK5Q87BG4", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 1", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.1@airbyte.io", "$first_name": "First Name 1", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.1@airbyte.io", "first_name": "First Name 1", "last_name": "Last Name 1", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:26"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156955} -{"stream": "events", "data": {"object": "event", "id": "3qvdgBgK", "statistic_id": "VFFb4u", "timestamp": 1621295124, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295124"}, "datetime": "2021-05-17 23:45:24+00:00", "uuid": "f2ed0200-b769-11eb-8001-2006a2b2b6e7", "person": {"object": "person", "id": "01F5YBGMK62AJR0955G7NW6EP7", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 2", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.2@airbyte.io", "$first_name": "First Name 2", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.2@airbyte.io", "first_name": "First Name 2", "last_name": "Last Name 2", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:38"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156955} -{"stream": "events", "data": {"object": "event", "id": "3qvdgs9P", "statistic_id": "VFFb4u", "timestamp": 1621295125, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295125"}, "datetime": "2021-05-17 23:45:25+00:00", "uuid": "f3859880-b769-11eb-8001-f6a061424b91", "person": {"object": "person", "id": "01F5YBGMK62AJR0955G7NW6EP7", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 2", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.2@airbyte.io", "$first_name": "First Name 2", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.2@airbyte.io", "first_name": "First Name 2", "last_name": "Last Name 2", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:38"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156955} -{"stream": "events", "data": {"object": "event", "id": "3qvdgtx9", "statistic_id": "VFFb4u", "timestamp": 1621295125, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295125"}, "datetime": "2021-05-17 23:45:25+00:00", "uuid": "f3859880-b769-11eb-8001-dce8405bb5bc", "person": {"object": "person", "id": "01F5YBGMTSM3B56W37QB9Q9CAD", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 3", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.3@airbyte.io", "$first_name": "First Name 3", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.3@airbyte.io", "first_name": "First Name 3", "last_name": "Last Name 3", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:25"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156955} -{"stream": "events", "data": {"object": "event", "id": "3qvdguhd", "statistic_id": "VFFb4u", "timestamp": 1621295125, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295125"}, "datetime": "2021-05-17 23:45:25+00:00", "uuid": "f3859880-b769-11eb-8001-e7f76d1244af", "person": {"object": "person", "id": "01F5YBGN65NTCBGTAR1Y7P5285", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 4", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.4@airbyte.io", "$first_name": "First Name 4", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.4@airbyte.io", "first_name": "First Name 4", "last_name": "Last Name 4", "created": "2021-05-17 23:45:26", "updated": "2021-05-17 23:45:26"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156956} -{"stream": "events", "data": {"object": "event", "id": "3qvdgv8j", "statistic_id": "VFFb4u", "timestamp": 1621295126, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295126"}, "datetime": "2021-05-17 23:45:26+00:00", "uuid": "f41e2f00-b769-11eb-8001-6facb4fa208f", "person": {"object": "person", "id": "01F5YBGN65NTCBGTAR1Y7P5285", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 4", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.4@airbyte.io", "$first_name": "First Name 4", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.4@airbyte.io", "first_name": "First Name 4", "last_name": "Last Name 4", "created": "2021-05-17 23:45:26", "updated": "2021-05-17 23:45:26"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156956} -{"stream": "events", "data": {"object": "event", "id": "3qvdgw5Y", "statistic_id": "VFFb4u", "timestamp": 1621295126, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295126"}, "datetime": "2021-05-17 23:45:26+00:00", "uuid": "f41e2f00-b769-11eb-8001-24d5644974cf", "person": {"object": "person", "id": "01F5YBGNK6H122QRC1K96GXY8C", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 5", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.5@airbyte.io", "$first_name": "First Name 5", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.5@airbyte.io", "first_name": "First Name 5", "last_name": "Last Name 5", "created": "2021-05-17 23:45:26", "updated": "2021-05-17 23:45:26"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156956} -{"stream": "events", "data": {"object": "event", "id": "3qvdgxdE", "statistic_id": "VFFb4u", "timestamp": 1621295126, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295126"}, "datetime": "2021-05-17 23:45:26+00:00", "uuid": "f41e2f00-b769-11eb-8001-4ad2f48c74fd", "person": {"object": "person", "id": "01F5YBGP0P02E9Q64KF26VB2MH", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 6", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.6@airbyte.io", "$first_name": "First Name 6", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.6@airbyte.io", "first_name": "First Name 6", "last_name": "Last Name 6", "created": "2021-05-17 23:45:27", "updated": "2021-05-17 23:45:27"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156956} -{"stream": "events", "data": {"object": "event", "id": "3qvdgHR6", "statistic_id": "VFFb4u", "timestamp": 1621295126, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295126"}, "datetime": "2021-05-17 23:45:26+00:00", "uuid": "f41e2f00-b769-11eb-8001-8b914d6343de", "person": {"object": "person", "id": "01F5YBGPCQESZDRKGW3DB1WPZ0", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 7", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.7@airbyte.io", "$first_name": "First Name 7", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.7@airbyte.io", "first_name": "First Name 7", "last_name": "Last Name 7", "created": "2021-05-17 23:45:27", "updated": "2021-05-17 23:45:30"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156956} -{"stream": "events", "data": {"object": "event", "id": "3qvdgyGz", "statistic_id": "VFFb4u", "timestamp": 1621295127, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295127"}, "datetime": "2021-05-17 23:45:27+00:00", "uuid": "f4b6c580-b769-11eb-8001-1f983e9a55a6", "person": {"object": "person", "id": "01F5YBGPCQESZDRKGW3DB1WPZ0", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 7", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.7@airbyte.io", "$first_name": "First Name 7", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.7@airbyte.io", "first_name": "First Name 7", "last_name": "Last Name 7", "created": "2021-05-17 23:45:27", "updated": "2021-05-17 23:45:30"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156957} -{"stream": "events", "data": {"object": "event", "id": "3qvdgzZ3", "statistic_id": "VFFb4u", "timestamp": 1621295127, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295127"}, "datetime": "2021-05-17 23:45:27+00:00", "uuid": "f4b6c580-b769-11eb-8001-eaa225ddf2b6", "person": {"object": "person", "id": "01F5YBGPSXF1N23RBJZ947R1N1", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 8", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.8@airbyte.io", "$first_name": "First Name 8", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.8@airbyte.io", "first_name": "First Name 8", "last_name": "Last Name 8", "created": "2021-05-17 23:45:27", "updated": "2021-05-17 23:45:27"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156957} -{"stream": "events", "data": {"object": "event", "id": "3qvdgBA8", "statistic_id": "VFFb4u", "timestamp": 1621295127, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295127"}, "datetime": "2021-05-17 23:45:27+00:00", "uuid": "f4b6c580-b769-11eb-8001-6e6f83a846c5", "person": {"object": "person", "id": "01F5YBGQ6X21SSWPGRDK9QK97C", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 9", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.9@airbyte.io", "$first_name": "First Name 9", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.9@airbyte.io", "first_name": "First Name 9", "last_name": "Last Name 9", "created": "2021-05-17 23:45:28", "updated": "2021-05-17 23:45:30"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156957} -{"stream": "events", "data": {"object": "event", "id": "3qvdgKvP", "statistic_id": "VFFb4u", "timestamp": 1621295128, "event_name": "Clicked Email", "event_properties": {"$event_id": "1621295128"}, "datetime": "2021-05-17 23:45:28+00:00", "uuid": "f54f5c00-b769-11eb-8001-cf36a74d6baa", "person": {"object": "person", "id": "01F5YBGQ6X21SSWPGRDK9QK97C", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 9", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.9@airbyte.io", "$first_name": "First Name 9", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.9@airbyte.io", "first_name": "First Name 9", "last_name": "Last Name 9", "created": "2021-05-17 23:45:28", "updated": "2021-05-17 23:45:30"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156958} -{"stream": "events", "data": {"object": "event", "id": "3qJt5VCm", "statistic_id": "WKHXf4", "timestamp": 1622071816, "event_name": "Received Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362"}, "datetime": "2021-05-26 23:30:16+00:00", "uuid": "536ef400-be7a-11eb-8001-12f4020b5fc8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156958} -{"stream": "events", "data": {"object": "event", "id": "3qJt5HFE", "statistic_id": "Yy9QKx", "timestamp": 1622071819, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Canonical": "Chrome", "Client Name": "Chrome", "Client OS": "Windows", "Client OS Family": "Windows", "Client Type": "Browser", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622071819"}, "datetime": "2021-05-26 23:30:19+00:00", "uuid": "5538b780-be7a-11eb-8001-27e9092e01af", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156959} -{"stream": "events", "data": {"object": "event", "id": "3qJt683P", "statistic_id": "Yy9QKx", "timestamp": 1622071820, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Canonical": "Chrome", "Client Name": "Chrome", "Client OS": "Windows", "Client OS Family": "Windows", "Client Type": "Browser", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622071820"}, "datetime": "2021-05-26 23:30:20+00:00", "uuid": "55d14e00-be7a-11eb-8001-6619a73066df", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156959} -{"stream": "events", "data": {"object": "event", "id": "3qJt65h2", "statistic_id": "Yy9QKx", "timestamp": 1622071821, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Canonical": "Chrome", "Client Name": "Chrome", "Client OS": "Windows", "Client OS Family": "Windows", "Client Type": "Browser", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622071821"}, "datetime": "2021-05-26 23:30:21+00:00", "uuid": "5669e480-be7a-11eb-8001-fad37bfdb29c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156959} -{"stream": "events", "data": {"object": "event", "id": "3qJtu3D6", "statistic_id": "Yy9QKx", "timestamp": 1622072022, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622072022"}, "datetime": "2021-05-26 23:33:42+00:00", "uuid": "ce380f00-be7a-11eb-8001-56afa08da1b6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156960} -{"stream": "events", "data": {"object": "event", "id": "3qJtumhb", "statistic_id": "Yy9QKx", "timestamp": 1622072025, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622072025"}, "datetime": "2021-05-26 23:33:45+00:00", "uuid": "d001d280-be7a-11eb-8001-7a7bdb0601bd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156960} -{"stream": "events", "data": {"object": "event", "id": "3qJtWgf7", "statistic_id": "Yy9QKx", "timestamp": 1622072549, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622072549"}, "datetime": "2021-05-26 23:42:29+00:00", "uuid": "0855e080-be7c-11eb-8001-670fcba20dce", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156960} -{"stream": "events", "data": {"object": "event", "id": "3qJwPMkp", "statistic_id": "Yy9QKx", "timestamp": 1622074875, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622074875"}, "datetime": "2021-05-27 00:21:15+00:00", "uuid": "72bd4f80-be81-11eb-8001-ca60a71029fd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156961} -{"stream": "events", "data": {"object": "event", "id": "3qJAYPMQ", "statistic_id": "Yy9QKx", "timestamp": 1622078700, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622078700"}, "datetime": "2021-05-27 01:25:00+00:00", "uuid": "5a9dfe00-be8a-11eb-8001-b861808b0f81", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156961} -{"stream": "events", "data": {"object": "event", "id": "3qJEvNR6", "statistic_id": "Yy9QKx", "timestamp": 1622082661, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Canonical": "iOS", "Client Name": "Mobile Safari", "Client OS": "iOS", "Client OS Family": "iOS", "Client Type": "Mobile Browser", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622082661"}, "datetime": "2021-05-27 02:31:01+00:00", "uuid": "938ea080-be93-11eb-8001-80050a92b4db", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156961} -{"stream": "events", "data": {"object": "event", "id": "3qJGMXUW", "statistic_id": "Yy9QKx", "timestamp": 1622085417, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622085417"}, "datetime": "2021-05-27 03:16:57+00:00", "uuid": "fe42da80-be99-11eb-8001-e0e441493e9a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156962} -{"stream": "events", "data": {"object": "event", "id": "3qJGN2Xg", "statistic_id": "Yy9QKx", "timestamp": 1622085419, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622085419"}, "datetime": "2021-05-27 03:16:59+00:00", "uuid": "ff740780-be99-11eb-8001-0d8f2a0963ab", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156962} -{"stream": "events", "data": {"object": "event", "id": "3qJJmZ3L", "statistic_id": "Yy9QKx", "timestamp": 1622088188, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Canonical": "iOS", "Client Name": "Mobile Safari", "Client OS": "iOS", "Client OS Family": "iOS", "Client Type": "Mobile Browser", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622088188"}, "datetime": "2021-05-27 04:03:08+00:00", "uuid": "71e7e600-bea0-11eb-8001-bce1011e71b7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156963} -{"stream": "events", "data": {"object": "event", "id": "3qJMEaV7", "statistic_id": "Yy9QKx", "timestamp": 1622095255, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622095255"}, "datetime": "2021-05-27 06:00:55+00:00", "uuid": "e62a8580-beb0-11eb-8001-8cd16a3917ca", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156963} -{"stream": "events", "data": {"object": "event", "id": "3qJMQBkV", "statistic_id": "Yy9QKx", "timestamp": 1622095413, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622095413"}, "datetime": "2021-05-27 06:03:33+00:00", "uuid": "44576880-beb1-11eb-8001-b44113f1fac1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156963} -{"stream": "events", "data": {"object": "event", "id": "3qJMQCLm", "statistic_id": "Yy9QKx", "timestamp": 1622095416, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622095416"}, "datetime": "2021-05-27 06:03:36+00:00", "uuid": "46212c00-beb1-11eb-8001-3e9cb993aebd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156964} -{"stream": "events", "data": {"object": "event", "id": "3qJMRHy5", "statistic_id": "Yy9QKx", "timestamp": 1622095429, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622095429"}, "datetime": "2021-05-27 06:03:49+00:00", "uuid": "4de0d080-beb1-11eb-8001-9cc09e89e7fa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156964} -{"stream": "events", "data": {"object": "event", "id": "3qJMWkHS", "statistic_id": "Yy9QKx", "timestamp": 1622095496, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622095496"}, "datetime": "2021-05-27 06:04:56+00:00", "uuid": "75d03400-beb1-11eb-8001-f45e141108db", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156964} -{"stream": "events", "data": {"object": "event", "id": "3qJNbhhS", "statistic_id": "Yy9QKx", "timestamp": 1622095850, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622095850"}, "datetime": "2021-05-27 06:10:50+00:00", "uuid": "48d05100-beb2-11eb-8001-966cc7694fa0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156964} -{"stream": "events", "data": {"object": "event", "id": "3qKzGP4g", "statistic_id": "Yy9QKx", "timestamp": 1622133534, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622133534"}, "datetime": "2021-05-27 16:38:54+00:00", "uuid": "063a6300-bf0a-11eb-8001-edd7853643d4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156965} -{"stream": "events", "data": {"object": "event", "id": "3qKzGCEb", "statistic_id": "Yy9QKx", "timestamp": 1622133537, "event_name": "Opened Email", "event_properties": {"$_cohort$message_send_cohort": "1622071813:VFaYVy", "$message": "VFaYVy", "$message_interaction": "VFaYVy", "Campaign Name": "Email Campaign 2021-05-16 19:17:45", "Client Name": "Gmail image proxy", "Client OS": "Linux", "Client OS Family": "Linux", "Client Type": "Other", "Email Domain": "airbyte.io", "Subject": "My Test subject", "$event_id": "VFaYVy:279215082126284570800765648039799968362:1622133537"}, "datetime": "2021-05-27 16:38:57+00:00", "uuid": "08042680-bf0a-11eb-8001-113e4b58428b", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$last_name": "Team", "$title": "", "$organization": "", "$phone_number": "", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": "VFaYVy", "campaign_id": "VFaYVy"}, "emitted_at": 1663367156965} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9WQsPq", "statistic_id": "SPnhc3", "timestamp": 1625641685, "event_name": "Checkout Started", "event_properties": {"Items": ["Gold Bird Necklace"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "21759638962365", "$value": 48.0, "$extra": {"id": 3943741849789, "admin_graphql_api_id": "gid://shopify/Order/3943741849789", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21759638962365, "checkout_token": "aa1fe9e0c5dfb02c2653c38b181cd93d", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": "2021-07-07T00:08:21-07:00", "confirmed": true, "contact_email": "damaris.kautzer@developer-tools.shopifyapps.com", "created_at": "2021-07-07T00:08:05-07:00", "currency": "USD", "current_subtotal_price": "48.00", "current_subtotal_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "48.00", "current_total_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1005", "note": null, "note_attributes": [], "number": 5, "order_number": 1005, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/35e6f378593e8c205bf2971f4df1cec1/authenticate?key=7d9eb2979322ca4f6f67ba9f85f92a3c", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T00:08:05-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "48.00", "subtotal_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "aa1fe9e0c5dfb02c2653c38b181cd93d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "48.00", "total_line_items_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "48.00", "total_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "total_price_usd": "48.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 34, "updated_at": "2021-07-07T00:08:21-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Damaris", "address1": "543 Cloyd Turnpike", "phone": "533.813.6477", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Kautzer", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Damaris Kautzer", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782912701, "email": "damaris.kautzer@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:11-07:00", "updated_at": "2021-07-07T00:08:05-07:00", "first_name": "Damaris", "last_name": "Kautzer", "orders_count": 1, "state": "disabled", "total_spent": "48.00", "last_order_id": 3943741849789, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1005", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:11-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782912701", "default_address": {"id": 6564640751805, "customer_id": 5330782912701, "first_name": "Damaris", "last_name": "Kautzer", "company": null, "address1": "543 Cloyd Turnpike", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "533.813.6477", "name": "Damaris Kautzer", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3504847421629, "admin_graphql_api_id": "gid://shopify/Fulfillment/3504847421629", "created_at": "2021-07-07T00:08:21-07:00", "location_id": 63590301885, "name": "#1005.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T00:08:21-07:00", "line_items": [{"id": 10146885632189, "admin_graphql_api_id": "gid://shopify/LineItem/10146885632189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "48.00", "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10146885632189, "admin_graphql_api_id": "gid://shopify/LineItem/10146885632189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 48.0, "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 48.0, "product": {"id": 6796234588349, "title": "Gold Bird Necklace", "handle": "gold-bird-necklace-2", "vendor": "Lindgren, Lang and Hintz", "tags": "developer-tools-generator", "body_html": "Close up of gold bird pendant necklace.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818_x240.jpg?v=1624410684", "alt": null, "width": 3511, "height": 2344, "position": 1, "variant_ids": [], "id": 29301307048125, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614038717, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Damaris", "address1": "543 Cloyd Turnpike", "phone": "533.813.6477", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Kautzer", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Damaris Kautzer", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/35e6f378593e8c205bf2971f4df1cec1/authenticate?key=7d9eb2979322ca4f6f67ba9f85f92a3c", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/35e6f378593e8c205bf2971f4df1cec1/authenticate?key=7d9eb2979322ca4f6f67ba9f85f92a3c"}}, "datetime": "2021-07-07 07:08:05+00:00", "uuid": "132b7880-def2-11eb-8001-20ec2c1569d4", "person": {"object": "person", "id": "01G4CDT9QA7MN895V8C155QHYR", "$address1": "543 Cloyd Turnpike", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Kautzer", "$title": "", "$organization": "", "$phone_number": "+15338136477", "$email": "damaris.kautzer@developer-tools.shopifyapps.com", "$first_name": "Damaris", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "first_name": "Damaris", "last_name": "Kautzer", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156966} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhte8GY", "statistic_id": "TspjNE", "timestamp": 1625641705, "event_name": "Placed Order", "event_properties": {"Items": ["Gold Bird Necklace"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "3943741849789", "$value": 48.0, "$extra": {"id": 3943741849789, "admin_graphql_api_id": "gid://shopify/Order/3943741849789", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21759638962365, "checkout_token": "aa1fe9e0c5dfb02c2653c38b181cd93d", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": "2021-07-07T00:08:21-07:00", "confirmed": true, "contact_email": "damaris.kautzer@developer-tools.shopifyapps.com", "created_at": "2021-07-07T00:08:05-07:00", "currency": "USD", "current_subtotal_price": "48.00", "current_subtotal_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "48.00", "current_total_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1005", "note": null, "note_attributes": [], "number": 5, "order_number": 1005, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/35e6f378593e8c205bf2971f4df1cec1/authenticate?key=7d9eb2979322ca4f6f67ba9f85f92a3c", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T00:08:05-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "48.00", "subtotal_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "35e6f378593e8c205bf2971f4df1cec1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "48.00", "total_line_items_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "48.00", "total_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "total_price_usd": "48.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 34, "updated_at": "2021-07-07T00:08:21-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Damaris", "address1": "543 Cloyd Turnpike", "phone": "533.813.6477", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Kautzer", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Damaris Kautzer", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782912701, "email": "damaris.kautzer@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:11-07:00", "updated_at": "2021-07-07T00:08:05-07:00", "first_name": "Damaris", "last_name": "Kautzer", "orders_count": 1, "state": "disabled", "total_spent": "48.00", "last_order_id": 3943741849789, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1005", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:11-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782912701", "default_address": {"id": 6564640751805, "customer_id": 5330782912701, "first_name": "Damaris", "last_name": "Kautzer", "company": null, "address1": "543 Cloyd Turnpike", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "533.813.6477", "name": "Damaris Kautzer", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3504847421629, "admin_graphql_api_id": "gid://shopify/Fulfillment/3504847421629", "created_at": "2021-07-07T00:08:21-07:00", "location_id": 63590301885, "name": "#1005.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T00:08:21-07:00", "line_items": [{"id": 10146885632189, "admin_graphql_api_id": "gid://shopify/LineItem/10146885632189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "48.00", "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10146885632189, "admin_graphql_api_id": "gid://shopify/LineItem/10146885632189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 48.0, "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 48.0, "product": {"id": 6796234588349, "title": "Gold Bird Necklace", "handle": "gold-bird-necklace-2", "vendor": "Lindgren, Lang and Hintz", "tags": "developer-tools-generator", "body_html": "Close up of gold bird pendant necklace.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818_x240.jpg?v=1624410684", "alt": null, "width": 3511, "height": 2344, "position": 1, "variant_ids": [], "id": 29301307048125, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614038717, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Damaris", "address1": "543 Cloyd Turnpike", "phone": "533.813.6477", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Kautzer", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Damaris Kautzer", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 07:08:25+00:00", "uuid": "1f173a80-def2-11eb-8001-482457fc02f6", "person": {"object": "person", "id": "01G4CDT9QA7MN895V8C155QHYR", "$address1": "543 Cloyd Turnpike", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Kautzer", "$title": "", "$organization": "", "$phone_number": "+15338136477", "$email": "damaris.kautzer@developer-tools.shopifyapps.com", "$first_name": "Damaris", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "first_name": "Damaris", "last_name": "Kautzer", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156967} -{"stream": "events", "data": {"object": "event", "id": "3mjAFdyS9bL", "statistic_id": "RDXsib", "timestamp": 1625641715, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234588349, "Name": "Gold Bird Necklace", "Variant Name": "Frozen", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Lindgren, Lang and Hintz", "Variant Option: Title": "Frozen", "Quantity": 1, "$event_id": "3943741849789:10146885632189:0", "$value": 48.0}, "datetime": "2021-07-07 07:08:35+00:00", "uuid": "250d1b80-def2-11eb-8001-8fd39129acac", "person": {"object": "person", "id": "01G4CDT9QA7MN895V8C155QHYR", "$address1": "543 Cloyd Turnpike", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Kautzer", "$title": "", "$organization": "", "$phone_number": "+15338136477", "$email": "damaris.kautzer@developer-tools.shopifyapps.com", "$first_name": "Damaris", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "first_name": "Damaris", "last_name": "Kautzer", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156969} -{"stream": "events", "data": {"object": "event", "id": "3mjAZ6e6Xsg", "statistic_id": "X3f6PC", "timestamp": 1625641751, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Gold Bird Necklace"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3943741849789", "$value": 48.0, "$extra": {"id": 3943741849789, "admin_graphql_api_id": "gid://shopify/Order/3943741849789", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21759638962365, "checkout_token": "aa1fe9e0c5dfb02c2653c38b181cd93d", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": "2021-07-07T00:08:21-07:00", "confirmed": true, "contact_email": "damaris.kautzer@developer-tools.shopifyapps.com", "created_at": "2021-07-07T00:08:05-07:00", "currency": "USD", "current_subtotal_price": "48.00", "current_subtotal_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "48.00", "current_total_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1005", "note": null, "note_attributes": [], "number": 5, "order_number": 1005, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/35e6f378593e8c205bf2971f4df1cec1/authenticate?key=7d9eb2979322ca4f6f67ba9f85f92a3c", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T00:08:05-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "48.00", "subtotal_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "35e6f378593e8c205bf2971f4df1cec1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "48.00", "total_line_items_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "48.00", "total_price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "total_price_usd": "48.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 34, "updated_at": "2021-07-07T00:08:21-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Damaris", "address1": "543 Cloyd Turnpike", "phone": "533.813.6477", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Kautzer", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Damaris Kautzer", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782912701, "email": "damaris.kautzer@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:11-07:00", "updated_at": "2021-07-07T00:08:05-07:00", "first_name": "Damaris", "last_name": "Kautzer", "orders_count": 1, "state": "disabled", "total_spent": "48.00", "last_order_id": 3943741849789, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1005", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:11-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782912701", "default_address": {"id": 6564640751805, "customer_id": 5330782912701, "first_name": "Damaris", "last_name": "Kautzer", "company": null, "address1": "543 Cloyd Turnpike", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "533.813.6477", "name": "Damaris Kautzer", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3504847421629, "admin_graphql_api_id": "gid://shopify/Fulfillment/3504847421629", "created_at": "2021-07-07T00:08:21-07:00", "location_id": 63590301885, "name": "#1005.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T00:08:21-07:00", "line_items": [{"id": 10146885632189, "admin_graphql_api_id": "gid://shopify/LineItem/10146885632189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "48.00", "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10146885632189, "admin_graphql_api_id": "gid://shopify/LineItem/10146885632189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 48.0, "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 48.0, "product": {"id": 6796234588349, "title": "Gold Bird Necklace", "handle": "gold-bird-necklace-2", "vendor": "Lindgren, Lang and Hintz", "tags": "developer-tools-generator", "body_html": "Close up of gold bird pendant necklace.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818_x240.jpg?v=1624410684", "alt": null, "width": 3511, "height": 2344, "position": 1, "variant_ids": [], "id": 29301307048125, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614038717, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Damaris", "address1": "543 Cloyd Turnpike", "phone": "533.813.6477", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Kautzer", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Damaris Kautzer", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 07:09:11+00:00", "uuid": "3a824580-def2-11eb-8001-e13124f40a83", "person": {"object": "person", "id": "01G4CDT9QA7MN895V8C155QHYR", "$address1": "543 Cloyd Turnpike", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Kautzer", "$title": "", "$organization": "", "$phone_number": "+15338136477", "$email": "damaris.kautzer@developer-tools.shopifyapps.com", "$first_name": "Damaris", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "damaris.kautzer@developer-tools.shopifyapps.com", "first_name": "Damaris", "last_name": "Kautzer", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156969} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9WQBNf", "statistic_id": "SPnhc3", "timestamp": 1625663098, "event_name": "Checkout Started", "event_properties": {"Items": ["Waterproof iPhone Speaker", "Gold Bird Necklace", "Blue And White Skate Shoes", "Blue And White Skate Shoes", "Blue And White Skate Shoes", "Red Bralette", "Strappy Black Dress Back"], "Collections": ["Test Collection"], "Item Count": 7, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "21763363111101", "$value": 375.0, "$extra": {"id": 3944044101821, "admin_graphql_api_id": "gid://shopify/Order/3944044101821", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21763363111101, "checkout_token": "cf03b330e25496162e895e0cbd5abb64", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "kameron.brown@developer-tools.shopifyapps.com", "created_at": "2021-07-07T06:04:58-07:00", "currency": "USD", "current_subtotal_price": "375.00", "current_subtotal_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "375.00", "current_total_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "kameron.brown@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1006", "note": null, "note_attributes": [], "number": 6, "order_number": 1006, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e5250223912687b8212b19bffd684665/authenticate?key=97e3ffee2e247384f434a8d88372053d", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T06:04:58-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "375.00", "subtotal_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "cf03b330e25496162e895e0cbd5abb64", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "375.00", "total_line_items_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "375.00", "total_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "total_price_usd": "375.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 50, "updated_at": "2021-07-07T06:05:03-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Kameron", "address1": "7734 Alexandria Wall", "phone": "1-735-105-4378 x68232", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Brown", "address2": null, "company": null, "latitude": 38.8048355, "longitude": -77.0469214, "name": "Kameron Brown", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782781629, "email": "kameron.brown@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:11-07:00", "updated_at": "2021-07-07T06:05:01-07:00", "first_name": "Kameron", "last_name": "Brown", "orders_count": 1, "state": "disabled", "total_spent": "375.00", "last_order_id": 3944044101821, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1006", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:11-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782781629", "default_address": {"id": 6564640620733, "customer_id": 5330782781629, "first_name": "Kameron", "last_name": "Brown", "company": null, "address1": "7734 Alexandria Wall", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "1-735-105-4378 x68232", "name": "Kameron Brown", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10147490791613, "admin_graphql_api_id": "gid://shopify/LineItem/10147490791613", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Waterproof iPhone Speaker - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 64.0, "price_set": {"shop_money": {"amount": "64.00", "currency_code": "USD"}, "presentment_money": {"amount": "64.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796825198781, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Waterproof iPhone Speaker", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40091751448765, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Lemke and Sons", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 64.0, "product": {"id": 6796825198781, "title": "Waterproof iPhone Speaker", "handle": "waterproof-iphone-speaker", "vendor": "Lemke and Sons", "tags": "developer-tools-generator", "body_html": "Pink waterproof iPhone speaker.", "product_type": "Beauty", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/waterproof-iphone-speaker.jpg?v=1624428281", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/waterproof-iphone-speaker_x240.jpg?v=1624428281", "alt": null, "width": 5518, "height": 3679, "position": 1, "variant_ids": [], "id": 29303489560765, "created_at": "2021-06-22T23:04:41-07:00", "updated_at": "2021-06-22T23:04:41-07:00"}], "variant": {"sku": 40091751448765, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}, {"id": 10147490824381, "admin_graphql_api_id": "gid://shopify/LineItem/10147490824381", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 48.0, "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 48.0, "product": {"id": 6796234588349, "title": "Gold Bird Necklace", "handle": "gold-bird-necklace-2", "vendor": "Lindgren, Lang and Hintz", "tags": "developer-tools-generator", "body_html": "Close up of gold bird pendant necklace.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818_x240.jpg?v=1624410684", "alt": null, "width": 3511, "height": 2344, "position": 1, "variant_ids": [], "id": 29301307048125, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614038717, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}, {"id": 10147490857149, "admin_graphql_api_id": "gid://shopify/LineItem/10147490857149", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 39.0, "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 39.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614005949, "title": "orchid", "options": {"Title": "orchid"}, "images": []}, "variant_options": {"Title": "orchid"}}}, {"id": 10147490889917, "admin_graphql_api_id": "gid://shopify/LineItem/10147490889917", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - Fresh", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 94.0, "price_set": {"shop_money": {"amount": "94.00", "currency_code": "USD"}, "presentment_money": {"amount": "94.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614071485, "variant_inventory_management": "shopify", "variant_title": "Fresh", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 94.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614071485, "title": "Fresh", "options": {"Title": "Fresh"}, "images": []}, "variant_options": {"Title": "Fresh"}}}, {"id": 10147490922685, "admin_graphql_api_id": "gid://shopify/LineItem/10147490922685", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 16, "name": "Blue And White Skate Shoes - lavender", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 72.0, "price_set": {"shop_money": {"amount": "72.00", "currency_code": "USD"}, "presentment_money": {"amount": "72.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614104253, "variant_inventory_management": "shopify", "variant_title": "lavender", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 72.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614104253, "title": "lavender", "options": {"Title": "lavender"}, "images": []}, "variant_options": {"Title": "lavender"}}}, {"id": 10147490955453, "admin_graphql_api_id": "gid://shopify/LineItem/10147490955453", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Red Bralette - silver", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 23.0, "price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234522813, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Red Bralette", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613907645, "variant_inventory_management": "shopify", "variant_title": "silver", "vendor": "Orn, D'Amore and Kutch", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 23.0, "product": {"id": 6796234522813, "title": "Red Bralette", "handle": "red-bralette", "vendor": "Orn, D'Amore and Kutch", "tags": "developer-tools-generator", "body_html": "A red bralette on a hanger. Bright and detailed lace undergarment for ladies.", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-bralette.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-bralette_x240.jpg?v=1624410684", "alt": null, "width": 5585, "height": 3723, "position": 1, "variant_ids": [], "id": 29301306982589, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090613907645, "title": "silver", "options": {"Title": "silver"}, "images": []}, "variant_options": {"Title": "silver"}}}, {"id": 10147490988221, "admin_graphql_api_id": "gid://shopify/LineItem/10147490988221", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Strappy Black Dress Back - fuchsia", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234490045, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Strappy Black Dress Back", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613842109, "variant_inventory_management": "shopify", "variant_title": "fuchsia", "vendor": "Hoeger - Mann", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 35.0, "product": {"id": 6796234490045, "title": "Strappy Black Dress Back", "handle": "strappy-black-dress-back-3", "vendor": "Hoeger - Mann", "tags": "developer-tools-generator", "body_html": "This shot of the back of a strappy black cocktail dress leaves little to the imagination...all beautiful backs need apply to this dress.", "product_type": "Music", "properties": {}, "images": [], "variant": {"sku": 40090613842109, "title": "fuchsia", "options": {"Title": "fuchsia"}, "images": []}, "variant_options": {"Title": "fuchsia"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Kameron", "address1": "7734 Alexandria Wall", "phone": "1-735-105-4378 x68232", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Brown", "address2": null, "company": null, "latitude": 38.8048355, "longitude": -77.0469214, "name": "Kameron Brown", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e5250223912687b8212b19bffd684665/authenticate?key=97e3ffee2e247384f434a8d88372053d", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e5250223912687b8212b19bffd684665/authenticate?key=97e3ffee2e247384f434a8d88372053d"}}, "datetime": "2021-07-07 13:04:58+00:00", "uuid": "ee4ff900-df23-11eb-8001-39b23236b5b6", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156970} -{"stream": "events", "data": {"object": "event", "id": "3mjz67SWxGM", "statistic_id": "TspjNE", "timestamp": 1625663118, "event_name": "Placed Order", "event_properties": {"Items": ["Waterproof iPhone Speaker", "Gold Bird Necklace", "Blue And White Skate Shoes", "Blue And White Skate Shoes", "Blue And White Skate Shoes", "Red Bralette", "Strappy Black Dress Back"], "Collections": ["Test Collection"], "Item Count": 7, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "3944044101821", "$value": 375.0, "$extra": {"id": 3944044101821, "admin_graphql_api_id": "gid://shopify/Order/3944044101821", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21763363111101, "checkout_token": "cf03b330e25496162e895e0cbd5abb64", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "kameron.brown@developer-tools.shopifyapps.com", "created_at": "2021-07-07T06:04:58-07:00", "currency": "USD", "current_subtotal_price": "375.00", "current_subtotal_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "375.00", "current_total_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "kameron.brown@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1006", "note": null, "note_attributes": [], "number": 6, "order_number": 1006, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e5250223912687b8212b19bffd684665/authenticate?key=97e3ffee2e247384f434a8d88372053d", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T06:04:58-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "375.00", "subtotal_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "e5250223912687b8212b19bffd684665", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "375.00", "total_line_items_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "375.00", "total_price_set": {"shop_money": {"amount": "375.00", "currency_code": "USD"}, "presentment_money": {"amount": "375.00", "currency_code": "USD"}}, "total_price_usd": "375.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 50, "updated_at": "2021-07-07T06:05:03-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Kameron", "address1": "7734 Alexandria Wall", "phone": "1-735-105-4378 x68232", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Brown", "address2": null, "company": null, "latitude": 38.8048355, "longitude": -77.0469214, "name": "Kameron Brown", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782781629, "email": "kameron.brown@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:11-07:00", "updated_at": "2021-07-07T06:05:01-07:00", "first_name": "Kameron", "last_name": "Brown", "orders_count": 1, "state": "disabled", "total_spent": "375.00", "last_order_id": 3944044101821, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1006", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:11-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782781629", "default_address": {"id": 6564640620733, "customer_id": 5330782781629, "first_name": "Kameron", "last_name": "Brown", "company": null, "address1": "7734 Alexandria Wall", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "1-735-105-4378 x68232", "name": "Kameron Brown", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10147490791613, "admin_graphql_api_id": "gid://shopify/LineItem/10147490791613", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Waterproof iPhone Speaker - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 64.0, "price_set": {"shop_money": {"amount": "64.00", "currency_code": "USD"}, "presentment_money": {"amount": "64.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796825198781, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Waterproof iPhone Speaker", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40091751448765, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Lemke and Sons", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 64.0, "product": {"id": 6796825198781, "title": "Waterproof iPhone Speaker", "handle": "waterproof-iphone-speaker", "vendor": "Lemke and Sons", "tags": "developer-tools-generator", "body_html": "Pink waterproof iPhone speaker.", "product_type": "Beauty", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/waterproof-iphone-speaker.jpg?v=1624428281", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/waterproof-iphone-speaker_x240.jpg?v=1624428281", "alt": null, "width": 5518, "height": 3679, "position": 1, "variant_ids": [], "id": 29303489560765, "created_at": "2021-06-22T23:04:41-07:00", "updated_at": "2021-06-22T23:04:41-07:00"}], "variant": {"sku": 40091751448765, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}, {"id": 10147490824381, "admin_graphql_api_id": "gid://shopify/LineItem/10147490824381", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 34, "name": "Gold Bird Necklace - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 48.0, "price_set": {"shop_money": {"amount": "48.00", "currency_code": "USD"}, "presentment_money": {"amount": "48.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234588349, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Gold Bird Necklace", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614038717, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Lindgren, Lang and Hintz", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 48.0, "product": {"id": 6796234588349, "title": "Gold Bird Necklace", "handle": "gold-bird-necklace-2", "vendor": "Lindgren, Lang and Hintz", "tags": "developer-tools-generator", "body_html": "Close up of gold bird pendant necklace.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/gold-bird-necklace_d039bfce-d8e8-4004-9f8c-e6ddcda6d818_x240.jpg?v=1624410684", "alt": null, "width": 3511, "height": 2344, "position": 1, "variant_ids": [], "id": 29301307048125, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614038717, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}, {"id": 10147490857149, "admin_graphql_api_id": "gid://shopify/LineItem/10147490857149", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 39.0, "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 39.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614005949, "title": "orchid", "options": {"Title": "orchid"}, "images": []}, "variant_options": {"Title": "orchid"}}}, {"id": 10147490889917, "admin_graphql_api_id": "gid://shopify/LineItem/10147490889917", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - Fresh", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 94.0, "price_set": {"shop_money": {"amount": "94.00", "currency_code": "USD"}, "presentment_money": {"amount": "94.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614071485, "variant_inventory_management": "shopify", "variant_title": "Fresh", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 94.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614071485, "title": "Fresh", "options": {"Title": "Fresh"}, "images": []}, "variant_options": {"Title": "Fresh"}}}, {"id": 10147490922685, "admin_graphql_api_id": "gid://shopify/LineItem/10147490922685", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 16, "name": "Blue And White Skate Shoes - lavender", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 72.0, "price_set": {"shop_money": {"amount": "72.00", "currency_code": "USD"}, "presentment_money": {"amount": "72.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614104253, "variant_inventory_management": "shopify", "variant_title": "lavender", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 72.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614104253, "title": "lavender", "options": {"Title": "lavender"}, "images": []}, "variant_options": {"Title": "lavender"}}}, {"id": 10147490955453, "admin_graphql_api_id": "gid://shopify/LineItem/10147490955453", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Red Bralette - silver", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 23.0, "price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234522813, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Red Bralette", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613907645, "variant_inventory_management": "shopify", "variant_title": "silver", "vendor": "Orn, D'Amore and Kutch", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 23.0, "product": {"id": 6796234522813, "title": "Red Bralette", "handle": "red-bralette", "vendor": "Orn, D'Amore and Kutch", "tags": "developer-tools-generator", "body_html": "A red bralette on a hanger. Bright and detailed lace undergarment for ladies.", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-bralette.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-bralette_x240.jpg?v=1624410684", "alt": null, "width": 5585, "height": 3723, "position": 1, "variant_ids": [], "id": 29301306982589, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090613907645, "title": "silver", "options": {"Title": "silver"}, "images": []}, "variant_options": {"Title": "silver"}}}, {"id": 10147490988221, "admin_graphql_api_id": "gid://shopify/LineItem/10147490988221", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Strappy Black Dress Back - fuchsia", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234490045, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Strappy Black Dress Back", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613842109, "variant_inventory_management": "shopify", "variant_title": "fuchsia", "vendor": "Hoeger - Mann", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 35.0, "product": {"id": 6796234490045, "title": "Strappy Black Dress Back", "handle": "strappy-black-dress-back-3", "vendor": "Hoeger - Mann", "tags": "developer-tools-generator", "body_html": "This shot of the back of a strappy black cocktail dress leaves little to the imagination...all beautiful backs need apply to this dress.", "product_type": "Music", "properties": {}, "images": [], "variant": {"sku": 40090613842109, "title": "fuchsia", "options": {"Title": "fuchsia"}, "images": []}, "variant_options": {"Title": "fuchsia"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Kameron", "address1": "7734 Alexandria Wall", "phone": "1-735-105-4378 x68232", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Brown", "address2": null, "company": null, "latitude": 38.8048355, "longitude": -77.0469214, "name": "Kameron Brown", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 13:05:18+00:00", "uuid": "fa3bbb00-df23-11eb-8001-3ab332a557f9", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156972} -{"stream": "events", "data": {"object": "event", "id": "3mjs5Yk5MtG", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234555581, "Name": "Blue And White Skate Shoes", "Variant Name": "Fresh", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Ullrich, Kris and Dicki", "Variant Option: Title": "Fresh", "Quantity": 1, "$event_id": "3944044101821:10147490889917:0", "$value": 94.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-f0cea7975de2", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156974} -{"stream": "events", "data": {"object": "event", "id": "3mjs64esgXz", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796825198781, "Name": "Waterproof iPhone Speaker", "Variant Name": "Metal", "SKU": "", "Collections": ["Test Collection"], "Tags": ["developer-tools-generator"], "Vendor": "Lemke and Sons", "Variant Option: Title": "Metal", "Quantity": 1, "$event_id": "3944044101821:10147490791613:0", "$value": 64.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-fb364bc27cfa", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156974} -{"stream": "events", "data": {"object": "event", "id": "3mjvTV2pEEj", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234555581, "Name": "Blue And White Skate Shoes", "Variant Name": "orchid", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Ullrich, Kris and Dicki", "Variant Option: Title": "orchid", "Quantity": 1, "$event_id": "3944044101821:10147490857149:0", "$value": 39.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-81f534de75d8", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156974} -{"stream": "events", "data": {"object": "event", "id": "3mjwRrDRHep", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234490045, "Name": "Strappy Black Dress Back", "Variant Name": "fuchsia", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Hoeger - Mann", "Variant Option: Title": "fuchsia", "Quantity": 1, "$event_id": "3944044101821:10147490988221:0", "$value": 35.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-3c4d22be0e92", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156975} -{"stream": "events", "data": {"object": "event", "id": "3mjz64tSDyW", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234555581, "Name": "Blue And White Skate Shoes", "Variant Name": "lavender", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Ullrich, Kris and Dicki", "Variant Option: Title": "lavender", "Quantity": 1, "$event_id": "3944044101821:10147490922685:0", "$value": 72.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-f20dd0b17cb3", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156975} -{"stream": "events", "data": {"object": "event", "id": "3mjz6dJuPTJ", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234588349, "Name": "Gold Bird Necklace", "Variant Name": "Frozen", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Lindgren, Lang and Hintz", "Variant Option: Title": "Frozen", "Quantity": 1, "$event_id": "3944044101821:10147490824381:0", "$value": 48.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-e25627c6aef6", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156975} -{"stream": "events", "data": {"object": "event", "id": "3mjF8KhD7be", "statistic_id": "RDXsib", "timestamp": 1625663128, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234522813, "Name": "Red Bralette", "Variant Name": "silver", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Orn, D'Amore and Kutch", "Variant Option: Title": "silver", "Quantity": 1, "$event_id": "3944044101821:10147490955453:0", "$value": 23.0}, "datetime": "2021-07-07 13:05:28+00:00", "uuid": "00319c00-df24-11eb-8001-b9d664ff67f1", "person": {"object": "person", "id": "01G4CDT9YVNWD1C4GFA9YG9190", "$address1": "7734 Alexandria Wall", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Brown", "$title": "", "$organization": "", "$phone_number": "+17351054378", "$email": "kameron.brown@developer-tools.shopifyapps.com", "$first_name": "Kameron", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "kameron.brown@developer-tools.shopifyapps.com", "first_name": "Kameron", "last_name": "Brown", "created": "2022-05-31 06:45:56", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156975} -{"stream": "events", "data": {"object": "event", "id": "3mjBWBQacLV", "statistic_id": "SPnhc3", "timestamp": 1625666505, "event_name": "Checkout Started", "event_properties": {"Items": ["8 Ounce Soy Candle", "8 Ounce Soy Candle", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "21763987407037", "$value": 275.0, "$extra": {"id": 3944114815165, "admin_graphql_api_id": "gid://shopify/Order/3944114815165", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21763987407037, "checkout_token": "39a32a6bc480025d4755b81abd7efa1e", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "colten.grimes@developer-tools.shopifyapps.com", "created_at": "2021-07-07T07:01:45-07:00", "currency": "USD", "current_subtotal_price": "275.00", "current_subtotal_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "275.00", "current_total_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "colten.grimes@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1007", "note": null, "note_attributes": [], "number": 7, "order_number": 1007, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c00357a6a4f78e38cdc1b41ec2c0582e/authenticate?key=423fe0c96671be36e7bedf1fd0c41e1e", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T07:01:45-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "275.00", "subtotal_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "39a32a6bc480025d4755b81abd7efa1e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "275.00", "total_line_items_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "275.00", "total_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "total_price_usd": "275.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 190, "updated_at": "2022-02-22T02:12:28-08:00", "user_id": 74861019325, "billing_address": {"first_name": "Colten", "address1": "3947 Kulas Lakes", "phone": "176.228.8541 x5165", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Grimes", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Colten Grimes", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782978237, "email": "colten.grimes@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:12-07:00", "updated_at": "2021-07-07T07:01:46-07:00", "first_name": "Colten", "last_name": "Grimes", "orders_count": 1, "state": "disabled", "total_spent": "275.00", "last_order_id": 3944114815165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1007", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:12-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782978237", "default_address": {"id": 6564640817341, "customer_id": 5330782978237, "first_name": "Colten", "last_name": "Grimes", "company": null, "address1": "3947 Kulas Lakes", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "176.228.8541 x5165", "name": "Colten Grimes", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10147623895229, "admin_graphql_api_id": "gid://shopify/LineItem/10147623895229", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}, {"id": 10147623927997, "admin_graphql_api_id": "gid://shopify/LineItem/10147623927997", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "8 Ounce Soy Candle - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 60.0, "price_set": {"shop_money": {"amount": "60.00", "currency_code": "USD"}, "presentment_money": {"amount": "60.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603978941, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 60.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603978941, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}, {"id": 10147623960765, "admin_graphql_api_id": "gid://shopify/LineItem/10147623960765", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Colten", "address1": "3947 Kulas Lakes", "phone": "176.228.8541 x5165", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Grimes", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Colten Grimes", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c00357a6a4f78e38cdc1b41ec2c0582e/authenticate?key=423fe0c96671be36e7bedf1fd0c41e1e", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c00357a6a4f78e38cdc1b41ec2c0582e/authenticate?key=423fe0c96671be36e7bedf1fd0c41e1e"}}, "datetime": "2021-07-07 14:01:45+00:00", "uuid": "dd0aea80-df2b-11eb-8001-629068b846d6", "person": {"object": "person", "id": "01G4CDT9KMMG7532C1JG8NQ3R3", "$address1": "3947 Kulas Lakes", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Grimes", "$title": "", "$organization": "", "$phone_number": "+11762288541", "$email": "colten.grimes@developer-tools.shopifyapps.com", "$first_name": "Colten", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "colten.grimes@developer-tools.shopifyapps.com", "first_name": "Colten", "last_name": "Grimes", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156976} -{"stream": "events", "data": {"object": "event", "id": "3mjvTPE9FwR", "statistic_id": "TspjNE", "timestamp": 1625666525, "event_name": "Placed Order", "event_properties": {"Items": ["8 Ounce Soy Candle", "8 Ounce Soy Candle", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "3944114815165", "$value": 275.0, "$extra": {"id": 3944114815165, "admin_graphql_api_id": "gid://shopify/Order/3944114815165", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21763987407037, "checkout_token": "39a32a6bc480025d4755b81abd7efa1e", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "colten.grimes@developer-tools.shopifyapps.com", "created_at": "2021-07-07T07:01:45-07:00", "currency": "USD", "current_subtotal_price": "275.00", "current_subtotal_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "275.00", "current_total_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "colten.grimes@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1007", "note": null, "note_attributes": [], "number": 7, "order_number": 1007, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c00357a6a4f78e38cdc1b41ec2c0582e/authenticate?key=423fe0c96671be36e7bedf1fd0c41e1e", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T07:01:45-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "275.00", "subtotal_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "c00357a6a4f78e38cdc1b41ec2c0582e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "275.00", "total_line_items_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "275.00", "total_price_set": {"shop_money": {"amount": "275.00", "currency_code": "USD"}, "presentment_money": {"amount": "275.00", "currency_code": "USD"}}, "total_price_usd": "275.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 190, "updated_at": "2022-02-22T02:12:28-08:00", "user_id": 74861019325, "billing_address": {"first_name": "Colten", "address1": "3947 Kulas Lakes", "phone": "176.228.8541 x5165", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Grimes", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Colten Grimes", "country_code": "CA", "province_code": null}, "customer": {"id": 5330782978237, "email": "colten.grimes@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:12-07:00", "updated_at": "2021-07-07T07:01:46-07:00", "first_name": "Colten", "last_name": "Grimes", "orders_count": 1, "state": "disabled", "total_spent": "275.00", "last_order_id": 3944114815165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1007", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:12-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782978237", "default_address": {"id": 6564640817341, "customer_id": 5330782978237, "first_name": "Colten", "last_name": "Grimes", "company": null, "address1": "3947 Kulas Lakes", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "176.228.8541 x5165", "name": "Colten Grimes", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10147623895229, "admin_graphql_api_id": "gid://shopify/LineItem/10147623895229", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}, {"id": 10147623927997, "admin_graphql_api_id": "gid://shopify/LineItem/10147623927997", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "8 Ounce Soy Candle - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 60.0, "price_set": {"shop_money": {"amount": "60.00", "currency_code": "USD"}, "presentment_money": {"amount": "60.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603978941, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 60.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603978941, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}, {"id": 10147623960765, "admin_graphql_api_id": "gid://shopify/LineItem/10147623960765", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Colten", "address1": "3947 Kulas Lakes", "phone": "176.228.8541 x5165", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Grimes", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Colten Grimes", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 14:02:05+00:00", "uuid": "e8f6ac80-df2b-11eb-8001-121d3ce4acfb", "person": {"object": "person", "id": "01G4CDT9KMMG7532C1JG8NQ3R3", "$address1": "3947 Kulas Lakes", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Grimes", "$title": "", "$organization": "", "$phone_number": "+11762288541", "$email": "colten.grimes@developer-tools.shopifyapps.com", "$first_name": "Colten", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "colten.grimes@developer-tools.shopifyapps.com", "first_name": "Colten", "last_name": "Grimes", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156978} -{"stream": "events", "data": {"object": "event", "id": "3mjvTWXA2et", "statistic_id": "RDXsib", "timestamp": 1625666535, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "Wooden", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "Wooden", "Quantity": 1, "$event_id": "3944114815165:10147623960765:0", "$value": 102.0}, "datetime": "2021-07-07 14:02:15+00:00", "uuid": "eeec8d80-df2b-11eb-8001-758d9cf7f4f7", "person": {"object": "person", "id": "01G4CDT9KMMG7532C1JG8NQ3R3", "$address1": "3947 Kulas Lakes", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Grimes", "$title": "", "$organization": "", "$phone_number": "+11762288541", "$email": "colten.grimes@developer-tools.shopifyapps.com", "$first_name": "Colten", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "colten.grimes@developer-tools.shopifyapps.com", "first_name": "Colten", "last_name": "Grimes", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156979} -{"stream": "events", "data": {"object": "event", "id": "3mjwdDpBZtc", "statistic_id": "RDXsib", "timestamp": 1625666535, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "purple", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "purple", "Quantity": 1, "$event_id": "3944114815165:10147623895229:0", "$value": 113.0}, "datetime": "2021-07-07 14:02:15+00:00", "uuid": "eeec8d80-df2b-11eb-8001-13859eb3c2b4", "person": {"object": "person", "id": "01G4CDT9KMMG7532C1JG8NQ3R3", "$address1": "3947 Kulas Lakes", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Grimes", "$title": "", "$organization": "", "$phone_number": "+11762288541", "$email": "colten.grimes@developer-tools.shopifyapps.com", "$first_name": "Colten", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "colten.grimes@developer-tools.shopifyapps.com", "first_name": "Colten", "last_name": "Grimes", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156979} -{"stream": "events", "data": {"object": "event", "id": "3mjDRB9Umzv", "statistic_id": "RDXsib", "timestamp": 1625666535, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "Frozen", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "Frozen", "Quantity": 1, "$event_id": "3944114815165:10147623927997:0", "$value": 60.0}, "datetime": "2021-07-07 14:02:15+00:00", "uuid": "eeec8d80-df2b-11eb-8001-6b714440058c", "person": {"object": "person", "id": "01G4CDT9KMMG7532C1JG8NQ3R3", "$address1": "3947 Kulas Lakes", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Grimes", "$title": "", "$organization": "", "$phone_number": "+11762288541", "$email": "colten.grimes@developer-tools.shopifyapps.com", "$first_name": "Colten", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "colten.grimes@developer-tools.shopifyapps.com", "first_name": "Colten", "last_name": "Grimes", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156979} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFMza", "statistic_id": "SPnhc3", "timestamp": 1625667724, "event_name": "Checkout Started", "event_properties": {"Items": ["8 Ounce Soy Candle", "8 Ounce Soy Candle", "8 Ounce Soy Candle", "Back Of Polka Dog Bandana"], "Collections": ["Test Collection"], "Item Count": 4, "tags": [], "Discount Codes": ["Custom discount"], "Total Discounts": "15.80", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "21764201578685", "$value": 300.2, "$extra": {"id": 3944141750461, "admin_graphql_api_id": "gid://shopify/Order/3944141750461", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21764201578685, "checkout_token": "114498a5361ca096b973a4953f3b659b", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "vernice.shanahan@developer-tools.shopifyapps.com", "created_at": "2021-07-07T07:22:04-07:00", "currency": "USD", "current_subtotal_price": "300.20", "current_subtotal_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "current_total_discounts": "15.80", "current_total_discounts_set": {"shop_money": {"amount": "15.80", "currency_code": "USD"}, "presentment_money": {"amount": "15.80", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "300.20", "current_total_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [{"code": "Custom discount", "amount": "15.80", "type": "percentage"}], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1008", "note": null, "note_attributes": [], "number": 8, "order_number": 1008, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/78b8b025553c7cb17f5df60f35269d6a/authenticate?key=4e67999c4d621ccfac00a6d3f9730c3d", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T07:22:04-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "300.20", "subtotal_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "114498a5361ca096b973a4953f3b659b", "total_discounts": "15.80", "total_discounts_set": {"shop_money": {"amount": "15.80", "currency_code": "USD"}, "presentment_money": {"amount": "15.80", "currency_code": "USD"}}, "total_line_items_price": "316.00", "total_line_items_price_set": {"shop_money": {"amount": "316.00", "currency_code": "USD"}, "presentment_money": {"amount": "316.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "300.20", "total_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "total_price_usd": "300.20", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 372, "updated_at": "2021-07-19T06:50:48-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Vernice", "address1": "177 Bartell Bridge", "phone": "(471) 633-4882", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Shanahan", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Vernice Shanahan", "country_code": "US", "province_code": null}, "customer": {"id": 5330781601981, "email": "vernice.shanahan@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:06-07:00", "updated_at": "2021-07-07T07:22:05-07:00", "first_name": "Vernice", "last_name": "Shanahan", "orders_count": 1, "state": "disabled", "total_spent": "300.20", "last_order_id": 3944141750461, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1008", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:06-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330781601981", "default_address": {"id": 6564639441085, "customer_id": 5330781601981, "first_name": "Vernice", "last_name": "Shanahan", "company": null, "address1": "177 Bartell Bridge", "address2": null, "city": null, "province": null, "country": "United States", "zip": null, "phone": "(471) 633-4882", "name": "Vernice Shanahan", "province_code": null, "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "manual", "value": "5.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "Custom discount", "description": "Custom discount"}], "fulfillments": [], "line_items": [{"id": 10147671081149, "admin_graphql_api_id": "gid://shopify/LineItem/10147671081149", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "5.65", "amount_set": {"shop_money": {"amount": "5.65", "currency_code": "USD"}, "presentment_money": {"amount": "5.65", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}, {"id": 10147671113917, "admin_graphql_api_id": "gid://shopify/LineItem/10147671113917", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "8 Ounce Soy Candle - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 60.0, "price_set": {"shop_money": {"amount": "60.00", "currency_code": "USD"}, "presentment_money": {"amount": "60.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603978941, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.00", "amount_set": {"shop_money": {"amount": "3.00", "currency_code": "USD"}, "presentment_money": {"amount": "3.00", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 60.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603978941, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}, {"id": 10147671146685, "admin_graphql_api_id": "gid://shopify/LineItem/10147671146685", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "5.10", "amount_set": {"shop_money": {"amount": "5.10", "currency_code": "USD"}, "presentment_money": {"amount": "5.10", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 10147671179453, "admin_graphql_api_id": "gid://shopify/LineItem/10147671179453", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 182, "name": "Back Of Polka Dog Bandana - fuchsia", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 41.0, "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234391741, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Back Of Polka Dog Bandana", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613776573, "variant_inventory_management": "shopify", "variant_title": "fuchsia", "vendor": "Lueilwitz - D'Amore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.05", "amount_set": {"shop_money": {"amount": "2.05", "currency_code": "USD"}, "presentment_money": {"amount": "2.05", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 41.0, "product": {"id": 6796234391741, "title": "Back Of Polka Dog Bandana", "handle": "back-of-polka-dog-bandana", "vendor": "Lueilwitz - D'Amore", "tags": "developer-tools-generator", "body_html": "Back Of Polka Dog Bandana", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090613776573, "title": "fuchsia", "options": {"Title": "fuchsia"}, "images": []}, "variant_options": {"Title": "fuchsia"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Vernice", "address1": "177 Bartell Bridge", "phone": "(471) 633-4882", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Shanahan", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Vernice Shanahan", "country_code": "US", "province_code": null}, "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/78b8b025553c7cb17f5df60f35269d6a/authenticate?key=4e67999c4d621ccfac00a6d3f9730c3d", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/78b8b025553c7cb17f5df60f35269d6a/authenticate?key=4e67999c4d621ccfac00a6d3f9730c3d"}}, "datetime": "2021-07-07 14:22:04+00:00", "uuid": "b39f8e00-df2e-11eb-8001-ac539fc90ecc", "person": {"object": "person", "id": "01G4CDTBVKDPV72F1VK07GGJN8", "$address1": "177 Bartell Bridge", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Shanahan", "$title": "", "$organization": "", "$phone_number": "+14716334882", "$email": "vernice.shanahan@developer-tools.shopifyapps.com", "$first_name": "Vernice", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "first_name": "Vernice", "last_name": "Shanahan", "created": "2022-05-31 06:45:58", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156980} -{"stream": "events", "data": {"object": "event", "id": "3mjAFe4aErW", "statistic_id": "TspjNE", "timestamp": 1625667744, "event_name": "Placed Order", "event_properties": {"Items": ["8 Ounce Soy Candle", "8 Ounce Soy Candle", "8 Ounce Soy Candle", "Back Of Polka Dog Bandana"], "Collections": ["Test Collection"], "Item Count": 4, "tags": [], "Discount Codes": ["Custom discount"], "Total Discounts": "15.80", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "3944141750461", "$value": 300.2, "$extra": {"id": 3944141750461, "admin_graphql_api_id": "gid://shopify/Order/3944141750461", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21764201578685, "checkout_token": "114498a5361ca096b973a4953f3b659b", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "vernice.shanahan@developer-tools.shopifyapps.com", "created_at": "2021-07-07T07:22:04-07:00", "currency": "USD", "current_subtotal_price": "300.20", "current_subtotal_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "current_total_discounts": "15.80", "current_total_discounts_set": {"shop_money": {"amount": "15.80", "currency_code": "USD"}, "presentment_money": {"amount": "15.80", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "300.20", "current_total_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [{"code": "Custom discount", "amount": "15.80", "type": "percentage"}], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1008", "note": null, "note_attributes": [], "number": 8, "order_number": 1008, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/78b8b025553c7cb17f5df60f35269d6a/authenticate?key=4e67999c4d621ccfac00a6d3f9730c3d", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T07:22:04-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "300.20", "subtotal_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "78b8b025553c7cb17f5df60f35269d6a", "total_discounts": "15.80", "total_discounts_set": {"shop_money": {"amount": "15.80", "currency_code": "USD"}, "presentment_money": {"amount": "15.80", "currency_code": "USD"}}, "total_line_items_price": "316.00", "total_line_items_price_set": {"shop_money": {"amount": "316.00", "currency_code": "USD"}, "presentment_money": {"amount": "316.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "300.20", "total_price_set": {"shop_money": {"amount": "300.20", "currency_code": "USD"}, "presentment_money": {"amount": "300.20", "currency_code": "USD"}}, "total_price_usd": "300.20", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 372, "updated_at": "2021-07-19T06:50:48-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Vernice", "address1": "177 Bartell Bridge", "phone": "(471) 633-4882", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Shanahan", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Vernice Shanahan", "country_code": "US", "province_code": null}, "customer": {"id": 5330781601981, "email": "vernice.shanahan@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:06-07:00", "updated_at": "2021-07-07T07:22:05-07:00", "first_name": "Vernice", "last_name": "Shanahan", "orders_count": 1, "state": "disabled", "total_spent": "300.20", "last_order_id": 3944141750461, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1008", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:06-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330781601981", "default_address": {"id": 6564639441085, "customer_id": 5330781601981, "first_name": "Vernice", "last_name": "Shanahan", "company": null, "address1": "177 Bartell Bridge", "address2": null, "city": null, "province": null, "country": "United States", "zip": null, "phone": "(471) 633-4882", "name": "Vernice Shanahan", "province_code": null, "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "manual", "value": "5.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "Custom discount", "description": "Custom discount"}], "fulfillments": [], "line_items": [{"id": 10147671081149, "admin_graphql_api_id": "gid://shopify/LineItem/10147671081149", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "5.65", "amount_set": {"shop_money": {"amount": "5.65", "currency_code": "USD"}, "presentment_money": {"amount": "5.65", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}, {"id": 10147671113917, "admin_graphql_api_id": "gid://shopify/LineItem/10147671113917", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "8 Ounce Soy Candle - Frozen", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 60.0, "price_set": {"shop_money": {"amount": "60.00", "currency_code": "USD"}, "presentment_money": {"amount": "60.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603978941, "variant_inventory_management": "shopify", "variant_title": "Frozen", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.00", "amount_set": {"shop_money": {"amount": "3.00", "currency_code": "USD"}, "presentment_money": {"amount": "3.00", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 60.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603978941, "title": "Frozen", "options": {"Title": "Frozen"}, "images": []}, "variant_options": {"Title": "Frozen"}}}, {"id": 10147671146685, "admin_graphql_api_id": "gid://shopify/LineItem/10147671146685", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "5.10", "amount_set": {"shop_money": {"amount": "5.10", "currency_code": "USD"}, "presentment_money": {"amount": "5.10", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 10147671179453, "admin_graphql_api_id": "gid://shopify/LineItem/10147671179453", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 182, "name": "Back Of Polka Dog Bandana - fuchsia", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 41.0, "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234391741, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Back Of Polka Dog Bandana", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613776573, "variant_inventory_management": "shopify", "variant_title": "fuchsia", "vendor": "Lueilwitz - D'Amore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.05", "amount_set": {"shop_money": {"amount": "2.05", "currency_code": "USD"}, "presentment_money": {"amount": "2.05", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 41.0, "product": {"id": 6796234391741, "title": "Back Of Polka Dog Bandana", "handle": "back-of-polka-dog-bandana", "vendor": "Lueilwitz - D'Amore", "tags": "developer-tools-generator", "body_html": "Back Of Polka Dog Bandana", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090613776573, "title": "fuchsia", "options": {"Title": "fuchsia"}, "images": []}, "variant_options": {"Title": "fuchsia"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Vernice", "address1": "177 Bartell Bridge", "phone": "(471) 633-4882", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Shanahan", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Vernice Shanahan", "country_code": "US", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 14:22:24+00:00", "uuid": "bf8b5000-df2e-11eb-8001-ba61f2193ef9", "person": {"object": "person", "id": "01G4CDTBVKDPV72F1VK07GGJN8", "$address1": "177 Bartell Bridge", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Shanahan", "$title": "", "$organization": "", "$phone_number": "+14716334882", "$email": "vernice.shanahan@developer-tools.shopifyapps.com", "$first_name": "Vernice", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "first_name": "Vernice", "last_name": "Shanahan", "created": "2022-05-31 06:45:58", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156981} -{"stream": "events", "data": {"object": "event", "id": "3mjwdKJsELs", "statistic_id": "RDXsib", "timestamp": 1625667754, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234391741, "Name": "Back Of Polka Dog Bandana", "Variant Name": "fuchsia", "SKU": "", "Collections": ["Test Collection"], "Tags": ["developer-tools-generator"], "Vendor": "Lueilwitz - D'Amore", "Variant Option: Title": "fuchsia", "Quantity": 1, "$event_id": "3944141750461:10147671179453:0", "$value": 41.0}, "datetime": "2021-07-07 14:22:34+00:00", "uuid": "c5813100-df2e-11eb-8001-b73a6964c8f7", "person": {"object": "person", "id": "01G4CDTBVKDPV72F1VK07GGJN8", "$address1": "177 Bartell Bridge", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Shanahan", "$title": "", "$organization": "", "$phone_number": "+14716334882", "$email": "vernice.shanahan@developer-tools.shopifyapps.com", "$first_name": "Vernice", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "first_name": "Vernice", "last_name": "Shanahan", "created": "2022-05-31 06:45:58", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156982} -{"stream": "events", "data": {"object": "event", "id": "3mjAFfw3Yjz", "statistic_id": "RDXsib", "timestamp": 1625667754, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "purple", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "purple", "Quantity": 1, "$event_id": "3944141750461:10147671081149:0", "$value": 113.0}, "datetime": "2021-07-07 14:22:34+00:00", "uuid": "c5813100-df2e-11eb-8001-528528984e9c", "person": {"object": "person", "id": "01G4CDTBVKDPV72F1VK07GGJN8", "$address1": "177 Bartell Bridge", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Shanahan", "$title": "", "$organization": "", "$phone_number": "+14716334882", "$email": "vernice.shanahan@developer-tools.shopifyapps.com", "$first_name": "Vernice", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "first_name": "Vernice", "last_name": "Shanahan", "created": "2022-05-31 06:45:58", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156982} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRT2JV", "statistic_id": "RDXsib", "timestamp": 1625667754, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "Wooden", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "Wooden", "Quantity": 1, "$event_id": "3944141750461:10147671146685:0", "$value": 102.0}, "datetime": "2021-07-07 14:22:34+00:00", "uuid": "c5813100-df2e-11eb-8001-b3ba00ff85e6", "person": {"object": "person", "id": "01G4CDTBVKDPV72F1VK07GGJN8", "$address1": "177 Bartell Bridge", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Shanahan", "$title": "", "$organization": "", "$phone_number": "+14716334882", "$email": "vernice.shanahan@developer-tools.shopifyapps.com", "$first_name": "Vernice", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "first_name": "Vernice", "last_name": "Shanahan", "created": "2022-05-31 06:45:58", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156983} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQsYx", "statistic_id": "RDXsib", "timestamp": 1625667754, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "Frozen", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "Frozen", "Quantity": 1, "$event_id": "3944141750461:10147671113917:0", "$value": 60.0}, "datetime": "2021-07-07 14:22:34+00:00", "uuid": "c5813100-df2e-11eb-8001-416fc23a8db1", "person": {"object": "person", "id": "01G4CDTBVKDPV72F1VK07GGJN8", "$address1": "177 Bartell Bridge", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Shanahan", "$title": "", "$organization": "", "$phone_number": "+14716334882", "$email": "vernice.shanahan@developer-tools.shopifyapps.com", "$first_name": "Vernice", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "vernice.shanahan@developer-tools.shopifyapps.com", "first_name": "Vernice", "last_name": "Shanahan", "created": "2022-05-31 06:45:58", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156983} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHCTT", "statistic_id": "SPnhc3", "timestamp": 1625671137, "event_name": "Checkout Started", "event_properties": {"Items": ["Back Of Polka Dog Bandana"], "Collections": ["Test Collection"], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "21764813684925", "$value": 41.0, "$extra": {"id": 3944225538237, "admin_graphql_api_id": "gid://shopify/Order/3944225538237", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21764813684925, "checkout_token": "73c76fab1f007e8baf8c0180a6505cba", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "benedict.farrell@developer-tools.shopifyapps.com", "created_at": "2021-07-07T08:18:57-07:00", "currency": "USD", "current_subtotal_price": "41.00", "current_subtotal_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "41.00", "current_total_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "benedict.farrell@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1009", "note": null, "note_attributes": [], "number": 9, "order_number": 1009, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5aa910ba37d306dd6c3cc454c8e1ecb2/authenticate?key=0a1214a374126c082307600ceb90855f", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:18:57-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "41.00", "subtotal_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "73c76fab1f007e8baf8c0180a6505cba", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "41.00", "total_line_items_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "41.00", "total_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "total_price_usd": "41.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 182, "updated_at": "2021-07-07T08:18:59-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Benedict", "address1": "4694 Monroe Gateway", "phone": "1-532-436-8221", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Farrell", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Benedict Farrell", "country_code": "US", "province_code": null}, "customer": {"id": 5330782224573, "email": "benedict.farrell@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:08-07:00", "updated_at": "2021-07-07T08:18:58-07:00", "first_name": "Benedict", "last_name": "Farrell", "orders_count": 1, "state": "disabled", "total_spent": "41.00", "last_order_id": 3944225538237, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1009", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:08-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782224573", "default_address": {"id": 6564640030909, "customer_id": 5330782224573, "first_name": "Benedict", "last_name": "Farrell", "company": null, "address1": "4694 Monroe Gateway", "address2": null, "city": null, "province": null, "country": "United States", "zip": null, "phone": "1-532-436-8221", "name": "Benedict Farrell", "province_code": null, "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10147831578813, "admin_graphql_api_id": "gid://shopify/LineItem/10147831578813", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 182, "name": "Back Of Polka Dog Bandana - fuchsia", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 41.0, "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234391741, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Back Of Polka Dog Bandana", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613776573, "variant_inventory_management": "shopify", "variant_title": "fuchsia", "vendor": "Lueilwitz - D'Amore", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 41.0, "product": {"id": 6796234391741, "title": "Back Of Polka Dog Bandana", "handle": "back-of-polka-dog-bandana", "vendor": "Lueilwitz - D'Amore", "tags": "developer-tools-generator", "body_html": "Back Of Polka Dog Bandana", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090613776573, "title": "fuchsia", "options": {"Title": "fuchsia"}, "images": []}, "variant_options": {"Title": "fuchsia"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Benedict", "address1": "4694 Monroe Gateway", "phone": "1-532-436-8221", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Farrell", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Benedict Farrell", "country_code": "US", "province_code": null}, "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5aa910ba37d306dd6c3cc454c8e1ecb2/authenticate?key=0a1214a374126c082307600ceb90855f", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5aa910ba37d306dd6c3cc454c8e1ecb2/authenticate?key=0a1214a374126c082307600ceb90855f"}}, "datetime": "2021-07-07 15:18:57+00:00", "uuid": "a5ee0680-df36-11eb-8001-ed287647e983", "person": {"object": "person", "id": "01G4CDTATNPKS8PE6AD34BT955", "$address1": "4694 Monroe Gateway", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Farrell", "$title": "", "$organization": "", "$phone_number": "+15324368221", "$email": "benedict.farrell@developer-tools.shopifyapps.com", "$first_name": "Benedict", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "benedict.farrell@developer-tools.shopifyapps.com", "first_name": "Benedict", "last_name": "Farrell", "created": "2022-05-31 06:45:57", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156984} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzG2Avm", "statistic_id": "TspjNE", "timestamp": 1625671157, "event_name": "Placed Order", "event_properties": {"Items": ["Back Of Polka Dog Bandana"], "Collections": ["Test Collection"], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "3944225538237", "$value": 41.0, "$extra": {"id": 3944225538237, "admin_graphql_api_id": "gid://shopify/Order/3944225538237", "app_id": 1354745, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 21764813684925, "checkout_token": "73c76fab1f007e8baf8c0180a6505cba", "client_details": {"accept_language": null, "browser_height": null, "browser_ip": null, "browser_width": null, "session_hash": null, "user_agent": null}, "closed_at": null, "confirmed": true, "contact_email": "benedict.farrell@developer-tools.shopifyapps.com", "created_at": "2021-07-07T08:18:57-07:00", "currency": "USD", "current_subtotal_price": "41.00", "current_subtotal_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "41.00", "current_total_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "benedict.farrell@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": null, "gateway": "manual", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1009", "note": null, "note_attributes": [], "number": 9, "order_number": 1009, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5aa910ba37d306dd6c3cc454c8e1ecb2/authenticate?key=0a1214a374126c082307600ceb90855f", "original_total_duties_set": null, "payment_gateway_names": ["manual"], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:18:57-07:00", "processing_method": "manual", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "41.00", "subtotal_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": false, "token": "5aa910ba37d306dd6c3cc454c8e1ecb2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "41.00", "total_line_items_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "41.00", "total_price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "total_price_usd": "41.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 182, "updated_at": "2021-07-07T08:18:59-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Benedict", "address1": "4694 Monroe Gateway", "phone": "1-532-436-8221", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Farrell", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Benedict Farrell", "country_code": "US", "province_code": null}, "customer": {"id": 5330782224573, "email": "benedict.farrell@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:08-07:00", "updated_at": "2021-07-07T08:18:58-07:00", "first_name": "Benedict", "last_name": "Farrell", "orders_count": 1, "state": "disabled", "total_spent": "41.00", "last_order_id": 3944225538237, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": "#1009", "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:08-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782224573", "default_address": {"id": 6564640030909, "customer_id": 5330782224573, "first_name": "Benedict", "last_name": "Farrell", "company": null, "address1": "4694 Monroe Gateway", "address2": null, "city": null, "province": null, "country": "United States", "zip": null, "phone": "1-532-436-8221", "name": "Benedict Farrell", "province_code": null, "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10147831578813, "admin_graphql_api_id": "gid://shopify/LineItem/10147831578813", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 182, "name": "Back Of Polka Dog Bandana - fuchsia", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 41.0, "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234391741, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Back Of Polka Dog Bandana", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090613776573, "variant_inventory_management": "shopify", "variant_title": "fuchsia", "vendor": "Lueilwitz - D'Amore", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 41.0, "product": {"id": 6796234391741, "title": "Back Of Polka Dog Bandana", "handle": "back-of-polka-dog-bandana", "vendor": "Lueilwitz - D'Amore", "tags": "developer-tools-generator", "body_html": "Back Of Polka Dog Bandana", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090613776573, "title": "fuchsia", "options": {"Title": "fuchsia"}, "images": []}, "variant_options": {"Title": "fuchsia"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Benedict", "address1": "4694 Monroe Gateway", "phone": "1-532-436-8221", "city": null, "zip": null, "province": null, "country": "United States", "last_name": "Farrell", "address2": null, "company": null, "latitude": null, "longitude": null, "name": "Benedict Farrell", "country_code": "US", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:19:17+00:00", "uuid": "b1d9c880-df36-11eb-8001-40b8e4170de3", "person": {"object": "person", "id": "01G4CDTATNPKS8PE6AD34BT955", "$address1": "4694 Monroe Gateway", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Farrell", "$title": "", "$organization": "", "$phone_number": "+15324368221", "$email": "benedict.farrell@developer-tools.shopifyapps.com", "$first_name": "Benedict", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "benedict.farrell@developer-tools.shopifyapps.com", "first_name": "Benedict", "last_name": "Farrell", "created": "2022-05-31 06:45:57", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156984} -{"stream": "events", "data": {"object": "event", "id": "3mjEbqTnkGV", "statistic_id": "RDXsib", "timestamp": 1625671167, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234391741, "Name": "Back Of Polka Dog Bandana", "Variant Name": "fuchsia", "SKU": "", "Collections": ["Test Collection"], "Tags": ["developer-tools-generator"], "Vendor": "Lueilwitz - D'Amore", "Variant Option: Title": "fuchsia", "Quantity": 1, "$event_id": "3944225538237:10147831578813:0", "$value": 41.0}, "datetime": "2021-07-07 15:19:27+00:00", "uuid": "b7cfa980-df36-11eb-8001-f8e284e260ab", "person": {"object": "person", "id": "01G4CDTATNPKS8PE6AD34BT955", "$address1": "4694 Monroe Gateway", "$address2": "", "$city": "", "$country": "United States", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Farrell", "$title": "", "$organization": "", "$phone_number": "+15324368221", "$email": "benedict.farrell@developer-tools.shopifyapps.com", "$first_name": "Benedict", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "benedict.farrell@developer-tools.shopifyapps.com", "first_name": "Benedict", "last_name": "Farrell", "created": "2022-05-31 06:45:57", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156985} -{"stream": "events", "data": {"object": "event", "id": "3mjA3DuZgax", "statistic_id": "TspjNE", "timestamp": 1625672284, "event_name": "Placed Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "EUR", "$event_id": "3944251719869", "$value": 238.47, "$extra": {"id": 3944251719869, "admin_graphql_api_id": "gid://shopify/Order/3944251719869", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:37:46-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:37:44-07:00", "currency": "EUR", "current_subtotal_price": "224.97", "current_subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "EUR"}, "presentment_money": {"amount": "224.97", "currency_code": "EUR"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "current_total_duties_set": null, "current_total_price": "238.47", "current_total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "EUR"}, "presentment_money": {"amount": "238.47", "currency_code": "EUR"}}, "current_total_tax": "13.50", "current_total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1011", "note": null, "note_attributes": [], "number": 11, "order_number": 1011, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/08fb4179826d4b6eb9cc09e735ef775a/authenticate?key=24c800591574ca7db589fdd62d14c9b8", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "EUR", "processed_at": "2021-07-07T08:37:44-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "EUR"}, "presentment_money": {"amount": "224.97", "currency_code": "EUR"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "08fb4179826d4b6eb9cc09e735ef775a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "EUR"}, "presentment_money": {"amount": "224.97", "currency_code": "EUR"}}, "total_outstanding": "0.00", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "EUR"}, "presentment_money": {"amount": "238.47", "currency_code": "EUR"}}, "total_price_usd": "281.93", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:37:46-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505540169917, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505540169917", "created_at": "2021-07-07T08:37:45-07:00", "location_id": 63590301885, "name": "#1011.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:37:45-07:00", "line_items": [{"id": 10147883647165, "admin_graphql_api_id": "gid://shopify/LineItem/10147883647165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "EUR"}, "presentment_money": {"amount": "74.99", "currency_code": "EUR"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147883647165, "admin_graphql_api_id": "gid://shopify/LineItem/10147883647165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "EUR"}, "presentment_money": {"amount": "74.99", "currency_code": "EUR"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:38:04+00:00", "uuid": "51985600-df39-11eb-8001-9fc999dfa4e3", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "foo@example.com", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156986} -{"stream": "events", "data": {"object": "event", "id": "3mjuWmtah9X", "statistic_id": "X3f6PC", "timestamp": 1625672315, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "EUR", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944251719869", "$value": 238.47, "$extra": {"id": 3944251719869, "admin_graphql_api_id": "gid://shopify/Order/3944251719869", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:37:46-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:37:44-07:00", "currency": "EUR", "current_subtotal_price": "224.97", "current_subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "EUR"}, "presentment_money": {"amount": "224.97", "currency_code": "EUR"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "current_total_duties_set": null, "current_total_price": "238.47", "current_total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "EUR"}, "presentment_money": {"amount": "238.47", "currency_code": "EUR"}}, "current_total_tax": "13.50", "current_total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1011", "note": null, "note_attributes": [], "number": 11, "order_number": 1011, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/08fb4179826d4b6eb9cc09e735ef775a/authenticate?key=24c800591574ca7db589fdd62d14c9b8", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "EUR", "processed_at": "2021-07-07T08:37:44-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "EUR"}, "presentment_money": {"amount": "224.97", "currency_code": "EUR"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "08fb4179826d4b6eb9cc09e735ef775a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "EUR"}, "presentment_money": {"amount": "224.97", "currency_code": "EUR"}}, "total_outstanding": "0.00", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "EUR"}, "presentment_money": {"amount": "238.47", "currency_code": "EUR"}}, "total_price_usd": "281.93", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:37:46-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505540169917, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505540169917", "created_at": "2021-07-07T08:37:45-07:00", "location_id": 63590301885, "name": "#1011.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:37:45-07:00", "line_items": [{"id": 10147883647165, "admin_graphql_api_id": "gid://shopify/LineItem/10147883647165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "EUR"}, "presentment_money": {"amount": "74.99", "currency_code": "EUR"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147883647165, "admin_graphql_api_id": "gid://shopify/LineItem/10147883647165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "EUR"}, "presentment_money": {"amount": "74.99", "currency_code": "EUR"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "EUR"}, "presentment_money": {"amount": "0.00", "currency_code": "EUR"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "EUR"}, "presentment_money": {"amount": "13.50", "currency_code": "EUR"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:38:35+00:00", "uuid": "64128f80-df39-11eb-8001-8d6e8b2456c6", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "foo@example.com", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156987} -{"stream": "events", "data": {"object": "event", "id": "3mjwxwy9BXW", "statistic_id": "TspjNE", "timestamp": 1625672337, "event_name": "Placed Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944253161661", "$value": 238.47, "$extra": {"id": 3944253161661, "admin_graphql_api_id": "gid://shopify/Order/3944253161661", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:38:39-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:38:37-07:00", "currency": "USD", "current_subtotal_price": "224.97", "current_subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "USD"}, "presentment_money": {"amount": "224.97", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "238.47", "current_total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "USD"}, "presentment_money": {"amount": "238.47", "currency_code": "USD"}}, "current_total_tax": "13.50", "current_total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1012", "note": null, "note_attributes": [], "number": 12, "order_number": 1012, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c094290a17a227671ce9d0f87a42cd07/authenticate?key=30049b7f3095a3fce8eee030d3dd6121", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:38:37-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "USD"}, "presentment_money": {"amount": "224.97", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "c094290a17a227671ce9d0f87a42cd07", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "USD"}, "presentment_money": {"amount": "224.97", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "USD"}, "presentment_money": {"amount": "238.47", "currency_code": "USD"}}, "total_price_usd": "238.47", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:38:39-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505543282877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505543282877", "created_at": "2021-07-07T08:38:38-07:00", "location_id": 63590301885, "name": "#1012.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:38:38-07:00", "line_items": [{"id": 10147887972541, "admin_graphql_api_id": "gid://shopify/LineItem/10147887972541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "USD"}, "presentment_money": {"amount": "74.99", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147887972541, "admin_graphql_api_id": "gid://shopify/LineItem/10147887972541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "USD"}, "presentment_money": {"amount": "74.99", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:38:57+00:00", "uuid": "712f7e80-df39-11eb-8001-a693f94849ac", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "foo@example.com", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156988} -{"stream": "events", "data": {"object": "event", "id": "3mjvTVvGhid", "statistic_id": "X3f6PC", "timestamp": 1625672368, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944253161661", "$value": 238.47, "$extra": {"id": 3944253161661, "admin_graphql_api_id": "gid://shopify/Order/3944253161661", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:38:39-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:38:37-07:00", "currency": "USD", "current_subtotal_price": "224.97", "current_subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "USD"}, "presentment_money": {"amount": "224.97", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "238.47", "current_total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "USD"}, "presentment_money": {"amount": "238.47", "currency_code": "USD"}}, "current_total_tax": "13.50", "current_total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1012", "note": null, "note_attributes": [], "number": 12, "order_number": 1012, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c094290a17a227671ce9d0f87a42cd07/authenticate?key=30049b7f3095a3fce8eee030d3dd6121", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:38:37-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "USD"}, "presentment_money": {"amount": "224.97", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "c094290a17a227671ce9d0f87a42cd07", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "USD"}, "presentment_money": {"amount": "224.97", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "USD"}, "presentment_money": {"amount": "238.47", "currency_code": "USD"}}, "total_price_usd": "238.47", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:38:39-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505543282877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505543282877", "created_at": "2021-07-07T08:38:38-07:00", "location_id": 63590301885, "name": "#1012.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:38:38-07:00", "line_items": [{"id": 10147887972541, "admin_graphql_api_id": "gid://shopify/LineItem/10147887972541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "USD"}, "presentment_money": {"amount": "74.99", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147887972541, "admin_graphql_api_id": "gid://shopify/LineItem/10147887972541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "USD"}, "presentment_money": {"amount": "74.99", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "USD"}, "presentment_money": {"amount": "13.50", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:39:28+00:00", "uuid": "83a9b800-df39-11eb-8001-33825fc15acb", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "foo@example.com", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156989} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV7SyU", "statistic_id": "TspjNE", "timestamp": 1625672379, "event_name": "Placed Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "UAH", "$event_id": "3944254079165", "$value": 238.47, "$extra": {"id": 3944254079165, "admin_graphql_api_id": "gid://shopify/Order/3944254079165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:39:20-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:39:19-07:00", "currency": "UAH", "current_subtotal_price": "149.98", "current_subtotal_price_set": {"shop_money": {"amount": "149.98", "currency_code": "UAH"}, "presentment_money": {"amount": "149.98", "currency_code": "UAH"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "current_total_duties_set": null, "current_total_price": "158.98", "current_total_price_set": {"shop_money": {"amount": "158.98", "currency_code": "UAH"}, "presentment_money": {"amount": "158.98", "currency_code": "UAH"}}, "current_total_tax": "9.00", "current_total_tax_set": {"shop_money": {"amount": "9.00", "currency_code": "UAH"}, "presentment_money": {"amount": "9.00", "currency_code": "UAH"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1013", "note": "Test Note", "note_attributes": [], "number": 13, "order_number": 1013, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/12fac6f8b01ed9ea5c5a64dcb3ec76a4/authenticate?key=89c7d74ae871f3a882fe65c4364e1418", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "UAH", "processed_at": "2021-07-07T08:39:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "UAH"}, "presentment_money": {"amount": "224.97", "currency_code": "UAH"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "12fac6f8b01ed9ea5c5a64dcb3ec76a4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "UAH"}, "presentment_money": {"amount": "224.97", "currency_code": "UAH"}}, "total_outstanding": "218.47", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "UAH"}, "presentment_money": {"amount": "238.47", "currency_code": "UAH"}}, "total_price_usd": "8.72", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T09:49:56-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505545445565, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505545445565", "created_at": "2021-07-07T08:39:19-07:00", "location_id": 63590301885, "name": "#1013.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:39:19-07:00", "line_items": [{"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [{"id": 828111093949, "admin_graphql_api_id": "gid://shopify/Refund/828111093949", "created_at": "2021-09-08T09:49:56-07:00", "note": "This is the test refund", "processed_at": "2021-09-08T09:49:56-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189684285629, "amount": "59.49", "amount_set": {"shop_money": {"amount": "59.49", "currency_code": "UAH"}, "presentment_money": {"amount": "59.49", "currency_code": "UAH"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828111093949, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}}], "transactions": [{"id": 5159204880573, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159204880573", "amount": "20.00", "authorization": null, "created_at": "2021-09-08T09:49:56-07:00", "currency": "UAH", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 20.00 from manual gateway", "parent_id": 4944827842749, "processed_at": "2021-09-08T09:49:56-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330844602557, "line_item_id": 10147889479869, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 74.99, "subtotal_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "total_tax": 4.5, "total_tax_set": {"shop_money": {"amount": "4.50", "currency_code": "UAH"}, "presentment_money": {"amount": "4.50", "currency_code": "UAH"}}, "line_item": {"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:39:39+00:00", "uuid": "8a382f80-df39-11eb-8001-761c483f1785", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "foo@example.com", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156990} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV84uT", "statistic_id": "X3f6PC", "timestamp": 1625672409, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "UAH", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3944254079165", "$value": 238.47, "$extra": {"id": 3944254079165, "admin_graphql_api_id": "gid://shopify/Order/3944254079165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:39:20-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:39:19-07:00", "currency": "UAH", "current_subtotal_price": "149.98", "current_subtotal_price_set": {"shop_money": {"amount": "149.98", "currency_code": "UAH"}, "presentment_money": {"amount": "149.98", "currency_code": "UAH"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "current_total_duties_set": null, "current_total_price": "158.98", "current_total_price_set": {"shop_money": {"amount": "158.98", "currency_code": "UAH"}, "presentment_money": {"amount": "158.98", "currency_code": "UAH"}}, "current_total_tax": "9.00", "current_total_tax_set": {"shop_money": {"amount": "9.00", "currency_code": "UAH"}, "presentment_money": {"amount": "9.00", "currency_code": "UAH"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1013", "note": "Test Note", "note_attributes": [], "number": 13, "order_number": 1013, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/12fac6f8b01ed9ea5c5a64dcb3ec76a4/authenticate?key=89c7d74ae871f3a882fe65c4364e1418", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "UAH", "processed_at": "2021-07-07T08:39:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "UAH"}, "presentment_money": {"amount": "224.97", "currency_code": "UAH"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "12fac6f8b01ed9ea5c5a64dcb3ec76a4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "UAH"}, "presentment_money": {"amount": "224.97", "currency_code": "UAH"}}, "total_outstanding": "218.47", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "UAH"}, "presentment_money": {"amount": "238.47", "currency_code": "UAH"}}, "total_price_usd": "8.72", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T09:49:56-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505545445565, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505545445565", "created_at": "2021-07-07T08:39:19-07:00", "location_id": 63590301885, "name": "#1013.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:39:19-07:00", "line_items": [{"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [{"id": 828111093949, "admin_graphql_api_id": "gid://shopify/Refund/828111093949", "created_at": "2021-09-08T09:49:56-07:00", "note": "This is the test refund", "processed_at": "2021-09-08T09:49:56-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189684285629, "amount": "59.49", "amount_set": {"shop_money": {"amount": "59.49", "currency_code": "UAH"}, "presentment_money": {"amount": "59.49", "currency_code": "UAH"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828111093949, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}}], "transactions": [{"id": 5159204880573, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159204880573", "amount": "20.00", "authorization": null, "created_at": "2021-09-08T09:49:56-07:00", "currency": "UAH", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 20.00 from manual gateway", "parent_id": 4944827842749, "processed_at": "2021-09-08T09:49:56-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330844602557, "line_item_id": 10147889479869, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 74.99, "subtotal_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "total_tax": 4.5, "total_tax_set": {"shop_money": {"amount": "4.50", "currency_code": "UAH"}, "presentment_money": {"amount": "4.50", "currency_code": "UAH"}}, "line_item": {"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:40:09+00:00", "uuid": "9c19d280-df39-11eb-8001-575713cbba85", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "foo@example.com", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156991} -{"stream": "events", "data": {"object": "event", "id": "3mjAFguDfic", "statistic_id": "TspjNE", "timestamp": 1625673133, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 288"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944273182909", "$value": 810.9, "$extra": {"id": 3944273182909, "admin_graphql_api_id": "gid://shopify/Order/3944273182909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:51:54-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:51:53-07:00", "currency": "USD", "current_subtotal_price": "791.00", "current_subtotal_price_set": {"shop_money": {"amount": "791.00", "currency_code": "USD"}, "presentment_money": {"amount": "791.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "830.00", "current_total_price_set": {"shop_money": {"amount": "830.00", "currency_code": "USD"}, "presentment_money": {"amount": "830.00", "currency_code": "USD"}}, "current_total_tax": "39.00", "current_total_tax_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1014", "note": null, "note_attributes": [], "number": 14, "order_number": 1014, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/206c33a3724ae230754326659d24f581/authenticate?key=fa66ad9a6f5ca46456cb3891128c34a1", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:51:53-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "791.00", "subtotal_price_set": {"shop_money": {"amount": "791.00", "currency_code": "USD"}, "presentment_money": {"amount": "791.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "39.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "206c33a3724ae230754326659d24f581", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "791.00", "total_line_items_price_set": {"shop_money": {"amount": "791.00", "currency_code": "USD"}, "presentment_money": {"amount": "791.00", "currency_code": "USD"}}, "total_outstanding": "802.00", "total_price": "810.90", "total_price_set": {"shop_money": {"amount": "810.90", "currency_code": "USD"}, "presentment_money": {"amount": "810.90", "currency_code": "USD"}}, "total_price_usd": "810.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:51:55-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565040829, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565040829", "created_at": "2021-07-07T08:51:54-07:00", "location_id": 63590301885, "name": "#1014.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:51:54-07:00", "line_items": [{"id": 10147929456829, "admin_graphql_api_id": "gid://shopify/LineItem/10147929456829", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 288", "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 7, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 288", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147929456829, "admin_graphql_api_id": "gid://shopify/LineItem/10147929456829", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 288", "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 7.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 288", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 791.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:13+00:00", "uuid": "4ba37480-df3b-11eb-8001-f42aca16bfb9", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156992} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7b", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:0", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-0fadd2a201ef", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156994} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7c", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:1", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-84a7cbf2aeae", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156994} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7d", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:2", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-35aa34766597", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156994} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7e", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:3", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-4aa2de7afcdf", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156995} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7f", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:4", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-6c5ce30db78b", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156995} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7g", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:5", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-47fd3951a0e8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156995} -{"stream": "events", "data": {"object": "event", "id": "3mjz65stP7h", "statistic_id": "RDXsib", "timestamp": 1625673143, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 288", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273182909:10147929456829:6", "$value": 113.0}, "datetime": "2021-07-07 15:52:23+00:00", "uuid": "51995580-df3b-11eb-8001-b8f28e5e24c3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156995} -{"stream": "events", "data": {"object": "event", "id": "3mjFsDU4Jnb", "statistic_id": "TspjNE", "timestamp": 1625673144, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944273576125", "$value": 127.9, "$extra": {"id": 3944273576125, "admin_graphql_api_id": "gid://shopify/Order/3944273576125", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:06-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:04-07:00", "currency": "USD", "current_subtotal_price": "108.00", "current_subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "149.00", "current_total_price_set": {"shop_money": {"amount": "149.00", "currency_code": "USD"}, "presentment_money": {"amount": "149.00", "currency_code": "USD"}}, "current_total_tax": "41.00", "current_total_tax_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1015", "note": null, "note_attributes": [], "number": 15, "order_number": 1015, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a17e0d8b599f74c372de7a42fb01a52a/authenticate?key=72fd72abe69589ce339fff467a036de6", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "a17e0d8b599f74c372de7a42fb01a52a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:52:06-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565237437, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565237437", "created_at": "2021-07-07T08:52:05-07:00", "location_id": 63590301885, "name": "#1015.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:05-07:00", "line_items": [{"id": 10147930669245, "admin_graphql_api_id": "gid://shopify/LineItem/10147930669245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147930669245, "admin_graphql_api_id": "gid://shopify/LineItem/10147930669245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:24+00:00", "uuid": "5231ec00-df3b-11eb-8001-77576b99ecd3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156995} -{"stream": "events", "data": {"object": "event", "id": "3mjAZ8EzKVC", "statistic_id": "TspjNE", "timestamp": 1625673147, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944273805501", "$value": 127.9, "$extra": {"id": 3944273805501, "admin_graphql_api_id": "gid://shopify/Order/3944273805501", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:08-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:07-07:00", "currency": "USD", "current_subtotal_price": "81.00", "current_subtotal_price_set": {"shop_money": {"amount": "81.00", "currency_code": "USD"}, "presentment_money": {"amount": "81.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "111.75", "current_total_price_set": {"shop_money": {"amount": "111.75", "currency_code": "USD"}, "presentment_money": {"amount": "111.75", "currency_code": "USD"}}, "current_total_tax": "30.75", "current_total_tax_set": {"shop_money": {"amount": "30.75", "currency_code": "USD"}, "presentment_money": {"amount": "30.75", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1016", "note": null, "note_attributes": [], "number": 16, "order_number": 1016, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cd5cd21e8a2856e5bd8aaa0b7392cb12/authenticate?key=ddefed114e6c464ff982f0fb2aef64aa", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:07-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "cd5cd21e8a2856e5bd8aaa0b7392cb12", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-09T02:57:43-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565302973, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565302973", "created_at": "2021-07-07T08:52:08-07:00", "location_id": 63590301885, "name": "#1016.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:08-07:00", "line_items": [{"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [{"id": 828202942653, "admin_graphql_api_id": "gid://shopify/Refund/828202942653", "created_at": "2021-09-09T02:57:43-07:00", "note": "Test refund 123", "processed_at": "2021-09-09T02:57:43-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189745856701, "amount": "29.25", "amount_set": {"shop_money": {"amount": "29.25", "currency_code": "USD"}, "presentment_money": {"amount": "29.25", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828202942653, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5160967209149, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5160967209149", "amount": "8.00", "authorization": null, "created_at": "2021-09-09T02:57:43-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 8.00 from manual gateway", "parent_id": 4944854188221, "processed_at": "2021-09-09T02:57:43-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330958864573, "line_item_id": 10147931127997, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 27.0, "subtotal_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_tax": 10.25, "total_tax_set": {"shop_money": {"amount": "10.25", "currency_code": "USD"}, "presentment_money": {"amount": "10.25", "currency_code": "USD"}}, "line_item": {"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:27+00:00", "uuid": "53fbaf80-df3b-11eb-8001-2637e778248e", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156997} -{"stream": "events", "data": {"object": "event", "id": "3mjuiHYe6qG", "statistic_id": "TspjNE", "timestamp": 1625673149, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944273903805", "$value": 127.9, "$extra": {"id": 3944273903805, "admin_graphql_api_id": "gid://shopify/Order/3944273903805", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:10-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:09-07:00", "currency": "USD", "current_subtotal_price": "108.00", "current_subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "149.00", "current_total_price_set": {"shop_money": {"amount": "149.00", "currency_code": "USD"}, "presentment_money": {"amount": "149.00", "currency_code": "USD"}}, "current_total_tax": "41.00", "current_total_tax_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1017", "note": null, "note_attributes": [], "number": 17, "order_number": 1017, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/78db25709f204c0e68f38ed18a505208/authenticate?key=284a1a59f8d1d722dd6bfa5e96cfea16", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:09-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "78db25709f204c0e68f38ed18a505208", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:52:11-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565401277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565401277", "created_at": "2021-07-07T08:52:10-07:00", "location_id": 63590301885, "name": "#1017.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:10-07:00", "line_items": [{"id": 10147931291837, "admin_graphql_api_id": "gid://shopify/LineItem/10147931291837", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147931291837, "admin_graphql_api_id": "gid://shopify/LineItem/10147931291837", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:29+00:00", "uuid": "552cdc80-df3b-11eb-8001-8c7be80144f4", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156998} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQnwx", "statistic_id": "RDXsib", "timestamp": 1625673154, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273576125:10147930669245:0", "$value": 27.0}, "datetime": "2021-07-07 15:52:34+00:00", "uuid": "5827cd00-df3b-11eb-8001-e5b42b4123f6", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367156999} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQnwy", "statistic_id": "RDXsib", "timestamp": 1625673154, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273576125:10147930669245:1", "$value": 27.0}, "datetime": "2021-07-07 15:52:34+00:00", "uuid": "5827cd00-df3b-11eb-8001-f4ec8f2b01b1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157000} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQnwz", "statistic_id": "RDXsib", "timestamp": 1625673154, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273576125:10147930669245:2", "$value": 27.0}, "datetime": "2021-07-07 15:52:34+00:00", "uuid": "5827cd00-df3b-11eb-8001-4fbc5a154ee5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157000} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQnwA", "statistic_id": "RDXsib", "timestamp": 1625673154, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273576125:10147930669245:3", "$value": 27.0}, "datetime": "2021-07-07 15:52:34+00:00", "uuid": "5827cd00-df3b-11eb-8001-0c8023f6edac", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157000} -{"stream": "events", "data": {"object": "event", "id": "3mjz68RxfCG", "statistic_id": "RDXsib", "timestamp": 1625673157, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273805501:10147931127997:0", "$value": 27.0}, "datetime": "2021-07-07 15:52:37+00:00", "uuid": "59f19080-df3b-11eb-8001-ca4adece4dbb", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157001} -{"stream": "events", "data": {"object": "event", "id": "3mjz68RxfCH", "statistic_id": "RDXsib", "timestamp": 1625673157, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273805501:10147931127997:1", "$value": 27.0}, "datetime": "2021-07-07 15:52:37+00:00", "uuid": "59f19080-df3b-11eb-8001-80cf116cc4b0", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157001} -{"stream": "events", "data": {"object": "event", "id": "3mjz68RxfCJ", "statistic_id": "RDXsib", "timestamp": 1625673157, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273805501:10147931127997:2", "$value": 27.0}, "datetime": "2021-07-07 15:52:37+00:00", "uuid": "59f19080-df3b-11eb-8001-c78db2efc8a5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157001} -{"stream": "events", "data": {"object": "event", "id": "3mjz68RxfCK", "statistic_id": "RDXsib", "timestamp": 1625673157, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273805501:10147931127997:3", "$value": 27.0}, "datetime": "2021-07-07 15:52:37+00:00", "uuid": "59f19080-df3b-11eb-8001-220f6c3ab3e1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157001} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHkgH", "statistic_id": "RDXsib", "timestamp": 1625673159, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273903805:10147931291837:0", "$value": 27.0}, "datetime": "2021-07-07 15:52:39+00:00", "uuid": "5b22bd80-df3b-11eb-8001-792ca60478e3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157002} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHkgJ", "statistic_id": "RDXsib", "timestamp": 1625673159, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273903805:10147931291837:1", "$value": 27.0}, "datetime": "2021-07-07 15:52:39+00:00", "uuid": "5b22bd80-df3b-11eb-8001-26f14fd908cb", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157002} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHkgK", "statistic_id": "RDXsib", "timestamp": 1625673159, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273903805:10147931291837:2", "$value": 27.0}, "datetime": "2021-07-07 15:52:39+00:00", "uuid": "5b22bd80-df3b-11eb-8001-5b2e6b3775cd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157002} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHkgL", "statistic_id": "RDXsib", "timestamp": 1625673159, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 189", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944273903805:10147931291837:3", "$value": 27.0}, "datetime": "2021-07-07 15:52:39+00:00", "uuid": "5b22bd80-df3b-11eb-8001-04e80c697ed4", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157003} -{"stream": "events", "data": {"object": "event", "id": "3mjBCM86Xvi", "statistic_id": "X3f6PC", "timestamp": 1625673164, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 288"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944273182909", "$value": 810.9, "$extra": {"id": 3944273182909, "admin_graphql_api_id": "gid://shopify/Order/3944273182909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:51:54-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:51:53-07:00", "currency": "USD", "current_subtotal_price": "791.00", "current_subtotal_price_set": {"shop_money": {"amount": "791.00", "currency_code": "USD"}, "presentment_money": {"amount": "791.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "830.00", "current_total_price_set": {"shop_money": {"amount": "830.00", "currency_code": "USD"}, "presentment_money": {"amount": "830.00", "currency_code": "USD"}}, "current_total_tax": "39.00", "current_total_tax_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1014", "note": null, "note_attributes": [], "number": 14, "order_number": 1014, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/206c33a3724ae230754326659d24f581/authenticate?key=fa66ad9a6f5ca46456cb3891128c34a1", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:51:53-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "791.00", "subtotal_price_set": {"shop_money": {"amount": "791.00", "currency_code": "USD"}, "presentment_money": {"amount": "791.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "39.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "206c33a3724ae230754326659d24f581", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "791.00", "total_line_items_price_set": {"shop_money": {"amount": "791.00", "currency_code": "USD"}, "presentment_money": {"amount": "791.00", "currency_code": "USD"}}, "total_outstanding": "802.00", "total_price": "810.90", "total_price_set": {"shop_money": {"amount": "810.90", "currency_code": "USD"}, "presentment_money": {"amount": "810.90", "currency_code": "USD"}}, "total_price_usd": "810.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:51:55-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565040829, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565040829", "created_at": "2021-07-07T08:51:54-07:00", "location_id": 63590301885, "name": "#1014.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:51:54-07:00", "line_items": [{"id": 10147929456829, "admin_graphql_api_id": "gid://shopify/LineItem/10147929456829", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 288", "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 7, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 288", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147929456829, "admin_graphql_api_id": "gid://shopify/LineItem/10147929456829", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 288", "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 7.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 288", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 791.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:44+00:00", "uuid": "5e1dae00-df3b-11eb-8001-4ff2097be6c2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157003} -{"stream": "events", "data": {"object": "event", "id": "3mjEP4nipR2", "statistic_id": "X3f6PC", "timestamp": 1625673175, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944273576125", "$value": 127.9, "$extra": {"id": 3944273576125, "admin_graphql_api_id": "gid://shopify/Order/3944273576125", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:06-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:04-07:00", "currency": "USD", "current_subtotal_price": "108.00", "current_subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "149.00", "current_total_price_set": {"shop_money": {"amount": "149.00", "currency_code": "USD"}, "presentment_money": {"amount": "149.00", "currency_code": "USD"}}, "current_total_tax": "41.00", "current_total_tax_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1015", "note": null, "note_attributes": [], "number": 15, "order_number": 1015, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a17e0d8b599f74c372de7a42fb01a52a/authenticate?key=72fd72abe69589ce339fff467a036de6", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "a17e0d8b599f74c372de7a42fb01a52a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:52:06-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565237437, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565237437", "created_at": "2021-07-07T08:52:05-07:00", "location_id": 63590301885, "name": "#1015.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:05-07:00", "line_items": [{"id": 10147930669245, "admin_graphql_api_id": "gid://shopify/LineItem/10147930669245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147930669245, "admin_graphql_api_id": "gid://shopify/LineItem/10147930669245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:55+00:00", "uuid": "64ac2580-df3b-11eb-8001-445d0a2279c0", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157004} -{"stream": "events", "data": {"object": "event", "id": "3mjz69Q8gPM", "statistic_id": "X3f6PC", "timestamp": 1625673178, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944273805501", "$value": 127.9, "$extra": {"id": 3944273805501, "admin_graphql_api_id": "gid://shopify/Order/3944273805501", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:08-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:07-07:00", "currency": "USD", "current_subtotal_price": "81.00", "current_subtotal_price_set": {"shop_money": {"amount": "81.00", "currency_code": "USD"}, "presentment_money": {"amount": "81.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "111.75", "current_total_price_set": {"shop_money": {"amount": "111.75", "currency_code": "USD"}, "presentment_money": {"amount": "111.75", "currency_code": "USD"}}, "current_total_tax": "30.75", "current_total_tax_set": {"shop_money": {"amount": "30.75", "currency_code": "USD"}, "presentment_money": {"amount": "30.75", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1016", "note": null, "note_attributes": [], "number": 16, "order_number": 1016, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cd5cd21e8a2856e5bd8aaa0b7392cb12/authenticate?key=ddefed114e6c464ff982f0fb2aef64aa", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:07-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "cd5cd21e8a2856e5bd8aaa0b7392cb12", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-09T02:57:43-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565302973, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565302973", "created_at": "2021-07-07T08:52:08-07:00", "location_id": 63590301885, "name": "#1016.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:08-07:00", "line_items": [{"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [{"id": 828202942653, "admin_graphql_api_id": "gid://shopify/Refund/828202942653", "created_at": "2021-09-09T02:57:43-07:00", "note": "Test refund 123", "processed_at": "2021-09-09T02:57:43-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189745856701, "amount": "29.25", "amount_set": {"shop_money": {"amount": "29.25", "currency_code": "USD"}, "presentment_money": {"amount": "29.25", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828202942653, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5160967209149, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5160967209149", "amount": "8.00", "authorization": null, "created_at": "2021-09-09T02:57:43-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 8.00 from manual gateway", "parent_id": 4944854188221, "processed_at": "2021-09-09T02:57:43-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330958864573, "line_item_id": 10147931127997, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 27.0, "subtotal_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_tax": 10.25, "total_tax_set": {"shop_money": {"amount": "10.25", "currency_code": "USD"}, "presentment_money": {"amount": "10.25", "currency_code": "USD"}}, "line_item": {"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:52:58+00:00", "uuid": "6675e900-df3b-11eb-8001-13e3130dd7a3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157005} -{"stream": "events", "data": {"object": "event", "id": "3mjFszwpR8Z", "statistic_id": "X3f6PC", "timestamp": 1625673180, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944273903805", "$value": 127.9, "$extra": {"id": 3944273903805, "admin_graphql_api_id": "gid://shopify/Order/3944273903805", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:10-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:09-07:00", "currency": "USD", "current_subtotal_price": "108.00", "current_subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "149.00", "current_total_price_set": {"shop_money": {"amount": "149.00", "currency_code": "USD"}, "presentment_money": {"amount": "149.00", "currency_code": "USD"}}, "current_total_tax": "41.00", "current_total_tax_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1017", "note": null, "note_attributes": [], "number": 17, "order_number": 1017, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/78db25709f204c0e68f38ed18a505208/authenticate?key=284a1a59f8d1d722dd6bfa5e96cfea16", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:09-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "78db25709f204c0e68f38ed18a505208", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:52:11-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565401277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565401277", "created_at": "2021-07-07T08:52:10-07:00", "location_id": 63590301885, "name": "#1017.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:10-07:00", "line_items": [{"id": 10147931291837, "admin_graphql_api_id": "gid://shopify/LineItem/10147931291837", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147931291837, "admin_graphql_api_id": "gid://shopify/LineItem/10147931291837", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:53:00+00:00", "uuid": "67a71600-df3b-11eb-8001-71c3d1a089db", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157006} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9WQBNe", "statistic_id": "TspjNE", "timestamp": 1625673301, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944277278909", "$value": 254.9, "$extra": {"id": 3944277278909, "admin_graphql_api_id": "gid://shopify/Order/3944277278909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:42-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:41-07:00", "currency": "USD", "current_subtotal_price": "94.00", "current_subtotal_price_set": {"shop_money": {"amount": "94.00", "currency_code": "USD"}, "presentment_money": {"amount": "94.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "103.60", "current_total_price_set": {"shop_money": {"amount": "103.60", "currency_code": "USD"}, "presentment_money": {"amount": "103.60", "currency_code": "USD"}}, "current_total_tax": "9.60", "current_total_tax_set": {"shop_money": {"amount": "9.60", "currency_code": "USD"}, "presentment_money": {"amount": "9.60", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1018", "note": null, "note_attributes": [], "number": 18, "order_number": 1018, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5c1166ff15a92366fd980ae495457f0f/authenticate?key=7b9c6edf066aa33378eb23bb93308ff4", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:41-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "5c1166ff15a92366fd980ae495457f0f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-09T04:13:54-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568678077, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568678077", "created_at": "2021-07-07T08:54:41-07:00", "location_id": 63590301885, "name": "#1018.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:41-07:00", "line_items": [{"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [{"id": 838038356157, "admin_graphql_api_id": "gid://shopify/Refund/838038356157", "created_at": "2021-12-09T04:13:54-08:00", "note": "Reproducing issue", "processed_at": "2021-12-09T04:13:54-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 196150362301, "amount": "130.40", "amount_set": {"shop_money": {"amount": "130.40", "currency_code": "USD"}, "presentment_money": {"amount": "130.40", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 838038356157, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5389681295549, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5389681295549", "amount": "25.00", "authorization": null, "created_at": "2021-12-09T04:13:54-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 25.00 from manual gateway", "parent_id": 4944858644669, "processed_at": "2021-12-09T04:13:54-08:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 344218894525, "line_item_id": 10147937845437, "location_id": null, "quantity": 3, "restock_type": "no_restock", "subtotal": 141.0, "subtotal_set": {"shop_money": {"amount": "141.00", "currency_code": "USD"}, "presentment_money": {"amount": "141.00", "currency_code": "USD"}}, "total_tax": 14.4, "total_tax_set": {"shop_money": {"amount": "14.40", "currency_code": "USD"}, "presentment_money": {"amount": "14.40", "currency_code": "USD"}}, "line_item": {"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:55:01+00:00", "uuid": "afc63880-df3b-11eb-8001-0ab5a7e725bc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157008} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzcJ4sr", "statistic_id": "TspjNE", "timestamp": 1625673303, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944277344445", "$value": 254.9, "$extra": {"id": 3944277344445, "admin_graphql_api_id": "gid://shopify/Order/3944277344445", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:44-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:43-07:00", "currency": "USD", "current_subtotal_price": "235.00", "current_subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "259.00", "current_total_price_set": {"shop_money": {"amount": "259.00", "currency_code": "USD"}, "presentment_money": {"amount": "259.00", "currency_code": "USD"}}, "current_total_tax": "24.00", "current_total_tax_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1019", "note": null, "note_attributes": [], "number": 19, "order_number": 1019, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8f60c279de5a9e2d74f5b31a753196d1/authenticate?key=5c6814d6c2e4047c6933d98f83c91c16", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:43-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "8f60c279de5a9e2d74f5b31a753196d1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:54:44-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568776381, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568776381", "created_at": "2021-07-07T08:54:43-07:00", "location_id": 63590301885, "name": "#1019.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:43-07:00", "line_items": [{"id": 10147938140349, "admin_graphql_api_id": "gid://shopify/LineItem/10147938140349", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147938140349, "admin_graphql_api_id": "gid://shopify/LineItem/10147938140349", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:55:03+00:00", "uuid": "b0f76580-df3b-11eb-8001-41f8da1aa88a", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157009} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFNU9", "statistic_id": "TspjNE", "timestamp": 1625673305, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3944277409981", "$value": 254.9, "$extra": {"id": 3944277409981, "admin_graphql_api_id": "gid://shopify/Order/3944277409981", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:46-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:45-07:00", "currency": "USD", "current_subtotal_price": "235.00", "current_subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "259.00", "current_total_price_set": {"shop_money": {"amount": "259.00", "currency_code": "USD"}, "presentment_money": {"amount": "259.00", "currency_code": "USD"}}, "current_total_tax": "24.00", "current_total_tax_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1020", "note": null, "note_attributes": [], "number": 20, "order_number": 1020, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8980aed31463ca9e4d8eaee4d586da03/authenticate?key=d1e4922f9eb2aaeaefb92dad8a35b6aa", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:45-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "8980aed31463ca9e4d8eaee4d586da03", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:54:47-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568841917, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568841917", "created_at": "2021-07-07T08:54:45-07:00", "location_id": 63590301885, "name": "#1020.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:45-07:00", "line_items": [{"id": 10147938205885, "admin_graphql_api_id": "gid://shopify/LineItem/10147938205885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147938205885, "admin_graphql_api_id": "gid://shopify/LineItem/10147938205885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:55:05+00:00", "uuid": "b2289280-df3b-11eb-8001-8b73d05427ce", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157009} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWNzN", "statistic_id": "RDXsib", "timestamp": 1625673311, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277278909:10147937845437:0", "$value": 47.0}, "datetime": "2021-07-07 15:55:11+00:00", "uuid": "b5bc1980-df3b-11eb-8001-55852aa6ccdc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "Customer", "$title": "", "$organization": "Test Company", "$phone_number": "", "$email": "airbyte-test@airbyte.com", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157010} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWNzP", "statistic_id": "RDXsib", "timestamp": 1625673311, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277278909:10147937845437:1", "$value": 47.0}, "datetime": "2021-07-07 15:55:11+00:00", "uuid": "b5bc1980-df3b-11eb-8001-634c884555b8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157359} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWNzQ", "statistic_id": "RDXsib", "timestamp": 1625673311, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277278909:10147937845437:2", "$value": 47.0}, "datetime": "2021-07-07 15:55:11+00:00", "uuid": "b5bc1980-df3b-11eb-8001-a09e2fe035c6", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157360} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWNzR", "statistic_id": "RDXsib", "timestamp": 1625673311, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277278909:10147937845437:3", "$value": 47.0}, "datetime": "2021-07-07 15:55:11+00:00", "uuid": "b5bc1980-df3b-11eb-8001-30dc5cc6b3e9", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157360} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWNzS", "statistic_id": "RDXsib", "timestamp": 1625673311, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277278909:10147937845437:4", "$value": 47.0}, "datetime": "2021-07-07 15:55:11+00:00", "uuid": "b5bc1980-df3b-11eb-8001-ce51a647b9bc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157360} -{"stream": "events", "data": {"object": "event", "id": "3mjwxzXdxBQ", "statistic_id": "RDXsib", "timestamp": 1625673313, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277344445:10147938140349:0", "$value": 47.0}, "datetime": "2021-07-07 15:55:13+00:00", "uuid": "b6ed4680-df3b-11eb-8001-83be2cb5d380", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157361} -{"stream": "events", "data": {"object": "event", "id": "3mjwxzXdxBR", "statistic_id": "RDXsib", "timestamp": 1625673313, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277344445:10147938140349:1", "$value": 47.0}, "datetime": "2021-07-07 15:55:13+00:00", "uuid": "b6ed4680-df3b-11eb-8001-046fe6a0699f", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157361} -{"stream": "events", "data": {"object": "event", "id": "3mjwxzXdxBS", "statistic_id": "RDXsib", "timestamp": 1625673313, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277344445:10147938140349:2", "$value": 47.0}, "datetime": "2021-07-07 15:55:13+00:00", "uuid": "b6ed4680-df3b-11eb-8001-d815037c9685", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157361} -{"stream": "events", "data": {"object": "event", "id": "3mjwxzXdxBT", "statistic_id": "RDXsib", "timestamp": 1625673313, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277344445:10147938140349:3", "$value": 47.0}, "datetime": "2021-07-07 15:55:13+00:00", "uuid": "b6ed4680-df3b-11eb-8001-e1a3ac8d07d2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157361} -{"stream": "events", "data": {"object": "event", "id": "3mjwxzXdxBU", "statistic_id": "RDXsib", "timestamp": 1625673313, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277344445:10147938140349:4", "$value": 47.0}, "datetime": "2021-07-07 15:55:13+00:00", "uuid": "b6ed4680-df3b-11eb-8001-d676e3376186", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157362} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLp35", "statistic_id": "RDXsib", "timestamp": 1625673315, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277409981:10147938205885:0", "$value": 47.0}, "datetime": "2021-07-07 15:55:15+00:00", "uuid": "b81e7380-df3b-11eb-8001-29d10c832cdc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157362} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLp36", "statistic_id": "RDXsib", "timestamp": 1625673315, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277409981:10147938205885:1", "$value": 47.0}, "datetime": "2021-07-07 15:55:15+00:00", "uuid": "b81e7380-df3b-11eb-8001-83a01e207de2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157362} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLp37", "statistic_id": "RDXsib", "timestamp": 1625673315, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277409981:10147938205885:2", "$value": 47.0}, "datetime": "2021-07-07 15:55:15+00:00", "uuid": "b81e7380-df3b-11eb-8001-124ed83245f8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157363} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLp38", "statistic_id": "RDXsib", "timestamp": 1625673315, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277409981:10147938205885:3", "$value": 47.0}, "datetime": "2021-07-07 15:55:15+00:00", "uuid": "b81e7380-df3b-11eb-8001-a22eb2793692", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157363} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLp39", "statistic_id": "RDXsib", "timestamp": 1625673315, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 482", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3944277409981:10147938205885:4", "$value": 47.0}, "datetime": "2021-07-07 15:55:15+00:00", "uuid": "b81e7380-df3b-11eb-8001-c5393c520bd8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157364} -{"stream": "events", "data": {"object": "event", "id": "3mjDRAEBGNV", "statistic_id": "X3f6PC", "timestamp": 1625673331, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3944277278909", "$value": 254.9, "$extra": {"id": 3944277278909, "admin_graphql_api_id": "gid://shopify/Order/3944277278909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:42-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:41-07:00", "currency": "USD", "current_subtotal_price": "94.00", "current_subtotal_price_set": {"shop_money": {"amount": "94.00", "currency_code": "USD"}, "presentment_money": {"amount": "94.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "103.60", "current_total_price_set": {"shop_money": {"amount": "103.60", "currency_code": "USD"}, "presentment_money": {"amount": "103.60", "currency_code": "USD"}}, "current_total_tax": "9.60", "current_total_tax_set": {"shop_money": {"amount": "9.60", "currency_code": "USD"}, "presentment_money": {"amount": "9.60", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1018", "note": null, "note_attributes": [], "number": 18, "order_number": 1018, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5c1166ff15a92366fd980ae495457f0f/authenticate?key=7b9c6edf066aa33378eb23bb93308ff4", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:41-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "5c1166ff15a92366fd980ae495457f0f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-09T04:13:54-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568678077, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568678077", "created_at": "2021-07-07T08:54:41-07:00", "location_id": 63590301885, "name": "#1018.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:41-07:00", "line_items": [{"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [{"id": 838038356157, "admin_graphql_api_id": "gid://shopify/Refund/838038356157", "created_at": "2021-12-09T04:13:54-08:00", "note": "Reproducing issue", "processed_at": "2021-12-09T04:13:54-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 196150362301, "amount": "130.40", "amount_set": {"shop_money": {"amount": "130.40", "currency_code": "USD"}, "presentment_money": {"amount": "130.40", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 838038356157, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5389681295549, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5389681295549", "amount": "25.00", "authorization": null, "created_at": "2021-12-09T04:13:54-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 25.00 from manual gateway", "parent_id": 4944858644669, "processed_at": "2021-12-09T04:13:54-08:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 344218894525, "line_item_id": 10147937845437, "location_id": null, "quantity": 3, "restock_type": "no_restock", "subtotal": 141.0, "subtotal_set": {"shop_money": {"amount": "141.00", "currency_code": "USD"}, "presentment_money": {"amount": "141.00", "currency_code": "USD"}}, "total_tax": 14.4, "total_tax_set": {"shop_money": {"amount": "14.40", "currency_code": "USD"}, "presentment_money": {"amount": "14.40", "currency_code": "USD"}}, "line_item": {"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:55:31+00:00", "uuid": "c1a7db80-df3b-11eb-8001-5e411cc230a6", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157364} -{"stream": "events", "data": {"object": "event", "id": "3mjEvbdLw5L", "statistic_id": "X3f6PC", "timestamp": 1625673333, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3944277344445", "$value": 254.9, "$extra": {"id": 3944277344445, "admin_graphql_api_id": "gid://shopify/Order/3944277344445", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:44-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:43-07:00", "currency": "USD", "current_subtotal_price": "235.00", "current_subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "259.00", "current_total_price_set": {"shop_money": {"amount": "259.00", "currency_code": "USD"}, "presentment_money": {"amount": "259.00", "currency_code": "USD"}}, "current_total_tax": "24.00", "current_total_tax_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1019", "note": null, "note_attributes": [], "number": 19, "order_number": 1019, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8f60c279de5a9e2d74f5b31a753196d1/authenticate?key=5c6814d6c2e4047c6933d98f83c91c16", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:43-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "8f60c279de5a9e2d74f5b31a753196d1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:54:44-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568776381, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568776381", "created_at": "2021-07-07T08:54:43-07:00", "location_id": 63590301885, "name": "#1019.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:43-07:00", "line_items": [{"id": 10147938140349, "admin_graphql_api_id": "gid://shopify/LineItem/10147938140349", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147938140349, "admin_graphql_api_id": "gid://shopify/LineItem/10147938140349", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:55:33+00:00", "uuid": "c2d90880-df3b-11eb-8001-c92ca5fec4b5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157367} -{"stream": "events", "data": {"object": "event", "id": "3mjAFdyScvh", "statistic_id": "X3f6PC", "timestamp": 1625673335, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3944277409981", "$value": 254.9, "$extra": {"id": 3944277409981, "admin_graphql_api_id": "gid://shopify/Order/3944277409981", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:46-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:45-07:00", "currency": "USD", "current_subtotal_price": "235.00", "current_subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "259.00", "current_total_price_set": {"shop_money": {"amount": "259.00", "currency_code": "USD"}, "presentment_money": {"amount": "259.00", "currency_code": "USD"}}, "current_total_tax": "24.00", "current_total_tax_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1020", "note": null, "note_attributes": [], "number": 20, "order_number": 1020, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8980aed31463ca9e4d8eaee4d586da03/authenticate?key=d1e4922f9eb2aaeaefb92dad8a35b6aa", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:45-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "8980aed31463ca9e4d8eaee4d586da03", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-07T08:54:47-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568841917, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568841917", "created_at": "2021-07-07T08:54:45-07:00", "location_id": 63590301885, "name": "#1020.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:45-07:00", "line_items": [{"id": 10147938205885, "admin_graphql_api_id": "gid://shopify/LineItem/10147938205885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147938205885, "admin_graphql_api_id": "gid://shopify/LineItem/10147938205885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-07 15:55:35+00:00", "uuid": "c40a3580-df3b-11eb-8001-75bec1c3ccc7", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157371} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhtekfZ", "statistic_id": "TspjNE", "timestamp": 1625737275, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945528426685", "$value": 253.9, "$extra": {"id": 3945528426685, "admin_graphql_api_id": "gid://shopify/Order/3945528426685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:40:56-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:40:55-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1021", "note": null, "note_attributes": [], "number": 21, "order_number": 1021, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/2bf411f200cfdc3a7b22a3f18233bbee/authenticate?key=97da7b365030c4787312728dc771d379", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:40:55-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "2bf411f200cfdc3a7b22a3f18233bbee", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T10:00:16-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836275389, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836275389", "created_at": "2021-07-08T02:40:56-07:00", "location_id": 63590301885, "name": "#1021.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:40:56-07:00", "line_items": [{"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828112240829, "admin_graphql_api_id": "gid://shopify/Refund/828112240829", "created_at": "2021-09-08T10:00:16-07:00", "note": "this is the test refund 3", "processed_at": "2021-09-08T10:00:16-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189684809917, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828112240829, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5159229292733, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159229292733", "amount": "33.00", "authorization": null, "created_at": "2021-09-08T10:00:16-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946429935805, "processed_at": "2021-09-08T10:00:16-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330846077117, "line_item_id": 10150319358141, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:41:15+00:00", "uuid": "a33fef80-dfd0-11eb-8001-b7e6587516ef", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157372} -{"stream": "events", "data": {"object": "event", "id": "3mjz67SWnJL", "statistic_id": "TspjNE", "timestamp": 1625737277, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945528492221", "$value": 253.9, "$extra": {"id": 3945528492221, "admin_graphql_api_id": "gid://shopify/Order/3945528492221", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:40:59-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:40:57-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1022", "note": null, "note_attributes": [], "number": 22, "order_number": 1022, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/728e04f16cca7e5778f921a777c44674/authenticate?key=579159f0efeeebeceb0b16f88c05f6c4", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:40:57-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "728e04f16cca7e5778f921a777c44674", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T07:29:22-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836308157, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836308157", "created_at": "2021-07-08T02:40:58-07:00", "location_id": 63590301885, "name": "#1022.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:40:58-07:00", "line_items": [{"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828219654333, "admin_graphql_api_id": "gid://shopify/Refund/828219654333", "created_at": "2021-09-09T05:51:36-07:00", "note": "test refund 45678", "processed_at": "2021-09-09T05:51:36-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189755228349, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828219654333, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5161264808125, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5161264808125", "amount": "33.00", "authorization": null, "created_at": "2021-09-09T05:51:36-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946430034109, "processed_at": "2021-09-09T05:51:36-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330985668797, "line_item_id": 10150319456445, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:41:17+00:00", "uuid": "a4711c80-dfd0-11eb-8001-717aefa7c3b1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157374} -{"stream": "events", "data": {"object": "event", "id": "3mjs5Tt8Eka", "statistic_id": "TspjNE", "timestamp": 1625737280, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945528524989", "$value": 253.9, "$extra": {"id": 3945528524989, "admin_graphql_api_id": "gid://shopify/Order/3945528524989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:41:01-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:41:00-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1023", "note": null, "note_attributes": [], "number": 23, "order_number": 1023, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/36f0cf097f74f5b0d0a9d32dc2c62192/authenticate?key=477bb707feaee1f177d2dd35cc1af48f", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:41:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "36f0cf097f74f5b0d0a9d32dc2c62192", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T09:42:32-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836340925, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836340925", "created_at": "2021-07-08T02:41:00-07:00", "location_id": 63590301885, "name": "#1023.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:41:00-07:00", "line_items": [{"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828109815997, "admin_graphql_api_id": "gid://shopify/Refund/828109815997", "created_at": "2021-09-08T09:42:32-07:00", "note": "This is the the test ", "processed_at": "2021-09-08T09:42:32-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189683466429, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828109815997, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5159184990397, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159184990397", "amount": "33.00", "authorization": null, "created_at": "2021-09-08T09:42:32-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946430132413, "processed_at": "2021-09-08T09:42:32-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330842570941, "line_item_id": 10150319489213, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:41:20+00:00", "uuid": "a63ae000-dfd0-11eb-8001-3b14d42dd490", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157376} -{"stream": "events", "data": {"object": "event", "id": "3mjDxKtfQeU", "statistic_id": "RDXsib", "timestamp": 1625737285, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528426685:10150319358141:0", "$value": 78.0}, "datetime": "2021-07-08 09:41:25+00:00", "uuid": "a935d080-dfd0-11eb-8001-3a88862df287", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157377} -{"stream": "events", "data": {"object": "event", "id": "3mjDxKtfQeV", "statistic_id": "RDXsib", "timestamp": 1625737285, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528426685:10150319358141:1", "$value": 78.0}, "datetime": "2021-07-08 09:41:25+00:00", "uuid": "a935d080-dfd0-11eb-8001-f094081bdd93", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157378} -{"stream": "events", "data": {"object": "event", "id": "3mjDxKtfQeW", "statistic_id": "RDXsib", "timestamp": 1625737285, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528426685:10150319358141:2", "$value": 78.0}, "datetime": "2021-07-08 09:41:25+00:00", "uuid": "a935d080-dfd0-11eb-8001-d64c325438f2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157378} -{"stream": "events", "data": {"object": "event", "id": "3mjwdMckjwv", "statistic_id": "RDXsib", "timestamp": 1625737287, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528492221:10150319456445:0", "$value": 78.0}, "datetime": "2021-07-08 09:41:27+00:00", "uuid": "aa66fd80-dfd0-11eb-8001-3a34139ce5e4", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157379} -{"stream": "events", "data": {"object": "event", "id": "3mjwdMckjww", "statistic_id": "RDXsib", "timestamp": 1625737287, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528492221:10150319456445:1", "$value": 78.0}, "datetime": "2021-07-08 09:41:27+00:00", "uuid": "aa66fd80-dfd0-11eb-8001-293561bfb4b4", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157379} -{"stream": "events", "data": {"object": "event", "id": "3mjwdMckjwx", "statistic_id": "RDXsib", "timestamp": 1625737287, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528492221:10150319456445:2", "$value": 78.0}, "datetime": "2021-07-08 09:41:27+00:00", "uuid": "aa66fd80-dfd0-11eb-8001-f78b8c642680", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157379} -{"stream": "events", "data": {"object": "event", "id": "3mjvTNcgbM3", "statistic_id": "RDXsib", "timestamp": 1625737290, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528524989:10150319489213:0", "$value": 78.0}, "datetime": "2021-07-08 09:41:30+00:00", "uuid": "ac30c100-dfd0-11eb-8001-9443b354e7fc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157379} -{"stream": "events", "data": {"object": "event", "id": "3mjvTNcgbM4", "statistic_id": "RDXsib", "timestamp": 1625737290, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528524989:10150319489213:1", "$value": 78.0}, "datetime": "2021-07-08 09:41:30+00:00", "uuid": "ac30c100-dfd0-11eb-8001-eecf7c7564e3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157380} -{"stream": "events", "data": {"object": "event", "id": "3mjvTNcgbM5", "statistic_id": "RDXsib", "timestamp": 1625737290, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 429", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945528524989:10150319489213:2", "$value": 78.0}, "datetime": "2021-07-08 09:41:30+00:00", "uuid": "ac30c100-dfd0-11eb-8001-3dd3a68ff798", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157380} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWQdv", "statistic_id": "X3f6PC", "timestamp": 1625737306, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3945528426685", "$value": 253.9, "$extra": {"id": 3945528426685, "admin_graphql_api_id": "gid://shopify/Order/3945528426685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:40:56-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:40:55-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1021", "note": null, "note_attributes": [], "number": 21, "order_number": 1021, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/2bf411f200cfdc3a7b22a3f18233bbee/authenticate?key=97da7b365030c4787312728dc771d379", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:40:55-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "2bf411f200cfdc3a7b22a3f18233bbee", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T10:00:16-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836275389, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836275389", "created_at": "2021-07-08T02:40:56-07:00", "location_id": 63590301885, "name": "#1021.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:40:56-07:00", "line_items": [{"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828112240829, "admin_graphql_api_id": "gid://shopify/Refund/828112240829", "created_at": "2021-09-08T10:00:16-07:00", "note": "this is the test refund 3", "processed_at": "2021-09-08T10:00:16-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189684809917, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828112240829, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5159229292733, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159229292733", "amount": "33.00", "authorization": null, "created_at": "2021-09-08T10:00:16-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946429935805, "processed_at": "2021-09-08T10:00:16-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330846077117, "line_item_id": 10150319358141, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:41:46+00:00", "uuid": "b5ba2900-dfd0-11eb-8001-e145dd8d51bd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157380} -{"stream": "events", "data": {"object": "event", "id": "3mjz69kQ6U4", "statistic_id": "X3f6PC", "timestamp": 1625737308, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3945528492221", "$value": 253.9, "$extra": {"id": 3945528492221, "admin_graphql_api_id": "gid://shopify/Order/3945528492221", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:40:59-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:40:57-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1022", "note": null, "note_attributes": [], "number": 22, "order_number": 1022, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/728e04f16cca7e5778f921a777c44674/authenticate?key=579159f0efeeebeceb0b16f88c05f6c4", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:40:57-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "728e04f16cca7e5778f921a777c44674", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T07:29:22-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836308157, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836308157", "created_at": "2021-07-08T02:40:58-07:00", "location_id": 63590301885, "name": "#1022.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:40:58-07:00", "line_items": [{"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828219654333, "admin_graphql_api_id": "gid://shopify/Refund/828219654333", "created_at": "2021-09-09T05:51:36-07:00", "note": "test refund 45678", "processed_at": "2021-09-09T05:51:36-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189755228349, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828219654333, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5161264808125, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5161264808125", "amount": "33.00", "authorization": null, "created_at": "2021-09-09T05:51:36-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946430034109, "processed_at": "2021-09-09T05:51:36-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330985668797, "line_item_id": 10150319456445, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:41:48+00:00", "uuid": "b6eb5600-dfd0-11eb-8001-a15bdfc9b0f5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157382} -{"stream": "events", "data": {"object": "event", "id": "3mjz69kPK58", "statistic_id": "X3f6PC", "timestamp": 1625737310, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3945528524989", "$value": 253.9, "$extra": {"id": 3945528524989, "admin_graphql_api_id": "gid://shopify/Order/3945528524989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:41:01-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:41:00-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1023", "note": null, "note_attributes": [], "number": 23, "order_number": 1023, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/36f0cf097f74f5b0d0a9d32dc2c62192/authenticate?key=477bb707feaee1f177d2dd35cc1af48f", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:41:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "36f0cf097f74f5b0d0a9d32dc2c62192", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T09:42:32-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836340925, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836340925", "created_at": "2021-07-08T02:41:00-07:00", "location_id": 63590301885, "name": "#1023.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:41:00-07:00", "line_items": [{"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828109815997, "admin_graphql_api_id": "gid://shopify/Refund/828109815997", "created_at": "2021-09-08T09:42:32-07:00", "note": "This is the the test ", "processed_at": "2021-09-08T09:42:32-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189683466429, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828109815997, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5159184990397, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159184990397", "amount": "33.00", "authorization": null, "created_at": "2021-09-08T09:42:32-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946430132413, "processed_at": "2021-09-08T09:42:32-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330842570941, "line_item_id": 10150319489213, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:41:50+00:00", "uuid": "b81c8300-dfd0-11eb-8001-44f053be40ba", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157383} -{"stream": "events", "data": {"object": "event", "id": "3mjFsDU4DV3", "statistic_id": "TspjNE", "timestamp": 1625737376, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945529737405", "$value": 2279.9, "$extra": {"id": 3945529737405, "admin_graphql_api_id": "gid://shopify/Order/3945529737405", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:36-07:00", "currency": "USD", "current_subtotal_price": "2034.00", "current_subtotal_price_set": {"shop_money": {"amount": "2034.00", "currency_code": "USD"}, "presentment_money": {"amount": "2034.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "2099.70", "current_total_price_set": {"shop_money": {"amount": "2099.70", "currency_code": "USD"}, "presentment_money": {"amount": "2099.70", "currency_code": "USD"}}, "current_total_tax": "65.70", "current_total_tax_set": {"shop_money": {"amount": "65.70", "currency_code": "USD"}, "presentment_money": {"amount": "65.70", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1024", "note": null, "note_attributes": [], "number": 24, "order_number": 1024, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b050afca104c8aecc15e9a8ed9820942/authenticate?key=659a6531051a380dee7cde10a506d4be", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:36-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "b050afca104c8aecc15e9a8ed9820942", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-19T06:52:05-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837323965, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837323965", "created_at": "2021-07-08T02:42:37-07:00", "location_id": 63590301885, "name": "#1024.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:42:37-07:00", "line_items": [{"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 812618219709, "admin_graphql_api_id": "gid://shopify/Refund/812618219709", "created_at": "2021-07-19T06:41:46-07:00", "note": "", "processed_at": "2021-07-19T06:41:46-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 180577534141, "amount": "232.30", "amount_set": {"shop_money": {"amount": "232.30", "currency_code": "USD"}, "presentment_money": {"amount": "232.30", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 812618219709, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 4974345715901, "admin_graphql_api_id": "gid://shopify/OrderTransaction/4974345715901", "amount": "1.00", "authorization": null, "created_at": "2021-07-19T06:41:45-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431770813, "processed_at": "2021-07-19T06:41:45-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 305405755581, "line_item_id": 10150321979581, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 226.0, "subtotal_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "total_tax": 7.3, "total_tax_set": {"shop_money": {"amount": "7.30", "currency_code": "USD"}, "presentment_money": {"amount": "7.30", "currency_code": "USD"}}, "line_item": {"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:42:56+00:00", "uuid": "df735000-dfd0-11eb-8001-afed3da2c583", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157384} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTa6m", "statistic_id": "TspjNE", "timestamp": 1625737378, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945529802941", "$value": 2279.9, "$extra": {"id": 3945529802941, "admin_graphql_api_id": "gid://shopify/Order/3945529802941", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:38-07:00", "currency": "USD", "current_subtotal_price": "1808.00", "current_subtotal_price_set": {"shop_money": {"amount": "1808.00", "currency_code": "USD"}, "presentment_money": {"amount": "1808.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "1866.40", "current_total_price_set": {"shop_money": {"amount": "1866.40", "currency_code": "USD"}, "presentment_money": {"amount": "1866.40", "currency_code": "USD"}}, "current_total_tax": "58.40", "current_total_tax_set": {"shop_money": {"amount": "58.40", "currency_code": "USD"}, "presentment_money": {"amount": "58.40", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1025", "note": null, "note_attributes": [], "number": 25, "order_number": 1025, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8031f8453a0db6300e60109c56efcd1/authenticate?key=b1d625a28779e1a460613528f6f75d0f", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:38-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "b8031f8453a0db6300e60109c56efcd1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-07T20:54:38-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837356733, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837356733", "created_at": "2021-07-08T02:42:38-07:00", "location_id": 63590301885, "name": "#1025.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:42:38-07:00", "line_items": [{"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 837898993853, "admin_graphql_api_id": "gid://shopify/Refund/837898993853", "created_at": "2021-12-07T20:54:37-08:00", "note": "Test order updated_at", "processed_at": "2021-12-07T20:54:37-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 196045734077, "amount": "465.60", "amount_set": {"shop_money": {"amount": "465.60", "currency_code": "USD"}, "presentment_money": {"amount": "465.60", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 837898993853, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5386423009469, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5386423009469", "amount": "1.00", "authorization": null, "created_at": "2021-12-07T20:54:37-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431869117, "processed_at": "2021-12-07T20:54:37-08:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 344055906493, "line_item_id": 10150322045117, "location_id": null, "quantity": 2, "restock_type": "no_restock", "subtotal": 452.0, "subtotal_set": {"shop_money": {"amount": "452.00", "currency_code": "USD"}, "presentment_money": {"amount": "452.00", "currency_code": "USD"}}, "total_tax": 14.6, "total_tax_set": {"shop_money": {"amount": "14.60", "currency_code": "USD"}, "presentment_money": {"amount": "14.60", "currency_code": "USD"}}, "line_item": {"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:42:58+00:00", "uuid": "e0a47d00-dfd0-11eb-8001-b6deb2951cab", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157385} -{"stream": "events", "data": {"object": "event", "id": "3mjFsDU4KFc", "statistic_id": "TspjNE", "timestamp": 1625737380, "event_name": "Placed Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945529835709", "$value": 2279.9, "$extra": {"id": 3945529835709, "admin_graphql_api_id": "gid://shopify/Order/3945529835709", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:40-07:00", "currency": "USD", "current_subtotal_price": "1808.00", "current_subtotal_price_set": {"shop_money": {"amount": "1808.00", "currency_code": "USD"}, "presentment_money": {"amount": "1808.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "1866.40", "current_total_price_set": {"shop_money": {"amount": "1866.40", "currency_code": "USD"}, "presentment_money": {"amount": "1866.40", "currency_code": "USD"}}, "current_total_tax": "58.40", "current_total_tax_set": {"shop_money": {"amount": "58.40", "currency_code": "USD"}, "presentment_money": {"amount": "58.40", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1026", "note": null, "note_attributes": [], "number": 26, "order_number": 1026, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fe9363a19b4720b42d66383d180c27f9/authenticate?key=7af240470595f35f311fc68fa69af05c", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:40-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "fe9363a19b4720b42d66383d180c27f9", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-02-22T01:05:13-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837422269, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837422269", "created_at": "2021-07-08T02:42:40-07:00", "location_id": 63590301885, "name": "#1026.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "1243557", "tracking_numbers": ["1243557"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-02-22T01:04:24-08:00", "line_items": [{"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 812618088637, "admin_graphql_api_id": "gid://shopify/Refund/812618088637", "created_at": "2021-07-19T06:40:57-07:00", "note": "Test Refund", "processed_at": "2021-07-19T06:40:57-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 180577435837, "amount": "465.60", "amount_set": {"shop_money": {"amount": "465.60", "currency_code": "USD"}, "presentment_money": {"amount": "465.60", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 812618088637, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 4974344470717, "admin_graphql_api_id": "gid://shopify/OrderTransaction/4974344470717", "amount": "1.00", "authorization": null, "created_at": "2021-07-19T06:40:56-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431934653, "processed_at": "2021-07-19T06:40:56-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 305405591741, "line_item_id": 10150322077885, "location_id": null, "quantity": 2, "restock_type": "no_restock", "subtotal": 452.0, "subtotal_set": {"shop_money": {"amount": "452.00", "currency_code": "USD"}, "presentment_money": {"amount": "452.00", "currency_code": "USD"}}, "total_tax": 14.6, "total_tax_set": {"shop_money": {"amount": "14.60", "currency_code": "USD"}, "presentment_money": {"amount": "14.60", "currency_code": "USD"}}, "line_item": {"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_address": {"first_name": "Airbyte", "address1": "san ", "phone": "", "city": "San Francisco", "zip": "91326", "province": "California", "country": "United States", "last_name": "Test", "address2": "2123", "company": "Airbyte", "latitude": 34.2928607, "longitude": -118.5703644, "name": "Airbyte Test", "country_code": "US", "province_code": "CA"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:43:00+00:00", "uuid": "e1d5aa00-dfd0-11eb-8001-78204f00ae93", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157387} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaq6", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:0", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-c5fb113b2990", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157387} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaq7", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:1", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-eac9fc3545bb", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157388} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaq8", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:2", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-75a9303f9efb", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157388} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaq9", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:3", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-23467fe324cf", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157389} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaqa", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:4", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-e5c9d5705b93", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157389} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaqb", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:5", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-65b0a08915a7", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157389} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaqc", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:6", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-053630e3cbdd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157390} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaqd", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:7", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-5737c6231cc3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157390} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaqe", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:8", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-1125d6d828e2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157391} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaqf", "statistic_id": "RDXsib", "timestamp": 1625737386, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529737405:10150321979581:9", "$value": 226.0}, "datetime": "2021-07-08 09:43:06+00:00", "uuid": "e5693100-dfd0-11eb-8001-072acdf51299", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157391} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeC", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:0", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-08d37872cae6", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157391} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeD", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:1", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-750aeff26b83", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157391} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeE", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:2", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-077e0c068b9d", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157392} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeF", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:3", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-a6086f8d6edd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157392} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeG", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:4", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-446b7e7226f9", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157392} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeH", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:5", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-e3351edeb0d9", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157392} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeJ", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:6", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-50300f0508ae", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157404} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeK", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:7", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-746ef7789bc5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157404} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeL", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:8", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-e49c26f3198e", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157405} -{"stream": "events", "data": {"object": "event", "id": "3mjzHQePNeM", "statistic_id": "RDXsib", "timestamp": 1625737388, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529802941:10150322045117:9", "$value": 226.0}, "datetime": "2021-07-08 09:43:08+00:00", "uuid": "e69a5e00-dfd0-11eb-8001-a6d7d1a689f2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157405} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUa", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:0", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-3f0e4f988caf", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157405} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUb", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:1", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-facebdc6f8dd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157405} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUc", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:2", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-507527971ba2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157406} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUd", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:3", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-1bdaa0dca9b1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157406} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUe", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:4", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-4cc4472a14e3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157406} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUf", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:5", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-5852c293c088", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157407} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUg", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:6", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-81b2bbed2dcc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157407} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUh", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:7", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-04fe19af87f1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157408} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUi", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:8", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-167bbe2248f2", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157408} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFJUj", "statistic_id": "RDXsib", "timestamp": 1625737390, "event_name": "Ordered Product", "event_properties": {"ProductID": null, "Name": "Test Order 196", "Variant Name": null, "SKU": null, "Collections": [], "Tags": [], "Quantity": 1, "$event_id": "3945529835709:10150322077885:9", "$value": 226.0}, "datetime": "2021-07-08 09:43:10+00:00", "uuid": "e7cb8b00-dfd0-11eb-8001-2d48195072a5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157409} -{"stream": "events", "data": {"object": "event", "id": "3mjAFjTH6iB", "statistic_id": "X3f6PC", "timestamp": 1625737407, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3945529737405", "$value": 2279.9, "$extra": {"id": 3945529737405, "admin_graphql_api_id": "gid://shopify/Order/3945529737405", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:36-07:00", "currency": "USD", "current_subtotal_price": "2034.00", "current_subtotal_price_set": {"shop_money": {"amount": "2034.00", "currency_code": "USD"}, "presentment_money": {"amount": "2034.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "2099.70", "current_total_price_set": {"shop_money": {"amount": "2099.70", "currency_code": "USD"}, "presentment_money": {"amount": "2099.70", "currency_code": "USD"}}, "current_total_tax": "65.70", "current_total_tax_set": {"shop_money": {"amount": "65.70", "currency_code": "USD"}, "presentment_money": {"amount": "65.70", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1024", "note": null, "note_attributes": [], "number": 24, "order_number": 1024, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b050afca104c8aecc15e9a8ed9820942/authenticate?key=659a6531051a380dee7cde10a506d4be", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:36-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "b050afca104c8aecc15e9a8ed9820942", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-19T06:52:05-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837323965, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837323965", "created_at": "2021-07-08T02:42:37-07:00", "location_id": 63590301885, "name": "#1024.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:42:37-07:00", "line_items": [{"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 812618219709, "admin_graphql_api_id": "gid://shopify/Refund/812618219709", "created_at": "2021-07-19T06:41:46-07:00", "note": "", "processed_at": "2021-07-19T06:41:46-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 180577534141, "amount": "232.30", "amount_set": {"shop_money": {"amount": "232.30", "currency_code": "USD"}, "presentment_money": {"amount": "232.30", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 812618219709, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 4974345715901, "admin_graphql_api_id": "gid://shopify/OrderTransaction/4974345715901", "amount": "1.00", "authorization": null, "created_at": "2021-07-19T06:41:45-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431770813, "processed_at": "2021-07-19T06:41:45-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 305405755581, "line_item_id": 10150321979581, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 226.0, "subtotal_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "total_tax": 7.3, "total_tax_set": {"shop_money": {"amount": "7.30", "currency_code": "USD"}, "presentment_money": {"amount": "7.30", "currency_code": "USD"}}, "line_item": {"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:43:27+00:00", "uuid": "f1ed8980-dfd0-11eb-8001-15c2aaf4c7cd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157409} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV82dH", "statistic_id": "X3f6PC", "timestamp": 1625737408, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3945529802941", "$value": 2279.9, "$extra": {"id": 3945529802941, "admin_graphql_api_id": "gid://shopify/Order/3945529802941", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:38-07:00", "currency": "USD", "current_subtotal_price": "1808.00", "current_subtotal_price_set": {"shop_money": {"amount": "1808.00", "currency_code": "USD"}, "presentment_money": {"amount": "1808.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "1866.40", "current_total_price_set": {"shop_money": {"amount": "1866.40", "currency_code": "USD"}, "presentment_money": {"amount": "1866.40", "currency_code": "USD"}}, "current_total_tax": "58.40", "current_total_tax_set": {"shop_money": {"amount": "58.40", "currency_code": "USD"}, "presentment_money": {"amount": "58.40", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1025", "note": null, "note_attributes": [], "number": 25, "order_number": 1025, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8031f8453a0db6300e60109c56efcd1/authenticate?key=b1d625a28779e1a460613528f6f75d0f", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:38-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "b8031f8453a0db6300e60109c56efcd1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-07T20:54:38-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837356733, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837356733", "created_at": "2021-07-08T02:42:38-07:00", "location_id": 63590301885, "name": "#1025.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:42:38-07:00", "line_items": [{"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 837898993853, "admin_graphql_api_id": "gid://shopify/Refund/837898993853", "created_at": "2021-12-07T20:54:37-08:00", "note": "Test order updated_at", "processed_at": "2021-12-07T20:54:37-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 196045734077, "amount": "465.60", "amount_set": {"shop_money": {"amount": "465.60", "currency_code": "USD"}, "presentment_money": {"amount": "465.60", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 837898993853, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5386423009469, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5386423009469", "amount": "1.00", "authorization": null, "created_at": "2021-12-07T20:54:37-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431869117, "processed_at": "2021-12-07T20:54:37-08:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 344055906493, "line_item_id": 10150322045117, "location_id": null, "quantity": 2, "restock_type": "no_restock", "subtotal": 452.0, "subtotal_set": {"shop_money": {"amount": "452.00", "currency_code": "USD"}, "presentment_money": {"amount": "452.00", "currency_code": "USD"}}, "total_tax": 14.6, "total_tax_set": {"shop_money": {"amount": "14.60", "currency_code": "USD"}, "presentment_money": {"amount": "14.60", "currency_code": "USD"}}, "line_item": {"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:43:28+00:00", "uuid": "f2862000-dfd0-11eb-8001-c4e3b853f7fd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157410} -{"stream": "events", "data": {"object": "event", "id": "3mjAFexsQdX", "statistic_id": "X3f6PC", "timestamp": 1625737410, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 5496, "HasPartialFulfillments": false, "$event_id": "3945529835709", "$value": 2279.9, "$extra": {"id": 3945529835709, "admin_graphql_api_id": "gid://shopify/Order/3945529835709", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:40-07:00", "currency": "USD", "current_subtotal_price": "1808.00", "current_subtotal_price_set": {"shop_money": {"amount": "1808.00", "currency_code": "USD"}, "presentment_money": {"amount": "1808.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "1866.40", "current_total_price_set": {"shop_money": {"amount": "1866.40", "currency_code": "USD"}, "presentment_money": {"amount": "1866.40", "currency_code": "USD"}}, "current_total_tax": "58.40", "current_total_tax_set": {"shop_money": {"amount": "58.40", "currency_code": "USD"}, "presentment_money": {"amount": "58.40", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1026", "note": null, "note_attributes": [], "number": 26, "order_number": 1026, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fe9363a19b4720b42d66383d180c27f9/authenticate?key=7af240470595f35f311fc68fa69af05c", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:40-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "fe9363a19b4720b42d66383d180c27f9", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-02-22T01:05:13-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837422269, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837422269", "created_at": "2021-07-08T02:42:40-07:00", "location_id": 63590301885, "name": "#1026.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "1243557", "tracking_numbers": ["1243557"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-02-22T01:04:24-08:00", "line_items": [{"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 812618088637, "admin_graphql_api_id": "gid://shopify/Refund/812618088637", "created_at": "2021-07-19T06:40:57-07:00", "note": "Test Refund", "processed_at": "2021-07-19T06:40:57-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 180577435837, "amount": "465.60", "amount_set": {"shop_money": {"amount": "465.60", "currency_code": "USD"}, "presentment_money": {"amount": "465.60", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 812618088637, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 4974344470717, "admin_graphql_api_id": "gid://shopify/OrderTransaction/4974344470717", "amount": "1.00", "authorization": null, "created_at": "2021-07-19T06:40:56-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431934653, "processed_at": "2021-07-19T06:40:56-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 305405591741, "line_item_id": 10150322077885, "location_id": null, "quantity": 2, "restock_type": "no_restock", "subtotal": 452.0, "subtotal_set": {"shop_money": {"amount": "452.00", "currency_code": "USD"}, "presentment_money": {"amount": "452.00", "currency_code": "USD"}}, "total_tax": 14.6, "total_tax_set": {"shop_money": {"amount": "14.60", "currency_code": "USD"}, "presentment_money": {"amount": "14.60", "currency_code": "USD"}}, "line_item": {"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_address": {"first_name": "Airbyte", "address1": "san ", "phone": "", "city": "San Francisco", "zip": "91326", "province": "California", "country": "United States", "last_name": "Test", "address2": "2123", "company": "Airbyte", "latitude": 34.2928607, "longitude": -118.5703644, "name": "Airbyte Test", "country_code": "US", "province_code": "CA"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 09:43:30+00:00", "uuid": "f3b74d00-dfd0-11eb-8001-8d60919d25a5", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157411} -{"stream": "events", "data": {"object": "event", "id": "3mjAFjTHdRM", "statistic_id": "TspjNE", "timestamp": 1625738582, "event_name": "Placed Order", "event_properties": {"Items": ["Beard Grooming Oil"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945552412861", "$value": 57.0, "$extra": {"id": 3945552412861, "admin_graphql_api_id": "gid://shopify/Order/3945552412861", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "jane@example.com", "created_at": "2021-07-08T03:02:42-07:00", "currency": "USD", "current_subtotal_price": "57.00", "current_subtotal_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.00", "current_total_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "jane@example.com", "estimated_taxes": false, "financial_status": "partially_paid", "fulfillment_status": null, "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1027", "note": null, "note_attributes": [], "number": 27, "order_number": 1027, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6c43a24d7a07f3508a53d061f3a0dc2f/authenticate?key=f761eb60728f111957ad61331d541142", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T03:02:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "57.00", "subtotal_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6c43a24d7a07f3508a53d061f3a0dc2f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "57.00", "total_line_items_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "total_outstanding": "7.00", "total_price": "57.00", "total_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "total_price_usd": "57.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-08T03:02:43-07:00", "user_id": null, "billing_address": {"first_name": "John", "address1": "123 Fake Street", "phone": "555-555-5555", "city": "Fakecity", "zip": "K2P 1L4", "province": "Ontario", "country": "Canada", "last_name": "Smith", "address2": null, "company": null, "latitude": 45.4203521, "longitude": -75.69314750000001, "name": "John Smith", "country_code": "CA", "province_code": "ON"}, "customer": {"id": 5361694179517, "email": "paul.norman@example.com", "accepts_marketing": false, "created_at": "2021-07-08T03:02:42-07:00", "updated_at": "2021-07-08T03:02:46-07:00", "first_name": "Paul", "last_name": "Norman", "orders_count": 3, "state": "disabled", "total_spent": "171.00", "last_order_id": 3945552707773, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1029", "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T03:02:42-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5361694179517", "default_address": {"id": 6602791911613, "customer_id": 5361694179517, "first_name": "Jane", "last_name": "Smith", "company": null, "address1": "123 Fake Street", "address2": null, "city": "Fakecity", "province": "Ontario", "country": "Canada", "zip": "K2P 1L4", "phone": "777-777-7777", "name": "Jane Smith", "province_code": "ON", "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10150365790397, "admin_graphql_api_id": "gid://shopify/LineItem/10150365790397", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Beard Grooming Oil - indigo", "price": 57.0, "price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796233408701, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Beard Grooming Oil", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090611843261, "variant_inventory_management": "shopify", "variant_title": "indigo", "vendor": "Jenkins LLC", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 57.0, "product": {"id": 6796233408701, "title": "Beard Grooming Oil", "handle": "beard-grooming-oil", "vendor": "Jenkins LLC", "tags": "developer-tools-generator", "body_html": "Close up beard grooming oil on a barbers table with towel and other tools.", "product_type": "Home", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/beard-grooming-oil.jpg?v=1624410676", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/beard-grooming-oil_x240.jpg?v=1624410676", "alt": null, "width": 2997, "height": 2000, "position": 1, "variant_ids": [], "id": 29301305770173, "created_at": "2021-06-22T18:11:16-07:00", "updated_at": "2021-06-22T18:11:16-07:00"}], "variant": {"sku": 40090611843261, "title": "indigo", "options": {"Title": "indigo"}, "images": []}, "variant_options": {"Title": "indigo"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Jane", "address1": "123 Fake Street", "phone": "777-777-7777", "city": "Fakecity", "zip": "K2P 1L4", "province": "Ontario", "country": "Canada", "last_name": "Smith", "address2": null, "company": null, "latitude": 45.4203521, "longitude": -75.69314750000001, "name": "Jane Smith", "country_code": "CA", "province_code": "ON"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 10:03:02+00:00", "uuid": "ae484f00-dfd3-11eb-8001-9b92bb91eee1", "person": {"object": "person", "id": "01G4CDT99QE4509WB08044XAWD", "$address1": "123 Fake Street", "$address2": "", "$city": "Fakecity", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Ontario", "$zip": "K2P 1L4", "$organization": "", "$email": "paul.norman@example.com", "$phone_number": "+15555555555", "$title": "", "$last_name": "Norman", "$first_name": "Paul", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "paul.norman@example.com", "first_name": "Paul", "last_name": "Norman", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157412} -{"stream": "events", "data": {"object": "event", "id": "3mjEbh6DJPT", "statistic_id": "TspjNE", "timestamp": 1625738584, "event_name": "Placed Order", "event_properties": {"Items": ["Beard Grooming Oil"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945552642237", "$value": 57.0, "$extra": {"id": 3945552642237, "admin_graphql_api_id": "gid://shopify/Order/3945552642237", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "jane@example.com", "created_at": "2021-07-08T03:02:44-07:00", "currency": "USD", "current_subtotal_price": "57.00", "current_subtotal_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.00", "current_total_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "jane@example.com", "estimated_taxes": false, "financial_status": "partially_paid", "fulfillment_status": null, "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1028", "note": null, "note_attributes": [], "number": 28, "order_number": 1028, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8015d389cd3caad8d64b4c45e8063ca1/authenticate?key=6d21570a7a624db89cead0e5a91f1aae", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T03:02:44-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "57.00", "subtotal_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8015d389cd3caad8d64b4c45e8063ca1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "57.00", "total_line_items_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "total_outstanding": "7.00", "total_price": "57.00", "total_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "total_price_usd": "57.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-08T03:02:45-07:00", "user_id": null, "billing_address": {"first_name": "John", "address1": "123 Fake Street", "phone": "555-555-5555", "city": "Fakecity", "zip": "K2P 1L4", "province": "Ontario", "country": "Canada", "last_name": "Smith", "address2": null, "company": null, "latitude": 45.4203521, "longitude": -75.69314750000001, "name": "John Smith", "country_code": "CA", "province_code": "ON"}, "customer": {"id": 5361694179517, "email": "paul.norman@example.com", "accepts_marketing": false, "created_at": "2021-07-08T03:02:42-07:00", "updated_at": "2021-07-08T03:02:46-07:00", "first_name": "Paul", "last_name": "Norman", "orders_count": 3, "state": "disabled", "total_spent": "171.00", "last_order_id": 3945552707773, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1029", "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T03:02:42-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5361694179517", "default_address": {"id": 6602791911613, "customer_id": 5361694179517, "first_name": "Jane", "last_name": "Smith", "company": null, "address1": "123 Fake Street", "address2": null, "city": "Fakecity", "province": "Ontario", "country": "Canada", "zip": "K2P 1L4", "phone": "777-777-7777", "name": "Jane Smith", "province_code": "ON", "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10150366183613, "admin_graphql_api_id": "gid://shopify/LineItem/10150366183613", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Beard Grooming Oil - indigo", "price": 57.0, "price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796233408701, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Beard Grooming Oil", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090611843261, "variant_inventory_management": "shopify", "variant_title": "indigo", "vendor": "Jenkins LLC", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 57.0, "product": {"id": 6796233408701, "title": "Beard Grooming Oil", "handle": "beard-grooming-oil", "vendor": "Jenkins LLC", "tags": "developer-tools-generator", "body_html": "Close up beard grooming oil on a barbers table with towel and other tools.", "product_type": "Home", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/beard-grooming-oil.jpg?v=1624410676", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/beard-grooming-oil_x240.jpg?v=1624410676", "alt": null, "width": 2997, "height": 2000, "position": 1, "variant_ids": [], "id": 29301305770173, "created_at": "2021-06-22T18:11:16-07:00", "updated_at": "2021-06-22T18:11:16-07:00"}], "variant": {"sku": 40090611843261, "title": "indigo", "options": {"Title": "indigo"}, "images": []}, "variant_options": {"Title": "indigo"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Jane", "address1": "123 Fake Street", "phone": "777-777-7777", "city": "Fakecity", "zip": "K2P 1L4", "province": "Ontario", "country": "Canada", "last_name": "Smith", "address2": null, "company": null, "latitude": 45.4203521, "longitude": -75.69314750000001, "name": "Jane Smith", "country_code": "CA", "province_code": "ON"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 10:03:04+00:00", "uuid": "af797c00-dfd3-11eb-8001-215e64a42fee", "person": {"object": "person", "id": "01G4CDT99QE4509WB08044XAWD", "$address1": "123 Fake Street", "$address2": "", "$city": "Fakecity", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Ontario", "$zip": "K2P 1L4", "$organization": "", "$email": "paul.norman@example.com", "$phone_number": "+15555555555", "$title": "", "$last_name": "Norman", "$first_name": "Paul", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "paul.norman@example.com", "first_name": "Paul", "last_name": "Norman", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157413} -{"stream": "events", "data": {"object": "event", "id": "3mjEv6kPakU", "statistic_id": "TspjNE", "timestamp": 1625738585, "event_name": "Placed Order", "event_properties": {"Items": ["Beard Grooming Oil"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "3945552707773", "$value": 57.0, "$extra": {"id": 3945552707773, "admin_graphql_api_id": "gid://shopify/Order/3945552707773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "jane@example.com", "created_at": "2021-07-08T03:02:45-07:00", "currency": "USD", "current_subtotal_price": "57.00", "current_subtotal_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.00", "current_total_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "jane@example.com", "estimated_taxes": false, "financial_status": "partially_paid", "fulfillment_status": null, "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1029", "note": null, "note_attributes": [], "number": 29, "order_number": 1029, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/dcb4b30c2a5d736d080065ee4881eee3/authenticate?key=cabaa16b5b339a9063eaf7d734ee05cb", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T03:02:45-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "57.00", "subtotal_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "dcb4b30c2a5d736d080065ee4881eee3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "57.00", "total_line_items_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "total_outstanding": "7.00", "total_price": "57.00", "total_price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "total_price_usd": "57.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-08T03:02:46-07:00", "user_id": null, "billing_address": {"first_name": "John", "address1": "123 Fake Street", "phone": "555-555-5555", "city": "Fakecity", "zip": "K2P 1L4", "province": "Ontario", "country": "Canada", "last_name": "Smith", "address2": null, "company": null, "latitude": 45.4203521, "longitude": -75.69314750000001, "name": "John Smith", "country_code": "CA", "province_code": "ON"}, "customer": {"id": 5361694179517, "email": "paul.norman@example.com", "accepts_marketing": false, "created_at": "2021-07-08T03:02:42-07:00", "updated_at": "2021-07-08T03:02:46-07:00", "first_name": "Paul", "last_name": "Norman", "orders_count": 3, "state": "disabled", "total_spent": "171.00", "last_order_id": 3945552707773, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1029", "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T03:02:42-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5361694179517", "default_address": {"id": 6602791911613, "customer_id": 5361694179517, "first_name": "Jane", "last_name": "Smith", "company": null, "address1": "123 Fake Street", "address2": null, "city": "Fakecity", "province": "Ontario", "country": "Canada", "zip": "K2P 1L4", "phone": "777-777-7777", "name": "Jane Smith", "province_code": "ON", "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [], "line_items": [{"id": 10150366216381, "admin_graphql_api_id": "gid://shopify/LineItem/10150366216381", "fulfillable_quantity": 1, "fulfillment_service": "manual", "fulfillment_status": null, "gift_card": false, "grams": 0, "name": "Beard Grooming Oil - indigo", "price": 57.0, "price_set": {"shop_money": {"amount": "57.00", "currency_code": "USD"}, "presentment_money": {"amount": "57.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796233408701, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Beard Grooming Oil", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090611843261, "variant_inventory_management": "shopify", "variant_title": "indigo", "vendor": "Jenkins LLC", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 57.0, "product": {"id": 6796233408701, "title": "Beard Grooming Oil", "handle": "beard-grooming-oil", "vendor": "Jenkins LLC", "tags": "developer-tools-generator", "body_html": "Close up beard grooming oil on a barbers table with towel and other tools.", "product_type": "Home", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/beard-grooming-oil.jpg?v=1624410676", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/beard-grooming-oil_x240.jpg?v=1624410676", "alt": null, "width": 2997, "height": 2000, "position": 1, "variant_ids": [], "id": 29301305770173, "created_at": "2021-06-22T18:11:16-07:00", "updated_at": "2021-06-22T18:11:16-07:00"}], "variant": {"sku": 40090611843261, "title": "indigo", "options": {"Title": "indigo"}, "images": []}, "variant_options": {"Title": "indigo"}}}], "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Jane", "address1": "123 Fake Street", "phone": "777-777-7777", "city": "Fakecity", "zip": "K2P 1L4", "province": "Ontario", "country": "Canada", "last_name": "Smith", "address2": null, "company": null, "latitude": 45.4203521, "longitude": -75.69314750000001, "name": "Jane Smith", "country_code": "CA", "province_code": "ON"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-08 10:03:05+00:00", "uuid": "b0121280-dfd3-11eb-8001-575d13a5deea", "person": {"object": "person", "id": "01G4CDT99QE4509WB08044XAWD", "$address1": "123 Fake Street", "$address2": "", "$city": "Fakecity", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Ontario", "$zip": "K2P 1L4", "$organization": "", "$email": "paul.norman@example.com", "$phone_number": "+15555555555", "$title": "", "$last_name": "Norman", "$first_name": "Paul", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "paul.norman@example.com", "first_name": "Paul", "last_name": "Norman", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157414} -{"stream": "events", "data": {"object": "event", "id": "3mjAFcAgWBY", "statistic_id": "R2WpFy", "timestamp": 1626702725, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3945529737405", "$value": 2279.9, "$extra": {"id": 3945529737405, "admin_graphql_api_id": "gid://shopify/Order/3945529737405", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:36-07:00", "currency": "USD", "current_subtotal_price": "2034.00", "current_subtotal_price_set": {"shop_money": {"amount": "2034.00", "currency_code": "USD"}, "presentment_money": {"amount": "2034.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "2099.70", "current_total_price_set": {"shop_money": {"amount": "2099.70", "currency_code": "USD"}, "presentment_money": {"amount": "2099.70", "currency_code": "USD"}}, "current_total_tax": "65.70", "current_total_tax_set": {"shop_money": {"amount": "65.70", "currency_code": "USD"}, "presentment_money": {"amount": "65.70", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1024", "note": null, "note_attributes": [], "number": 24, "order_number": 1024, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b050afca104c8aecc15e9a8ed9820942/authenticate?key=659a6531051a380dee7cde10a506d4be", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:36-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "b050afca104c8aecc15e9a8ed9820942", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-07-19T06:52:05-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837323965, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837323965", "created_at": "2021-07-08T02:42:37-07:00", "location_id": 63590301885, "name": "#1024.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:42:37-07:00", "line_items": [{"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 812618219709, "admin_graphql_api_id": "gid://shopify/Refund/812618219709", "created_at": "2021-07-19T06:41:46-07:00", "note": "", "processed_at": "2021-07-19T06:41:46-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 180577534141, "amount": "232.30", "amount_set": {"shop_money": {"amount": "232.30", "currency_code": "USD"}, "presentment_money": {"amount": "232.30", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 812618219709, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 4974345715901, "admin_graphql_api_id": "gid://shopify/OrderTransaction/4974345715901", "amount": "1.00", "authorization": null, "created_at": "2021-07-19T06:41:45-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431770813, "processed_at": "2021-07-19T06:41:45-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 305405755581, "line_item_id": 10150321979581, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 226.0, "subtotal_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "total_tax": 7.3, "total_tax_set": {"shop_money": {"amount": "7.30", "currency_code": "USD"}, "presentment_money": {"amount": "7.30", "currency_code": "USD"}}, "line_item": {"id": 10150321979581, "admin_graphql_api_id": "gid://shopify/LineItem/10150321979581", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-07-19 13:52:05+00:00", "uuid": "804af080-e898-11eb-8001-3ee6662724e8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157416} -{"stream": "events", "data": {"object": "event", "id": "3mjwxx3spn7", "statistic_id": "R2WpFy", "timestamp": 1631119352, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3945528524989", "$value": 253.9, "$extra": {"id": 3945528524989, "admin_graphql_api_id": "gid://shopify/Order/3945528524989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:41:01-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:41:00-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1023", "note": null, "note_attributes": [], "number": 23, "order_number": 1023, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/36f0cf097f74f5b0d0a9d32dc2c62192/authenticate?key=477bb707feaee1f177d2dd35cc1af48f", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:41:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "36f0cf097f74f5b0d0a9d32dc2c62192", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T09:42:32-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836340925, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836340925", "created_at": "2021-07-08T02:41:00-07:00", "location_id": 63590301885, "name": "#1023.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:41:00-07:00", "line_items": [{"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828109815997, "admin_graphql_api_id": "gid://shopify/Refund/828109815997", "created_at": "2021-09-08T09:42:32-07:00", "note": "This is the the test ", "processed_at": "2021-09-08T09:42:32-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189683466429, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828109815997, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5159184990397, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159184990397", "amount": "33.00", "authorization": null, "created_at": "2021-09-08T09:42:32-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946430132413, "processed_at": "2021-09-08T09:42:32-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330842570941, "line_item_id": 10150319489213, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319489213, "admin_graphql_api_id": "gid://shopify/LineItem/10150319489213", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-08 16:42:32+00:00", "uuid": "c3208c00-10c3-11ec-8001-8dcba42897e3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157417} -{"stream": "events", "data": {"object": "event", "id": "3mjz67pDRhr", "statistic_id": "R2WpFy", "timestamp": 1631119796, "event_name": "Refunded Order", "event_properties": {"Items": ["Big Brown Bear Boots"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "UAH", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3944254079165", "$value": 238.47, "$extra": {"id": 3944254079165, "admin_graphql_api_id": "gid://shopify/Order/3944254079165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:39:20-07:00", "confirmed": true, "contact_email": "foo@example.com", "created_at": "2021-07-07T08:39:19-07:00", "currency": "UAH", "current_subtotal_price": "149.98", "current_subtotal_price_set": {"shop_money": {"amount": "149.98", "currency_code": "UAH"}, "presentment_money": {"amount": "149.98", "currency_code": "UAH"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "current_total_duties_set": null, "current_total_price": "158.98", "current_total_price_set": {"shop_money": {"amount": "158.98", "currency_code": "UAH"}, "presentment_money": {"amount": "158.98", "currency_code": "UAH"}}, "current_total_tax": "9.00", "current_total_tax_set": {"shop_money": {"amount": "9.00", "currency_code": "UAH"}, "presentment_money": {"amount": "9.00", "currency_code": "UAH"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "foo@example.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1013", "note": "Test Note", "note_attributes": [], "number": 13, "order_number": 1013, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/12fac6f8b01ed9ea5c5a64dcb3ec76a4/authenticate?key=89c7d74ae871f3a882fe65c4364e1418", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "UAH", "processed_at": "2021-07-07T08:39:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "224.97", "subtotal_price_set": {"shop_money": {"amount": "224.97", "currency_code": "UAH"}, "presentment_money": {"amount": "224.97", "currency_code": "UAH"}}, "tags": "", "tax_lines": [{"price": "13.50", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "12fac6f8b01ed9ea5c5a64dcb3ec76a4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "total_line_items_price": "224.97", "total_line_items_price_set": {"shop_money": {"amount": "224.97", "currency_code": "UAH"}, "presentment_money": {"amount": "224.97", "currency_code": "UAH"}}, "total_outstanding": "218.47", "total_price": "238.47", "total_price_set": {"shop_money": {"amount": "238.47", "currency_code": "UAH"}, "presentment_money": {"amount": "238.47", "currency_code": "UAH"}}, "total_price_usd": "8.72", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "total_tax": "13.50", "total_tax_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T09:49:56-07:00", "user_id": null, "customer": {"id": 5359701229757, "email": "foo@example.com", "accepts_marketing": false, "created_at": "2021-07-07T08:37:44-07:00", "updated_at": "2021-09-08T09:49:59-07:00", "first_name": null, "last_name": null, "orders_count": 3, "state": "disabled", "total_spent": "520.89", "last_order_id": 3944254079165, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1013", "currency": "UAH", "accepts_marketing_updated_at": "2021-07-07T08:37:45-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359701229757"}, "discount_applications": [], "fulfillments": [{"id": 3505545445565, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505545445565", "created_at": "2021-07-07T08:39:19-07:00", "location_id": 63590301885, "name": "#1013.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:39:19-07:00", "line_items": [{"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": 74.99, "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 224.96999999999997}], "payment_terms": null, "refunds": [{"id": 828111093949, "admin_graphql_api_id": "gid://shopify/Refund/828111093949", "created_at": "2021-09-08T09:49:56-07:00", "note": "This is the test refund", "processed_at": "2021-09-08T09:49:56-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189684285629, "amount": "59.49", "amount_set": {"shop_money": {"amount": "59.49", "currency_code": "UAH"}, "presentment_money": {"amount": "59.49", "currency_code": "UAH"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828111093949, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}}], "transactions": [{"id": 5159204880573, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159204880573", "amount": "20.00", "authorization": null, "created_at": "2021-09-08T09:49:56-07:00", "currency": "UAH", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 20.00 from manual gateway", "parent_id": 4944827842749, "processed_at": "2021-09-08T09:49:56-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330844602557, "line_item_id": 10147889479869, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 74.99, "subtotal_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "total_tax": 4.5, "total_tax_set": {"shop_money": {"amount": "4.50", "currency_code": "UAH"}, "presentment_money": {"amount": "4.50", "currency_code": "UAH"}}, "line_item": {"id": 10147889479869, "admin_graphql_api_id": "gid://shopify/LineItem/10147889479869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 1300, "name": "Big Brown Bear Boots", "price": "74.99", "price_set": {"shop_money": {"amount": "74.99", "currency_code": "UAH"}, "presentment_money": {"amount": "74.99", "currency_code": "UAH"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Big Brown Bear Boots", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "UAH"}, "presentment_money": {"amount": "0.00", "currency_code": "UAH"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "13.50", "price_set": {"shop_money": {"amount": "13.50", "currency_code": "UAH"}, "presentment_money": {"amount": "13.50", "currency_code": "UAH"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-08 16:49:56+00:00", "uuid": "cbc59200-10c4-11ec-8001-b1b4d9b256d3", "person": {"object": "person", "id": "01G4CDT9C3KTG868XSE7QRM94A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "foo@example.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "foo@example.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:55", "updated": "2022-05-31 06:46:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157419} -{"stream": "events", "data": {"object": "event", "id": "3mjDdThj4aX", "statistic_id": "R2WpFy", "timestamp": 1631120416, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3945528426685", "$value": 253.9, "$extra": {"id": 3945528426685, "admin_graphql_api_id": "gid://shopify/Order/3945528426685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:40:56-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:40:55-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1021", "note": null, "note_attributes": [], "number": 21, "order_number": 1021, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/2bf411f200cfdc3a7b22a3f18233bbee/authenticate?key=97da7b365030c4787312728dc771d379", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:40:55-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "2bf411f200cfdc3a7b22a3f18233bbee", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-08T10:00:16-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836275389, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836275389", "created_at": "2021-07-08T02:40:56-07:00", "location_id": 63590301885, "name": "#1021.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:40:56-07:00", "line_items": [{"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828112240829, "admin_graphql_api_id": "gid://shopify/Refund/828112240829", "created_at": "2021-09-08T10:00:16-07:00", "note": "this is the test refund 3", "processed_at": "2021-09-08T10:00:16-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189684809917, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828112240829, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5159229292733, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5159229292733", "amount": "33.00", "authorization": null, "created_at": "2021-09-08T10:00:16-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946429935805, "processed_at": "2021-09-08T10:00:16-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330846077117, "line_item_id": 10150319358141, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319358141, "admin_graphql_api_id": "gid://shopify/LineItem/10150319358141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-08 17:00:16+00:00", "uuid": "3d521000-10c6-11ec-8001-53318a8dfedf", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157421} -{"stream": "events", "data": {"object": "event", "id": "3mjDxFyTDWz", "statistic_id": "R2WpFy", "timestamp": 1631181463, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 189"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3944273805501", "$value": 127.9, "$extra": {"id": 3944273805501, "admin_graphql_api_id": "gid://shopify/Order/3944273805501", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:52:08-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:52:07-07:00", "currency": "USD", "current_subtotal_price": "81.00", "current_subtotal_price_set": {"shop_money": {"amount": "81.00", "currency_code": "USD"}, "presentment_money": {"amount": "81.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "111.75", "current_total_price_set": {"shop_money": {"amount": "111.75", "currency_code": "USD"}, "presentment_money": {"amount": "111.75", "currency_code": "USD"}}, "current_total_tax": "30.75", "current_total_tax_set": {"shop_money": {"amount": "30.75", "currency_code": "USD"}, "presentment_money": {"amount": "30.75", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1016", "note": null, "note_attributes": [], "number": 16, "order_number": 1016, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cd5cd21e8a2856e5bd8aaa0b7392cb12/authenticate?key=ddefed114e6c464ff982f0fb2aef64aa", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:52:07-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "108.00", "subtotal_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "41.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "cd5cd21e8a2856e5bd8aaa0b7392cb12", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "108.00", "total_line_items_price_set": {"shop_money": {"amount": "108.00", "currency_code": "USD"}, "presentment_money": {"amount": "108.00", "currency_code": "USD"}}, "total_outstanding": "141.00", "total_price": "127.90", "total_price_set": {"shop_money": {"amount": "127.90", "currency_code": "USD"}, "presentment_money": {"amount": "127.90", "currency_code": "USD"}}, "total_price_usd": "127.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-09T02:57:43-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505565302973, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505565302973", "created_at": "2021-07-07T08:52:08-07:00", "location_id": 63590301885, "name": "#1016.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:52:08-07:00", "line_items": [{"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 108.0}], "payment_terms": null, "refunds": [{"id": 828202942653, "admin_graphql_api_id": "gid://shopify/Refund/828202942653", "created_at": "2021-09-09T02:57:43-07:00", "note": "Test refund 123", "processed_at": "2021-09-09T02:57:43-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189745856701, "amount": "29.25", "amount_set": {"shop_money": {"amount": "29.25", "currency_code": "USD"}, "presentment_money": {"amount": "29.25", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828202942653, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5160967209149, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5160967209149", "amount": "8.00", "authorization": null, "created_at": "2021-09-09T02:57:43-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 8.00 from manual gateway", "parent_id": 4944854188221, "processed_at": "2021-09-09T02:57:43-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330958864573, "line_item_id": 10147931127997, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 27.0, "subtotal_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_tax": 10.25, "total_tax_set": {"shop_money": {"amount": "10.25", "currency_code": "USD"}, "presentment_money": {"amount": "10.25", "currency_code": "USD"}}, "line_item": {"id": 10147931127997, "admin_graphql_api_id": "gid://shopify/LineItem/10147931127997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 189", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 4, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 189", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "41.00", "price_set": {"shop_money": {"amount": "41.00", "currency_code": "USD"}, "presentment_money": {"amount": "41.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-09 09:57:43+00:00", "uuid": "602b0580-1154-11ec-8001-68e6dca100f3", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157422} -{"stream": "events", "data": {"object": "event", "id": "3mjz64XaUNJ", "statistic_id": "R2WpFy", "timestamp": 1632061762, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 429"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "3945528492221", "$value": 253.9, "$extra": {"id": 3945528492221, "admin_graphql_api_id": "gid://shopify/Order/3945528492221", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-08T02:40:59-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:40:57-07:00", "currency": "USD", "current_subtotal_price": "156.00", "current_subtotal_price_set": {"shop_money": {"amount": "156.00", "currency_code": "USD"}, "presentment_money": {"amount": "156.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "158.67", "current_total_price_set": {"shop_money": {"amount": "158.67", "currency_code": "USD"}, "presentment_money": {"amount": "158.67", "currency_code": "USD"}}, "current_total_tax": "2.67", "current_total_tax_set": {"shop_money": {"amount": "2.67", "currency_code": "USD"}, "presentment_money": {"amount": "2.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1022", "note": null, "note_attributes": [], "number": 22, "order_number": 1022, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/728e04f16cca7e5778f921a777c44674/authenticate?key=579159f0efeeebeceb0b16f88c05f6c4", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:40:57-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "234.00", "subtotal_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "4.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "728e04f16cca7e5778f921a777c44674", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "234.00", "total_line_items_price_set": {"shop_money": {"amount": "234.00", "currency_code": "USD"}, "presentment_money": {"amount": "234.00", "currency_code": "USD"}}, "total_outstanding": "205.01", "total_price": "253.90", "total_price_set": {"shop_money": {"amount": "253.90", "currency_code": "USD"}, "presentment_money": {"amount": "253.90", "currency_code": "USD"}}, "total_price_usd": "253.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T07:29:22-07:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506836308157, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506836308157", "created_at": "2021-07-08T02:40:58-07:00", "location_id": 63590301885, "name": "#1022.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:40:58-07:00", "line_items": [{"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": 78.0, "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 234.0}], "payment_terms": null, "refunds": [{"id": 828219654333, "admin_graphql_api_id": "gid://shopify/Refund/828219654333", "created_at": "2021-09-09T05:51:36-07:00", "note": "test refund 45678", "processed_at": "2021-09-09T05:51:36-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 189755228349, "amount": "46.34", "amount_set": {"shop_money": {"amount": "46.34", "currency_code": "USD"}, "presentment_money": {"amount": "46.34", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 828219654333, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5161264808125, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5161264808125", "amount": "33.00", "authorization": null, "created_at": "2021-09-09T05:51:36-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 33.00 from manual gateway", "parent_id": 4946430034109, "processed_at": "2021-09-09T05:51:36-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 330985668797, "line_item_id": 10150319456445, "location_id": null, "quantity": 1, "restock_type": "no_restock", "subtotal": 78.0, "subtotal_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "total_tax": 1.33, "total_tax_set": {"shop_money": {"amount": "1.33", "currency_code": "USD"}, "presentment_money": {"amount": "1.33", "currency_code": "USD"}}, "line_item": {"id": 10150319456445, "admin_graphql_api_id": "gid://shopify/LineItem/10150319456445", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 429", "price": "78.00", "price_set": {"shop_money": {"amount": "78.00", "currency_code": "USD"}, "presentment_money": {"amount": "78.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 3, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 429", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 14:29:22+00:00", "uuid": "fb428d00-1955-11ec-8001-c3d46966f1ab", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "Test Company", "$email": "airbyte-test@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "Customer", "$first_name": "Test", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157424} -{"stream": "events", "data": {"object": "event", "id": "3mjuiJWPmAX", "statistic_id": "TspjNE", "timestamp": 1632065485, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147927777469", "$value": 27.0, "$extra": {"id": 4147927777469, "admin_graphql_api_id": "gid://shopify/Order/4147927777469", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:06-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:05-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1030", "note": null, "note_attributes": [], "number": 30, "order_number": 1030, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cb2b9238b6a9c1d2bee9d3ecc39360ff/authenticate?key=374e1c91c926c73e867b59cb023fd217", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:05-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "cb2b9238b6a9c1d2bee9d3ecc39360ff", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:06-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384466621, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384466621", "created_at": "2021-09-19T08:31:05-07:00", "location_id": 63590301885, "name": "#1030.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:05-07:00", "line_items": [{"id": 10576676094141, "admin_graphql_api_id": "gid://shopify/LineItem/10576676094141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676094141, "admin_graphql_api_id": "gid://shopify/LineItem/10576676094141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:31:25+00:00", "uuid": "a6574480-195e-11ec-8001-de7f0385c786", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157426} -{"stream": "events", "data": {"object": "event", "id": "3mjAFextd7b", "statistic_id": "RDXsib", "timestamp": 1632065495, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147927777469:10576676094141:0", "$value": 27.0}, "datetime": "2021-09-19 15:31:35+00:00", "uuid": "ac4d2580-195e-11ec-8001-a9e4ec8172fe", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157427} -{"stream": "events", "data": {"object": "event", "id": "3mjEbiyxfNr", "statistic_id": "TspjNE", "timestamp": 1632065504, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147928170685", "$value": 27.0, "$extra": {"id": 4147928170685, "admin_graphql_api_id": "gid://shopify/Order/4147928170685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:24-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1031", "note": null, "note_attributes": [], "number": 31, "order_number": 1031, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ded4174c7e01615caafd0998b400cf5b/authenticate?key=1fa5f824d4e8884d56a661c9284c5224", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:24-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ded4174c7e01615caafd0998b400cf5b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:25-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384663229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384663229", "created_at": "2021-09-19T08:31:24-07:00", "location_id": 63590301885, "name": "#1031.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:24-07:00", "line_items": [{"id": 10576676585661, "admin_graphql_api_id": "gid://shopify/LineItem/10576676585661", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676585661, "admin_graphql_api_id": "gid://shopify/LineItem/10576676585661", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:31:44+00:00", "uuid": "b1aa7000-195e-11ec-8001-7de3b9fc83bc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157428} -{"stream": "events", "data": {"object": "event", "id": "3mjBCJFChYD", "statistic_id": "TspjNE", "timestamp": 1632065506, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147928268989", "$value": 27.0, "$extra": {"id": 4147928268989, "admin_graphql_api_id": "gid://shopify/Order/4147928268989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:28-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:26-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1032", "note": null, "note_attributes": [], "number": 32, "order_number": 1032, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f7ad3a2faa7c8a1983437d175e7e35a4/authenticate?key=1904faf4def24657a60d75c26d8e4b50", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:26-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f7ad3a2faa7c8a1983437d175e7e35a4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384695997, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384695997", "created_at": "2021-09-19T08:31:26-07:00", "location_id": 63590301885, "name": "#1032.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:26-07:00", "line_items": [{"id": 10576676683965, "admin_graphql_api_id": "gid://shopify/LineItem/10576676683965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676683965, "admin_graphql_api_id": "gid://shopify/LineItem/10576676683965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:31:46+00:00", "uuid": "b2db9d00-195e-11ec-8001-9195bb6c69e8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157429} -{"stream": "events", "data": {"object": "event", "id": "3mjCU45Xkch", "statistic_id": "TspjNE", "timestamp": 1632065510, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147928334525", "$value": 27.0, "$extra": {"id": 4147928334525, "admin_graphql_api_id": "gid://shopify/Order/4147928334525", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:30-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:30-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1033", "note": null, "note_attributes": [], "number": 33, "order_number": 1033, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aed8297746c4c1c7dcee384b8d83b2e3/authenticate?key=78c93224d0e48076989e2ec61602d434", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:30-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "aed8297746c4c1c7dcee384b8d83b2e3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:31-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384761533, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384761533", "created_at": "2021-09-19T08:31:30-07:00", "location_id": 63590301885, "name": "#1033.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:30-07:00", "line_items": [{"id": 10576676880573, "admin_graphql_api_id": "gid://shopify/LineItem/10576676880573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676880573, "admin_graphql_api_id": "gid://shopify/LineItem/10576676880573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:31:50+00:00", "uuid": "b53df700-195e-11ec-8001-a2ad62a106ce", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157431} -{"stream": "events", "data": {"object": "event", "id": "3mjAFguDt36", "statistic_id": "TspjNE", "timestamp": 1632065511, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147928465597", "$value": 27.0, "$extra": {"id": 4147928465597, "admin_graphql_api_id": "gid://shopify/Order/4147928465597", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:32-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1034", "note": null, "note_attributes": [], "number": 34, "order_number": 1034, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/63633def2219710a2735f9bbc944216d/authenticate?key=7424de94aaae44c9bd14888ee87f586d", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "63633def2219710a2735f9bbc944216d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384794301, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384794301", "created_at": "2021-09-19T08:31:32-07:00", "location_id": 63590301885, "name": "#1034.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:32-07:00", "line_items": [{"id": 10576676978877, "admin_graphql_api_id": "gid://shopify/LineItem/10576676978877", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676978877, "admin_graphql_api_id": "gid://shopify/LineItem/10576676978877", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:31:51+00:00", "uuid": "b5d68d80-195e-11ec-8001-3ce9e422b2fc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157432} -{"stream": "events", "data": {"object": "event", "id": "3mjAZ8EzKVB", "statistic_id": "RDXsib", "timestamp": 1632065514, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147928170685:10576676585661:0", "$value": 27.0}, "datetime": "2021-09-19 15:31:54+00:00", "uuid": "b7a05100-195e-11ec-8001-01bdba136eb6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157433} -{"stream": "events", "data": {"object": "event", "id": "3mjz68Rx9RK", "statistic_id": "X3f6PC", "timestamp": 1632065515, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147927777469", "$value": 27.0, "$extra": {"id": 4147927777469, "admin_graphql_api_id": "gid://shopify/Order/4147927777469", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:06-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:05-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1030", "note": null, "note_attributes": [], "number": 30, "order_number": 1030, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cb2b9238b6a9c1d2bee9d3ecc39360ff/authenticate?key=374e1c91c926c73e867b59cb023fd217", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:05-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "cb2b9238b6a9c1d2bee9d3ecc39360ff", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:06-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384466621, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384466621", "created_at": "2021-09-19T08:31:05-07:00", "location_id": 63590301885, "name": "#1030.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:05-07:00", "line_items": [{"id": 10576676094141, "admin_graphql_api_id": "gid://shopify/LineItem/10576676094141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676094141, "admin_graphql_api_id": "gid://shopify/LineItem/10576676094141", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:31:55+00:00", "uuid": "b838e780-195e-11ec-8001-836553bd3ce3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157434} -{"stream": "events", "data": {"object": "event", "id": "3mjz66UmrdX", "statistic_id": "RDXsib", "timestamp": 1632065516, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147928268989:10576676683965:0", "$value": 27.0}, "datetime": "2021-09-19 15:31:56+00:00", "uuid": "b8d17e00-195e-11ec-8001-0fdb7a6419ef", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157435} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHCTS", "statistic_id": "RDXsib", "timestamp": 1632065520, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147928334525:10576676880573:0", "$value": 27.0}, "datetime": "2021-09-19 15:32:00+00:00", "uuid": "bb33d800-195e-11ec-8001-46ff12e757a0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157435} -{"stream": "events", "data": {"object": "event", "id": "3mjDRvimHyZ", "statistic_id": "RDXsib", "timestamp": 1632065521, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147928465597:10576676978877:0", "$value": 27.0}, "datetime": "2021-09-19 15:32:01+00:00", "uuid": "bbcc6e80-195e-11ec-8001-eb96867a4bad", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157435} -{"stream": "events", "data": {"object": "event", "id": "3mjDRAbj9LM", "statistic_id": "X3f6PC", "timestamp": 1632065534, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147928170685", "$value": 27.0, "$extra": {"id": 4147928170685, "admin_graphql_api_id": "gid://shopify/Order/4147928170685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:24-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1031", "note": null, "note_attributes": [], "number": 31, "order_number": 1031, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ded4174c7e01615caafd0998b400cf5b/authenticate?key=1fa5f824d4e8884d56a661c9284c5224", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:24-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ded4174c7e01615caafd0998b400cf5b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:25-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384663229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384663229", "created_at": "2021-09-19T08:31:24-07:00", "location_id": 63590301885, "name": "#1031.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:24-07:00", "line_items": [{"id": 10576676585661, "admin_graphql_api_id": "gid://shopify/LineItem/10576676585661", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676585661, "admin_graphql_api_id": "gid://shopify/LineItem/10576676585661", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:32:14+00:00", "uuid": "c38c1300-195e-11ec-8001-d363e5a647fa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157435} -{"stream": "events", "data": {"object": "event", "id": "3mjAFfZkNWs", "statistic_id": "X3f6PC", "timestamp": 1632065536, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147928268989", "$value": 27.0, "$extra": {"id": 4147928268989, "admin_graphql_api_id": "gid://shopify/Order/4147928268989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:28-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:26-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1032", "note": null, "note_attributes": [], "number": 32, "order_number": 1032, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f7ad3a2faa7c8a1983437d175e7e35a4/authenticate?key=1904faf4def24657a60d75c26d8e4b50", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:26-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f7ad3a2faa7c8a1983437d175e7e35a4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384695997, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384695997", "created_at": "2021-09-19T08:31:26-07:00", "location_id": 63590301885, "name": "#1032.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:26-07:00", "line_items": [{"id": 10576676683965, "admin_graphql_api_id": "gid://shopify/LineItem/10576676683965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676683965, "admin_graphql_api_id": "gid://shopify/LineItem/10576676683965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:32:16+00:00", "uuid": "c4bd4000-195e-11ec-8001-ce35b2f649f1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157436} -{"stream": "events", "data": {"object": "event", "id": "3mjy8DcEh7D", "statistic_id": "X3f6PC", "timestamp": 1632065540, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147928334525", "$value": 27.0, "$extra": {"id": 4147928334525, "admin_graphql_api_id": "gid://shopify/Order/4147928334525", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:30-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:30-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1033", "note": null, "note_attributes": [], "number": 33, "order_number": 1033, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aed8297746c4c1c7dcee384b8d83b2e3/authenticate?key=78c93224d0e48076989e2ec61602d434", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:30-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "aed8297746c4c1c7dcee384b8d83b2e3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:31-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384761533, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384761533", "created_at": "2021-09-19T08:31:30-07:00", "location_id": 63590301885, "name": "#1033.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:30-07:00", "line_items": [{"id": 10576676880573, "admin_graphql_api_id": "gid://shopify/LineItem/10576676880573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676880573, "admin_graphql_api_id": "gid://shopify/LineItem/10576676880573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:32:20+00:00", "uuid": "c71f9a00-195e-11ec-8001-16738c5a3fdc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157437} -{"stream": "events", "data": {"object": "event", "id": "3mjwxsDMuyx", "statistic_id": "X3f6PC", "timestamp": 1632065542, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147928465597", "$value": 27.0, "$extra": {"id": 4147928465597, "admin_graphql_api_id": "gid://shopify/Order/4147928465597", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:31:32-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:31:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1034", "note": null, "note_attributes": [], "number": 34, "order_number": 1034, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/63633def2219710a2735f9bbc944216d/authenticate?key=7424de94aaae44c9bd14888ee87f586d", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:31:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "63633def2219710a2735f9bbc944216d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:31:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693384794301, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693384794301", "created_at": "2021-09-19T08:31:32-07:00", "location_id": 63590301885, "name": "#1034.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:31:32-07:00", "line_items": [{"id": 10576676978877, "admin_graphql_api_id": "gid://shopify/LineItem/10576676978877", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576676978877, "admin_graphql_api_id": "gid://shopify/LineItem/10576676978877", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:32:22+00:00", "uuid": "c850c700-195e-11ec-8001-67d7d9432d90", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157438} -{"stream": "events", "data": {"object": "event", "id": "3mjBWzpFz2b", "statistic_id": "TspjNE", "timestamp": 1632065613, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147930661053", "$value": 27.0, "$extra": {"id": 4147930661053, "admin_graphql_api_id": "gid://shopify/Order/4147930661053", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:14-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:13-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1035", "note": null, "note_attributes": [], "number": 35, "order_number": 1035, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a7600bb243d5bc84b3499fe7d76ee710/authenticate?key=cc9cb2dd217c6090f117164520fd3bca", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:13-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a7600bb243d5bc84b3499fe7d76ee710", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:15-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693386924221, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693386924221", "created_at": "2021-09-19T08:33:14-07:00", "location_id": 63590301885, "name": "#1035.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:14-07:00", "line_items": [{"id": 10576681828541, "admin_graphql_api_id": "gid://shopify/LineItem/10576681828541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576681828541, "admin_graphql_api_id": "gid://shopify/LineItem/10576681828541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:33:33+00:00", "uuid": "f2a28480-195e-11ec-8001-43c1dcd8e0d4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157439} -{"stream": "events", "data": {"object": "event", "id": "3mjAFd5zxrX", "statistic_id": "TspjNE", "timestamp": 1632065622, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147930857661", "$value": 27.0, "$extra": {"id": 4147930857661, "admin_graphql_api_id": "gid://shopify/Order/4147930857661", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:22-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:22-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1036", "note": null, "note_attributes": [], "number": 36, "order_number": 1036, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/01c906b550f546dae3a828330e2b0b1a/authenticate?key=33b04eaaeac2c9060d7d1853d8a3aa9d", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:22-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "01c906b550f546dae3a828330e2b0b1a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:23-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387153597, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387153597", "created_at": "2021-09-19T08:33:22-07:00", "location_id": 63590301885, "name": "#1036.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:22-07:00", "line_items": [{"id": 10576682090685, "admin_graphql_api_id": "gid://shopify/LineItem/10576682090685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576682090685, "admin_graphql_api_id": "gid://shopify/LineItem/10576682090685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:33:42+00:00", "uuid": "f7ffcf00-195e-11ec-8001-bab7dc92adde", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157440} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRT2JU", "statistic_id": "RDXsib", "timestamp": 1632065623, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147930661053:10576681828541:0", "$value": 27.0}, "datetime": "2021-09-19 15:33:43+00:00", "uuid": "f8986580-195e-11ec-8001-c45ed59158d8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157443} -{"stream": "events", "data": {"object": "event", "id": "3mjDdSiHZZs", "statistic_id": "TspjNE", "timestamp": 1632065624, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147930923197", "$value": 27.0, "$extra": {"id": 4147930923197, "admin_graphql_api_id": "gid://shopify/Order/4147930923197", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:24-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1037", "note": null, "note_attributes": [], "number": 37, "order_number": 1037, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9625e02978591658631a9c59bd448528/authenticate?key=94c182afca29b34f3e2bbe5721899dcb", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:24-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9625e02978591658631a9c59bd448528", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:26-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387219133, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387219133", "created_at": "2021-09-19T08:33:25-07:00", "location_id": 63590301885, "name": "#1037.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:25-07:00", "line_items": [{"id": 10576682156221, "admin_graphql_api_id": "gid://shopify/LineItem/10576682156221", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576682156221, "admin_graphql_api_id": "gid://shopify/LineItem/10576682156221", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:33:44+00:00", "uuid": "f930fc00-195e-11ec-8001-5d0cc545c5d6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157444} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9WQBNd", "statistic_id": "TspjNE", "timestamp": 1632065627, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147931054269", "$value": 27.0, "$extra": {"id": 4147931054269, "admin_graphql_api_id": "gid://shopify/Order/4147931054269", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:29-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:27-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1038", "note": null, "note_attributes": [], "number": 38, "order_number": 1038, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f669234d7ac13d6ed1d4ec34ce11bb31/authenticate?key=47a090c9ff26538da7121c9c26bb6a91", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:27-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f669234d7ac13d6ed1d4ec34ce11bb31", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387317437, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387317437", "created_at": "2021-09-19T08:33:28-07:00", "location_id": 63590301885, "name": "#1038.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:28-07:00", "line_items": [{"id": 10576682385597, "admin_graphql_api_id": "gid://shopify/LineItem/10576682385597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576682385597, "admin_graphql_api_id": "gid://shopify/LineItem/10576682385597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:33:47+00:00", "uuid": "fafabf80-195e-11ec-8001-d763c70474c4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157445} -{"stream": "events", "data": {"object": "event", "id": "3mjwxsDMhWS", "statistic_id": "RDXsib", "timestamp": 1632065632, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147930857661:10576682090685:0", "$value": 27.0}, "datetime": "2021-09-19 15:33:52+00:00", "uuid": "fdf5b000-195e-11ec-8001-d648a2f6cdcf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157445} -{"stream": "events", "data": {"object": "event", "id": "3mjs62Lypub", "statistic_id": "RDXsib", "timestamp": 1632065634, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147930923197:10576682156221:0", "$value": 27.0}, "datetime": "2021-09-19 15:33:54+00:00", "uuid": "ff26dd00-195e-11ec-8001-e2695e0aee81", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157446} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFWjN", "statistic_id": "RDXsib", "timestamp": 1632065637, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147931054269:10576682385597:0", "$value": 27.0}, "datetime": "2021-09-19 15:33:57+00:00", "uuid": "00f0a080-195f-11ec-8001-ef34f16fa3f1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$organization": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$title": "", "$last_name": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157446} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQubUK", "statistic_id": "X3f6PC", "timestamp": 1632065644, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147930661053", "$value": 27.0, "$extra": {"id": 4147930661053, "admin_graphql_api_id": "gid://shopify/Order/4147930661053", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:14-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:13-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1035", "note": null, "note_attributes": [], "number": 35, "order_number": 1035, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a7600bb243d5bc84b3499fe7d76ee710/authenticate?key=cc9cb2dd217c6090f117164520fd3bca", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:13-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a7600bb243d5bc84b3499fe7d76ee710", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:15-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693386924221, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693386924221", "created_at": "2021-09-19T08:33:14-07:00", "location_id": 63590301885, "name": "#1035.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:14-07:00", "line_items": [{"id": 10576681828541, "admin_graphql_api_id": "gid://shopify/LineItem/10576681828541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576681828541, "admin_graphql_api_id": "gid://shopify/LineItem/10576681828541", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:04+00:00", "uuid": "051cbe00-195f-11ec-8001-daecef489fba", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157883} -{"stream": "events", "data": {"object": "event", "id": "3mjvTUx7aVp", "statistic_id": "X3f6PC", "timestamp": 1632065652, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147930857661", "$value": 27.0, "$extra": {"id": 4147930857661, "admin_graphql_api_id": "gid://shopify/Order/4147930857661", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:22-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:22-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1036", "note": null, "note_attributes": [], "number": 36, "order_number": 1036, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/01c906b550f546dae3a828330e2b0b1a/authenticate?key=33b04eaaeac2c9060d7d1853d8a3aa9d", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:22-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "01c906b550f546dae3a828330e2b0b1a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:23-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387153597, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387153597", "created_at": "2021-09-19T08:33:22-07:00", "location_id": 63590301885, "name": "#1036.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:22-07:00", "line_items": [{"id": 10576682090685, "admin_graphql_api_id": "gid://shopify/LineItem/10576682090685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576682090685, "admin_graphql_api_id": "gid://shopify/LineItem/10576682090685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:12+00:00", "uuid": "09e17200-195f-11ec-8001-4bb7ff691881", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157885} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhte8GX", "statistic_id": "X3f6PC", "timestamp": 1632065655, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147930923197", "$value": 27.0, "$extra": {"id": 4147930923197, "admin_graphql_api_id": "gid://shopify/Order/4147930923197", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:24-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1037", "note": null, "note_attributes": [], "number": 37, "order_number": 1037, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9625e02978591658631a9c59bd448528/authenticate?key=94c182afca29b34f3e2bbe5721899dcb", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:24-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9625e02978591658631a9c59bd448528", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:26-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387219133, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387219133", "created_at": "2021-09-19T08:33:25-07:00", "location_id": 63590301885, "name": "#1037.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:25-07:00", "line_items": [{"id": 10576682156221, "admin_graphql_api_id": "gid://shopify/LineItem/10576682156221", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576682156221, "admin_graphql_api_id": "gid://shopify/LineItem/10576682156221", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:15+00:00", "uuid": "0bab3580-195f-11ec-8001-0db82e0f1eda", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157886} -{"stream": "events", "data": {"object": "event", "id": "3mjAFd5zgxJ", "statistic_id": "X3f6PC", "timestamp": 1632065658, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147931054269", "$value": 27.0, "$extra": {"id": 4147931054269, "admin_graphql_api_id": "gid://shopify/Order/4147931054269", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:33:29-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:33:27-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1038", "note": null, "note_attributes": [], "number": 38, "order_number": 1038, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f669234d7ac13d6ed1d4ec34ce11bb31/authenticate?key=47a090c9ff26538da7121c9c26bb6a91", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:33:27-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f669234d7ac13d6ed1d4ec34ce11bb31", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:33:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387317437, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387317437", "created_at": "2021-09-19T08:33:28-07:00", "location_id": 63590301885, "name": "#1038.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:33:28-07:00", "line_items": [{"id": 10576682385597, "admin_graphql_api_id": "gid://shopify/LineItem/10576682385597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576682385597, "admin_graphql_api_id": "gid://shopify/LineItem/10576682385597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:18+00:00", "uuid": "0d74f900-195f-11ec-8001-90c61ab699b5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157889} -{"stream": "events", "data": {"object": "event", "id": "3mjz6bMin8v", "statistic_id": "TspjNE", "timestamp": 1632065683, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147932233917", "$value": 27.0, "$extra": {"id": 4147932233917, "admin_graphql_api_id": "gid://shopify/Order/4147932233917", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:23-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:23-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1039", "note": null, "note_attributes": [], "number": 39, "order_number": 1039, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/02449ec2a7e040c251542f0dda802ee8/authenticate?key=701745abbee4f0c3be5dfc4946f27361", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:23-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "02449ec2a7e040c251542f0dda802ee8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:24-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387776189, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387776189", "created_at": "2021-09-19T08:34:23-07:00", "location_id": 63590301885, "name": "#1039.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:23-07:00", "line_items": [{"id": 10576684581053, "admin_graphql_api_id": "gid://shopify/LineItem/10576684581053", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684581053, "admin_graphql_api_id": "gid://shopify/LineItem/10576684581053", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:43+00:00", "uuid": "1c5bab80-195f-11ec-8001-e9bf24067390", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157890} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRSZux", "statistic_id": "TspjNE", "timestamp": 1632065685, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147932266685", "$value": 27.0, "$extra": {"id": 4147932266685, "admin_graphql_api_id": "gid://shopify/Order/4147932266685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:26-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:25-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1040", "note": null, "note_attributes": [], "number": 40, "order_number": 1040, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a9967adcaff73ae36ccd25e096e6c460/authenticate?key=b686a50aaf6574ed9709dfdd52a7a534", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:25-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a9967adcaff73ae36ccd25e096e6c460", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:27-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387808957, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387808957", "created_at": "2021-09-19T08:34:26-07:00", "location_id": 63590301885, "name": "#1040.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:26-07:00", "line_items": [{"id": 10576684613821, "admin_graphql_api_id": "gid://shopify/LineItem/10576684613821", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684613821, "admin_graphql_api_id": "gid://shopify/LineItem/10576684613821", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:45+00:00", "uuid": "1d8cd880-195f-11ec-8001-2e9258b887eb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157892} -{"stream": "events", "data": {"object": "event", "id": "3mjvTPaRptW", "statistic_id": "TspjNE", "timestamp": 1632065688, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147932364989", "$value": 27.0, "$extra": {"id": 4147932364989, "admin_graphql_api_id": "gid://shopify/Order/4147932364989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:30-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:28-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1041", "note": null, "note_attributes": [], "number": 41, "order_number": 1041, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8f3baebd8075eba09670b0a18c255ea/authenticate?key=000b7bea082c198617f63c49cf75055b", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:28-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "b8f3baebd8075eba09670b0a18c255ea", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:30-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387907261, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387907261", "created_at": "2021-09-19T08:34:29-07:00", "location_id": 63590301885, "name": "#1041.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:29-07:00", "line_items": [{"id": 10576684712125, "admin_graphql_api_id": "gid://shopify/LineItem/10576684712125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684712125, "admin_graphql_api_id": "gid://shopify/LineItem/10576684712125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:48+00:00", "uuid": "1f569c00-195f-11ec-8001-8eeefd976ea5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157894} -{"stream": "events", "data": {"object": "event", "id": "3mjFsHj8nh8", "statistic_id": "TspjNE", "timestamp": 1632065691, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147932397757", "$value": 27.0, "$extra": {"id": 4147932397757, "admin_graphql_api_id": "gid://shopify/Order/4147932397757", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:32-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1042", "note": null, "note_attributes": [], "number": 42, "order_number": 1042, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6f61087e43f4a13ed5e90b65c39bb50e/authenticate?key=8d96de067ef51789923d0230b3fedfe2", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6f61087e43f4a13ed5e90b65c39bb50e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387972797, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387972797", "created_at": "2021-09-19T08:34:32-07:00", "location_id": 63590301885, "name": "#1042.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:32-07:00", "line_items": [{"id": 10576684744893, "admin_graphql_api_id": "gid://shopify/LineItem/10576684744893", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684744893, "admin_graphql_api_id": "gid://shopify/LineItem/10576684744893", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:34:51+00:00", "uuid": "21205f80-195f-11ec-8001-e822d42a98f1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157895} -{"stream": "events", "data": {"object": "event", "id": "3mjFszwpMiE", "statistic_id": "RDXsib", "timestamp": 1632065693, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147932233917:10576684581053:0", "$value": 27.0}, "datetime": "2021-09-19 15:34:53+00:00", "uuid": "22518c80-195f-11ec-8001-219bc965e5c5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157900} -{"stream": "events", "data": {"object": "event", "id": "3mjF8Ndqj7b", "statistic_id": "RDXsib", "timestamp": 1632065695, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147932266685:10576684613821:0", "$value": 27.0}, "datetime": "2021-09-19 15:34:55+00:00", "uuid": "2382b980-195f-11ec-8001-665d7ec55ba2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157901} -{"stream": "events", "data": {"object": "event", "id": "3mjEvafbr4a", "statistic_id": "RDXsib", "timestamp": 1632065698, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147932364989:10576684712125:0", "$value": 27.0}, "datetime": "2021-09-19 15:34:58+00:00", "uuid": "254c7d00-195f-11ec-8001-991f7c8fd9dc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157902} -{"stream": "events", "data": {"object": "event", "id": "3mjxv4acFaG", "statistic_id": "RDXsib", "timestamp": 1632065701, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147932397757:10576684744893:0", "$value": 27.0}, "datetime": "2021-09-19 15:35:01+00:00", "uuid": "27164080-195f-11ec-8001-8dd115efc7f9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157902} -{"stream": "events", "data": {"object": "event", "id": "3mjz6bi2jbs", "statistic_id": "X3f6PC", "timestamp": 1632065713, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147932233917", "$value": 27.0, "$extra": {"id": 4147932233917, "admin_graphql_api_id": "gid://shopify/Order/4147932233917", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:23-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:23-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1039", "note": null, "note_attributes": [], "number": 39, "order_number": 1039, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/02449ec2a7e040c251542f0dda802ee8/authenticate?key=701745abbee4f0c3be5dfc4946f27361", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:23-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "02449ec2a7e040c251542f0dda802ee8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:24-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387776189, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387776189", "created_at": "2021-09-19T08:34:23-07:00", "location_id": 63590301885, "name": "#1039.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:23-07:00", "line_items": [{"id": 10576684581053, "admin_graphql_api_id": "gid://shopify/LineItem/10576684581053", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684581053, "admin_graphql_api_id": "gid://shopify/LineItem/10576684581053", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:35:13+00:00", "uuid": "2e3d4e80-195f-11ec-8001-55e725f7dee3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157903} -{"stream": "events", "data": {"object": "event", "id": "3mjwxBUpFcN", "statistic_id": "X3f6PC", "timestamp": 1632065716, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147932266685", "$value": 27.0, "$extra": {"id": 4147932266685, "admin_graphql_api_id": "gid://shopify/Order/4147932266685", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:26-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:25-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1040", "note": null, "note_attributes": [], "number": 40, "order_number": 1040, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a9967adcaff73ae36ccd25e096e6c460/authenticate?key=b686a50aaf6574ed9709dfdd52a7a534", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:25-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a9967adcaff73ae36ccd25e096e6c460", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:27-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387808957, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387808957", "created_at": "2021-09-19T08:34:26-07:00", "location_id": 63590301885, "name": "#1040.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:26-07:00", "line_items": [{"id": 10576684613821, "admin_graphql_api_id": "gid://shopify/LineItem/10576684613821", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684613821, "admin_graphql_api_id": "gid://shopify/LineItem/10576684613821", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:35:16+00:00", "uuid": "30071200-195f-11ec-8001-ee82e478cb8a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157904} -{"stream": "events", "data": {"object": "event", "id": "3mjwxu7EW2G", "statistic_id": "X3f6PC", "timestamp": 1632065719, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147932364989", "$value": 27.0, "$extra": {"id": 4147932364989, "admin_graphql_api_id": "gid://shopify/Order/4147932364989", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:30-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:28-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1041", "note": null, "note_attributes": [], "number": 41, "order_number": 1041, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8f3baebd8075eba09670b0a18c255ea/authenticate?key=000b7bea082c198617f63c49cf75055b", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:28-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "b8f3baebd8075eba09670b0a18c255ea", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:30-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387907261, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387907261", "created_at": "2021-09-19T08:34:29-07:00", "location_id": 63590301885, "name": "#1041.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:29-07:00", "line_items": [{"id": 10576684712125, "admin_graphql_api_id": "gid://shopify/LineItem/10576684712125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684712125, "admin_graphql_api_id": "gid://shopify/LineItem/10576684712125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:35:19+00:00", "uuid": "31d0d580-195f-11ec-8001-b74ed8c931c5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157906} -{"stream": "events", "data": {"object": "event", "id": "3mjBiUWaaCk", "statistic_id": "X3f6PC", "timestamp": 1632065722, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147932397757", "$value": 27.0, "$extra": {"id": 4147932397757, "admin_graphql_api_id": "gid://shopify/Order/4147932397757", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:34:32-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:34:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1042", "note": null, "note_attributes": [], "number": 42, "order_number": 1042, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6f61087e43f4a13ed5e90b65c39bb50e/authenticate?key=8d96de067ef51789923d0230b3fedfe2", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:34:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6f61087e43f4a13ed5e90b65c39bb50e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:34:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693387972797, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693387972797", "created_at": "2021-09-19T08:34:32-07:00", "location_id": 63590301885, "name": "#1042.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:34:32-07:00", "line_items": [{"id": 10576684744893, "admin_graphql_api_id": "gid://shopify/LineItem/10576684744893", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576684744893, "admin_graphql_api_id": "gid://shopify/LineItem/10576684744893", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:35:22+00:00", "uuid": "339a9900-195f-11ec-8001-20e59110d7ba", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157907} -{"stream": "events", "data": {"object": "event", "id": "3mjysyMEPQT", "statistic_id": "TspjNE", "timestamp": 1632066016, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147939868861", "$value": 27.0, "$extra": {"id": 4147939868861, "admin_graphql_api_id": "gid://shopify/Order/4147939868861", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:39:57-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:39:56-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1043", "note": null, "note_attributes": [], "number": 43, "order_number": 1043, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/690a1a28cbbdd1e75d086a067c04893e/authenticate?key=6dba24f2d4fbaf6c9f61aa33bd3abc7c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:39:56-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "690a1a28cbbdd1e75d086a067c04893e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:39:57-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693390954685, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693390954685", "created_at": "2021-09-19T08:39:57-07:00", "location_id": 63590301885, "name": "#1043.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:39:57-07:00", "line_items": [{"id": 10576699392189, "admin_graphql_api_id": "gid://shopify/LineItem/10576699392189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576699392189, "admin_graphql_api_id": "gid://shopify/LineItem/10576699392189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:16+00:00", "uuid": "e2d77000-195f-11ec-8001-a27ee9ab17db", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157908} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzcJ4sq", "statistic_id": "TspjNE", "timestamp": 1632066020, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147939967165", "$value": 27.0, "$extra": {"id": 4147939967165, "admin_graphql_api_id": "gid://shopify/Order/4147939967165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:40:01-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:40:00-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1044", "note": null, "note_attributes": [], "number": 44, "order_number": 1044, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6199db9a4ae78ad08700b65105d62c7f/authenticate?key=b309b745e7ca63dd0a0394d1252f5cbd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:40:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6199db9a4ae78ad08700b65105d62c7f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:40:01-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693391020221, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693391020221", "created_at": "2021-09-19T08:40:00-07:00", "location_id": 63590301885, "name": "#1044.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:40:00-07:00", "line_items": [{"id": 10576699719869, "admin_graphql_api_id": "gid://shopify/LineItem/10576699719869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576699719869, "admin_graphql_api_id": "gid://shopify/LineItem/10576699719869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:20+00:00", "uuid": "e539ca00-195f-11ec-8001-389f95846c82", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157910} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQqJV", "statistic_id": "TspjNE", "timestamp": 1632066024, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147940098237", "$value": 27.0, "$extra": {"id": 4147940098237, "admin_graphql_api_id": "gid://shopify/Order/4147940098237", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:40:05-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:40:04-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1045", "note": null, "note_attributes": [], "number": 45, "order_number": 1045, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aec4eaa87607695171ca829edd39685b/authenticate?key=93495b2ca2049826afc9579dc81906e4", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:40:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "aec4eaa87607695171ca829edd39685b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:40:05-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693391052989, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693391052989", "created_at": "2021-09-19T08:40:04-07:00", "location_id": 63590301885, "name": "#1045.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:40:04-07:00", "line_items": [{"id": 10576699850941, "admin_graphql_api_id": "gid://shopify/LineItem/10576699850941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576699850941, "admin_graphql_api_id": "gid://shopify/LineItem/10576699850941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:24+00:00", "uuid": "e79c2400-195f-11ec-8001-a7b12c4f9ff0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157911} -{"stream": "events", "data": {"object": "event", "id": "3mjEbmuiwia", "statistic_id": "RDXsib", "timestamp": 1632066026, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147939868861:10576699392189:0", "$value": 27.0}, "datetime": "2021-09-19 15:40:26+00:00", "uuid": "e8cd5100-195f-11ec-8001-070f10d59cf2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157913} -{"stream": "events", "data": {"object": "event", "id": "3mjAFcAgWep", "statistic_id": "TspjNE", "timestamp": 1632066028, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147940262077", "$value": 27.0, "$extra": {"id": 4147940262077, "admin_graphql_api_id": "gid://shopify/Order/4147940262077", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:40:10-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:40:08-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1046", "note": null, "note_attributes": [], "number": 46, "order_number": 1046, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/3de3f04dbbcd1b1542916a0c76d97031/authenticate?key=1efcf903f236be0deada1cffda505e61", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:40:08-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "3de3f04dbbcd1b1542916a0c76d97031", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:40:11-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693391151293, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693391151293", "created_at": "2021-09-19T08:40:10-07:00", "location_id": 63590301885, "name": "#1046.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:40:10-07:00", "line_items": [{"id": 10576700211389, "admin_graphql_api_id": "gid://shopify/LineItem/10576700211389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576700211389, "admin_graphql_api_id": "gid://shopify/LineItem/10576700211389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:28+00:00", "uuid": "e9fe7e00-195f-11ec-8001-b7b56d195b93", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157913} -{"stream": "events", "data": {"object": "event", "id": "3mjxuZervLz", "statistic_id": "RDXsib", "timestamp": 1632066030, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147939967165:10576699719869:0", "$value": 27.0}, "datetime": "2021-09-19 15:40:30+00:00", "uuid": "eb2fab00-195f-11ec-8001-cda234e192ec", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157914} -{"stream": "events", "data": {"object": "event", "id": "3mjwRneMRaq", "statistic_id": "RDXsib", "timestamp": 1632066034, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147940098237:10576699850941:0", "$value": 27.0}, "datetime": "2021-09-19 15:40:34+00:00", "uuid": "ed920500-195f-11ec-8001-c5966db257ff", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157914} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLfwxS", "statistic_id": "RDXsib", "timestamp": 1632066038, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147940262077:10576700211389:0", "$value": 27.0}, "datetime": "2021-09-19 15:40:38+00:00", "uuid": "eff45f00-195f-11ec-8001-01a0ef0b6d8c", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157915} -{"stream": "events", "data": {"object": "event", "id": "3mjFsHj8ab8", "statistic_id": "X3f6PC", "timestamp": 1632066047, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147939868861", "$value": 27.0, "$extra": {"id": 4147939868861, "admin_graphql_api_id": "gid://shopify/Order/4147939868861", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:39:57-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:39:56-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1043", "note": null, "note_attributes": [], "number": 43, "order_number": 1043, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/690a1a28cbbdd1e75d086a067c04893e/authenticate?key=6dba24f2d4fbaf6c9f61aa33bd3abc7c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:39:56-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "690a1a28cbbdd1e75d086a067c04893e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:39:57-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693390954685, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693390954685", "created_at": "2021-09-19T08:39:57-07:00", "location_id": 63590301885, "name": "#1043.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:39:57-07:00", "line_items": [{"id": 10576699392189, "admin_graphql_api_id": "gid://shopify/LineItem/10576699392189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576699392189, "admin_graphql_api_id": "gid://shopify/LineItem/10576699392189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:47+00:00", "uuid": "f551a980-195f-11ec-8001-01afc10f7ca6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157915} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTa6k", "statistic_id": "X3f6PC", "timestamp": 1632066050, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147939967165", "$value": 27.0, "$extra": {"id": 4147939967165, "admin_graphql_api_id": "gid://shopify/Order/4147939967165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:40:01-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:40:00-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1044", "note": null, "note_attributes": [], "number": 44, "order_number": 1044, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6199db9a4ae78ad08700b65105d62c7f/authenticate?key=b309b745e7ca63dd0a0394d1252f5cbd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:40:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6199db9a4ae78ad08700b65105d62c7f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:40:01-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693391020221, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693391020221", "created_at": "2021-09-19T08:40:00-07:00", "location_id": 63590301885, "name": "#1044.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:40:00-07:00", "line_items": [{"id": 10576699719869, "admin_graphql_api_id": "gid://shopify/LineItem/10576699719869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576699719869, "admin_graphql_api_id": "gid://shopify/LineItem/10576699719869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:50+00:00", "uuid": "f71b6d00-195f-11ec-8001-a91459c1abed", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157916} -{"stream": "events", "data": {"object": "event", "id": "3mjAFbBFKYm", "statistic_id": "X3f6PC", "timestamp": 1632066054, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147940098237", "$value": 27.0, "$extra": {"id": 4147940098237, "admin_graphql_api_id": "gid://shopify/Order/4147940098237", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:40:05-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:40:04-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1045", "note": null, "note_attributes": [], "number": 45, "order_number": 1045, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aec4eaa87607695171ca829edd39685b/authenticate?key=93495b2ca2049826afc9579dc81906e4", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:40:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "aec4eaa87607695171ca829edd39685b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:40:05-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693391052989, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693391052989", "created_at": "2021-09-19T08:40:04-07:00", "location_id": 63590301885, "name": "#1045.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:40:04-07:00", "line_items": [{"id": 10576699850941, "admin_graphql_api_id": "gid://shopify/LineItem/10576699850941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576699850941, "admin_graphql_api_id": "gid://shopify/LineItem/10576699850941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:40:54+00:00", "uuid": "f97dc700-195f-11ec-8001-d170f9655ff7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157916} -{"stream": "events", "data": {"object": "event", "id": "3mjz6cKTRkB", "statistic_id": "X3f6PC", "timestamp": 1632066060, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147940262077", "$value": 27.0, "$extra": {"id": 4147940262077, "admin_graphql_api_id": "gid://shopify/Order/4147940262077", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:40:10-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:40:08-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1046", "note": null, "note_attributes": [], "number": 46, "order_number": 1046, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/3de3f04dbbcd1b1542916a0c76d97031/authenticate?key=1efcf903f236be0deada1cffda505e61", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:40:08-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "3de3f04dbbcd1b1542916a0c76d97031", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:40:11-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693391151293, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693391151293", "created_at": "2021-09-19T08:40:10-07:00", "location_id": 63590301885, "name": "#1046.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:40:10-07:00", "line_items": [{"id": 10576700211389, "admin_graphql_api_id": "gid://shopify/LineItem/10576700211389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576700211389, "admin_graphql_api_id": "gid://shopify/LineItem/10576700211389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:41:00+00:00", "uuid": "fd114e00-195f-11ec-8001-58315784b182", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157917} -{"stream": "events", "data": {"object": "event", "id": "3mjBiZjPbbR", "statistic_id": "TspjNE", "timestamp": 1632066099, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147941507261", "$value": 27.0, "$extra": {"id": 4147941507261, "admin_graphql_api_id": "gid://shopify/Order/4147941507261", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:20-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:19-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1047", "note": null, "note_attributes": [], "number": 47, "order_number": 1047, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a2461c0977fd9350661769d9ebafc6c0/authenticate?key=c3ad6115e3dbb674e28728bb03e530cd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a2461c0977fd9350661769d9ebafc6c0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:20-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392167101, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392167101", "created_at": "2021-09-19T08:41:19-07:00", "location_id": 63590301885, "name": "#1047.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:19-07:00", "line_items": [{"id": 10576702767293, "admin_graphql_api_id": "gid://shopify/LineItem/10576702767293", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576702767293, "admin_graphql_api_id": "gid://shopify/LineItem/10576702767293", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:41:39+00:00", "uuid": "14503b80-1960-11ec-8001-fb6210385fd3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157918} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV86Jx", "statistic_id": "TspjNE", "timestamp": 1632066105, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147941834941", "$value": 27.0, "$extra": {"id": 4147941834941, "admin_graphql_api_id": "gid://shopify/Order/4147941834941", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:28-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:25-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1048", "note": null, "note_attributes": [], "number": 48, "order_number": 1048, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/bce66a0a39e440d83f121aea77039ffb/authenticate?key=458c08b56a1252a610d67cc7d08e51e7", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:25-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "bce66a0a39e440d83f121aea77039ffb", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:30-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392396477, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392396477", "created_at": "2021-09-19T08:41:27-07:00", "location_id": 63590301885, "name": "#1048.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:27-07:00", "line_items": [{"id": 10576703127741, "admin_graphql_api_id": "gid://shopify/LineItem/10576703127741", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576703127741, "admin_graphql_api_id": "gid://shopify/LineItem/10576703127741", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:41:45+00:00", "uuid": "17e3c280-1960-11ec-8001-96817f7f76e7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157920} -{"stream": "events", "data": {"object": "event", "id": "3mjxNUk9vJJ", "statistic_id": "RDXsib", "timestamp": 1632066109, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147941507261:10576702767293:0", "$value": 27.0}, "datetime": "2021-09-19 15:41:49+00:00", "uuid": "1a461c80-1960-11ec-8001-6eac05788390", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157920} -{"stream": "events", "data": {"object": "event", "id": "3mjvTT5dt2J", "statistic_id": "RDXsib", "timestamp": 1632066115, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147941834941:10576703127741:0", "$value": 27.0}, "datetime": "2021-09-19 15:41:55+00:00", "uuid": "1dd9a380-1960-11ec-8001-7a7d38f3e280", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157920} -{"stream": "events", "data": {"object": "event", "id": "3mjENWwKXmC", "statistic_id": "TspjNE", "timestamp": 1632066115, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147941966013", "$value": 27.0, "$extra": {"id": 4147941966013, "admin_graphql_api_id": "gid://shopify/Order/4147941966013", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:36-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1049", "note": null, "note_attributes": [], "number": 49, "order_number": 1049, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/609591421b20df0be2e076c9d0f8dc1b/authenticate?key=b7966a565400d5fba88403e0411de5a9", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "609591421b20df0be2e076c9d0f8dc1b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:36-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392625853, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392625853", "created_at": "2021-09-19T08:41:35-07:00", "location_id": 63590301885, "name": "#1049.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:35-07:00", "line_items": [{"id": 10576703422653, "admin_graphql_api_id": "gid://shopify/LineItem/10576703422653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576703422653, "admin_graphql_api_id": "gid://shopify/LineItem/10576703422653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:41:55+00:00", "uuid": "1dd9a380-1960-11ec-8001-af416e47079a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157921} -{"stream": "events", "data": {"object": "event", "id": "3mjCTZacfBQ", "statistic_id": "TspjNE", "timestamp": 1632066122, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147942064317", "$value": 27.0, "$extra": {"id": 4147942064317, "admin_graphql_api_id": "gid://shopify/Order/4147942064317", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:43-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:42-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1050", "note": null, "note_attributes": [], "number": 50, "order_number": 1050, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aac744121d6cbe6cd3e051558e239882/authenticate?key=8f693f364ca461c318757c3a0f8093fe", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "aac744121d6cbe6cd3e051558e239882", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:43-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392855229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392855229", "created_at": "2021-09-19T08:41:42-07:00", "location_id": 63590301885, "name": "#1050.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:42-07:00", "line_items": [{"id": 10576703553725, "admin_graphql_api_id": "gid://shopify/LineItem/10576703553725", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576703553725, "admin_graphql_api_id": "gid://shopify/LineItem/10576703553725", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:42:02+00:00", "uuid": "2205c100-1960-11ec-8001-f6e3eee553d3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157922} -{"stream": "events", "data": {"object": "event", "id": "3mjz67SW8qM", "statistic_id": "RDXsib", "timestamp": 1632066125, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147941966013:10576703422653:0", "$value": 27.0}, "datetime": "2021-09-19 15:42:05+00:00", "uuid": "23cf8480-1960-11ec-8001-e17afd94adad", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157924} -{"stream": "events", "data": {"object": "event", "id": "3mjz6bi2aNV", "statistic_id": "X3f6PC", "timestamp": 1632066129, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147941507261", "$value": 27.0, "$extra": {"id": 4147941507261, "admin_graphql_api_id": "gid://shopify/Order/4147941507261", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:20-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:19-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1047", "note": null, "note_attributes": [], "number": 47, "order_number": 1047, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a2461c0977fd9350661769d9ebafc6c0/authenticate?key=c3ad6115e3dbb674e28728bb03e530cd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a2461c0977fd9350661769d9ebafc6c0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:20-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392167101, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392167101", "created_at": "2021-09-19T08:41:19-07:00", "location_id": 63590301885, "name": "#1047.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:19-07:00", "line_items": [{"id": 10576702767293, "admin_graphql_api_id": "gid://shopify/LineItem/10576702767293", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576702767293, "admin_graphql_api_id": "gid://shopify/LineItem/10576702767293", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:42:09+00:00", "uuid": "2631de80-1960-11ec-8001-9a50423d54f0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157924} -{"stream": "events", "data": {"object": "event", "id": "3mjvTRBk29V", "statistic_id": "RDXsib", "timestamp": 1632066132, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147942064317:10576703553725:0", "$value": 27.0}, "datetime": "2021-09-19 15:42:12+00:00", "uuid": "27fba200-1960-11ec-8001-a9a881dc2fdf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157925} -{"stream": "events", "data": {"object": "event", "id": "3mjz6dJuPTG", "statistic_id": "X3f6PC", "timestamp": 1632066137, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147941834941", "$value": 27.0, "$extra": {"id": 4147941834941, "admin_graphql_api_id": "gid://shopify/Order/4147941834941", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:28-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:25-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1048", "note": null, "note_attributes": [], "number": 48, "order_number": 1048, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/bce66a0a39e440d83f121aea77039ffb/authenticate?key=458c08b56a1252a610d67cc7d08e51e7", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:25-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "bce66a0a39e440d83f121aea77039ffb", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:30-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392396477, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392396477", "created_at": "2021-09-19T08:41:27-07:00", "location_id": 63590301885, "name": "#1048.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:27-07:00", "line_items": [{"id": 10576703127741, "admin_graphql_api_id": "gid://shopify/LineItem/10576703127741", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576703127741, "admin_graphql_api_id": "gid://shopify/LineItem/10576703127741", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:42:17+00:00", "uuid": "2af69280-1960-11ec-8001-cdb47ed8d293", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157926} -{"stream": "events", "data": {"object": "event", "id": "3mjz6bMiKdp", "statistic_id": "X3f6PC", "timestamp": 1632066145, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147941966013", "$value": 27.0, "$extra": {"id": 4147941966013, "admin_graphql_api_id": "gid://shopify/Order/4147941966013", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:36-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1049", "note": null, "note_attributes": [], "number": 49, "order_number": 1049, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/609591421b20df0be2e076c9d0f8dc1b/authenticate?key=b7966a565400d5fba88403e0411de5a9", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "609591421b20df0be2e076c9d0f8dc1b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:36-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392625853, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392625853", "created_at": "2021-09-19T08:41:35-07:00", "location_id": 63590301885, "name": "#1049.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:35-07:00", "line_items": [{"id": 10576703422653, "admin_graphql_api_id": "gid://shopify/LineItem/10576703422653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576703422653, "admin_graphql_api_id": "gid://shopify/LineItem/10576703422653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:42:25+00:00", "uuid": "2fbb4680-1960-11ec-8001-a7df33c1199a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157926} -{"stream": "events", "data": {"object": "event", "id": "3mjxNRqnncE", "statistic_id": "X3f6PC", "timestamp": 1632066152, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147942064317", "$value": 27.0, "$extra": {"id": 4147942064317, "admin_graphql_api_id": "gid://shopify/Order/4147942064317", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:41:43-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:41:42-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1050", "note": null, "note_attributes": [], "number": 50, "order_number": 1050, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aac744121d6cbe6cd3e051558e239882/authenticate?key=8f693f364ca461c318757c3a0f8093fe", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:41:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "aac744121d6cbe6cd3e051558e239882", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:41:43-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693392855229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693392855229", "created_at": "2021-09-19T08:41:42-07:00", "location_id": 63590301885, "name": "#1050.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:41:42-07:00", "line_items": [{"id": 10576703553725, "admin_graphql_api_id": "gid://shopify/LineItem/10576703553725", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576703553725, "admin_graphql_api_id": "gid://shopify/LineItem/10576703553725", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:42:32+00:00", "uuid": "33e76400-1960-11ec-8001-cabd0abb0dd9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157927} -{"stream": "events", "data": {"object": "event", "id": "3mjy8zMAuvV", "statistic_id": "TspjNE", "timestamp": 1632066221, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147943932093", "$value": 27.0, "$extra": {"id": 4147943932093, "admin_graphql_api_id": "gid://shopify/Order/4147943932093", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:22-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:21-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1051", "note": null, "note_attributes": [], "number": 51, "order_number": 1051, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fdd1054eb75700c66717080d93c9d4dc/authenticate?key=8e0b30b4e0403163d35f88db459dcc06", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:21-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "fdd1054eb75700c66717080d93c9d4dc", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:22-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395574973, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395574973", "created_at": "2021-09-19T08:43:21-07:00", "location_id": 63590301885, "name": "#1051.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:21-07:00", "line_items": [{"id": 10576707256509, "admin_graphql_api_id": "gid://shopify/LineItem/10576707256509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707256509, "admin_graphql_api_id": "gid://shopify/LineItem/10576707256509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:43:41+00:00", "uuid": "5d07f480-1960-11ec-8001-1b8cdc0c9d8a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157928} -{"stream": "events", "data": {"object": "event", "id": "3mjAmwaewjW", "statistic_id": "TspjNE", "timestamp": 1632066228, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147944063165", "$value": 27.0, "$extra": {"id": 4147944063165, "admin_graphql_api_id": "gid://shopify/Order/4147944063165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:29-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:28-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1052", "note": null, "note_attributes": [], "number": 52, "order_number": 1052, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/0e1b054be5f927c563aa8108476fade5/authenticate?key=873eb695117d75475ab18790114cd9ce", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:28-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "0e1b054be5f927c563aa8108476fade5", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:30-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395607741, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395607741", "created_at": "2021-09-19T08:43:29-07:00", "location_id": 63590301885, "name": "#1052.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:29-07:00", "line_items": [{"id": 10576707682493, "admin_graphql_api_id": "gid://shopify/LineItem/10576707682493", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707682493, "admin_graphql_api_id": "gid://shopify/LineItem/10576707682493", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:43:48+00:00", "uuid": "61341200-1960-11ec-8001-c16ebb3df0c1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157929} -{"stream": "events", "data": {"object": "event", "id": "3mjDRyHrpHG", "statistic_id": "RDXsib", "timestamp": 1632066231, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147943932093:10576707256509:0", "$value": 27.0}, "datetime": "2021-09-19 15:43:51+00:00", "uuid": "62fdd580-1960-11ec-8001-a2a9ae8400e4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157930} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9txTwb", "statistic_id": "TspjNE", "timestamp": 1632066235, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147944161469", "$value": 27.0, "$extra": {"id": 4147944161469, "admin_graphql_api_id": "gid://shopify/Order/4147944161469", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:36-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1053", "note": null, "note_attributes": [], "number": 53, "order_number": 1053, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f0d5f4acecbd0fe3ea4d4d5c0ab86b75/authenticate?key=44641dc8c821387cb2f564d34f0dcb25", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f0d5f4acecbd0fe3ea4d4d5c0ab86b75", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:36-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395673277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395673277", "created_at": "2021-09-19T08:43:35-07:00", "location_id": 63590301885, "name": "#1053.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:35-07:00", "line_items": [{"id": 10576707879101, "admin_graphql_api_id": "gid://shopify/LineItem/10576707879101", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707879101, "admin_graphql_api_id": "gid://shopify/LineItem/10576707879101", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:43:55+00:00", "uuid": "65602f80-1960-11ec-8001-f52db6b0bab6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157930} -{"stream": "events", "data": {"object": "event", "id": "3mjxNUPrYzB", "statistic_id": "RDXsib", "timestamp": 1632066238, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147944063165:10576707682493:0", "$value": 27.0}, "datetime": "2021-09-19 15:43:58+00:00", "uuid": "6729f300-1960-11ec-8001-23f9a712cdc6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157931} -{"stream": "events", "data": {"object": "event", "id": "3mjwxwy9BXU", "statistic_id": "TspjNE", "timestamp": 1632066242, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147944292541", "$value": 27.0, "$extra": {"id": 4147944292541, "admin_graphql_api_id": "gid://shopify/Order/4147944292541", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:43-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:42-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1054", "note": null, "note_attributes": [], "number": 54, "order_number": 1054, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8a30102b9c9fde3755c11bad63ecfd93/authenticate?key=331dfeb79734af3671c4f2929dbff143", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8a30102b9c9fde3755c11bad63ecfd93", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:43-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395804349, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395804349", "created_at": "2021-09-19T08:43:42-07:00", "location_id": 63590301885, "name": "#1054.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:42-07:00", "line_items": [{"id": 10576707944637, "admin_graphql_api_id": "gid://shopify/LineItem/10576707944637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707944637, "admin_graphql_api_id": "gid://shopify/LineItem/10576707944637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:44:02+00:00", "uuid": "698c4d00-1960-11ec-8001-81db6c189199", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157931} -{"stream": "events", "data": {"object": "event", "id": "3mjvTV2pMZD", "statistic_id": "RDXsib", "timestamp": 1632066245, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147944161469:10576707879101:0", "$value": 27.0}, "datetime": "2021-09-19 15:44:05+00:00", "uuid": "6b561080-1960-11ec-8001-45da8c8421d1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157933} -{"stream": "events", "data": {"object": "event", "id": "3mjDRujLJC2", "statistic_id": "X3f6PC", "timestamp": 1632066251, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147943932093", "$value": 27.0, "$extra": {"id": 4147943932093, "admin_graphql_api_id": "gid://shopify/Order/4147943932093", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:22-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:21-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1051", "note": null, "note_attributes": [], "number": 51, "order_number": 1051, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fdd1054eb75700c66717080d93c9d4dc/authenticate?key=8e0b30b4e0403163d35f88db459dcc06", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:21-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "fdd1054eb75700c66717080d93c9d4dc", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:22-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395574973, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395574973", "created_at": "2021-09-19T08:43:21-07:00", "location_id": 63590301885, "name": "#1051.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:21-07:00", "line_items": [{"id": 10576707256509, "admin_graphql_api_id": "gid://shopify/LineItem/10576707256509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707256509, "admin_graphql_api_id": "gid://shopify/LineItem/10576707256509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:44:11+00:00", "uuid": "6ee99780-1960-11ec-8001-1232d9036dc7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157933} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzcJba4", "statistic_id": "RDXsib", "timestamp": 1632066252, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147944292541:10576707944637:0", "$value": 27.0}, "datetime": "2021-09-19 15:44:12+00:00", "uuid": "6f822e00-1960-11ec-8001-3c94e8182ef0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157934} -{"stream": "events", "data": {"object": "event", "id": "3mjFsGPPRw5", "statistic_id": "X3f6PC", "timestamp": 1632066259, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147944063165", "$value": 27.0, "$extra": {"id": 4147944063165, "admin_graphql_api_id": "gid://shopify/Order/4147944063165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:29-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:28-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1052", "note": null, "note_attributes": [], "number": 52, "order_number": 1052, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/0e1b054be5f927c563aa8108476fade5/authenticate?key=873eb695117d75475ab18790114cd9ce", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:28-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "0e1b054be5f927c563aa8108476fade5", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:30-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395607741, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395607741", "created_at": "2021-09-19T08:43:29-07:00", "location_id": 63590301885, "name": "#1052.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:29-07:00", "line_items": [{"id": 10576707682493, "admin_graphql_api_id": "gid://shopify/LineItem/10576707682493", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707682493, "admin_graphql_api_id": "gid://shopify/LineItem/10576707682493", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:44:19+00:00", "uuid": "73ae4b80-1960-11ec-8001-c91857eadafa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157934} -{"stream": "events", "data": {"object": "event", "id": "3mjEP5kTqHM", "statistic_id": "X3f6PC", "timestamp": 1632066265, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147944161469", "$value": 27.0, "$extra": {"id": 4147944161469, "admin_graphql_api_id": "gid://shopify/Order/4147944161469", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:36-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1053", "note": null, "note_attributes": [], "number": 53, "order_number": 1053, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f0d5f4acecbd0fe3ea4d4d5c0ab86b75/authenticate?key=44641dc8c821387cb2f564d34f0dcb25", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f0d5f4acecbd0fe3ea4d4d5c0ab86b75", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:36-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395673277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395673277", "created_at": "2021-09-19T08:43:35-07:00", "location_id": 63590301885, "name": "#1053.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:35-07:00", "line_items": [{"id": 10576707879101, "admin_graphql_api_id": "gid://shopify/LineItem/10576707879101", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707879101, "admin_graphql_api_id": "gid://shopify/LineItem/10576707879101", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:44:25+00:00", "uuid": "7741d280-1960-11ec-8001-b41421df0abd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157935} -{"stream": "events", "data": {"object": "event", "id": "3mjz6ajra2r", "statistic_id": "X3f6PC", "timestamp": 1632066272, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147944292541", "$value": 27.0, "$extra": {"id": 4147944292541, "admin_graphql_api_id": "gid://shopify/Order/4147944292541", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:43:43-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:43:42-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1054", "note": null, "note_attributes": [], "number": 54, "order_number": 1054, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8a30102b9c9fde3755c11bad63ecfd93/authenticate?key=331dfeb79734af3671c4f2929dbff143", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:43:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8a30102b9c9fde3755c11bad63ecfd93", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:43:43-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693395804349, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693395804349", "created_at": "2021-09-19T08:43:42-07:00", "location_id": 63590301885, "name": "#1054.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:43:42-07:00", "line_items": [{"id": 10576707944637, "admin_graphql_api_id": "gid://shopify/LineItem/10576707944637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576707944637, "admin_graphql_api_id": "gid://shopify/LineItem/10576707944637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:44:32+00:00", "uuid": "7b6df000-1960-11ec-8001-4b06e33c1ca0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157936} -{"stream": "events", "data": {"object": "event", "id": "3mjDRuP5byh", "statistic_id": "TspjNE", "timestamp": 1632066619, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147952943293", "$value": 27.0, "$extra": {"id": 4147952943293, "admin_graphql_api_id": "gid://shopify/Order/4147952943293", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:00-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:49:59-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1055", "note": null, "note_attributes": [], "number": 55, "order_number": 1055, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c25746b711a06a63cdf1661675010b4e/authenticate?key=a43d79606247d8c01ee5d940a32774fe", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:49:59-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "c25746b711a06a63cdf1661675010b4e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:01-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693400752317, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693400752317", "created_at": "2021-09-19T08:50:00-07:00", "location_id": 63590301885, "name": "#1055.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:00-07:00", "line_items": [{"id": 10576722690237, "admin_graphql_api_id": "gid://shopify/LineItem/10576722690237", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576722690237, "admin_graphql_api_id": "gid://shopify/LineItem/10576722690237", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:50:19+00:00", "uuid": "4a41ef80-1961-11ec-8001-9913fb3019c3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157937} -{"stream": "events", "data": {"object": "event", "id": "3mjvTUx7aVn", "statistic_id": "RDXsib", "timestamp": 1632066629, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147952943293:10576722690237:0", "$value": 27.0}, "datetime": "2021-09-19 15:50:29+00:00", "uuid": "5037d080-1961-11ec-8001-e40f9a435fbf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157938} -{"stream": "events", "data": {"object": "event", "id": "3mjwdDpBM5G", "statistic_id": "X3f6PC", "timestamp": 1632066650, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147952943293", "$value": 27.0, "$extra": {"id": 4147952943293, "admin_graphql_api_id": "gid://shopify/Order/4147952943293", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:00-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:49:59-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1055", "note": null, "note_attributes": [], "number": 55, "order_number": 1055, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c25746b711a06a63cdf1661675010b4e/authenticate?key=a43d79606247d8c01ee5d940a32774fe", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:49:59-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "c25746b711a06a63cdf1661675010b4e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:01-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693400752317, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693400752317", "created_at": "2021-09-19T08:50:00-07:00", "location_id": 63590301885, "name": "#1055.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:00-07:00", "line_items": [{"id": 10576722690237, "admin_graphql_api_id": "gid://shopify/LineItem/10576722690237", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576722690237, "admin_graphql_api_id": "gid://shopify/LineItem/10576722690237", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:50:50+00:00", "uuid": "5cbc2900-1961-11ec-8001-84ea0fccec9c", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157939} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV7TQU", "statistic_id": "TspjNE", "timestamp": 1632066651, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147953533117", "$value": 27.0, "$extra": {"id": 4147953533117, "admin_graphql_api_id": "gid://shopify/Order/4147953533117", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:33-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:50:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1056", "note": null, "note_attributes": [], "number": 56, "order_number": 1056, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/616bcae3a4bb68f4b6902459c898e69f/authenticate?key=6d8ea3104b30a153a3f18072f0897ea5", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:50:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "616bcae3a4bb68f4b6902459c898e69f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401047229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401047229", "created_at": "2021-09-19T08:50:32-07:00", "location_id": 63590301885, "name": "#1056.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:32-07:00", "line_items": [{"id": 10576723640509, "admin_graphql_api_id": "gid://shopify/LineItem/10576723640509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576723640509, "admin_graphql_api_id": "gid://shopify/LineItem/10576723640509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:50:51+00:00", "uuid": "5d54bf80-1961-11ec-8001-adc2990dded9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157940} -{"stream": "events", "data": {"object": "event", "id": "3mjDxBExfB4", "statistic_id": "TspjNE", "timestamp": 1632066654, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147953598653", "$value": 27.0, "$extra": {"id": 4147953598653, "admin_graphql_api_id": "gid://shopify/Order/4147953598653", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:34-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:50:34-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1057", "note": null, "note_attributes": [], "number": 57, "order_number": 1057, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d96a7022bccc4961cadfcad5495b76b7/authenticate?key=233e0a37acf923ee9c702875ef9c64b6", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:50:34-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d96a7022bccc4961cadfcad5495b76b7", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:35-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401079997, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401079997", "created_at": "2021-09-19T08:50:34-07:00", "location_id": 63590301885, "name": "#1057.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:34-07:00", "line_items": [{"id": 10576723837117, "admin_graphql_api_id": "gid://shopify/LineItem/10576723837117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576723837117, "admin_graphql_api_id": "gid://shopify/LineItem/10576723837117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:50:54+00:00", "uuid": "5f1e8300-1961-11ec-8001-0db87b7e0faa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157941} -{"stream": "events", "data": {"object": "event", "id": "3mjz68Rx9RJ", "statistic_id": "TspjNE", "timestamp": 1632066655, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147953664189", "$value": 27.0, "$extra": {"id": 4147953664189, "admin_graphql_api_id": "gid://shopify/Order/4147953664189", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:37-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:50:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1058", "note": null, "note_attributes": [], "number": 58, "order_number": 1058, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ad4183982325843eefe14e8a9a99760b/authenticate?key=4d03a96ecbe4808d39d533399e8b7103", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:50:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ad4183982325843eefe14e8a9a99760b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:37-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401112765, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401112765", "created_at": "2021-09-19T08:50:36-07:00", "location_id": 63590301885, "name": "#1058.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:36-07:00", "line_items": [{"id": 10576723902653, "admin_graphql_api_id": "gid://shopify/LineItem/10576723902653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576723902653, "admin_graphql_api_id": "gid://shopify/LineItem/10576723902653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:50:55+00:00", "uuid": "5fb71980-1961-11ec-8001-5a1a6bb1d9a9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157942} -{"stream": "events", "data": {"object": "event", "id": "3mjEP2r8f2T", "statistic_id": "RDXsib", "timestamp": 1632066661, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147953533117:10576723640509:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:01+00:00", "uuid": "634aa080-1961-11ec-8001-60c6110ccdbb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157942} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9WQsPn", "statistic_id": "RDXsib", "timestamp": 1632066664, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147953598653:10576723837117:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:04+00:00", "uuid": "65146400-1961-11ec-8001-16b073a86bec", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157943} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLffU2", "statistic_id": "RDXsib", "timestamp": 1632066665, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147953664189:10576723902653:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:05+00:00", "uuid": "65acfa80-1961-11ec-8001-063faf5ead9c", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157943} -{"stream": "events", "data": {"object": "event", "id": "3mjuiJWPmAW", "statistic_id": "X3f6PC", "timestamp": 1632066682, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147953533117", "$value": 27.0, "$extra": {"id": 4147953533117, "admin_graphql_api_id": "gid://shopify/Order/4147953533117", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:33-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:50:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1056", "note": null, "note_attributes": [], "number": 56, "order_number": 1056, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/616bcae3a4bb68f4b6902459c898e69f/authenticate?key=6d8ea3104b30a153a3f18072f0897ea5", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:50:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "616bcae3a4bb68f4b6902459c898e69f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401047229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401047229", "created_at": "2021-09-19T08:50:32-07:00", "location_id": 63590301885, "name": "#1056.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:32-07:00", "line_items": [{"id": 10576723640509, "admin_graphql_api_id": "gid://shopify/LineItem/10576723640509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576723640509, "admin_graphql_api_id": "gid://shopify/LineItem/10576723640509", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:22+00:00", "uuid": "6fcef900-1961-11ec-8001-f6fb353a8dc1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157944} -{"stream": "events", "data": {"object": "event", "id": "3mjwxsDMhWR", "statistic_id": "X3f6PC", "timestamp": 1632066684, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147953598653", "$value": 27.0, "$extra": {"id": 4147953598653, "admin_graphql_api_id": "gid://shopify/Order/4147953598653", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:34-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:50:34-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1057", "note": null, "note_attributes": [], "number": 57, "order_number": 1057, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d96a7022bccc4961cadfcad5495b76b7/authenticate?key=233e0a37acf923ee9c702875ef9c64b6", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:50:34-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d96a7022bccc4961cadfcad5495b76b7", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:35-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401079997, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401079997", "created_at": "2021-09-19T08:50:34-07:00", "location_id": 63590301885, "name": "#1057.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:34-07:00", "line_items": [{"id": 10576723837117, "admin_graphql_api_id": "gid://shopify/LineItem/10576723837117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576723837117, "admin_graphql_api_id": "gid://shopify/LineItem/10576723837117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:24+00:00", "uuid": "71002600-1961-11ec-8001-0ef9a87525d1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157944} -{"stream": "events", "data": {"object": "event", "id": "3mjBWzpFz2a", "statistic_id": "X3f6PC", "timestamp": 1632066686, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147953664189", "$value": 27.0, "$extra": {"id": 4147953664189, "admin_graphql_api_id": "gid://shopify/Order/4147953664189", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:50:37-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:50:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1058", "note": null, "note_attributes": [], "number": 58, "order_number": 1058, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ad4183982325843eefe14e8a9a99760b/authenticate?key=4d03a96ecbe4808d39d533399e8b7103", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:50:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ad4183982325843eefe14e8a9a99760b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:50:37-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401112765, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401112765", "created_at": "2021-09-19T08:50:36-07:00", "location_id": 63590301885, "name": "#1058.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:50:36-07:00", "line_items": [{"id": 10576723902653, "admin_graphql_api_id": "gid://shopify/LineItem/10576723902653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576723902653, "admin_graphql_api_id": "gid://shopify/LineItem/10576723902653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:26+00:00", "uuid": "72315300-1961-11ec-8001-9bb4285cd5f1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157945} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRT6fj", "statistic_id": "TspjNE", "timestamp": 1632066691, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147954745533", "$value": 27.0, "$extra": {"id": 4147954745533, "admin_graphql_api_id": "gid://shopify/Order/4147954745533", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:12-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:11-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1059", "note": null, "note_attributes": [], "number": 59, "order_number": 1059, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/50ab4d656ff045566f99b9b0339a16ec/authenticate?key=f2e95901174ca9a1421287c6b5bed22c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:11-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "50ab4d656ff045566f99b9b0339a16ec", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:12-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401505981, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401505981", "created_at": "2021-09-19T08:51:11-07:00", "location_id": 63590301885, "name": "#1059.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:11-07:00", "line_items": [{"id": 10576725770429, "admin_graphql_api_id": "gid://shopify/LineItem/10576725770429", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576725770429, "admin_graphql_api_id": "gid://shopify/LineItem/10576725770429", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:31+00:00", "uuid": "752c4380-1961-11ec-8001-76b4cc75f1a4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157946} -{"stream": "events", "data": {"object": "event", "id": "3mjz69Q8gPK", "statistic_id": "TspjNE", "timestamp": 1632066693, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147954811069", "$value": 27.0, "$extra": {"id": 4147954811069, "admin_graphql_api_id": "gid://shopify/Order/4147954811069", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:14-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:13-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1060", "note": null, "note_attributes": [], "number": 60, "order_number": 1060, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/1ea49fa977130d96523a76813a59101b/authenticate?key=554933f67e84da3f973221de4fd8e982", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:13-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "1ea49fa977130d96523a76813a59101b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:14-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401538749, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401538749", "created_at": "2021-09-19T08:51:13-07:00", "location_id": 63590301885, "name": "#1060.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:13-07:00", "line_items": [{"id": 10576725835965, "admin_graphql_api_id": "gid://shopify/LineItem/10576725835965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576725835965, "admin_graphql_api_id": "gid://shopify/LineItem/10576725835965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:33+00:00", "uuid": "765d7080-1961-11ec-8001-1d6b846ed8dd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157947} -{"stream": "events", "data": {"object": "event", "id": "3mjBWBQacLT", "statistic_id": "TspjNE", "timestamp": 1632066695, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147954974909", "$value": 27.0, "$extra": {"id": 4147954974909, "admin_graphql_api_id": "gid://shopify/Order/4147954974909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:16-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:15-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1061", "note": null, "note_attributes": [], "number": 61, "order_number": 1061, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d895edd8880ba2684f8da5e380e21f62/authenticate?key=c7de42edf9c28f6fdd16d7a3aac33f9c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:15-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d895edd8880ba2684f8da5e380e21f62", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:16-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401571517, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401571517", "created_at": "2021-09-19T08:51:15-07:00", "location_id": 63590301885, "name": "#1061.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:15-07:00", "line_items": [{"id": 10576726032573, "admin_graphql_api_id": "gid://shopify/LineItem/10576726032573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576726032573, "admin_graphql_api_id": "gid://shopify/LineItem/10576726032573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:35+00:00", "uuid": "778e9d80-1961-11ec-8001-ea723371bdc3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157948} -{"stream": "events", "data": {"object": "event", "id": "3mjFsHj8nh7", "statistic_id": "TspjNE", "timestamp": 1632066697, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147955105981", "$value": 27.0, "$extra": {"id": 4147955105981, "admin_graphql_api_id": "gid://shopify/Order/4147955105981", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:18-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:17-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1062", "note": null, "note_attributes": [], "number": 62, "order_number": 1062, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8d5731ff1857d02672f9128642ef6bf2/authenticate?key=de8345a88b3a9831975ae48abd48de5f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:17-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8d5731ff1857d02672f9128642ef6bf2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:18-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401604285, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401604285", "created_at": "2021-09-19T08:51:17-07:00", "location_id": 63590301885, "name": "#1062.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:17-07:00", "line_items": [{"id": 10576726655165, "admin_graphql_api_id": "gid://shopify/LineItem/10576726655165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576726655165, "admin_graphql_api_id": "gid://shopify/LineItem/10576726655165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:51:37+00:00", "uuid": "78bfca80-1961-11ec-8001-75034d2be8e6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157949} -{"stream": "events", "data": {"object": "event", "id": "3mjAFc6YtnT", "statistic_id": "RDXsib", "timestamp": 1632066701, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147954745533:10576725770429:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:41+00:00", "uuid": "7b222480-1961-11ec-8001-6ac4e37cecda", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157950} -{"stream": "events", "data": {"object": "event", "id": "3mjCgjeMkqV", "statistic_id": "RDXsib", "timestamp": 1632066703, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147954811069:10576725835965:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:43+00:00", "uuid": "7c535180-1961-11ec-8001-99b776362fbe", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157950} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQmyk", "statistic_id": "RDXsib", "timestamp": 1632066705, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147954974909:10576726032573:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:45+00:00", "uuid": "7d847e80-1961-11ec-8001-263f1c1064c2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157950} -{"stream": "events", "data": {"object": "event", "id": "3mjxNTmypqw", "statistic_id": "RDXsib", "timestamp": 1632066707, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147955105981:10576726655165:0", "$value": 27.0}, "datetime": "2021-09-19 15:51:47+00:00", "uuid": "7eb5ab80-1961-11ec-8001-ba76bda9999e", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157950} -{"stream": "events", "data": {"object": "event", "id": "3mjAFfw4bJk", "statistic_id": "X3f6PC", "timestamp": 1632066721, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147954745533", "$value": 27.0, "$extra": {"id": 4147954745533, "admin_graphql_api_id": "gid://shopify/Order/4147954745533", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:12-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:11-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1059", "note": null, "note_attributes": [], "number": 59, "order_number": 1059, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/50ab4d656ff045566f99b9b0339a16ec/authenticate?key=f2e95901174ca9a1421287c6b5bed22c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:11-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "50ab4d656ff045566f99b9b0339a16ec", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:12-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401505981, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401505981", "created_at": "2021-09-19T08:51:11-07:00", "location_id": 63590301885, "name": "#1059.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:11-07:00", "line_items": [{"id": 10576725770429, "admin_graphql_api_id": "gid://shopify/LineItem/10576725770429", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576725770429, "admin_graphql_api_id": "gid://shopify/LineItem/10576725770429", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:01+00:00", "uuid": "870de680-1961-11ec-8001-78ca6385abf8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157950} -{"stream": "events", "data": {"object": "event", "id": "3mjCTZac6EP", "statistic_id": "X3f6PC", "timestamp": 1632066723, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147954811069", "$value": 27.0, "$extra": {"id": 4147954811069, "admin_graphql_api_id": "gid://shopify/Order/4147954811069", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:14-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:13-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1060", "note": null, "note_attributes": [], "number": 60, "order_number": 1060, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/1ea49fa977130d96523a76813a59101b/authenticate?key=554933f67e84da3f973221de4fd8e982", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:13-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "1ea49fa977130d96523a76813a59101b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:14-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401538749, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401538749", "created_at": "2021-09-19T08:51:13-07:00", "location_id": 63590301885, "name": "#1060.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:13-07:00", "line_items": [{"id": 10576725835965, "admin_graphql_api_id": "gid://shopify/LineItem/10576725835965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576725835965, "admin_graphql_api_id": "gid://shopify/LineItem/10576725835965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:03+00:00", "uuid": "883f1380-1961-11ec-8001-4356ab4eacb2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157951} -{"stream": "events", "data": {"object": "event", "id": "3mjz6bi2jbr", "statistic_id": "X3f6PC", "timestamp": 1632066725, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147954974909", "$value": 27.0, "$extra": {"id": 4147954974909, "admin_graphql_api_id": "gid://shopify/Order/4147954974909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:16-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:15-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1061", "note": null, "note_attributes": [], "number": 61, "order_number": 1061, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d895edd8880ba2684f8da5e380e21f62/authenticate?key=c7de42edf9c28f6fdd16d7a3aac33f9c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:15-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d895edd8880ba2684f8da5e380e21f62", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:16-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401571517, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401571517", "created_at": "2021-09-19T08:51:15-07:00", "location_id": 63590301885, "name": "#1061.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:15-07:00", "line_items": [{"id": 10576726032573, "admin_graphql_api_id": "gid://shopify/LineItem/10576726032573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576726032573, "admin_graphql_api_id": "gid://shopify/LineItem/10576726032573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:05+00:00", "uuid": "89704080-1961-11ec-8001-c7c17498c8f9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157952} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhterFY", "statistic_id": "X3f6PC", "timestamp": 1632066727, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147955105981", "$value": 27.0, "$extra": {"id": 4147955105981, "admin_graphql_api_id": "gid://shopify/Order/4147955105981", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:51:18-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:51:17-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1062", "note": null, "note_attributes": [], "number": 62, "order_number": 1062, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8d5731ff1857d02672f9128642ef6bf2/authenticate?key=de8345a88b3a9831975ae48abd48de5f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:51:17-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8d5731ff1857d02672f9128642ef6bf2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:51:18-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693401604285, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693401604285", "created_at": "2021-09-19T08:51:17-07:00", "location_id": 63590301885, "name": "#1062.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:51:17-07:00", "line_items": [{"id": 10576726655165, "admin_graphql_api_id": "gid://shopify/LineItem/10576726655165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576726655165, "admin_graphql_api_id": "gid://shopify/LineItem/10576726655165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:07+00:00", "uuid": "8aa16d80-1961-11ec-8001-b793c07af7cd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157953} -{"stream": "events", "data": {"object": "event", "id": "3mjxbcWQZx7", "statistic_id": "TspjNE", "timestamp": 1632066759, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147956351165", "$value": 27.0, "$extra": {"id": 4147956351165, "admin_graphql_api_id": "gid://shopify/Order/4147956351165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:19-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:19-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1063", "note": null, "note_attributes": [], "number": 63, "order_number": 1063, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/1d839ec6308c303df68daf96a3948c1f/authenticate?key=83aed051ba6feeed7cad7693458d54ee", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "1d839ec6308c303df68daf96a3948c1f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:20-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402226877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402226877", "created_at": "2021-09-19T08:52:19-07:00", "location_id": 63590301885, "name": "#1063.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:19-07:00", "line_items": [{"id": 10576728785085, "admin_graphql_api_id": "gid://shopify/LineItem/10576728785085", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728785085, "admin_graphql_api_id": "gid://shopify/LineItem/10576728785085", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:39+00:00", "uuid": "9db43d80-1961-11ec-8001-189dfe944897", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157953} -{"stream": "events", "data": {"object": "event", "id": "3mjz69kPK56", "statistic_id": "TspjNE", "timestamp": 1632066760, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147956416701", "$value": 27.0, "$extra": {"id": 4147956416701, "admin_graphql_api_id": "gid://shopify/Order/4147956416701", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:21-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:20-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1064", "note": null, "note_attributes": [], "number": 64, "order_number": 1064, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/da16d9a6ac3398a340e613d7417dc7d1/authenticate?key=e3e87311603bb29a7383983030bd3421", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:20-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "da16d9a6ac3398a340e613d7417dc7d1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:21-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402259645, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402259645", "created_at": "2021-09-19T08:52:21-07:00", "location_id": 63590301885, "name": "#1064.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:21-07:00", "line_items": [{"id": 10576728850621, "admin_graphql_api_id": "gid://shopify/LineItem/10576728850621", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728850621, "admin_graphql_api_id": "gid://shopify/LineItem/10576728850621", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:40+00:00", "uuid": "9e4cd400-1961-11ec-8001-8fa7c2e7c5a7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157954} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLfqNn", "statistic_id": "TspjNE", "timestamp": 1632066762, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147956449469", "$value": 27.0, "$extra": {"id": 4147956449469, "admin_graphql_api_id": "gid://shopify/Order/4147956449469", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:22-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1065", "note": null, "note_attributes": [], "number": 65, "order_number": 1065, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ebd3490ba177d93a9513d77cf0c8d5c8/authenticate?key=4922ac59828836cadd5c70565355d228", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:22-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ebd3490ba177d93a9513d77cf0c8d5c8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:26-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402292413, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402292413", "created_at": "2021-09-19T08:52:23-07:00", "location_id": 63590301885, "name": "#1065.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:23-07:00", "line_items": [{"id": 10576728883389, "admin_graphql_api_id": "gid://shopify/LineItem/10576728883389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728883389, "admin_graphql_api_id": "gid://shopify/LineItem/10576728883389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:42+00:00", "uuid": "9f7e0100-1961-11ec-8001-386750df7a9e", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157956} -{"stream": "events", "data": {"object": "event", "id": "3mjwdDpBZt9", "statistic_id": "TspjNE", "timestamp": 1632066767, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147956547773", "$value": 27.0, "$extra": {"id": 4147956547773, "admin_graphql_api_id": "gid://shopify/Order/4147956547773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:28-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:27-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1066", "note": null, "note_attributes": [], "number": 66, "order_number": 1066, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9efe123639788319663342caebfb1fba/authenticate?key=f0820d46dc2c434d2f0ce2424645302f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:27-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9efe123639788319663342caebfb1fba", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402357949, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402357949", "created_at": "2021-09-19T08:52:28-07:00", "location_id": 63590301885, "name": "#1066.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:28-07:00", "line_items": [{"id": 10576728981693, "admin_graphql_api_id": "gid://shopify/LineItem/10576728981693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728981693, "admin_graphql_api_id": "gid://shopify/LineItem/10576728981693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:52:47+00:00", "uuid": "a278f180-1961-11ec-8001-66b03de62580", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157957} -{"stream": "events", "data": {"object": "event", "id": "3mjENX24yT2", "statistic_id": "RDXsib", "timestamp": 1632066769, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147956351165:10576728785085:0", "$value": 27.0}, "datetime": "2021-09-19 15:52:49+00:00", "uuid": "a3aa1e80-1961-11ec-8001-d8ba6f1b94dc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157957} -{"stream": "events", "data": {"object": "event", "id": "3mjwxx3sc7h", "statistic_id": "RDXsib", "timestamp": 1632066770, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147956416701:10576728850621:0", "$value": 27.0}, "datetime": "2021-09-19 15:52:50+00:00", "uuid": "a442b500-1961-11ec-8001-dc6f491a79f5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157958} -{"stream": "events", "data": {"object": "event", "id": "3mjAFcAh2sQ", "statistic_id": "RDXsib", "timestamp": 1632066772, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147956449469:10576728883389:0", "$value": 27.0}, "datetime": "2021-09-19 15:52:52+00:00", "uuid": "a573e200-1961-11ec-8001-75788279f8e0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157958} -{"stream": "events", "data": {"object": "event", "id": "3mjDRAEBRKf", "statistic_id": "RDXsib", "timestamp": 1632066777, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147956547773:10576728981693:0", "$value": 27.0}, "datetime": "2021-09-19 15:52:57+00:00", "uuid": "a86ed280-1961-11ec-8001-d695abb8d5dd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157958} -{"stream": "events", "data": {"object": "event", "id": "3mjuiMSAGcK", "statistic_id": "X3f6PC", "timestamp": 1632066789, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147956351165", "$value": 27.0, "$extra": {"id": 4147956351165, "admin_graphql_api_id": "gid://shopify/Order/4147956351165", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:19-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:19-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1063", "note": null, "note_attributes": [], "number": 63, "order_number": 1063, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/1d839ec6308c303df68daf96a3948c1f/authenticate?key=83aed051ba6feeed7cad7693458d54ee", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "1d839ec6308c303df68daf96a3948c1f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:20-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402226877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402226877", "created_at": "2021-09-19T08:52:19-07:00", "location_id": 63590301885, "name": "#1063.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:19-07:00", "line_items": [{"id": 10576728785085, "admin_graphql_api_id": "gid://shopify/LineItem/10576728785085", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728785085, "admin_graphql_api_id": "gid://shopify/LineItem/10576728785085", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:09+00:00", "uuid": "af95e080-1961-11ec-8001-eb1b6a949bc7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157959} -{"stream": "events", "data": {"object": "event", "id": "3mjF8S7LyyC", "statistic_id": "X3f6PC", "timestamp": 1632066791, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147956416701", "$value": 27.0, "$extra": {"id": 4147956416701, "admin_graphql_api_id": "gid://shopify/Order/4147956416701", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:21-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:20-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1064", "note": null, "note_attributes": [], "number": 64, "order_number": 1064, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/da16d9a6ac3398a340e613d7417dc7d1/authenticate?key=e3e87311603bb29a7383983030bd3421", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:20-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "da16d9a6ac3398a340e613d7417dc7d1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:21-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402259645, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402259645", "created_at": "2021-09-19T08:52:21-07:00", "location_id": 63590301885, "name": "#1064.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:21-07:00", "line_items": [{"id": 10576728850621, "admin_graphql_api_id": "gid://shopify/LineItem/10576728850621", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728850621, "admin_graphql_api_id": "gid://shopify/LineItem/10576728850621", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:11+00:00", "uuid": "b0c70d80-1961-11ec-8001-5c740ba042da", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157959} -{"stream": "events", "data": {"object": "event", "id": "3mjAFguDt34", "statistic_id": "X3f6PC", "timestamp": 1632066793, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147956449469", "$value": 27.0, "$extra": {"id": 4147956449469, "admin_graphql_api_id": "gid://shopify/Order/4147956449469", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:22-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1065", "note": null, "note_attributes": [], "number": 65, "order_number": 1065, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ebd3490ba177d93a9513d77cf0c8d5c8/authenticate?key=4922ac59828836cadd5c70565355d228", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:22-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ebd3490ba177d93a9513d77cf0c8d5c8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:26-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402292413, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402292413", "created_at": "2021-09-19T08:52:23-07:00", "location_id": 63590301885, "name": "#1065.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:23-07:00", "line_items": [{"id": 10576728883389, "admin_graphql_api_id": "gid://shopify/LineItem/10576728883389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728883389, "admin_graphql_api_id": "gid://shopify/LineItem/10576728883389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:13+00:00", "uuid": "b1f83a80-1961-11ec-8001-ba79ef932687", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157960} -{"stream": "events", "data": {"object": "event", "id": "3mjBiUWaaCj", "statistic_id": "X3f6PC", "timestamp": 1632066798, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147956547773", "$value": 27.0, "$extra": {"id": 4147956547773, "admin_graphql_api_id": "gid://shopify/Order/4147956547773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:52:28-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:52:27-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1066", "note": null, "note_attributes": [], "number": 66, "order_number": 1066, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9efe123639788319663342caebfb1fba/authenticate?key=f0820d46dc2c434d2f0ce2424645302f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:52:27-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9efe123639788319663342caebfb1fba", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:52:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402357949, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402357949", "created_at": "2021-09-19T08:52:28-07:00", "location_id": 63590301885, "name": "#1066.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:52:28-07:00", "line_items": [{"id": 10576728981693, "admin_graphql_api_id": "gid://shopify/LineItem/10576728981693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576728981693, "admin_graphql_api_id": "gid://shopify/LineItem/10576728981693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:18+00:00", "uuid": "b4f32b00-1961-11ec-8001-6adf9bd843db", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157961} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhWwGLH", "statistic_id": "TspjNE", "timestamp": 1632066830, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147957792957", "$value": 27.0, "$extra": {"id": 4147957792957, "admin_graphql_api_id": "gid://shopify/Order/4147957792957", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:30-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:30-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1067", "note": null, "note_attributes": [], "number": 67, "order_number": 1067, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c8d9d04593d91be95a8d8417574267e1/authenticate?key=0df18d18b7e585fc49e34ab63a1eb202", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:30-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "c8d9d04593d91be95a8d8417574267e1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:31-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402620093, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402620093", "created_at": "2021-09-19T08:53:30-07:00", "location_id": 63590301885, "name": "#1067.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:30-07:00", "line_items": [{"id": 10576732094653, "admin_graphql_api_id": "gid://shopify/LineItem/10576732094653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732094653, "admin_graphql_api_id": "gid://shopify/LineItem/10576732094653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:50+00:00", "uuid": "c805fb00-1961-11ec-8001-e68c5f4aaf8b", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157962} -{"stream": "events", "data": {"object": "event", "id": "3mjuiKs7MEH", "statistic_id": "TspjNE", "timestamp": 1632066831, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147957825725", "$value": 27.0, "$extra": {"id": 4147957825725, "admin_graphql_api_id": "gid://shopify/Order/4147957825725", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:32-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1068", "note": null, "note_attributes": [], "number": 68, "order_number": 1068, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/818a98e5580ee1143e5b1e5e45ed24cc/authenticate?key=4000df4f5ec902fc10b20c6d015accf6", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "818a98e5580ee1143e5b1e5e45ed24cc", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:32-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402652861, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402652861", "created_at": "2021-09-19T08:53:32-07:00", "location_id": 63590301885, "name": "#1068.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:32-07:00", "line_items": [{"id": 10576732127421, "admin_graphql_api_id": "gid://shopify/LineItem/10576732127421", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732127421, "admin_graphql_api_id": "gid://shopify/LineItem/10576732127421", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:51+00:00", "uuid": "c89e9180-1961-11ec-8001-9fe8c62e10cf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157963} -{"stream": "events", "data": {"object": "event", "id": "3mjAFe4aPBC", "statistic_id": "TspjNE", "timestamp": 1632066833, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147957858493", "$value": 27.0, "$extra": {"id": 4147957858493, "admin_graphql_api_id": "gid://shopify/Order/4147957858493", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:34-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:33-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1069", "note": null, "note_attributes": [], "number": 69, "order_number": 1069, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f2cc26caddf9a4fb5cc7ee8bd8033e95/authenticate?key=e174738037384164572b1f008aa27d40", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:33-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f2cc26caddf9a4fb5cc7ee8bd8033e95", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:34-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402718397, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402718397", "created_at": "2021-09-19T08:53:33-07:00", "location_id": 63590301885, "name": "#1069.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:33-07:00", "line_items": [{"id": 10576732160189, "admin_graphql_api_id": "gid://shopify/LineItem/10576732160189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732160189, "admin_graphql_api_id": "gid://shopify/LineItem/10576732160189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:53+00:00", "uuid": "c9cfbe80-1961-11ec-8001-f1e05ae0b29a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157964} -{"stream": "events", "data": {"object": "event", "id": "3mjFsz3747y", "statistic_id": "TspjNE", "timestamp": 1632066835, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147957891261", "$value": 27.0, "$extra": {"id": 4147957891261, "admin_graphql_api_id": "gid://shopify/Order/4147957891261", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:36-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1070", "note": null, "note_attributes": [], "number": 70, "order_number": 1070, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/68d92bc790c522dd1b9e6b1f336e79f4/authenticate?key=11d693e6083b397b90cfaa06f896bb70", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "68d92bc790c522dd1b9e6b1f336e79f4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:36-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402751165, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402751165", "created_at": "2021-09-19T08:53:35-07:00", "location_id": 63590301885, "name": "#1070.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:35-07:00", "line_items": [{"id": 10576732192957, "admin_graphql_api_id": "gid://shopify/LineItem/10576732192957", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732192957, "admin_graphql_api_id": "gid://shopify/LineItem/10576732192957", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:53:55+00:00", "uuid": "cb00eb80-1961-11ec-8001-c0c9298118dc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157966} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV86Jw", "statistic_id": "RDXsib", "timestamp": 1632066840, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147957792957:10576732094653:0", "$value": 27.0}, "datetime": "2021-09-19 15:54:00+00:00", "uuid": "cdfbdc00-1961-11ec-8001-da9380cdd7f3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157967} -{"stream": "events", "data": {"object": "event", "id": "3mjAFgXVQXj", "statistic_id": "RDXsib", "timestamp": 1632066841, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147957825725:10576732127421:0", "$value": 27.0}, "datetime": "2021-09-19 15:54:01+00:00", "uuid": "ce947280-1961-11ec-8001-d3d329e08b89", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157967} -{"stream": "events", "data": {"object": "event", "id": "3mjz68neCag", "statistic_id": "RDXsib", "timestamp": 1632066843, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147957858493:10576732160189:0", "$value": 27.0}, "datetime": "2021-09-19 15:54:03+00:00", "uuid": "cfc59f80-1961-11ec-8001-c1d32e57fa86", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157967} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtmbzJz", "statistic_id": "RDXsib", "timestamp": 1632066845, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147957891261:10576732192957:0", "$value": 27.0}, "datetime": "2021-09-19 15:54:05+00:00", "uuid": "d0f6cc80-1961-11ec-8001-566d8a8480b6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157967} -{"stream": "events", "data": {"object": "event", "id": "3mjAFc6Yxgi", "statistic_id": "X3f6PC", "timestamp": 1632066860, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147957792957", "$value": 27.0, "$extra": {"id": 4147957792957, "admin_graphql_api_id": "gid://shopify/Order/4147957792957", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:30-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:30-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1067", "note": null, "note_attributes": [], "number": 67, "order_number": 1067, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c8d9d04593d91be95a8d8417574267e1/authenticate?key=0df18d18b7e585fc49e34ab63a1eb202", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:30-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "c8d9d04593d91be95a8d8417574267e1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:31-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402620093, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402620093", "created_at": "2021-09-19T08:53:30-07:00", "location_id": 63590301885, "name": "#1067.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:30-07:00", "line_items": [{"id": 10576732094653, "admin_graphql_api_id": "gid://shopify/LineItem/10576732094653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732094653, "admin_graphql_api_id": "gid://shopify/LineItem/10576732094653", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:54:20+00:00", "uuid": "d9e79e00-1961-11ec-8001-381ba28c87c9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157968} -{"stream": "events", "data": {"object": "event", "id": "3mjxba35LcF", "statistic_id": "X3f6PC", "timestamp": 1632066862, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147957825725", "$value": 27.0, "$extra": {"id": 4147957825725, "admin_graphql_api_id": "gid://shopify/Order/4147957825725", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:32-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:31-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1068", "note": null, "note_attributes": [], "number": 68, "order_number": 1068, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/818a98e5580ee1143e5b1e5e45ed24cc/authenticate?key=4000df4f5ec902fc10b20c6d015accf6", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:31-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "818a98e5580ee1143e5b1e5e45ed24cc", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:32-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402652861, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402652861", "created_at": "2021-09-19T08:53:32-07:00", "location_id": 63590301885, "name": "#1068.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:32-07:00", "line_items": [{"id": 10576732127421, "admin_graphql_api_id": "gid://shopify/LineItem/10576732127421", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732127421, "admin_graphql_api_id": "gid://shopify/LineItem/10576732127421", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:54:22+00:00", "uuid": "db18cb00-1961-11ec-8001-c54e9ca9d3f9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157969} -{"stream": "events", "data": {"object": "event", "id": "3mjuiHYee6d", "statistic_id": "X3f6PC", "timestamp": 1632066863, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147957858493", "$value": 27.0, "$extra": {"id": 4147957858493, "admin_graphql_api_id": "gid://shopify/Order/4147957858493", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:34-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:33-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1069", "note": null, "note_attributes": [], "number": 69, "order_number": 1069, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f2cc26caddf9a4fb5cc7ee8bd8033e95/authenticate?key=e174738037384164572b1f008aa27d40", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:33-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f2cc26caddf9a4fb5cc7ee8bd8033e95", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:34-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402718397, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402718397", "created_at": "2021-09-19T08:53:33-07:00", "location_id": 63590301885, "name": "#1069.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:33-07:00", "line_items": [{"id": 10576732160189, "admin_graphql_api_id": "gid://shopify/LineItem/10576732160189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732160189, "admin_graphql_api_id": "gid://shopify/LineItem/10576732160189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:54:23+00:00", "uuid": "dbb16180-1961-11ec-8001-c551a2f22eaa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157971} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtmbEwX", "statistic_id": "X3f6PC", "timestamp": 1632066865, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147957891261", "$value": 27.0, "$extra": {"id": 4147957891261, "admin_graphql_api_id": "gid://shopify/Order/4147957891261", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:53:36-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:53:35-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1070", "note": null, "note_attributes": [], "number": 70, "order_number": 1070, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/68d92bc790c522dd1b9e6b1f336e79f4/authenticate?key=11d693e6083b397b90cfaa06f896bb70", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:53:35-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "68d92bc790c522dd1b9e6b1f336e79f4", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:53:36-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693402751165, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693402751165", "created_at": "2021-09-19T08:53:35-07:00", "location_id": 63590301885, "name": "#1070.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:53:35-07:00", "line_items": [{"id": 10576732192957, "admin_graphql_api_id": "gid://shopify/LineItem/10576732192957", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576732192957, "admin_graphql_api_id": "gid://shopify/LineItem/10576732192957", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:54:25+00:00", "uuid": "dce28e80-1961-11ec-8001-fcc234a7a8c1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$phone_number": "", "$organization": "", "$title": "", "$email": "airbyte@airbyte.com", "$last_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367157972} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxfxVA7", "statistic_id": "TspjNE", "timestamp": 1632066897, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147959398589", "$value": 27.0, "$extra": {"id": 4147959398589, "admin_graphql_api_id": "gid://shopify/Order/4147959398589", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:38-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:37-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1071", "note": null, "note_attributes": [], "number": 71, "order_number": 1071, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/becd1f174103026fff086bededaa0d60/authenticate?key=db5291f00a92a26f26d8400982bd3f46", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:37-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "becd1f174103026fff086bededaa0d60", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:38-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403472061, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403472061", "created_at": "2021-09-19T08:54:37-07:00", "location_id": 63590301885, "name": "#1071.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:37-07:00", "line_items": [{"id": 10576734716093, "admin_graphql_api_id": "gid://shopify/LineItem/10576734716093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734716093, "admin_graphql_api_id": "gid://shopify/LineItem/10576734716093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:54:57+00:00", "uuid": "eff55e80-1961-11ec-8001-557091937db7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158403} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWNzK", "statistic_id": "TspjNE", "timestamp": 1632066899, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147959431357", "$value": 27.0, "$extra": {"id": 4147959431357, "admin_graphql_api_id": "gid://shopify/Order/4147959431357", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:40-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:39-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1072", "note": null, "note_attributes": [], "number": 72, "order_number": 1072, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fe8d7372d590763536ee028bccb4d023/authenticate?key=086d174f07ea15595746386fff362db0", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:39-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "fe8d7372d590763536ee028bccb4d023", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:40-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403504829, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403504829", "created_at": "2021-09-19T08:54:39-07:00", "location_id": 63590301885, "name": "#1072.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:39-07:00", "line_items": [{"id": 10576734748861, "admin_graphql_api_id": "gid://shopify/LineItem/10576734748861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734748861, "admin_graphql_api_id": "gid://shopify/LineItem/10576734748861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:54:59+00:00", "uuid": "f1268b80-1961-11ec-8001-485070ac4c8f", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158405} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTaq4", "statistic_id": "TspjNE", "timestamp": 1632066901, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147959464125", "$value": 27.0, "$extra": {"id": 4147959464125, "admin_graphql_api_id": "gid://shopify/Order/4147959464125", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:42-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:41-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1073", "note": null, "note_attributes": [], "number": 73, "order_number": 1073, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/af7d7b1986e37ed7b1e465ef64f00f0b/authenticate?key=0d0589c45105f87b75b8ef47d0f24a36", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:41-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "af7d7b1986e37ed7b1e465ef64f00f0b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:42-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403537597, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403537597", "created_at": "2021-09-19T08:54:41-07:00", "location_id": 63590301885, "name": "#1073.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:41-07:00", "line_items": [{"id": 10576734781629, "admin_graphql_api_id": "gid://shopify/LineItem/10576734781629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734781629, "admin_graphql_api_id": "gid://shopify/LineItem/10576734781629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:55:01+00:00", "uuid": "f257b880-1961-11ec-8001-b9bfef5ef2ea", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158406} -{"stream": "events", "data": {"object": "event", "id": "3mjxNRqnncD", "statistic_id": "TspjNE", "timestamp": 1632066902, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147959562429", "$value": 27.0, "$extra": {"id": 4147959562429, "admin_graphql_api_id": "gid://shopify/Order/4147959562429", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:43-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:42-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1074", "note": null, "note_attributes": [], "number": 74, "order_number": 1074, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/664586d9da46fa921b84c98f52863160/authenticate?key=ed3c24d6f50a90eab04ae17fa796cb74", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "664586d9da46fa921b84c98f52863160", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:44-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403570365, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403570365", "created_at": "2021-09-19T08:54:43-07:00", "location_id": 63590301885, "name": "#1074.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:43-07:00", "line_items": [{"id": 10576734847165, "admin_graphql_api_id": "gid://shopify/LineItem/10576734847165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734847165, "admin_graphql_api_id": "gid://shopify/LineItem/10576734847165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:55:02+00:00", "uuid": "f2f04f00-1961-11ec-8001-fa9d9d24e7f1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158408} -{"stream": "events", "data": {"object": "event", "id": "3mjy8zMAuvU", "statistic_id": "RDXsib", "timestamp": 1632066907, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147959398589:10576734716093:0", "$value": 27.0}, "datetime": "2021-09-19 15:55:07+00:00", "uuid": "f5eb3f80-1961-11ec-8001-bd571f280bbb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158410} -{"stream": "events", "data": {"object": "event", "id": "3mjz64tSDRj", "statistic_id": "RDXsib", "timestamp": 1632066909, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147959431357:10576734748861:0", "$value": 27.0}, "datetime": "2021-09-19 15:55:09+00:00", "uuid": "f71c6c80-1961-11ec-8001-4c2ae86699e6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158411} -{"stream": "events", "data": {"object": "event", "id": "3mjz68Rxrdg", "statistic_id": "RDXsib", "timestamp": 1632066911, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147959464125:10576734781629:0", "$value": 27.0}, "datetime": "2021-09-19 15:55:11+00:00", "uuid": "f84d9980-1961-11ec-8001-47df88d00ce8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158411} -{"stream": "events", "data": {"object": "event", "id": "3mjz64tSrVZ", "statistic_id": "RDXsib", "timestamp": 1632066912, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147959562429:10576734847165:0", "$value": 27.0}, "datetime": "2021-09-19 15:55:12+00:00", "uuid": "f8e63000-1961-11ec-8001-96c6b5a94cef", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158411} -{"stream": "events", "data": {"object": "event", "id": "3mjDRvimCaq", "statistic_id": "X3f6PC", "timestamp": 1632066927, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147959398589", "$value": 27.0, "$extra": {"id": 4147959398589, "admin_graphql_api_id": "gid://shopify/Order/4147959398589", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:38-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:37-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1071", "note": null, "note_attributes": [], "number": 71, "order_number": 1071, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/becd1f174103026fff086bededaa0d60/authenticate?key=db5291f00a92a26f26d8400982bd3f46", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:37-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "becd1f174103026fff086bededaa0d60", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:38-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403472061, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403472061", "created_at": "2021-09-19T08:54:37-07:00", "location_id": 63590301885, "name": "#1071.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:37-07:00", "line_items": [{"id": 10576734716093, "admin_graphql_api_id": "gid://shopify/LineItem/10576734716093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734716093, "admin_graphql_api_id": "gid://shopify/LineItem/10576734716093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:55:27+00:00", "uuid": "01d70180-1962-11ec-8001-2a1eacd4a490", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158412} -{"stream": "events", "data": {"object": "event", "id": "3mjwxzXdqMt", "statistic_id": "X3f6PC", "timestamp": 1632066929, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147959431357", "$value": 27.0, "$extra": {"id": 4147959431357, "admin_graphql_api_id": "gid://shopify/Order/4147959431357", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:40-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:39-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1072", "note": null, "note_attributes": [], "number": 72, "order_number": 1072, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fe8d7372d590763536ee028bccb4d023/authenticate?key=086d174f07ea15595746386fff362db0", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:39-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "fe8d7372d590763536ee028bccb4d023", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:40-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403504829, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403504829", "created_at": "2021-09-19T08:54:39-07:00", "location_id": 63590301885, "name": "#1072.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:39-07:00", "line_items": [{"id": 10576734748861, "admin_graphql_api_id": "gid://shopify/LineItem/10576734748861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734748861, "admin_graphql_api_id": "gid://shopify/LineItem/10576734748861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:55:29+00:00", "uuid": "03082e80-1962-11ec-8001-2f0646721feb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158413} -{"stream": "events", "data": {"object": "event", "id": "3mjBWBQa9Ap", "statistic_id": "X3f6PC", "timestamp": 1632066931, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147959464125", "$value": 27.0, "$extra": {"id": 4147959464125, "admin_graphql_api_id": "gid://shopify/Order/4147959464125", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:42-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:41-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1073", "note": null, "note_attributes": [], "number": 73, "order_number": 1073, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/af7d7b1986e37ed7b1e465ef64f00f0b/authenticate?key=0d0589c45105f87b75b8ef47d0f24a36", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:41-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "af7d7b1986e37ed7b1e465ef64f00f0b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:42-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403537597, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403537597", "created_at": "2021-09-19T08:54:41-07:00", "location_id": 63590301885, "name": "#1073.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:41-07:00", "line_items": [{"id": 10576734781629, "admin_graphql_api_id": "gid://shopify/LineItem/10576734781629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734781629, "admin_graphql_api_id": "gid://shopify/LineItem/10576734781629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:55:31+00:00", "uuid": "04395b80-1962-11ec-8001-22e774ec2cfe", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158415} -{"stream": "events", "data": {"object": "event", "id": "3mjAFknZzyi", "statistic_id": "X3f6PC", "timestamp": 1632066933, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147959562429", "$value": 27.0, "$extra": {"id": 4147959562429, "admin_graphql_api_id": "gid://shopify/Order/4147959562429", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:54:43-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:54:42-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1074", "note": null, "note_attributes": [], "number": 74, "order_number": 1074, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/664586d9da46fa921b84c98f52863160/authenticate?key=ed3c24d6f50a90eab04ae17fa796cb74", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:54:42-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "664586d9da46fa921b84c98f52863160", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:54:44-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403570365, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403570365", "created_at": "2021-09-19T08:54:43-07:00", "location_id": 63590301885, "name": "#1074.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:54:43-07:00", "line_items": [{"id": 10576734847165, "admin_graphql_api_id": "gid://shopify/LineItem/10576734847165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576734847165, "admin_graphql_api_id": "gid://shopify/LineItem/10576734847165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:55:33+00:00", "uuid": "056a8880-1962-11ec-8001-8c8639ca15e3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158417} -{"stream": "events", "data": {"object": "event", "id": "3mjvTVYYHJ2", "statistic_id": "TspjNE", "timestamp": 1632066965, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147960512701", "$value": 27.0, "$extra": {"id": 4147960512701, "admin_graphql_api_id": "gid://shopify/Order/4147960512701", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:45-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:45-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1075", "note": null, "note_attributes": [], "number": 75, "order_number": 1075, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/62ad707c52fb9478a595d85e5b14eee6/authenticate?key=a413128b934bcdd1a0e3c47973cce698", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:45-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "62ad707c52fb9478a595d85e5b14eee6", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:46-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403963581, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403963581", "created_at": "2021-09-19T08:55:45-07:00", "location_id": 63590301885, "name": "#1075.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:45-07:00", "line_items": [{"id": 10576736616637, "admin_graphql_api_id": "gid://shopify/LineItem/10576736616637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576736616637, "admin_graphql_api_id": "gid://shopify/LineItem/10576736616637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:05+00:00", "uuid": "187d5880-1962-11ec-8001-a279074c86d7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158418} -{"stream": "events", "data": {"object": "event", "id": "3mjAFguDaKD", "statistic_id": "TspjNE", "timestamp": 1632066966, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147960643773", "$value": 27.0, "$extra": {"id": 4147960643773, "admin_graphql_api_id": "gid://shopify/Order/4147960643773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:48-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:46-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1076", "note": null, "note_attributes": [], "number": 76, "order_number": 1076, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6b7d9fdd1ab89352854002974aaf39f6/authenticate?key=264ca8d061ec6876f4eda541a5bf3829", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:46-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6b7d9fdd1ab89352854002974aaf39f6", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:48-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403996349, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403996349", "created_at": "2021-09-19T08:55:47-07:00", "location_id": 63590301885, "name": "#1076.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:47-07:00", "line_items": [{"id": 10576737173693, "admin_graphql_api_id": "gid://shopify/LineItem/10576737173693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576737173693, "admin_graphql_api_id": "gid://shopify/LineItem/10576737173693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:06+00:00", "uuid": "1915ef00-1962-11ec-8001-d1b99c173ee0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158420} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLfwxR", "statistic_id": "TspjNE", "timestamp": 1632066969, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147960676541", "$value": 27.0, "$extra": {"id": 4147960676541, "admin_graphql_api_id": "gid://shopify/Order/4147960676541", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:50-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:49-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1077", "note": null, "note_attributes": [], "number": 77, "order_number": 1077, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/73e824a2c8aa96096f5b9ddd1e762d01/authenticate?key=e27168faf5168b7b696ff5b3429e0a7c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:49-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "73e824a2c8aa96096f5b9ddd1e762d01", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:50-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404029117, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404029117", "created_at": "2021-09-19T08:55:49-07:00", "location_id": 63590301885, "name": "#1077.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:49-07:00", "line_items": [{"id": 10576737206461, "admin_graphql_api_id": "gid://shopify/LineItem/10576737206461", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576737206461, "admin_graphql_api_id": "gid://shopify/LineItem/10576737206461", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:09+00:00", "uuid": "1adfb280-1962-11ec-8001-59dea91f97fc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158421} -{"stream": "events", "data": {"object": "event", "id": "3mjvTVvGdtN", "statistic_id": "TspjNE", "timestamp": 1632066970, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147960742077", "$value": 27.0, "$extra": {"id": 4147960742077, "admin_graphql_api_id": "gid://shopify/Order/4147960742077", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:51-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:50-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1078", "note": null, "note_attributes": [], "number": 78, "order_number": 1078, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6a1abfa5011e678bcae28508bc35286a/authenticate?key=071be1b565d485124884a110a9bf96dd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:50-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6a1abfa5011e678bcae28508bc35286a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:51-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404061885, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404061885", "created_at": "2021-09-19T08:55:51-07:00", "location_id": 63590301885, "name": "#1078.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:51-07:00", "line_items": [{"id": 10576737271997, "admin_graphql_api_id": "gid://shopify/LineItem/10576737271997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576737271997, "admin_graphql_api_id": "gid://shopify/LineItem/10576737271997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:10+00:00", "uuid": "1b784900-1962-11ec-8001-90e4abb23fc7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158422} -{"stream": "events", "data": {"object": "event", "id": "3mjAFd5zyCH", "statistic_id": "RDXsib", "timestamp": 1632066975, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147960512701:10576736616637:0", "$value": 27.0}, "datetime": "2021-09-19 15:56:15+00:00", "uuid": "1e733980-1962-11ec-8001-2b32a57664c4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158424} -{"stream": "events", "data": {"object": "event", "id": "3mjAmsKaNyK", "statistic_id": "RDXsib", "timestamp": 1632066976, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147960643773:10576737173693:0", "$value": 27.0}, "datetime": "2021-09-19 15:56:16+00:00", "uuid": "1f0bd000-1962-11ec-8001-51eecbb43ae9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158424} -{"stream": "events", "data": {"object": "event", "id": "3mjEbh6DDFP", "statistic_id": "RDXsib", "timestamp": 1632066979, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147960676541:10576737206461:0", "$value": 27.0}, "datetime": "2021-09-19 15:56:19+00:00", "uuid": "20d59380-1962-11ec-8001-e520cf9e8685", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158425} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxfxNSy", "statistic_id": "RDXsib", "timestamp": 1632066980, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147960742077:10576737271997:0", "$value": 27.0}, "datetime": "2021-09-19 15:56:20+00:00", "uuid": "216e2a00-1962-11ec-8001-34f2be5f70a5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158425} -{"stream": "events", "data": {"object": "event", "id": "3mjz6dfc5YF", "statistic_id": "X3f6PC", "timestamp": 1632066995, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147960512701", "$value": 27.0, "$extra": {"id": 4147960512701, "admin_graphql_api_id": "gid://shopify/Order/4147960512701", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:45-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:45-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1075", "note": null, "note_attributes": [], "number": 75, "order_number": 1075, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/62ad707c52fb9478a595d85e5b14eee6/authenticate?key=a413128b934bcdd1a0e3c47973cce698", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:45-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "62ad707c52fb9478a595d85e5b14eee6", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:46-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403963581, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403963581", "created_at": "2021-09-19T08:55:45-07:00", "location_id": 63590301885, "name": "#1075.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:45-07:00", "line_items": [{"id": 10576736616637, "admin_graphql_api_id": "gid://shopify/LineItem/10576736616637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576736616637, "admin_graphql_api_id": "gid://shopify/LineItem/10576736616637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:35+00:00", "uuid": "2a5efb80-1962-11ec-8001-7774d345b9c5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158426} -{"stream": "events", "data": {"object": "event", "id": "3mjF8PFhMT9", "statistic_id": "X3f6PC", "timestamp": 1632066997, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147960643773", "$value": 27.0, "$extra": {"id": 4147960643773, "admin_graphql_api_id": "gid://shopify/Order/4147960643773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:48-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:46-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1076", "note": null, "note_attributes": [], "number": 76, "order_number": 1076, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6b7d9fdd1ab89352854002974aaf39f6/authenticate?key=264ca8d061ec6876f4eda541a5bf3829", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:46-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6b7d9fdd1ab89352854002974aaf39f6", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:48-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693403996349, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693403996349", "created_at": "2021-09-19T08:55:47-07:00", "location_id": 63590301885, "name": "#1076.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:47-07:00", "line_items": [{"id": 10576737173693, "admin_graphql_api_id": "gid://shopify/LineItem/10576737173693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576737173693, "admin_graphql_api_id": "gid://shopify/LineItem/10576737173693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:37+00:00", "uuid": "2b902880-1962-11ec-8001-1d4ead58bfb8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158427} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRT4Vf", "statistic_id": "X3f6PC", "timestamp": 1632066999, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147960676541", "$value": 27.0, "$extra": {"id": 4147960676541, "admin_graphql_api_id": "gid://shopify/Order/4147960676541", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:50-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:49-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1077", "note": null, "note_attributes": [], "number": 77, "order_number": 1077, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/73e824a2c8aa96096f5b9ddd1e762d01/authenticate?key=e27168faf5168b7b696ff5b3429e0a7c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:49-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "73e824a2c8aa96096f5b9ddd1e762d01", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:50-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404029117, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404029117", "created_at": "2021-09-19T08:55:49-07:00", "location_id": 63590301885, "name": "#1077.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:49-07:00", "line_items": [{"id": 10576737206461, "admin_graphql_api_id": "gid://shopify/LineItem/10576737206461", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576737206461, "admin_graphql_api_id": "gid://shopify/LineItem/10576737206461", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:39+00:00", "uuid": "2cc15580-1962-11ec-8001-ba7ff4dd6deb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158428} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwgWQdr", "statistic_id": "X3f6PC", "timestamp": 1632067001, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147960742077", "$value": 27.0, "$extra": {"id": 4147960742077, "admin_graphql_api_id": "gid://shopify/Order/4147960742077", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:55:51-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:55:50-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1078", "note": null, "note_attributes": [], "number": 78, "order_number": 1078, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6a1abfa5011e678bcae28508bc35286a/authenticate?key=071be1b565d485124884a110a9bf96dd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:55:50-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6a1abfa5011e678bcae28508bc35286a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:55:51-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404061885, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404061885", "created_at": "2021-09-19T08:55:51-07:00", "location_id": 63590301885, "name": "#1078.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:55:51-07:00", "line_items": [{"id": 10576737271997, "admin_graphql_api_id": "gid://shopify/LineItem/10576737271997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576737271997, "admin_graphql_api_id": "gid://shopify/LineItem/10576737271997", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:56:41+00:00", "uuid": "2df28280-1962-11ec-8001-faad259f62d6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158429} -{"stream": "events", "data": {"object": "event", "id": "3mjwdKJsNcp", "statistic_id": "TspjNE", "timestamp": 1632067033, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147962151101", "$value": 27.0, "$extra": {"id": 4147962151101, "admin_graphql_api_id": "gid://shopify/Order/4147962151101", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:56:54-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:56:53-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1079", "note": null, "note_attributes": [], "number": 79, "order_number": 1079, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ef8a43e1b7f7375569ceec3fd4469d9a/authenticate?key=c4b6e707bb64ed78bc31c517fcaecb61", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:56:53-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ef8a43e1b7f7375569ceec3fd4469d9a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:56:54-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404815549, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404815549", "created_at": "2021-09-19T08:56:53-07:00", "location_id": 63590301885, "name": "#1079.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:56:53-07:00", "line_items": [{"id": 10576739729597, "admin_graphql_api_id": "gid://shopify/LineItem/10576739729597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576739729597, "admin_graphql_api_id": "gid://shopify/LineItem/10576739729597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:57:13+00:00", "uuid": "41055280-1962-11ec-8001-423e9182d8f3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158430} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQu9FQ", "statistic_id": "TspjNE", "timestamp": 1632067035, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147962249405", "$value": 27.0, "$extra": {"id": 4147962249405, "admin_graphql_api_id": "gid://shopify/Order/4147962249405", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:56:55-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:56:55-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1080", "note": null, "note_attributes": [], "number": 80, "order_number": 1080, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9a8ea3ffc50d24e0bcfc4913e2660add/authenticate?key=b8e61414186776a2548d99c75af02040", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:56:55-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9a8ea3ffc50d24e0bcfc4913e2660add", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:56:56-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404881085, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404881085", "created_at": "2021-09-19T08:56:55-07:00", "location_id": 63590301885, "name": "#1080.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:56:55-07:00", "line_items": [{"id": 10576739827901, "admin_graphql_api_id": "gid://shopify/LineItem/10576739827901", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576739827901, "admin_graphql_api_id": "gid://shopify/LineItem/10576739827901", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:57:15+00:00", "uuid": "42367f80-1962-11ec-8001-84d540141b88", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158431} -{"stream": "events", "data": {"object": "event", "id": "3mjDRvimHyX", "statistic_id": "TspjNE", "timestamp": 1632067036, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147962347709", "$value": 27.0, "$extra": {"id": 4147962347709, "admin_graphql_api_id": "gid://shopify/Order/4147962347709", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:56:57-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:56:56-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1081", "note": null, "note_attributes": [], "number": 81, "order_number": 1081, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8f4bcf041671fd962a24efeffad6a817/authenticate?key=7956218898677177fa7189090ffe303f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:56:56-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8f4bcf041671fd962a24efeffad6a817", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:56:58-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404913853, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404913853", "created_at": "2021-09-19T08:56:57-07:00", "location_id": 63590301885, "name": "#1081.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:56:57-07:00", "line_items": [{"id": 10576739926205, "admin_graphql_api_id": "gid://shopify/LineItem/10576739926205", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576739926205, "admin_graphql_api_id": "gid://shopify/LineItem/10576739926205", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:57:16+00:00", "uuid": "42cf1600-1962-11ec-8001-93a4465e90ae", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158432} -{"stream": "events", "data": {"object": "event", "id": "3mjDRyHrpHF", "statistic_id": "RDXsib", "timestamp": 1632067043, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147962151101:10576739729597:0", "$value": 27.0}, "datetime": "2021-09-19 15:57:23+00:00", "uuid": "46fb3380-1962-11ec-8001-be024730e9ec", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158432} -{"stream": "events", "data": {"object": "event", "id": "3mjFszwpR8X", "statistic_id": "RDXsib", "timestamp": 1632067045, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147962249405:10576739827901:0", "$value": 27.0}, "datetime": "2021-09-19 15:57:25+00:00", "uuid": "482c6080-1962-11ec-8001-1b4ab873d8b3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158433} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsnAEgb", "statistic_id": "RDXsib", "timestamp": 1632067046, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147962347709:10576739926205:0", "$value": 27.0}, "datetime": "2021-09-19 15:57:26+00:00", "uuid": "48c4f700-1962-11ec-8001-7f863967ad83", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158433} -{"stream": "events", "data": {"object": "event", "id": "3mjBWzpFsKd", "statistic_id": "X3f6PC", "timestamp": 1632067063, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147962151101", "$value": 27.0, "$extra": {"id": 4147962151101, "admin_graphql_api_id": "gid://shopify/Order/4147962151101", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:56:54-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:56:53-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1079", "note": null, "note_attributes": [], "number": 79, "order_number": 1079, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ef8a43e1b7f7375569ceec3fd4469d9a/authenticate?key=c4b6e707bb64ed78bc31c517fcaecb61", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:56:53-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "ef8a43e1b7f7375569ceec3fd4469d9a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:56:54-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404815549, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404815549", "created_at": "2021-09-19T08:56:53-07:00", "location_id": 63590301885, "name": "#1079.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:56:53-07:00", "line_items": [{"id": 10576739729597, "admin_graphql_api_id": "gid://shopify/LineItem/10576739729597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576739729597, "admin_graphql_api_id": "gid://shopify/LineItem/10576739729597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:57:43+00:00", "uuid": "52e6f580-1962-11ec-8001-fa414f3f73a3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158433} -{"stream": "events", "data": {"object": "event", "id": "3mjDdThj4aU", "statistic_id": "X3f6PC", "timestamp": 1632067065, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147962249405", "$value": 27.0, "$extra": {"id": 4147962249405, "admin_graphql_api_id": "gid://shopify/Order/4147962249405", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:56:55-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:56:55-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1080", "note": null, "note_attributes": [], "number": 80, "order_number": 1080, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9a8ea3ffc50d24e0bcfc4913e2660add/authenticate?key=b8e61414186776a2548d99c75af02040", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:56:55-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9a8ea3ffc50d24e0bcfc4913e2660add", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:56:56-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404881085, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404881085", "created_at": "2021-09-19T08:56:55-07:00", "location_id": 63590301885, "name": "#1080.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:56:55-07:00", "line_items": [{"id": 10576739827901, "admin_graphql_api_id": "gid://shopify/LineItem/10576739827901", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576739827901, "admin_graphql_api_id": "gid://shopify/LineItem/10576739827901", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:57:45+00:00", "uuid": "54182280-1962-11ec-8001-8ae718de20ac", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158434} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhtekfW", "statistic_id": "X3f6PC", "timestamp": 1632067067, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147962347709", "$value": 27.0, "$extra": {"id": 4147962347709, "admin_graphql_api_id": "gid://shopify/Order/4147962347709", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:56:57-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:56:56-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1081", "note": null, "note_attributes": [], "number": 81, "order_number": 1081, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8f4bcf041671fd962a24efeffad6a817/authenticate?key=7956218898677177fa7189090ffe303f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:56:56-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8f4bcf041671fd962a24efeffad6a817", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:56:58-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693404913853, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693404913853", "created_at": "2021-09-19T08:56:57-07:00", "location_id": 63590301885, "name": "#1081.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:56:57-07:00", "line_items": [{"id": 10576739926205, "admin_graphql_api_id": "gid://shopify/LineItem/10576739926205", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576739926205, "admin_graphql_api_id": "gid://shopify/LineItem/10576739926205", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:57:47+00:00", "uuid": "55494f80-1962-11ec-8001-b7fdf3a983f2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158434} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLfr7W", "statistic_id": "TspjNE", "timestamp": 1632067099, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147963822269", "$value": 27.0, "$extra": {"id": 4147963822269, "admin_graphql_api_id": "gid://shopify/Order/4147963822269", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:57:59-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:57:59-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1082", "note": null, "note_attributes": [], "number": 82, "order_number": 1082, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/191a1ccd35ad267fc53cc0e82d943da7/authenticate?key=4adcacd2ab532d50331b8dcf567bce8f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:57:59-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "191a1ccd35ad267fc53cc0e82d943da7", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:00-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405438141, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405438141", "created_at": "2021-09-19T08:57:59-07:00", "location_id": 63590301885, "name": "#1082.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:57:59-07:00", "line_items": [{"id": 10576742580413, "admin_graphql_api_id": "gid://shopify/LineItem/10576742580413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576742580413, "admin_graphql_api_id": "gid://shopify/LineItem/10576742580413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:19+00:00", "uuid": "685c1f80-1962-11ec-8001-62c9dd9d8aae", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158436} -{"stream": "events", "data": {"object": "event", "id": "3mjxuYK8TME", "statistic_id": "TspjNE", "timestamp": 1632067100, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147963887805", "$value": 27.0, "$extra": {"id": 4147963887805, "admin_graphql_api_id": "gid://shopify/Order/4147963887805", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:58:01-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:58:00-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1083", "note": null, "note_attributes": [], "number": 83, "order_number": 1083, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/3f527be25447123d1f7aa7f3ecd84cc0/authenticate?key=6b79f341d040eae0ad8d5238793058d6", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:58:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "3f527be25447123d1f7aa7f3ecd84cc0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:02-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405503677, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405503677", "created_at": "2021-09-19T08:58:01-07:00", "location_id": 63590301885, "name": "#1083.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:58:01-07:00", "line_items": [{"id": 10576742645949, "admin_graphql_api_id": "gid://shopify/LineItem/10576742645949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576742645949, "admin_graphql_api_id": "gid://shopify/LineItem/10576742645949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:20+00:00", "uuid": "68f4b600-1962-11ec-8001-c97a3952dc9c", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158437} -{"stream": "events", "data": {"object": "event", "id": "3mjs5Tt8Ek8", "statistic_id": "TspjNE", "timestamp": 1632067102, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147964051645", "$value": 27.0, "$extra": {"id": 4147964051645, "admin_graphql_api_id": "gid://shopify/Order/4147964051645", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:58:03-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:58:02-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1084", "note": null, "note_attributes": [], "number": 84, "order_number": 1084, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cf728e8a2be5feebe1686adbd6d969f3/authenticate?key=d45eddbd9ae9ad0bdb282952ca5dca99", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:58:02-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "cf728e8a2be5feebe1686adbd6d969f3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:03-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405536445, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405536445", "created_at": "2021-09-19T08:58:03-07:00", "location_id": 63590301885, "name": "#1084.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:58:03-07:00", "line_items": [{"id": 10576743006397, "admin_graphql_api_id": "gid://shopify/LineItem/10576743006397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576743006397, "admin_graphql_api_id": "gid://shopify/LineItem/10576743006397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:22+00:00", "uuid": "6a25e300-1962-11ec-8001-8fa219731595", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158438} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtmbBZ4", "statistic_id": "TspjNE", "timestamp": 1632067104, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147964084413", "$value": 27.0, "$extra": {"id": 4147964084413, "admin_graphql_api_id": "gid://shopify/Order/4147964084413", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:58:05-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:58:04-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1085", "note": null, "note_attributes": [], "number": 85, "order_number": 1085, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/73360c3c9d1a3c9c21ad68071578fb94/authenticate?key=2a9e743f3f78f54c8a9d42b4aad2c365", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:58:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "73360c3c9d1a3c9c21ad68071578fb94", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:06-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405601981, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405601981", "created_at": "2021-09-19T08:58:04-07:00", "location_id": 63590301885, "name": "#1085.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:58:04-07:00", "line_items": [{"id": 10576743039165, "admin_graphql_api_id": "gid://shopify/LineItem/10576743039165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576743039165, "admin_graphql_api_id": "gid://shopify/LineItem/10576743039165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:24+00:00", "uuid": "6b571000-1962-11ec-8001-e7e244cfe7bb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158439} -{"stream": "events", "data": {"object": "event", "id": "3mjAFf2KJJL", "statistic_id": "RDXsib", "timestamp": 1632067109, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147963822269:10576742580413:0", "$value": 27.0}, "datetime": "2021-09-19 15:58:29+00:00", "uuid": "6e520080-1962-11ec-8001-d2abce3aa8cf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158439} -{"stream": "events", "data": {"object": "event", "id": "3mjvTNFyE2T", "statistic_id": "RDXsib", "timestamp": 1632067110, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147963887805:10576742645949:0", "$value": 27.0}, "datetime": "2021-09-19 15:58:30+00:00", "uuid": "6eea9700-1962-11ec-8001-4dbf24a9bcb0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158440} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHDPP", "statistic_id": "RDXsib", "timestamp": 1632067112, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147964051645:10576743006397:0", "$value": 27.0}, "datetime": "2021-09-19 15:58:32+00:00", "uuid": "701bc400-1962-11ec-8001-67b9507805a0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158440} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQu25E", "statistic_id": "RDXsib", "timestamp": 1632067114, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147964084413:10576743039165:0", "$value": 27.0}, "datetime": "2021-09-19 15:58:34+00:00", "uuid": "714cf100-1962-11ec-8001-166f372e02fe", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158440} -{"stream": "events", "data": {"object": "event", "id": "3mjAFextd78", "statistic_id": "X3f6PC", "timestamp": 1632067129, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147963822269", "$value": 27.0, "$extra": {"id": 4147963822269, "admin_graphql_api_id": "gid://shopify/Order/4147963822269", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:57:59-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:57:59-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1082", "note": null, "note_attributes": [], "number": 82, "order_number": 1082, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/191a1ccd35ad267fc53cc0e82d943da7/authenticate?key=4adcacd2ab532d50331b8dcf567bce8f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:57:59-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "191a1ccd35ad267fc53cc0e82d943da7", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:00-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405438141, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405438141", "created_at": "2021-09-19T08:57:59-07:00", "location_id": 63590301885, "name": "#1082.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:57:59-07:00", "line_items": [{"id": 10576742580413, "admin_graphql_api_id": "gid://shopify/LineItem/10576742580413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576742580413, "admin_graphql_api_id": "gid://shopify/LineItem/10576742580413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:49+00:00", "uuid": "7a3dc280-1962-11ec-8001-75989afd7bca", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158441} -{"stream": "events", "data": {"object": "event", "id": "3mjCA9WQBNb", "statistic_id": "X3f6PC", "timestamp": 1632067131, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147963887805", "$value": 27.0, "$extra": {"id": 4147963887805, "admin_graphql_api_id": "gid://shopify/Order/4147963887805", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:58:01-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:58:00-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1083", "note": null, "note_attributes": [], "number": 83, "order_number": 1083, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/3f527be25447123d1f7aa7f3ecd84cc0/authenticate?key=6b79f341d040eae0ad8d5238793058d6", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:58:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "3f527be25447123d1f7aa7f3ecd84cc0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:02-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405503677, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405503677", "created_at": "2021-09-19T08:58:01-07:00", "location_id": 63590301885, "name": "#1083.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:58:01-07:00", "line_items": [{"id": 10576742645949, "admin_graphql_api_id": "gid://shopify/LineItem/10576742645949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576742645949, "admin_graphql_api_id": "gid://shopify/LineItem/10576742645949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:51+00:00", "uuid": "7b6eef80-1962-11ec-8001-e3fe7dbf9ef2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158442} -{"stream": "events", "data": {"object": "event", "id": "3mjz67SWnJH", "statistic_id": "X3f6PC", "timestamp": 1632067133, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147964051645", "$value": 27.0, "$extra": {"id": 4147964051645, "admin_graphql_api_id": "gid://shopify/Order/4147964051645", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:58:03-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:58:02-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1084", "note": null, "note_attributes": [], "number": 84, "order_number": 1084, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/cf728e8a2be5feebe1686adbd6d969f3/authenticate?key=d45eddbd9ae9ad0bdb282952ca5dca99", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:58:02-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "cf728e8a2be5feebe1686adbd6d969f3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:03-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405536445, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405536445", "created_at": "2021-09-19T08:58:03-07:00", "location_id": 63590301885, "name": "#1084.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:58:03-07:00", "line_items": [{"id": 10576743006397, "admin_graphql_api_id": "gid://shopify/LineItem/10576743006397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576743006397, "admin_graphql_api_id": "gid://shopify/LineItem/10576743006397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:53+00:00", "uuid": "7ca01c80-1962-11ec-8001-d607c0196efa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158443} -{"stream": "events", "data": {"object": "event", "id": "3mjAFcAgQy4", "statistic_id": "X3f6PC", "timestamp": 1632067134, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147964084413", "$value": 27.0, "$extra": {"id": 4147964084413, "admin_graphql_api_id": "gid://shopify/Order/4147964084413", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:58:05-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:58:04-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1085", "note": null, "note_attributes": [], "number": 85, "order_number": 1085, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/73360c3c9d1a3c9c21ad68071578fb94/authenticate?key=2a9e743f3f78f54c8a9d42b4aad2c365", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:58:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "73360c3c9d1a3c9c21ad68071578fb94", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:58:06-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693405601981, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693405601981", "created_at": "2021-09-19T08:58:04-07:00", "location_id": 63590301885, "name": "#1085.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:58:04-07:00", "line_items": [{"id": 10576743039165, "admin_graphql_api_id": "gid://shopify/LineItem/10576743039165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576743039165, "admin_graphql_api_id": "gid://shopify/LineItem/10576743039165", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:58:54+00:00", "uuid": "7d38b300-1962-11ec-8001-0d5494c520bd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158444} -{"stream": "events", "data": {"object": "event", "id": "3mjDRrThZfW", "statistic_id": "TspjNE", "timestamp": 1632067167, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147965919421", "$value": 27.0, "$extra": {"id": 4147965919421, "admin_graphql_api_id": "gid://shopify/Order/4147965919421", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:07-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:07-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1086", "note": null, "note_attributes": [], "number": 86, "order_number": 1086, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/62dc6c751d30d04dfbef9f6e38a3d0b5/authenticate?key=041be5fc27fb664377c4a339b27d312f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:07-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "62dc6c751d30d04dfbef9f6e38a3d0b5", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:08-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406290109, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406290109", "created_at": "2021-09-19T08:59:07-07:00", "location_id": 63590301885, "name": "#1086.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:07-07:00", "line_items": [{"id": 10576746283197, "admin_graphql_api_id": "gid://shopify/LineItem/10576746283197", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746283197, "admin_graphql_api_id": "gid://shopify/LineItem/10576746283197", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:59:27+00:00", "uuid": "90e41980-1962-11ec-8001-bc6303a389be", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158445} -{"stream": "events", "data": {"object": "event", "id": "3mjF8PFhKFC", "statistic_id": "TspjNE", "timestamp": 1632067168, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147965984957", "$value": 27.0, "$extra": {"id": 4147965984957, "admin_graphql_api_id": "gid://shopify/Order/4147965984957", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:09-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:08-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1087", "note": null, "note_attributes": [], "number": 87, "order_number": 1087, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/3d9c4483fe1d6eda1bae7413c4b56bb2/authenticate?key=fd4f7013b114e1e1f6953ce18659f34d", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:08-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "3d9c4483fe1d6eda1bae7413c4b56bb2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:10-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406322877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406322877", "created_at": "2021-09-19T08:59:09-07:00", "location_id": 63590301885, "name": "#1087.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:09-07:00", "line_items": [{"id": 10576746348733, "admin_graphql_api_id": "gid://shopify/LineItem/10576746348733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746348733, "admin_graphql_api_id": "gid://shopify/LineItem/10576746348733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:59:28+00:00", "uuid": "917cb000-1962-11ec-8001-1fc2b34eb9ed", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158446} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHtH3", "statistic_id": "TspjNE", "timestamp": 1632067170, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147966050493", "$value": 27.0, "$extra": {"id": 4147966050493, "admin_graphql_api_id": "gid://shopify/Order/4147966050493", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:11-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:10-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1088", "note": null, "note_attributes": [], "number": 88, "order_number": 1088, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a753fa3ce6127bbfc30a252d8273b998/authenticate?key=dfff139059ad70843ad65a6b6c3a3b0e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:10-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a753fa3ce6127bbfc30a252d8273b998", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:12-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406453949, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406453949", "created_at": "2021-09-19T08:59:11-07:00", "location_id": 63590301885, "name": "#1088.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:11-07:00", "line_items": [{"id": 10576746414269, "admin_graphql_api_id": "gid://shopify/LineItem/10576746414269", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746414269, "admin_graphql_api_id": "gid://shopify/LineItem/10576746414269", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:59:30+00:00", "uuid": "92addd00-1962-11ec-8001-044a7c5290c4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158448} -{"stream": "events", "data": {"object": "event", "id": "3mjFsHj8kFM", "statistic_id": "TspjNE", "timestamp": 1632067172, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147966083261", "$value": 27.0, "$extra": {"id": 4147966083261, "admin_graphql_api_id": "gid://shopify/Order/4147966083261", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:15-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:12-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1089", "note": null, "note_attributes": [], "number": 89, "order_number": 1089, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6951dbf59729be24df4d968e07bb5831/authenticate?key=49b9f2f67f09f71d47e302f89e777aec", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:12-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6951dbf59729be24df4d968e07bb5831", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:17-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406519485, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406519485", "created_at": "2021-09-19T08:59:13-07:00", "location_id": 63590301885, "name": "#1089.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:13-07:00", "line_items": [{"id": 10576746447037, "admin_graphql_api_id": "gid://shopify/LineItem/10576746447037", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746447037, "admin_graphql_api_id": "gid://shopify/LineItem/10576746447037", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:59:32+00:00", "uuid": "93df0a00-1962-11ec-8001-d8683d1dcba2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158448} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQtZpK", "statistic_id": "RDXsib", "timestamp": 1632067177, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147965919421:10576746283197:0", "$value": 27.0}, "datetime": "2021-09-19 15:59:37+00:00", "uuid": "96d9fa80-1962-11ec-8001-0dd6a4dea3b0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158449} -{"stream": "events", "data": {"object": "event", "id": "3mjz6dJuybF", "statistic_id": "RDXsib", "timestamp": 1632067178, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147965984957:10576746348733:0", "$value": 27.0}, "datetime": "2021-09-19 15:59:38+00:00", "uuid": "97729100-1962-11ec-8001-303cde286ecc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158450} -{"stream": "events", "data": {"object": "event", "id": "3mjy8G8rqar", "statistic_id": "RDXsib", "timestamp": 1632067180, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147966050493:10576746414269:0", "$value": 27.0}, "datetime": "2021-09-19 15:59:40+00:00", "uuid": "98a3be00-1962-11ec-8001-45223e7e7ba6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158450} -{"stream": "events", "data": {"object": "event", "id": "3mjDxLV9tWT", "statistic_id": "RDXsib", "timestamp": 1632067182, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147966083261:10576746447037:0", "$value": 27.0}, "datetime": "2021-09-19 15:59:42+00:00", "uuid": "99d4eb00-1962-11ec-8001-5b3132f6ccec", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158451} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhterFX", "statistic_id": "X3f6PC", "timestamp": 1632067197, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147965919421", "$value": 27.0, "$extra": {"id": 4147965919421, "admin_graphql_api_id": "gid://shopify/Order/4147965919421", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:07-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:07-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1086", "note": null, "note_attributes": [], "number": 86, "order_number": 1086, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/62dc6c751d30d04dfbef9f6e38a3d0b5/authenticate?key=041be5fc27fb664377c4a339b27d312f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:07-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "62dc6c751d30d04dfbef9f6e38a3d0b5", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:08-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406290109, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406290109", "created_at": "2021-09-19T08:59:07-07:00", "location_id": 63590301885, "name": "#1086.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:07-07:00", "line_items": [{"id": 10576746283197, "admin_graphql_api_id": "gid://shopify/LineItem/10576746283197", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746283197, "admin_graphql_api_id": "gid://shopify/LineItem/10576746283197", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:59:57+00:00", "uuid": "a2c5bc80-1962-11ec-8001-de1bad33c5d1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158451} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzcHXYZ", "statistic_id": "X3f6PC", "timestamp": 1632067199, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147965984957", "$value": 27.0, "$extra": {"id": 4147965984957, "admin_graphql_api_id": "gid://shopify/Order/4147965984957", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:09-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:08-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1087", "note": null, "note_attributes": [], "number": 87, "order_number": 1087, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/3d9c4483fe1d6eda1bae7413c4b56bb2/authenticate?key=fd4f7013b114e1e1f6953ce18659f34d", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:08-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "3d9c4483fe1d6eda1bae7413c4b56bb2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:10-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406322877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406322877", "created_at": "2021-09-19T08:59:09-07:00", "location_id": 63590301885, "name": "#1087.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:09-07:00", "line_items": [{"id": 10576746348733, "admin_graphql_api_id": "gid://shopify/LineItem/10576746348733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746348733, "admin_graphql_api_id": "gid://shopify/LineItem/10576746348733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 15:59:59+00:00", "uuid": "a3f6e980-1962-11ec-8001-b1cbac9ef7a5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158452} -{"stream": "events", "data": {"object": "event", "id": "3mjvTU3NJZM", "statistic_id": "X3f6PC", "timestamp": 1632067201, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147966050493", "$value": 27.0, "$extra": {"id": 4147966050493, "admin_graphql_api_id": "gid://shopify/Order/4147966050493", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:11-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:10-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1088", "note": null, "note_attributes": [], "number": 88, "order_number": 1088, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a753fa3ce6127bbfc30a252d8273b998/authenticate?key=dfff139059ad70843ad65a6b6c3a3b0e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:10-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a753fa3ce6127bbfc30a252d8273b998", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:12-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406453949, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406453949", "created_at": "2021-09-19T08:59:11-07:00", "location_id": 63590301885, "name": "#1088.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:11-07:00", "line_items": [{"id": 10576746414269, "admin_graphql_api_id": "gid://shopify/LineItem/10576746414269", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746414269, "admin_graphql_api_id": "gid://shopify/LineItem/10576746414269", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:00:01+00:00", "uuid": "a5281680-1962-11ec-8001-486ea4fe1b97", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158452} -{"stream": "events", "data": {"object": "event", "id": "3mjrL6cX7fU", "statistic_id": "X3f6PC", "timestamp": 1632067203, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147966083261", "$value": 27.0, "$extra": {"id": 4147966083261, "admin_graphql_api_id": "gid://shopify/Order/4147966083261", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T08:59:15-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T08:59:12-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1089", "note": null, "note_attributes": [], "number": 89, "order_number": 1089, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6951dbf59729be24df4d968e07bb5831/authenticate?key=49b9f2f67f09f71d47e302f89e777aec", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T08:59:12-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6951dbf59729be24df4d968e07bb5831", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T08:59:17-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693406519485, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693406519485", "created_at": "2021-09-19T08:59:13-07:00", "location_id": 63590301885, "name": "#1089.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T08:59:13-07:00", "line_items": [{"id": 10576746447037, "admin_graphql_api_id": "gid://shopify/LineItem/10576746447037", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576746447037, "admin_graphql_api_id": "gid://shopify/LineItem/10576746447037", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:00:03+00:00", "uuid": "a6594380-1962-11ec-8001-d549e63cbfe9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158453} -{"stream": "events", "data": {"object": "event", "id": "3mjwdCr2E9Y", "statistic_id": "TspjNE", "timestamp": 1632067238, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147967525053", "$value": 27.0, "$extra": {"id": 4147967525053, "admin_graphql_api_id": "gid://shopify/Order/4147967525053", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:19-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:18-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1090", "note": null, "note_attributes": [], "number": 90, "order_number": 1090, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/63b1430fa6af824ccea9844fa47c450b/authenticate?key=e8072ace37699b26e1b4145ba5cf9078", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:18-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "63b1430fa6af824ccea9844fa47c450b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:19-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407174845, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407174845", "created_at": "2021-09-19T09:00:18-07:00", "location_id": 63590301885, "name": "#1090.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:18-07:00", "line_items": [{"id": 10576748970173, "admin_graphql_api_id": "gid://shopify/LineItem/10576748970173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576748970173, "admin_graphql_api_id": "gid://shopify/LineItem/10576748970173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:00:38+00:00", "uuid": "bb35d700-1962-11ec-8001-b78861eb85ad", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158454} -{"stream": "events", "data": {"object": "event", "id": "3mjz64tSqmz", "statistic_id": "TspjNE", "timestamp": 1632067240, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147967557821", "$value": 27.0, "$extra": {"id": 4147967557821, "admin_graphql_api_id": "gid://shopify/Order/4147967557821", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:21-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:20-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1091", "note": null, "note_attributes": [], "number": 91, "order_number": 1091, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5cb3c9c5d5669128033d3e9aaef0a7f7/authenticate?key=a1ca7775a04dbb8c8b33e8947ca6d7dd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:20-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "5cb3c9c5d5669128033d3e9aaef0a7f7", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:21-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407207613, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407207613", "created_at": "2021-09-19T09:00:20-07:00", "location_id": 63590301885, "name": "#1091.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:20-07:00", "line_items": [{"id": 10576749002941, "admin_graphql_api_id": "gid://shopify/LineItem/10576749002941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576749002941, "admin_graphql_api_id": "gid://shopify/LineItem/10576749002941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:00:40+00:00", "uuid": "bc670400-1962-11ec-8001-78ab66d4c0f4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158455} -{"stream": "events", "data": {"object": "event", "id": "3mjvTRBk29T", "statistic_id": "TspjNE", "timestamp": 1632067242, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147967590589", "$value": 27.0, "$extra": {"id": 4147967590589, "admin_graphql_api_id": "gid://shopify/Order/4147967590589", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:23-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:22-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1092", "note": null, "note_attributes": [], "number": 92, "order_number": 1092, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/be6d7d2af1880d162a2df5008f7daad0/authenticate?key=528e01f078c351a2a744d202e471d302", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:22-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "be6d7d2af1880d162a2df5008f7daad0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:23-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407240381, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407240381", "created_at": "2021-09-19T09:00:22-07:00", "location_id": 63590301885, "name": "#1092.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:22-07:00", "line_items": [{"id": 10576749035709, "admin_graphql_api_id": "gid://shopify/LineItem/10576749035709", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576749035709, "admin_graphql_api_id": "gid://shopify/LineItem/10576749035709", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:00:42+00:00", "uuid": "bd983100-1962-11ec-8001-298860026ae1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158456} -{"stream": "events", "data": {"object": "event", "id": "3mjuiLqGPQQ", "statistic_id": "TspjNE", "timestamp": 1632067244, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147967656125", "$value": 27.0, "$extra": {"id": 4147967656125, "admin_graphql_api_id": "gid://shopify/Order/4147967656125", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:24-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1093", "note": null, "note_attributes": [], "number": 93, "order_number": 1093, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8d1b05fed40c5e80206e1733ff7b31b3/authenticate?key=b4fabec81336cede0d34c2c095a92b7e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:24-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8d1b05fed40c5e80206e1733ff7b31b3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:25-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407273149, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407273149", "created_at": "2021-09-19T09:00:24-07:00", "location_id": 63590301885, "name": "#1093.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:24-07:00", "line_items": [{"id": 10576749101245, "admin_graphql_api_id": "gid://shopify/LineItem/10576749101245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576749101245, "admin_graphql_api_id": "gid://shopify/LineItem/10576749101245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:00:44+00:00", "uuid": "bec95e00-1962-11ec-8001-75beb68e96a1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158457} -{"stream": "events", "data": {"object": "event", "id": "3mjuWmtah9T", "statistic_id": "RDXsib", "timestamp": 1632067248, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147967525053:10576748970173:0", "$value": 27.0}, "datetime": "2021-09-19 16:00:48+00:00", "uuid": "c12bb800-1962-11ec-8001-7ad426311bee", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158458} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLmcb", "statistic_id": "RDXsib", "timestamp": 1632067250, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147967557821:10576749002941:0", "$value": 27.0}, "datetime": "2021-09-19 16:00:50+00:00", "uuid": "c25ce500-1962-11ec-8001-cb5d836d1d99", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158458} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQuiVn", "statistic_id": "RDXsib", "timestamp": 1632067252, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147967590589:10576749035709:0", "$value": 27.0}, "datetime": "2021-09-19 16:00:52+00:00", "uuid": "c38e1200-1962-11ec-8001-7ab634d197e3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158458} -{"stream": "events", "data": {"object": "event", "id": "3mjBWDi3QZy", "statistic_id": "RDXsib", "timestamp": 1632067254, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147967656125:10576749101245:0", "$value": 27.0}, "datetime": "2021-09-19 16:00:54+00:00", "uuid": "c4bf3f00-1962-11ec-8001-81e7fc7e0ae9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158458} -{"stream": "events", "data": {"object": "event", "id": "3mjs5UrHuLL", "statistic_id": "X3f6PC", "timestamp": 1632067268, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147967525053", "$value": 27.0, "$extra": {"id": 4147967525053, "admin_graphql_api_id": "gid://shopify/Order/4147967525053", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:19-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:18-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1090", "note": null, "note_attributes": [], "number": 90, "order_number": 1090, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/63b1430fa6af824ccea9844fa47c450b/authenticate?key=e8072ace37699b26e1b4145ba5cf9078", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:18-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "63b1430fa6af824ccea9844fa47c450b", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:19-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407174845, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407174845", "created_at": "2021-09-19T09:00:18-07:00", "location_id": 63590301885, "name": "#1090.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:18-07:00", "line_items": [{"id": 10576748970173, "admin_graphql_api_id": "gid://shopify/LineItem/10576748970173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576748970173, "admin_graphql_api_id": "gid://shopify/LineItem/10576748970173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:08+00:00", "uuid": "cd177a00-1962-11ec-8001-3a19e40715ce", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158458} -{"stream": "events", "data": {"object": "event", "id": "3mjwdLH3Phz", "statistic_id": "X3f6PC", "timestamp": 1632067270, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147967557821", "$value": 27.0, "$extra": {"id": 4147967557821, "admin_graphql_api_id": "gid://shopify/Order/4147967557821", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:21-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:20-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1091", "note": null, "note_attributes": [], "number": 91, "order_number": 1091, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5cb3c9c5d5669128033d3e9aaef0a7f7/authenticate?key=a1ca7775a04dbb8c8b33e8947ca6d7dd", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:20-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "5cb3c9c5d5669128033d3e9aaef0a7f7", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:21-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407207613, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407207613", "created_at": "2021-09-19T09:00:20-07:00", "location_id": 63590301885, "name": "#1091.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:20-07:00", "line_items": [{"id": 10576749002941, "admin_graphql_api_id": "gid://shopify/LineItem/10576749002941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576749002941, "admin_graphql_api_id": "gid://shopify/LineItem/10576749002941", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:10+00:00", "uuid": "ce48a700-1962-11ec-8001-9668c4ccf084", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158459} -{"stream": "events", "data": {"object": "event", "id": "3mjDRye94Nf", "statistic_id": "X3f6PC", "timestamp": 1632067272, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147967590589", "$value": 27.0, "$extra": {"id": 4147967590589, "admin_graphql_api_id": "gid://shopify/Order/4147967590589", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:23-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:22-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1092", "note": null, "note_attributes": [], "number": 92, "order_number": 1092, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/be6d7d2af1880d162a2df5008f7daad0/authenticate?key=528e01f078c351a2a744d202e471d302", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:22-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "be6d7d2af1880d162a2df5008f7daad0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:23-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407240381, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407240381", "created_at": "2021-09-19T09:00:22-07:00", "location_id": 63590301885, "name": "#1092.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:22-07:00", "line_items": [{"id": 10576749035709, "admin_graphql_api_id": "gid://shopify/LineItem/10576749035709", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576749035709, "admin_graphql_api_id": "gid://shopify/LineItem/10576749035709", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:12+00:00", "uuid": "cf79d400-1962-11ec-8001-133a5c12bfeb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158460} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV7SyQ", "statistic_id": "X3f6PC", "timestamp": 1632067274, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147967656125", "$value": 27.0, "$extra": {"id": 4147967656125, "admin_graphql_api_id": "gid://shopify/Order/4147967656125", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:00:25-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:00:24-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1093", "note": null, "note_attributes": [], "number": 93, "order_number": 1093, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/8d1b05fed40c5e80206e1733ff7b31b3/authenticate?key=b4fabec81336cede0d34c2c095a92b7e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:00:24-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "8d1b05fed40c5e80206e1733ff7b31b3", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:00:25-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693407273149, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693407273149", "created_at": "2021-09-19T09:00:24-07:00", "location_id": 63590301885, "name": "#1093.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:00:24-07:00", "line_items": [{"id": 10576749101245, "admin_graphql_api_id": "gid://shopify/LineItem/10576749101245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576749101245, "admin_graphql_api_id": "gid://shopify/LineItem/10576749101245", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:14+00:00", "uuid": "d0ab0100-1962-11ec-8001-5b3ab5b4eed0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158461} -{"stream": "events", "data": {"object": "event", "id": "3mjwRpdnQv9", "statistic_id": "TspjNE", "timestamp": 1632067306, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147969556669", "$value": 27.0, "$extra": {"id": 4147969556669, "admin_graphql_api_id": "gid://shopify/Order/4147969556669", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:27-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:26-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1094", "note": null, "note_attributes": [], "number": 94, "order_number": 1094, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/327360961d55b1a911889416309ac266/authenticate?key=25ddfc695a4765ee23d57050054ec895", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:26-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "327360961d55b1a911889416309ac266", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:27-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408190653, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408190653", "created_at": "2021-09-19T09:01:27-07:00", "location_id": 63590301885, "name": "#1094.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:27-07:00", "line_items": [{"id": 10576752148669, "admin_graphql_api_id": "gid://shopify/LineItem/10576752148669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752148669, "admin_graphql_api_id": "gid://shopify/LineItem/10576752148669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:46+00:00", "uuid": "e3bdd100-1962-11ec-8001-3942adabac89", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158462} -{"stream": "events", "data": {"object": "event", "id": "3mjCU28Mh85", "statistic_id": "TspjNE", "timestamp": 1632067308, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147969589437", "$value": 27.0, "$extra": {"id": 4147969589437, "admin_graphql_api_id": "gid://shopify/Order/4147969589437", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:29-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:28-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1095", "note": null, "note_attributes": [], "number": 95, "order_number": 1095, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b7b465ed75d4af3e608307036041e831/authenticate?key=45037eb2ec474ab355074c307f86825a", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:28-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "b7b465ed75d4af3e608307036041e831", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408321725, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408321725", "created_at": "2021-09-19T09:01:28-07:00", "location_id": 63590301885, "name": "#1095.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:28-07:00", "line_items": [{"id": 10576752181437, "admin_graphql_api_id": "gid://shopify/LineItem/10576752181437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752181437, "admin_graphql_api_id": "gid://shopify/LineItem/10576752181437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:48+00:00", "uuid": "e4eefe00-1962-11ec-8001-8a1475983495", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158463} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxfxVA6", "statistic_id": "TspjNE", "timestamp": 1632067310, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147969654973", "$value": 27.0, "$extra": {"id": 4147969654973, "admin_graphql_api_id": "gid://shopify/Order/4147969654973", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:31-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:30-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1096", "note": null, "note_attributes": [], "number": 96, "order_number": 1096, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/95b43e4848b8813da1b431e100a4a2dd/authenticate?key=d1080e9dcde0c8d8030d5a0dff0a3614", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:30-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "95b43e4848b8813da1b431e100a4a2dd", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:31-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408420029, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408420029", "created_at": "2021-09-19T09:01:30-07:00", "location_id": 63590301885, "name": "#1096.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:30-07:00", "line_items": [{"id": 10576752246973, "admin_graphql_api_id": "gid://shopify/LineItem/10576752246973", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752246973, "admin_graphql_api_id": "gid://shopify/LineItem/10576752246973", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:50+00:00", "uuid": "e6202b00-1962-11ec-8001-864fe286fd86", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158464} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhtee59", "statistic_id": "TspjNE", "timestamp": 1632067312, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147969753277", "$value": 27.0, "$extra": {"id": 4147969753277, "admin_graphql_api_id": "gid://shopify/Order/4147969753277", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:33-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:32-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1097", "note": null, "note_attributes": [], "number": 97, "order_number": 1097, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8c71f4e3da5e25af25131b7996a1b38/authenticate?key=4cef8d26b444ee335bb5ade36e30341f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:32-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "b8c71f4e3da5e25af25131b7996a1b38", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408583869, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408583869", "created_at": "2021-09-19T09:01:32-07:00", "location_id": 63590301885, "name": "#1097.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:32-07:00", "line_items": [{"id": 10576752345277, "admin_graphql_api_id": "gid://shopify/LineItem/10576752345277", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752345277, "admin_graphql_api_id": "gid://shopify/LineItem/10576752345277", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:01:52+00:00", "uuid": "e7515800-1962-11ec-8001-e532e8662ab3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158465} -{"stream": "events", "data": {"object": "event", "id": "3mjEP5QbZ2c", "statistic_id": "RDXsib", "timestamp": 1632067316, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147969556669:10576752148669:0", "$value": 27.0}, "datetime": "2021-09-19 16:01:56+00:00", "uuid": "e9b3b200-1962-11ec-8001-5a838f4347c8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158466} -{"stream": "events", "data": {"object": "event", "id": "3mjFsFReLaL", "statistic_id": "RDXsib", "timestamp": 1632067318, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147969589437:10576752181437:0", "$value": 27.0}, "datetime": "2021-09-19 16:01:58+00:00", "uuid": "eae4df00-1962-11ec-8001-ce0bddc1b1c9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158466} -{"stream": "events", "data": {"object": "event", "id": "3mjxNRqnncC", "statistic_id": "RDXsib", "timestamp": 1632067320, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147969654973:10576752246973:0", "$value": 27.0}, "datetime": "2021-09-19 16:02:00+00:00", "uuid": "ec160c00-1962-11ec-8001-e1533f3948d1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158467} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzG2rzw", "statistic_id": "RDXsib", "timestamp": 1632067322, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147969753277:10576752345277:0", "$value": 27.0}, "datetime": "2021-09-19 16:02:02+00:00", "uuid": "ed473900-1962-11ec-8001-7824ab8f5ca9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158467} -{"stream": "events", "data": {"object": "event", "id": "3mjz6cgAWH5", "statistic_id": "X3f6PC", "timestamp": 1632067337, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147969556669", "$value": 27.0, "$extra": {"id": 4147969556669, "admin_graphql_api_id": "gid://shopify/Order/4147969556669", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:27-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:26-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1094", "note": null, "note_attributes": [], "number": 94, "order_number": 1094, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/327360961d55b1a911889416309ac266/authenticate?key=25ddfc695a4765ee23d57050054ec895", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:26-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "327360961d55b1a911889416309ac266", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:27-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408190653, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408190653", "created_at": "2021-09-19T09:01:27-07:00", "location_id": 63590301885, "name": "#1094.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:27-07:00", "line_items": [{"id": 10576752148669, "admin_graphql_api_id": "gid://shopify/LineItem/10576752148669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752148669, "admin_graphql_api_id": "gid://shopify/LineItem/10576752148669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:17+00:00", "uuid": "f6380a80-1962-11ec-8001-22926bc09db7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158467} -{"stream": "events", "data": {"object": "event", "id": "3mjFsCsb47a", "statistic_id": "X3f6PC", "timestamp": 1632067338, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147969589437", "$value": 27.0, "$extra": {"id": 4147969589437, "admin_graphql_api_id": "gid://shopify/Order/4147969589437", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:29-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:28-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1095", "note": null, "note_attributes": [], "number": 95, "order_number": 1095, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b7b465ed75d4af3e608307036041e831/authenticate?key=45037eb2ec474ab355074c307f86825a", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:28-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "b7b465ed75d4af3e608307036041e831", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:29-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408321725, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408321725", "created_at": "2021-09-19T09:01:28-07:00", "location_id": 63590301885, "name": "#1095.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:28-07:00", "line_items": [{"id": 10576752181437, "admin_graphql_api_id": "gid://shopify/LineItem/10576752181437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752181437, "admin_graphql_api_id": "gid://shopify/LineItem/10576752181437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:18+00:00", "uuid": "f6d0a100-1962-11ec-8001-97b5b96d5de3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158468} -{"stream": "events", "data": {"object": "event", "id": "3mjAFgXVQXi", "statistic_id": "X3f6PC", "timestamp": 1632067340, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147969654973", "$value": 27.0, "$extra": {"id": 4147969654973, "admin_graphql_api_id": "gid://shopify/Order/4147969654973", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:31-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:30-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1096", "note": null, "note_attributes": [], "number": 96, "order_number": 1096, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/95b43e4848b8813da1b431e100a4a2dd/authenticate?key=d1080e9dcde0c8d8030d5a0dff0a3614", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:30-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "95b43e4848b8813da1b431e100a4a2dd", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:31-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408420029, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408420029", "created_at": "2021-09-19T09:01:30-07:00", "location_id": 63590301885, "name": "#1096.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:30-07:00", "line_items": [{"id": 10576752246973, "admin_graphql_api_id": "gid://shopify/LineItem/10576752246973", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752246973, "admin_graphql_api_id": "gid://shopify/LineItem/10576752246973", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:20+00:00", "uuid": "f801ce00-1962-11ec-8001-1e46e51732ff", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158469} -{"stream": "events", "data": {"object": "event", "id": "3mjDRvMEeZ4", "statistic_id": "X3f6PC", "timestamp": 1632067342, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147969753277", "$value": 27.0, "$extra": {"id": 4147969753277, "admin_graphql_api_id": "gid://shopify/Order/4147969753277", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:01:33-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:01:32-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1097", "note": null, "note_attributes": [], "number": 97, "order_number": 1097, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8c71f4e3da5e25af25131b7996a1b38/authenticate?key=4cef8d26b444ee335bb5ade36e30341f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:01:32-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "b8c71f4e3da5e25af25131b7996a1b38", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:01:33-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693408583869, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693408583869", "created_at": "2021-09-19T09:01:32-07:00", "location_id": 63590301885, "name": "#1097.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:01:32-07:00", "line_items": [{"id": 10576752345277, "admin_graphql_api_id": "gid://shopify/LineItem/10576752345277", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576752345277, "admin_graphql_api_id": "gid://shopify/LineItem/10576752345277", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:22+00:00", "uuid": "f932fb00-1962-11ec-8001-9a701157e5a0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158469} -{"stream": "events", "data": {"object": "event", "id": "3mjEbqTn9yq", "statistic_id": "TspjNE", "timestamp": 1632067374, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147971424445", "$value": 27.0, "$extra": {"id": 4147971424445, "admin_graphql_api_id": "gid://shopify/Order/4147971424445", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:02:35-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:34-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1098", "note": null, "note_attributes": [], "number": 98, "order_number": 1098, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e534b937e4dd64b23ae2c1435d96ce5d/authenticate?key=c332044be1158efac2a7f34852859eca", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:34-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "e534b937e4dd64b23ae2c1435d96ce5d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:02:35-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411041469, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411041469", "created_at": "2021-09-19T09:02:35-07:00", "location_id": 63590301885, "name": "#1098.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:35-07:00", "line_items": [{"id": 10576754704573, "admin_graphql_api_id": "gid://shopify/LineItem/10576754704573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754704573, "admin_graphql_api_id": "gid://shopify/LineItem/10576754704573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:54+00:00", "uuid": "0c45cb00-1963-11ec-8001-fcb92bc8bfcf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158470} -{"stream": "events", "data": {"object": "event", "id": "3mjz65VLjXJ", "statistic_id": "TspjNE", "timestamp": 1632067376, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147971555517", "$value": 27.0, "$extra": {"id": 4147971555517, "admin_graphql_api_id": "gid://shopify/Order/4147971555517", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:36-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1099", "note": null, "note_attributes": [], "number": 99, "order_number": 1099, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/71bc0d54fa6ef9d4361ae48ceccbf24a/authenticate?key=cae2b5e52a38a86e86f56f625c952f55", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:36-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "71bc0d54fa6ef9d4361ae48ceccbf24a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-09T05:14:59-08:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411107005, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411107005", "created_at": "2021-09-19T09:02:36-07:00", "location_id": 63590301885, "name": "#1099.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:36-07:00", "line_items": [{"id": 10576754868413, "admin_graphql_api_id": "gid://shopify/LineItem/10576754868413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754868413, "admin_graphql_api_id": "gid://shopify/LineItem/10576754868413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:56+00:00", "uuid": "0d76f800-1963-11ec-8001-2249749f6fcb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158471} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRT2JP", "statistic_id": "TspjNE", "timestamp": 1632067378, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147971621053", "$value": 27.0, "$extra": {"id": 4147971621053, "admin_graphql_api_id": "gid://shopify/Order/4147971621053", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:02:39-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:38-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1100", "note": null, "note_attributes": [], "number": 100, "order_number": 1100, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/07968357152fc9c1908a57473c03b615/authenticate?key=0c83db4d883c76e6a83d0505af5c288c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:38-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "07968357152fc9c1908a57473c03b615", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:02:39-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411205309, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411205309", "created_at": "2021-09-19T09:02:38-07:00", "location_id": 63590301885, "name": "#1100.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:38-07:00", "line_items": [{"id": 10576754933949, "admin_graphql_api_id": "gid://shopify/LineItem/10576754933949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754933949, "admin_graphql_api_id": "gid://shopify/LineItem/10576754933949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:02:58+00:00", "uuid": "0ea82500-1963-11ec-8001-847678b639de", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158472} -{"stream": "events", "data": {"object": "event", "id": "3mjAFirPK4e", "statistic_id": "TspjNE", "timestamp": 1632067380, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147971653821", "$value": 27.0, "$extra": {"id": 4147971653821, "admin_graphql_api_id": "gid://shopify/Order/4147971653821", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:02:40-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:40-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1101", "note": null, "note_attributes": [], "number": 101, "order_number": 1101, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/0c80d184ab92e7fc8a6524a5a27f9f11/authenticate?key=cf7c106e2776d292396aa23a86215efb", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:40-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "0c80d184ab92e7fc8a6524a5a27f9f11", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:02:41-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411238077, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411238077", "created_at": "2021-09-19T09:02:40-07:00", "location_id": 63590301885, "name": "#1101.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:40-07:00", "line_items": [{"id": 10576754966717, "admin_graphql_api_id": "gid://shopify/LineItem/10576754966717", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754966717, "admin_graphql_api_id": "gid://shopify/LineItem/10576754966717", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:03:00+00:00", "uuid": "0fd95200-1963-11ec-8001-02aa276df4b8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158473} -{"stream": "events", "data": {"object": "event", "id": "3mjFsHj8ab5", "statistic_id": "RDXsib", "timestamp": 1632067384, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147971424445:10576754704573:0", "$value": 27.0}, "datetime": "2021-09-19 16:03:04+00:00", "uuid": "123bac00-1963-11ec-8001-eb838cb4e183", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158474} -{"stream": "events", "data": {"object": "event", "id": "3mjCU6ws6xg", "statistic_id": "RDXsib", "timestamp": 1632067386, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147971555517:10576754868413:0", "$value": 27.0}, "datetime": "2021-09-19 16:03:06+00:00", "uuid": "136cd900-1963-11ec-8001-8dbd3990b2e9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158474} -{"stream": "events", "data": {"object": "event", "id": "3mjz6ajqMhP", "statistic_id": "RDXsib", "timestamp": 1632067388, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147971621053:10576754933949:0", "$value": 27.0}, "datetime": "2021-09-19 16:03:08+00:00", "uuid": "149e0600-1963-11ec-8001-cded01c8e2ab", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158475} -{"stream": "events", "data": {"object": "event", "id": "3mjDRye8RFN", "statistic_id": "RDXsib", "timestamp": 1632067390, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147971653821:10576754966717:0", "$value": 27.0}, "datetime": "2021-09-19 16:03:10+00:00", "uuid": "15cf3300-1963-11ec-8001-a595bdbed6b7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158475} -{"stream": "events", "data": {"object": "event", "id": "3mjBiUWaaCi", "statistic_id": "X3f6PC", "timestamp": 1632067405, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147971424445", "$value": 27.0, "$extra": {"id": 4147971424445, "admin_graphql_api_id": "gid://shopify/Order/4147971424445", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:02:35-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:34-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1098", "note": null, "note_attributes": [], "number": 98, "order_number": 1098, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e534b937e4dd64b23ae2c1435d96ce5d/authenticate?key=c332044be1158efac2a7f34852859eca", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:34-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "e534b937e4dd64b23ae2c1435d96ce5d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:02:35-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411041469, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411041469", "created_at": "2021-09-19T09:02:35-07:00", "location_id": 63590301885, "name": "#1098.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:35-07:00", "line_items": [{"id": 10576754704573, "admin_graphql_api_id": "gid://shopify/LineItem/10576754704573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754704573, "admin_graphql_api_id": "gid://shopify/LineItem/10576754704573", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:03:25+00:00", "uuid": "1ec00480-1963-11ec-8001-2935e64560f8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158475} -{"stream": "events", "data": {"object": "event", "id": "3mjBiWq3Ydp", "statistic_id": "X3f6PC", "timestamp": 1632067406, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147971555517", "$value": 27.0, "$extra": {"id": 4147971555517, "admin_graphql_api_id": "gid://shopify/Order/4147971555517", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:36-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1099", "note": null, "note_attributes": [], "number": 99, "order_number": 1099, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/71bc0d54fa6ef9d4361ae48ceccbf24a/authenticate?key=cae2b5e52a38a86e86f56f625c952f55", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:36-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "71bc0d54fa6ef9d4361ae48ceccbf24a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-09T05:14:59-08:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411107005, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411107005", "created_at": "2021-09-19T09:02:36-07:00", "location_id": 63590301885, "name": "#1099.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:36-07:00", "line_items": [{"id": 10576754868413, "admin_graphql_api_id": "gid://shopify/LineItem/10576754868413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754868413, "admin_graphql_api_id": "gid://shopify/LineItem/10576754868413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:03:26+00:00", "uuid": "1f589b00-1963-11ec-8001-589eaba19efa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158476} -{"stream": "events", "data": {"object": "event", "id": "3mjCgt3vUsR", "statistic_id": "X3f6PC", "timestamp": 1632067408, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147971621053", "$value": 27.0, "$extra": {"id": 4147971621053, "admin_graphql_api_id": "gid://shopify/Order/4147971621053", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:02:39-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:38-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1100", "note": null, "note_attributes": [], "number": 100, "order_number": 1100, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/07968357152fc9c1908a57473c03b615/authenticate?key=0c83db4d883c76e6a83d0505af5c288c", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:38-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "07968357152fc9c1908a57473c03b615", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:02:39-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411205309, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411205309", "created_at": "2021-09-19T09:02:38-07:00", "location_id": 63590301885, "name": "#1100.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:38-07:00", "line_items": [{"id": 10576754933949, "admin_graphql_api_id": "gid://shopify/LineItem/10576754933949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754933949, "admin_graphql_api_id": "gid://shopify/LineItem/10576754933949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:03:28+00:00", "uuid": "2089c800-1963-11ec-8001-c91f74ad5c99", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158477} -{"stream": "events", "data": {"object": "event", "id": "3mjA3DYhQEe", "statistic_id": "X3f6PC", "timestamp": 1632067410, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147971653821", "$value": 27.0, "$extra": {"id": 4147971653821, "admin_graphql_api_id": "gid://shopify/Order/4147971653821", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:02:40-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:02:40-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1101", "note": null, "note_attributes": [], "number": 101, "order_number": 1101, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/0c80d184ab92e7fc8a6524a5a27f9f11/authenticate?key=cf7c106e2776d292396aa23a86215efb", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:02:40-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "0c80d184ab92e7fc8a6524a5a27f9f11", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:02:41-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693411238077, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693411238077", "created_at": "2021-09-19T09:02:40-07:00", "location_id": 63590301885, "name": "#1101.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:02:40-07:00", "line_items": [{"id": 10576754966717, "admin_graphql_api_id": "gid://shopify/LineItem/10576754966717", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576754966717, "admin_graphql_api_id": "gid://shopify/LineItem/10576754966717", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:03:30+00:00", "uuid": "21baf500-1963-11ec-8001-225157cfcfbd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158478} -{"stream": "events", "data": {"object": "event", "id": "3mjvTWuhtHY", "statistic_id": "TspjNE", "timestamp": 1632067443, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147972931773", "$value": 27.0, "$extra": {"id": 4147972931773, "admin_graphql_api_id": "gid://shopify/Order/4147972931773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:44-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:43-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1102", "note": null, "note_attributes": [], "number": 102, "order_number": 1102, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d0be30356fb354228a3f86ddf0f1b9e2/authenticate?key=955a7f6671098a215b397897c9dad9c0", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:43-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d0be30356fb354228a3f86ddf0f1b9e2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:44-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412024509, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412024509", "created_at": "2021-09-19T09:03:43-07:00", "location_id": 63590301885, "name": "#1102.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:43-07:00", "line_items": [{"id": 10576756736189, "admin_graphql_api_id": "gid://shopify/LineItem/10576756736189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576756736189, "admin_graphql_api_id": "gid://shopify/LineItem/10576756736189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:03+00:00", "uuid": "35665b80-1963-11ec-8001-ce595dea2fc5", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158479} -{"stream": "events", "data": {"object": "event", "id": "3mjDxH2Mkx2", "statistic_id": "TspjNE", "timestamp": 1632067444, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147973030077", "$value": 27.0, "$extra": {"id": 4147973030077, "admin_graphql_api_id": "gid://shopify/Order/4147973030077", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:45-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:44-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1103", "note": null, "note_attributes": [], "number": 103, "order_number": 1103, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/762838670a12e876fb65837a9e6c45a2/authenticate?key=50b4abc0a3693956ffd3ea350715cfab", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:44-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "762838670a12e876fb65837a9e6c45a2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:46-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412090045, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412090045", "created_at": "2021-09-19T09:03:45-07:00", "location_id": 63590301885, "name": "#1103.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:45-07:00", "line_items": [{"id": 10576756965565, "admin_graphql_api_id": "gid://shopify/LineItem/10576756965565", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576756965565, "admin_graphql_api_id": "gid://shopify/LineItem/10576756965565", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:04+00:00", "uuid": "35fef200-1963-11ec-8001-c83200bdfeaa", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158480} -{"stream": "events", "data": {"object": "event", "id": "3mjxNSSfTim", "statistic_id": "TspjNE", "timestamp": 1632067446, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147973193917", "$value": 27.0, "$extra": {"id": 4147973193917, "admin_graphql_api_id": "gid://shopify/Order/4147973193917", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:47-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:46-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1104", "note": null, "note_attributes": [], "number": 104, "order_number": 1104, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/23f7c0b8ded1db0ca66165a671064f3d/authenticate?key=5aac221f6379bbb96c1ec98004f92fee", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:46-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "23f7c0b8ded1db0ca66165a671064f3d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:48-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412155581, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412155581", "created_at": "2021-09-19T09:03:47-07:00", "location_id": 63590301885, "name": "#1104.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:47-07:00", "line_items": [{"id": 10576757424317, "admin_graphql_api_id": "gid://shopify/LineItem/10576757424317", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576757424317, "admin_graphql_api_id": "gid://shopify/LineItem/10576757424317", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:06+00:00", "uuid": "37301f00-1963-11ec-8001-ecaa128e85fe", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158481} -{"stream": "events", "data": {"object": "event", "id": "3mjz66r4MPi", "statistic_id": "TspjNE", "timestamp": 1632067448, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147973259453", "$value": 27.0, "$extra": {"id": 4147973259453, "admin_graphql_api_id": "gid://shopify/Order/4147973259453", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:49-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:48-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1105", "note": null, "note_attributes": [], "number": 105, "order_number": 1105, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/984c2d77a720d2ab9f8f51972df7fac8/authenticate?key=cc48e50d2f85adafcadd68ae437acf95", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:48-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "984c2d77a720d2ab9f8f51972df7fac8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:49-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412253885, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412253885", "created_at": "2021-09-19T09:03:49-07:00", "location_id": 63590301885, "name": "#1105.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:49-07:00", "line_items": [{"id": 10576757653693, "admin_graphql_api_id": "gid://shopify/LineItem/10576757653693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576757653693, "admin_graphql_api_id": "gid://shopify/LineItem/10576757653693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:08+00:00", "uuid": "38614c00-1963-11ec-8001-f50c1634fcb2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158482} -{"stream": "events", "data": {"object": "event", "id": "3mjEv7jqeJg", "statistic_id": "RDXsib", "timestamp": 1632067453, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147972931773:10576756736189:0", "$value": 27.0}, "datetime": "2021-09-19 16:04:13+00:00", "uuid": "3b5c3c80-1963-11ec-8001-cb95ac8de3e7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158483} -{"stream": "events", "data": {"object": "event", "id": "3mjz66UmpZB", "statistic_id": "RDXsib", "timestamp": 1632067454, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147973030077:10576756965565:0", "$value": 27.0}, "datetime": "2021-09-19 16:04:14+00:00", "uuid": "3bf4d300-1963-11ec-8001-4429845a938a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158483} -{"stream": "events", "data": {"object": "event", "id": "3mjDdSiHZZp", "statistic_id": "RDXsib", "timestamp": 1632067456, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147973193917:10576757424317:0", "$value": 27.0}, "datetime": "2021-09-19 16:04:16+00:00", "uuid": "3d260000-1963-11ec-8001-4092bdd86da3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$title": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$phone_number": "", "$first_name": "", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158484} -{"stream": "events", "data": {"object": "event", "id": "3mjvTQCJNJp", "statistic_id": "RDXsib", "timestamp": 1632067458, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147973259453:10576757653693:0", "$value": 27.0}, "datetime": "2021-09-19 16:04:18+00:00", "uuid": "3e572d00-1963-11ec-8001-10833e8376bc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158897} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhWwSWd", "statistic_id": "X3f6PC", "timestamp": 1632067473, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147972931773", "$value": 27.0, "$extra": {"id": 4147972931773, "admin_graphql_api_id": "gid://shopify/Order/4147972931773", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:44-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:43-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1102", "note": null, "note_attributes": [], "number": 102, "order_number": 1102, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d0be30356fb354228a3f86ddf0f1b9e2/authenticate?key=955a7f6671098a215b397897c9dad9c0", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:43-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d0be30356fb354228a3f86ddf0f1b9e2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:44-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412024509, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412024509", "created_at": "2021-09-19T09:03:43-07:00", "location_id": 63590301885, "name": "#1102.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:43-07:00", "line_items": [{"id": 10576756736189, "admin_graphql_api_id": "gid://shopify/LineItem/10576756736189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576756736189, "admin_graphql_api_id": "gid://shopify/LineItem/10576756736189", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:33+00:00", "uuid": "4747fe80-1963-11ec-8001-6e8b66e80dc9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158898} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzG2xD9", "statistic_id": "X3f6PC", "timestamp": 1632067475, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147973030077", "$value": 27.0, "$extra": {"id": 4147973030077, "admin_graphql_api_id": "gid://shopify/Order/4147973030077", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:45-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:44-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1103", "note": null, "note_attributes": [], "number": 103, "order_number": 1103, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/762838670a12e876fb65837a9e6c45a2/authenticate?key=50b4abc0a3693956ffd3ea350715cfab", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:44-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "762838670a12e876fb65837a9e6c45a2", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:46-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412090045, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412090045", "created_at": "2021-09-19T09:03:45-07:00", "location_id": 63590301885, "name": "#1103.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:45-07:00", "line_items": [{"id": 10576756965565, "admin_graphql_api_id": "gid://shopify/LineItem/10576756965565", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576756965565, "admin_graphql_api_id": "gid://shopify/LineItem/10576756965565", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:35+00:00", "uuid": "48792b80-1963-11ec-8001-16c6bef91e83", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158899} -{"stream": "events", "data": {"object": "event", "id": "3mjAFhteky8", "statistic_id": "X3f6PC", "timestamp": 1632067477, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147973193917", "$value": 27.0, "$extra": {"id": 4147973193917, "admin_graphql_api_id": "gid://shopify/Order/4147973193917", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:47-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:46-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1104", "note": null, "note_attributes": [], "number": 104, "order_number": 1104, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/23f7c0b8ded1db0ca66165a671064f3d/authenticate?key=5aac221f6379bbb96c1ec98004f92fee", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:46-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "23f7c0b8ded1db0ca66165a671064f3d", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:48-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412155581, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412155581", "created_at": "2021-09-19T09:03:47-07:00", "location_id": 63590301885, "name": "#1104.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:47-07:00", "line_items": [{"id": 10576757424317, "admin_graphql_api_id": "gid://shopify/LineItem/10576757424317", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576757424317, "admin_graphql_api_id": "gid://shopify/LineItem/10576757424317", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:37+00:00", "uuid": "49aa5880-1963-11ec-8001-59f9d816c8c8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158901} -{"stream": "events", "data": {"object": "event", "id": "3mjz6cKTRky", "statistic_id": "X3f6PC", "timestamp": 1632067479, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147973259453", "$value": 27.0, "$extra": {"id": 4147973259453, "admin_graphql_api_id": "gid://shopify/Order/4147973259453", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:03:49-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:03:48-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1105", "note": null, "note_attributes": [], "number": 105, "order_number": 1105, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/984c2d77a720d2ab9f8f51972df7fac8/authenticate?key=cc48e50d2f85adafcadd68ae437acf95", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:03:48-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "984c2d77a720d2ab9f8f51972df7fac8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:03:49-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693412253885, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693412253885", "created_at": "2021-09-19T09:03:49-07:00", "location_id": 63590301885, "name": "#1105.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:03:49-07:00", "line_items": [{"id": 10576757653693, "admin_graphql_api_id": "gid://shopify/LineItem/10576757653693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576757653693, "admin_graphql_api_id": "gid://shopify/LineItem/10576757653693", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:04:39+00:00", "uuid": "4adb8580-1963-11ec-8001-9c9431e3d59a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158902} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRTcBW", "statistic_id": "TspjNE", "timestamp": 1632067510, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147974734013", "$value": 27.0, "$extra": {"id": 4147974734013, "admin_graphql_api_id": "gid://shopify/Order/4147974734013", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:52-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:50-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1106", "note": null, "note_attributes": [], "number": 106, "order_number": 1106, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/863253894118c96eea5b5c6f64b2a129/authenticate?key=bfae35d564c18df63c0c957a8cf0f210", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:50-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "863253894118c96eea5b5c6f64b2a129", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:52-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413105853, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413105853", "created_at": "2021-09-19T09:04:51-07:00", "location_id": 63590301885, "name": "#1106.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:51-07:00", "line_items": [{"id": 10576759914685, "admin_graphql_api_id": "gid://shopify/LineItem/10576759914685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576759914685, "admin_graphql_api_id": "gid://shopify/LineItem/10576759914685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:10+00:00", "uuid": "5d55bf00-1963-11ec-8001-1ee73f761dd0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158904} -{"stream": "events", "data": {"object": "event", "id": "3mjxNUPrYzz", "statistic_id": "TspjNE", "timestamp": 1632067512, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147974832317", "$value": 27.0, "$extra": {"id": 4147974832317, "admin_graphql_api_id": "gid://shopify/Order/4147974832317", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:53-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:52-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1107", "note": null, "note_attributes": [], "number": 107, "order_number": 1107, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/910750c6be7b836df30466ef15fb8c08/authenticate?key=ed113f037e97c371e115732c33898423", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:52-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "910750c6be7b836df30466ef15fb8c08", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:54-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413204157, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413204157", "created_at": "2021-09-19T09:04:53-07:00", "location_id": 63590301885, "name": "#1107.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:53-07:00", "line_items": [{"id": 10576760045757, "admin_graphql_api_id": "gid://shopify/LineItem/10576760045757", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576760045757, "admin_graphql_api_id": "gid://shopify/LineItem/10576760045757", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:12+00:00", "uuid": "5e86ec00-1963-11ec-8001-2e2481ed07f2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158905} -{"stream": "events", "data": {"object": "event", "id": "3mjAFdyRZ2K", "statistic_id": "TspjNE", "timestamp": 1632067514, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147974963389", "$value": 27.0, "$extra": {"id": 4147974963389, "admin_graphql_api_id": "gid://shopify/Order/4147974963389", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:55-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:54-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1108", "note": null, "note_attributes": [], "number": 108, "order_number": 1108, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f9d63b132e27c694f02b06e562dca794/authenticate?key=cdc40b1b0662c4eaf322191c70092f35", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:54-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f9d63b132e27c694f02b06e562dca794", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:56-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413269693, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413269693", "created_at": "2021-09-19T09:04:55-07:00", "location_id": 63590301885, "name": "#1108.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:55-07:00", "line_items": [{"id": 10576760209597, "admin_graphql_api_id": "gid://shopify/LineItem/10576760209597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576760209597, "admin_graphql_api_id": "gid://shopify/LineItem/10576760209597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:14+00:00", "uuid": "5fb81900-1963-11ec-8001-3675efb90889", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158906} -{"stream": "events", "data": {"object": "event", "id": "3mjz68RxvF7", "statistic_id": "TspjNE", "timestamp": 1632067516, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147975028925", "$value": 27.0, "$extra": {"id": 4147975028925, "admin_graphql_api_id": "gid://shopify/Order/4147975028925", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:57-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:56-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1109", "note": null, "note_attributes": [], "number": 109, "order_number": 1109, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/88541119c2d1f3f77a04a2ccbfc64389/authenticate?key=bd2ff50d85923939b546d27c1e983054", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:56-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "88541119c2d1f3f77a04a2ccbfc64389", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:57-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413335229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413335229", "created_at": "2021-09-19T09:04:57-07:00", "location_id": 63590301885, "name": "#1109.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:57-07:00", "line_items": [{"id": 10576760242365, "admin_graphql_api_id": "gid://shopify/LineItem/10576760242365", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576760242365, "admin_graphql_api_id": "gid://shopify/LineItem/10576760242365", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:16+00:00", "uuid": "60e94600-1963-11ec-8001-f8b61933f996", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158907} -{"stream": "events", "data": {"object": "event", "id": "3mjAmsKaNyJ", "statistic_id": "RDXsib", "timestamp": 1632067520, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147974734013:10576759914685:0", "$value": 27.0}, "datetime": "2021-09-19 16:05:20+00:00", "uuid": "634ba000-1963-11ec-8001-6075b747a181", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158909} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxfxWNm", "statistic_id": "RDXsib", "timestamp": 1632067522, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147974832317:10576760045757:0", "$value": 27.0}, "datetime": "2021-09-19 16:05:22+00:00", "uuid": "647ccd00-1963-11ec-8001-ff9691066fb6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158909} -{"stream": "events", "data": {"object": "event", "id": "3mjDRyHrrYk", "statistic_id": "RDXsib", "timestamp": 1632067524, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147974963389:10576760209597:0", "$value": 27.0}, "datetime": "2021-09-19 16:05:24+00:00", "uuid": "65adfa00-1963-11ec-8001-c6ebe48ed8e3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158909} -{"stream": "events", "data": {"object": "event", "id": "3mjwdDSUu2K", "statistic_id": "RDXsib", "timestamp": 1632067526, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147975028925:10576760242365:0", "$value": 27.0}, "datetime": "2021-09-19 16:05:26+00:00", "uuid": "66df2700-1963-11ec-8001-6ff7fe948390", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158909} -{"stream": "events", "data": {"object": "event", "id": "3mjAFknZnvs", "statistic_id": "X3f6PC", "timestamp": 1632067541, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147974734013", "$value": 27.0, "$extra": {"id": 4147974734013, "admin_graphql_api_id": "gid://shopify/Order/4147974734013", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:52-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:50-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1106", "note": null, "note_attributes": [], "number": 106, "order_number": 1106, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/863253894118c96eea5b5c6f64b2a129/authenticate?key=bfae35d564c18df63c0c957a8cf0f210", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:50-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "863253894118c96eea5b5c6f64b2a129", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:52-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413105853, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413105853", "created_at": "2021-09-19T09:04:51-07:00", "location_id": 63590301885, "name": "#1106.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:51-07:00", "line_items": [{"id": 10576759914685, "admin_graphql_api_id": "gid://shopify/LineItem/10576759914685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576759914685, "admin_graphql_api_id": "gid://shopify/LineItem/10576759914685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:41+00:00", "uuid": "6fcff880-1963-11ec-8001-8bc592c902f4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158910} -{"stream": "events", "data": {"object": "event", "id": "3mjuiF4sRT2", "statistic_id": "X3f6PC", "timestamp": 1632067543, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147974832317", "$value": 27.0, "$extra": {"id": 4147974832317, "admin_graphql_api_id": "gid://shopify/Order/4147974832317", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:53-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:52-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1107", "note": null, "note_attributes": [], "number": 107, "order_number": 1107, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/910750c6be7b836df30466ef15fb8c08/authenticate?key=ed113f037e97c371e115732c33898423", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:52-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "910750c6be7b836df30466ef15fb8c08", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:54-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413204157, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413204157", "created_at": "2021-09-19T09:04:53-07:00", "location_id": 63590301885, "name": "#1107.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:53-07:00", "line_items": [{"id": 10576760045757, "admin_graphql_api_id": "gid://shopify/LineItem/10576760045757", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576760045757, "admin_graphql_api_id": "gid://shopify/LineItem/10576760045757", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:43+00:00", "uuid": "71012580-1963-11ec-8001-c0b04dcc61ba", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158913} -{"stream": "events", "data": {"object": "event", "id": "3mjBCJFCjCw", "statistic_id": "X3f6PC", "timestamp": 1632067545, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147974963389", "$value": 27.0, "$extra": {"id": 4147974963389, "admin_graphql_api_id": "gid://shopify/Order/4147974963389", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:55-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:54-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1108", "note": null, "note_attributes": [], "number": 108, "order_number": 1108, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f9d63b132e27c694f02b06e562dca794/authenticate?key=cdc40b1b0662c4eaf322191c70092f35", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:54-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f9d63b132e27c694f02b06e562dca794", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:56-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413269693, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413269693", "created_at": "2021-09-19T09:04:55-07:00", "location_id": 63590301885, "name": "#1108.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:55-07:00", "line_items": [{"id": 10576760209597, "admin_graphql_api_id": "gid://shopify/LineItem/10576760209597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576760209597, "admin_graphql_api_id": "gid://shopify/LineItem/10576760209597", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:45+00:00", "uuid": "72325280-1963-11ec-8001-b1d76d690188", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158914} -{"stream": "events", "data": {"object": "event", "id": "3mjwRpdnQv8", "statistic_id": "X3f6PC", "timestamp": 1632067547, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147975028925", "$value": 27.0, "$extra": {"id": 4147975028925, "admin_graphql_api_id": "gid://shopify/Order/4147975028925", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:04:57-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:04:56-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1109", "note": null, "note_attributes": [], "number": 109, "order_number": 1109, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/88541119c2d1f3f77a04a2ccbfc64389/authenticate?key=bd2ff50d85923939b546d27c1e983054", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:04:56-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "88541119c2d1f3f77a04a2ccbfc64389", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:04:57-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413335229, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413335229", "created_at": "2021-09-19T09:04:57-07:00", "location_id": 63590301885, "name": "#1109.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:04:57-07:00", "line_items": [{"id": 10576760242365, "admin_graphql_api_id": "gid://shopify/LineItem/10576760242365", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576760242365, "admin_graphql_api_id": "gid://shopify/LineItem/10576760242365", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:05:47+00:00", "uuid": "73637f80-1963-11ec-8001-ad568db877d3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158916} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLffTY", "statistic_id": "TspjNE", "timestamp": 1632067578, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147976536253", "$value": 27.0, "$extra": {"id": 4147976536253, "admin_graphql_api_id": "gid://shopify/Order/4147976536253", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:05:59-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:05:58-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1110", "note": null, "note_attributes": [], "number": 110, "order_number": 1110, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/264a37c19187b4952301cbe64f0f2d65/authenticate?key=b079f25df725f7b99a33fa6124e0e58e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:05:58-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "264a37c19187b4952301cbe64f0f2d65", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:00-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413630141, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413630141", "created_at": "2021-09-19T09:05:59-07:00", "location_id": 63590301885, "name": "#1110.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:05:59-07:00", "line_items": [{"id": 10576763715773, "admin_graphql_api_id": "gid://shopify/LineItem/10576763715773", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576763715773, "admin_graphql_api_id": "gid://shopify/LineItem/10576763715773", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:18+00:00", "uuid": "85ddb900-1963-11ec-8001-10462ebb20cb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158918} -{"stream": "events", "data": {"object": "event", "id": "3mjEbmuiwi7", "statistic_id": "TspjNE", "timestamp": 1632067580, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147976634557", "$value": 27.0, "$extra": {"id": 4147976634557, "admin_graphql_api_id": "gid://shopify/Order/4147976634557", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:06:01-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:06:00-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1111", "note": null, "note_attributes": [], "number": 111, "order_number": 1111, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d8ffaf87d5bdc705bc387d04a4843291/authenticate?key=55f0010eb86350253ffb5dc67df3ec2e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:06:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d8ffaf87d5bdc705bc387d04a4843291", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:01-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413662909, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413662909", "created_at": "2021-09-19T09:06:01-07:00", "location_id": 63590301885, "name": "#1111.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:06:01-07:00", "line_items": [{"id": 10576763912381, "admin_graphql_api_id": "gid://shopify/LineItem/10576763912381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576763912381, "admin_graphql_api_id": "gid://shopify/LineItem/10576763912381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:20+00:00", "uuid": "870ee600-1963-11ec-8001-e3810a2700ec", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158919} -{"stream": "events", "data": {"object": "event", "id": "3mjz6ajr6RQ", "statistic_id": "TspjNE", "timestamp": 1632067582, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147976667325", "$value": 27.0, "$extra": {"id": 4147976667325, "admin_graphql_api_id": "gid://shopify/Order/4147976667325", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:06:03-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:06:02-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1112", "note": null, "note_attributes": [], "number": 112, "order_number": 1112, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c8decff4852c59dcc3bba6a04ba48410/authenticate?key=74dcb745c3db2b6c432c303049107f8f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:06:02-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "c8decff4852c59dcc3bba6a04ba48410", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:03-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413761213, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413761213", "created_at": "2021-09-19T09:06:02-07:00", "location_id": 63590301885, "name": "#1112.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:06:03-07:00", "line_items": [{"id": 10576763945149, "admin_graphql_api_id": "gid://shopify/LineItem/10576763945149", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576763945149, "admin_graphql_api_id": "gid://shopify/LineItem/10576763945149", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:22+00:00", "uuid": "88401300-1963-11ec-8001-6394ee879ac1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158920} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLfrKJ", "statistic_id": "TspjNE", "timestamp": 1632067584, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147976732861", "$value": 27.0, "$extra": {"id": 4147976732861, "admin_graphql_api_id": "gid://shopify/Order/4147976732861", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:06:06-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:06:04-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1113", "note": null, "note_attributes": [], "number": 113, "order_number": 1113, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4f8d4943aeb211e25ec273952c2f8f8/authenticate?key=6cb2130b63618cf271e692781e0c96fa", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:06:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f4f8d4943aeb211e25ec273952c2f8f8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:07-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413826749, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413826749", "created_at": "2021-09-19T09:06:06-07:00", "location_id": 63590301885, "name": "#1113.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:06:06-07:00", "line_items": [{"id": 10576764010685, "admin_graphql_api_id": "gid://shopify/LineItem/10576764010685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576764010685, "admin_graphql_api_id": "gid://shopify/LineItem/10576764010685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:24+00:00", "uuid": "89714000-1963-11ec-8001-714a36afdf89", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158921} -{"stream": "events", "data": {"object": "event", "id": "3mjDRwLfwxQ", "statistic_id": "RDXsib", "timestamp": 1632067588, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147976536253:10576763715773:0", "$value": 27.0}, "datetime": "2021-09-19 16:06:28+00:00", "uuid": "8bd39a00-1963-11ec-8001-96cb899b9b85", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158922} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQu79v", "statistic_id": "RDXsib", "timestamp": 1632067590, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147976634557:10576763912381:0", "$value": 27.0}, "datetime": "2021-09-19 16:06:30+00:00", "uuid": "8d04c700-1963-11ec-8001-e2f18666ee8a", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158923} -{"stream": "events", "data": {"object": "event", "id": "3mjBWCjsKLz", "statistic_id": "RDXsib", "timestamp": 1632067592, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147976667325:10576763945149:0", "$value": 27.0}, "datetime": "2021-09-19 16:06:32+00:00", "uuid": "8e35f400-1963-11ec-8001-2bcd29ef63df", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158923} -{"stream": "events", "data": {"object": "event", "id": "3mjz6bMiKdj", "statistic_id": "RDXsib", "timestamp": 1632067594, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147976732861:10576764010685:0", "$value": 27.0}, "datetime": "2021-09-19 16:06:34+00:00", "uuid": "8f672100-1963-11ec-8001-6051e3dba0bb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158923} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQrGC", "statistic_id": "X3f6PC", "timestamp": 1632067609, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147976536253", "$value": 27.0, "$extra": {"id": 4147976536253, "admin_graphql_api_id": "gid://shopify/Order/4147976536253", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:05:59-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:05:58-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1110", "note": null, "note_attributes": [], "number": 110, "order_number": 1110, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/264a37c19187b4952301cbe64f0f2d65/authenticate?key=b079f25df725f7b99a33fa6124e0e58e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:05:58-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "264a37c19187b4952301cbe64f0f2d65", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:00-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413630141, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413630141", "created_at": "2021-09-19T09:05:59-07:00", "location_id": 63590301885, "name": "#1110.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:05:59-07:00", "line_items": [{"id": 10576763715773, "admin_graphql_api_id": "gid://shopify/LineItem/10576763715773", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576763715773, "admin_graphql_api_id": "gid://shopify/LineItem/10576763715773", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:49+00:00", "uuid": "9857f280-1963-11ec-8001-d72e4d00e597", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158924} -{"stream": "events", "data": {"object": "event", "id": "3mjDRrThTvt", "statistic_id": "X3f6PC", "timestamp": 1632067611, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147976634557", "$value": 27.0, "$extra": {"id": 4147976634557, "admin_graphql_api_id": "gid://shopify/Order/4147976634557", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:06:01-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:06:00-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1111", "note": null, "note_attributes": [], "number": 111, "order_number": 1111, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d8ffaf87d5bdc705bc387d04a4843291/authenticate?key=55f0010eb86350253ffb5dc67df3ec2e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:06:00-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "d8ffaf87d5bdc705bc387d04a4843291", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:01-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413662909, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413662909", "created_at": "2021-09-19T09:06:01-07:00", "location_id": 63590301885, "name": "#1111.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:06:01-07:00", "line_items": [{"id": 10576763912381, "admin_graphql_api_id": "gid://shopify/LineItem/10576763912381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576763912381, "admin_graphql_api_id": "gid://shopify/LineItem/10576763912381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:51+00:00", "uuid": "99891f80-1963-11ec-8001-a340685681e0", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158925} -{"stream": "events", "data": {"object": "event", "id": "3mjAZ6e6Xsc", "statistic_id": "X3f6PC", "timestamp": 1632067612, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147976667325", "$value": 27.0, "$extra": {"id": 4147976667325, "admin_graphql_api_id": "gid://shopify/Order/4147976667325", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:06:03-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:06:02-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1112", "note": null, "note_attributes": [], "number": 112, "order_number": 1112, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c8decff4852c59dcc3bba6a04ba48410/authenticate?key=74dcb745c3db2b6c432c303049107f8f", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:06:02-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "c8decff4852c59dcc3bba6a04ba48410", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:03-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413761213, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413761213", "created_at": "2021-09-19T09:06:02-07:00", "location_id": 63590301885, "name": "#1112.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:06:03-07:00", "line_items": [{"id": 10576763945149, "admin_graphql_api_id": "gid://shopify/LineItem/10576763945149", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576763945149, "admin_graphql_api_id": "gid://shopify/LineItem/10576763945149", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:52+00:00", "uuid": "9a21b600-1963-11ec-8001-b0bfbd2c80ff", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158926} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtmbMSg", "statistic_id": "X3f6PC", "timestamp": 1632067616, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147976732861", "$value": 27.0, "$extra": {"id": 4147976732861, "admin_graphql_api_id": "gid://shopify/Order/4147976732861", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:06:06-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:06:04-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1113", "note": null, "note_attributes": [], "number": 113, "order_number": 1113, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4f8d4943aeb211e25ec273952c2f8f8/authenticate?key=6cb2130b63618cf271e692781e0c96fa", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:06:04-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f4f8d4943aeb211e25ec273952c2f8f8", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:06:07-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693413826749, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693413826749", "created_at": "2021-09-19T09:06:06-07:00", "location_id": 63590301885, "name": "#1113.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:06:06-07:00", "line_items": [{"id": 10576764010685, "admin_graphql_api_id": "gid://shopify/LineItem/10576764010685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576764010685, "admin_graphql_api_id": "gid://shopify/LineItem/10576764010685", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:06:56+00:00", "uuid": "9c841000-1963-11ec-8001-decae130d594", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158927} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHtH2", "statistic_id": "TspjNE", "timestamp": 1632067648, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147978076349", "$value": 27.0, "$extra": {"id": 4147978076349, "admin_graphql_api_id": "gid://shopify/Order/4147978076349", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:09-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:08-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1114", "note": null, "note_attributes": [], "number": 114, "order_number": 1114, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/77e2b10fdc5dd559ef6a6574d6a9a413/authenticate?key=957d0f0d347e1a4ec47a2b655a941b65", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:08-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "77e2b10fdc5dd559ef6a6574d6a9a413", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:09-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414514877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414514877", "created_at": "2021-09-19T09:07:08-07:00", "location_id": 63590301885, "name": "#1114.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:08-07:00", "line_items": [{"id": 10576766369981, "admin_graphql_api_id": "gid://shopify/LineItem/10576766369981", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576766369981, "admin_graphql_api_id": "gid://shopify/LineItem/10576766369981", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:07:28+00:00", "uuid": "af96e000-1963-11ec-8001-5de772d8f199", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158928} -{"stream": "events", "data": {"object": "event", "id": "3mjCgiKuTmN", "statistic_id": "TspjNE", "timestamp": 1632067650, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147978207421", "$value": 27.0, "$extra": {"id": 4147978207421, "admin_graphql_api_id": "gid://shopify/Order/4147978207421", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:12-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:10-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1115", "note": null, "note_attributes": [], "number": 115, "order_number": 1115, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9a5ed0f3632e53b084395bd26b50c9ad/authenticate?key=a75e342bb34ed961bf533424099fedbc", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:10-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9a5ed0f3632e53b084395bd26b50c9ad", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:12-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414613181, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414613181", "created_at": "2021-09-19T09:07:11-07:00", "location_id": 63590301885, "name": "#1115.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:11-07:00", "line_items": [{"id": 10576766632125, "admin_graphql_api_id": "gid://shopify/LineItem/10576766632125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576766632125, "admin_graphql_api_id": "gid://shopify/LineItem/10576766632125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:07:30+00:00", "uuid": "b0c80d00-1963-11ec-8001-abbfe76920ae", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158929} -{"stream": "events", "data": {"object": "event", "id": "3mjAFfw4i7x", "statistic_id": "TspjNE", "timestamp": 1632067653, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147978305725", "$value": 27.0, "$extra": {"id": 4147978305725, "admin_graphql_api_id": "gid://shopify/Order/4147978305725", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:14-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:13-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1116", "note": null, "note_attributes": [], "number": 116, "order_number": 1116, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a3244deb6c36697c998f6415eb210647/authenticate?key=812e618e3844eb9c8cf878354458f7f5", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:13-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a3244deb6c36697c998f6415eb210647", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:14-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414645949, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414645949", "created_at": "2021-09-19T09:07:13-07:00", "location_id": 63590301885, "name": "#1116.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:13-07:00", "line_items": [{"id": 10576766828733, "admin_graphql_api_id": "gid://shopify/LineItem/10576766828733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576766828733, "admin_graphql_api_id": "gid://shopify/LineItem/10576766828733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:07:33+00:00", "uuid": "b291d080-1963-11ec-8001-91a790774adf", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158930} -{"stream": "events", "data": {"object": "event", "id": "3mjA3DuZgas", "statistic_id": "TspjNE", "timestamp": 1632067655, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147978404029", "$value": 27.0, "$extra": {"id": 4147978404029, "admin_graphql_api_id": "gid://shopify/Order/4147978404029", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:16-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:15-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1117", "note": null, "note_attributes": [], "number": 117, "order_number": 1117, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/bee2ef2a40c1d20adfc88fe8efedfc7c/authenticate?key=96a712775924fff0ffca69e1759f9fbe", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:15-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "bee2ef2a40c1d20adfc88fe8efedfc7c", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:16-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414678717, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414678717", "created_at": "2021-09-19T09:07:15-07:00", "location_id": 63590301885, "name": "#1117.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:15-07:00", "line_items": [{"id": 10576767189181, "admin_graphql_api_id": "gid://shopify/LineItem/10576767189181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576767189181, "admin_graphql_api_id": "gid://shopify/LineItem/10576767189181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:07:35+00:00", "uuid": "b3c2fd80-1963-11ec-8001-8b3965f8b5fd", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158930} -{"stream": "events", "data": {"object": "event", "id": "3mjAFiV86Ju", "statistic_id": "RDXsib", "timestamp": 1632067658, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147978076349:10576766369981:0", "$value": 27.0}, "datetime": "2021-09-19 16:07:38+00:00", "uuid": "b58cc100-1963-11ec-8001-29779c4010fb", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158931} -{"stream": "events", "data": {"object": "event", "id": "3mjBWBQacLR", "statistic_id": "RDXsib", "timestamp": 1632067660, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147978207421:10576766632125:0", "$value": 27.0}, "datetime": "2021-09-19 16:07:40+00:00", "uuid": "b6bdee00-1963-11ec-8001-8ee5f7d210b7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158932} -{"stream": "events", "data": {"object": "event", "id": "3mjDRye94Ne", "statistic_id": "RDXsib", "timestamp": 1632067663, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147978305725:10576766828733:0", "$value": 27.0}, "datetime": "2021-09-19 16:07:43+00:00", "uuid": "b887b180-1963-11ec-8001-90e49d0ba2c6", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158932} -{"stream": "events", "data": {"object": "event", "id": "3mjvTNFyQyX", "statistic_id": "RDXsib", "timestamp": 1632067665, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147978404029:10576767189181:0", "$value": 27.0}, "datetime": "2021-09-19 16:07:45+00:00", "uuid": "b9b8de80-1963-11ec-8001-43ee001ffab9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158933} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQu9FP", "statistic_id": "X3f6PC", "timestamp": 1632067678, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147978076349", "$value": 27.0, "$extra": {"id": 4147978076349, "admin_graphql_api_id": "gid://shopify/Order/4147978076349", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:09-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:08-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1114", "note": null, "note_attributes": [], "number": 114, "order_number": 1114, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/77e2b10fdc5dd559ef6a6574d6a9a413/authenticate?key=957d0f0d347e1a4ec47a2b655a941b65", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:08-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "77e2b10fdc5dd559ef6a6574d6a9a413", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:09-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414514877, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414514877", "created_at": "2021-09-19T09:07:08-07:00", "location_id": 63590301885, "name": "#1114.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:08-07:00", "line_items": [{"id": 10576766369981, "admin_graphql_api_id": "gid://shopify/LineItem/10576766369981", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576766369981, "admin_graphql_api_id": "gid://shopify/LineItem/10576766369981", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:07:58+00:00", "uuid": "c1788300-1963-11ec-8001-0700808f98ef", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158933} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzcHYiX", "statistic_id": "X3f6PC", "timestamp": 1632067681, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147978207421", "$value": 27.0, "$extra": {"id": 4147978207421, "admin_graphql_api_id": "gid://shopify/Order/4147978207421", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:12-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:10-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1115", "note": null, "note_attributes": [], "number": 115, "order_number": 1115, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/9a5ed0f3632e53b084395bd26b50c9ad/authenticate?key=a75e342bb34ed961bf533424099fedbc", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:10-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "9a5ed0f3632e53b084395bd26b50c9ad", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:12-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414613181, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414613181", "created_at": "2021-09-19T09:07:11-07:00", "location_id": 63590301885, "name": "#1115.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:11-07:00", "line_items": [{"id": 10576766632125, "admin_graphql_api_id": "gid://shopify/LineItem/10576766632125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576766632125, "admin_graphql_api_id": "gid://shopify/LineItem/10576766632125", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:01+00:00", "uuid": "c3424680-1963-11ec-8001-ba07d014ede9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158934} -{"stream": "events", "data": {"object": "event", "id": "3mjDRuP5f3L", "statistic_id": "X3f6PC", "timestamp": 1632067683, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147978305725", "$value": 27.0, "$extra": {"id": 4147978305725, "admin_graphql_api_id": "gid://shopify/Order/4147978305725", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:14-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:13-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1116", "note": null, "note_attributes": [], "number": 116, "order_number": 1116, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a3244deb6c36697c998f6415eb210647/authenticate?key=812e618e3844eb9c8cf878354458f7f5", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:13-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "a3244deb6c36697c998f6415eb210647", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:14-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414645949, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414645949", "created_at": "2021-09-19T09:07:13-07:00", "location_id": 63590301885, "name": "#1116.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:13-07:00", "line_items": [{"id": 10576766828733, "admin_graphql_api_id": "gid://shopify/LineItem/10576766828733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576766828733, "admin_graphql_api_id": "gid://shopify/LineItem/10576766828733", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:03+00:00", "uuid": "c4737380-1963-11ec-8001-993d8c9b2be8", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158935} -{"stream": "events", "data": {"object": "event", "id": "3mjEbjx8iBf", "statistic_id": "X3f6PC", "timestamp": 1632067685, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147978404029", "$value": 27.0, "$extra": {"id": 4147978404029, "admin_graphql_api_id": "gid://shopify/Order/4147978404029", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:07:16-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:07:15-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1117", "note": null, "note_attributes": [], "number": 117, "order_number": 1117, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/bee2ef2a40c1d20adfc88fe8efedfc7c/authenticate?key=96a712775924fff0ffca69e1759f9fbe", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:07:15-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "bee2ef2a40c1d20adfc88fe8efedfc7c", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:07:16-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693414678717, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693414678717", "created_at": "2021-09-19T09:07:15-07:00", "location_id": 63590301885, "name": "#1117.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:07:15-07:00", "line_items": [{"id": 10576767189181, "admin_graphql_api_id": "gid://shopify/LineItem/10576767189181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576767189181, "admin_graphql_api_id": "gid://shopify/LineItem/10576767189181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:05+00:00", "uuid": "c5a4a080-1963-11ec-8001-343159ef5f94", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158936} -{"stream": "events", "data": {"object": "event", "id": "3mjwxsDMuyu", "statistic_id": "TspjNE", "timestamp": 1632067717, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147979714749", "$value": 27.0, "$extra": {"id": 4147979714749, "admin_graphql_api_id": "gid://shopify/Order/4147979714749", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:08:18-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:17-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1118", "note": null, "note_attributes": [], "number": 118, "order_number": 1118, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f058fba395aef6e83c0732fc396ba976/authenticate?key=5b4b62b77d58b68dee4206fe5abfb18b", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:17-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f058fba395aef6e83c0732fc396ba976", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:08:18-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416513725, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416513725", "created_at": "2021-09-19T09:08:17-07:00", "location_id": 63590301885, "name": "#1118.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:08:17-07:00", "line_items": [{"id": 10576770891965, "admin_graphql_api_id": "gid://shopify/LineItem/10576770891965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576770891965, "admin_graphql_api_id": "gid://shopify/LineItem/10576770891965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:37+00:00", "uuid": "d8b77080-1963-11ec-8001-fa7528301eb1", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158937} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRT4Vd", "statistic_id": "TspjNE", "timestamp": 1632067719, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147979911357", "$value": 27.0, "$extra": {"id": 4147979911357, "admin_graphql_api_id": "gid://shopify/Order/4147979911357", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:08:20-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:19-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1119", "note": null, "note_attributes": [], "number": 119, "order_number": 1119, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6f1b457d74e46b813f885fd6704dab07/authenticate?key=13995ce1a88e2be5436add447d4cf3be", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6f1b457d74e46b813f885fd6704dab07", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:08:21-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416644797, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416644797", "created_at": "2021-09-19T09:08:20-07:00", "location_id": 63590301885, "name": "#1119.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:08:20-07:00", "line_items": [{"id": 10576771121341, "admin_graphql_api_id": "gid://shopify/LineItem/10576771121341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576771121341, "admin_graphql_api_id": "gid://shopify/LineItem/10576771121341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:39+00:00", "uuid": "d9e89d80-1963-11ec-8001-51a558afe7d9", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158938} -{"stream": "events", "data": {"object": "event", "id": "3mjBWv22DX4", "statistic_id": "TspjNE", "timestamp": 1632067721, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147980075197", "$value": 27.0, "$extra": {"id": 4147980075197, "admin_graphql_api_id": "gid://shopify/Order/4147980075197", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:08:22-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:21-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1120", "note": null, "note_attributes": [], "number": 120, "order_number": 1120, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f0729ec0e4dbf2a142be43a50a4c165a/authenticate?key=c9f961b5090b86da4c82da542af4f2d9", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:21-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f0729ec0e4dbf2a142be43a50a4c165a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:08:22-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416677565, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416677565", "created_at": "2021-09-19T09:08:22-07:00", "location_id": 63590301885, "name": "#1120.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:08:22-07:00", "line_items": [{"id": 10576771285181, "admin_graphql_api_id": "gid://shopify/LineItem/10576771285181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576771285181, "admin_graphql_api_id": "gid://shopify/LineItem/10576771285181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:41+00:00", "uuid": "db19ca80-1963-11ec-8001-0ddf7580ad96", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158939} -{"stream": "events", "data": {"object": "event", "id": "3mjvTRBjPFp", "statistic_id": "TspjNE", "timestamp": 1632067723, "event_name": "Placed Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "$event_id": "4147980107965", "$value": 27.0, "$extra": {"id": 4147980107965, "admin_graphql_api_id": "gid://shopify/Order/4147980107965", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:23-07:00", "currency": "USD", "current_subtotal_price": "0.00", "current_subtotal_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "0.00", "current_total_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1121", "note": null, "note_attributes": [], "number": 121, "order_number": 1121, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6adf11e07ccb49b280ea4b9f53d64f12/authenticate?key=4cef2ff10ba4d18f31114df33933f81e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:23-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6adf11e07ccb49b280ea4b9f53d64f12", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-03-07T02:09:04-08:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416710333, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416710333", "created_at": "2021-09-19T09:08:23-07:00", "location_id": 63590301885, "name": "#1121.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics US", "tracking_number": "123456", "tracking_numbers": ["123456"], "tracking_url": "https://track.amazon.com/tracking/123456", "tracking_urls": ["https://track.amazon.com/tracking/123456"], "updated_at": "2022-02-22T00:35:47-08:00", "line_items": [{"id": 10576771317949, "admin_graphql_api_id": "gid://shopify/LineItem/10576771317949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576771317949, "admin_graphql_api_id": "gid://shopify/LineItem/10576771317949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [{"id": 845032358077, "admin_graphql_api_id": "gid://shopify/Refund/845032358077", "created_at": "2022-03-07T02:09:04-08:00", "note": null, "processed_at": "2022-03-07T02:09:04-08:00", "restock": true, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [], "transactions": [], "refund_line_items": [{"id": 352716947645, "line_item_id": 10576771317949, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 27.0, "subtotal_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 10576771317949, "admin_graphql_api_id": "gid://shopify/LineItem/10576771317949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_address": {"first_name": "John", "address1": "San Francisco", "phone": "", "city": "San Francisco", "zip": "91326", "province": "California", "country": "United States", "last_name": "Doe", "address2": "10", "company": "Umbrella LLC", "latitude": 34.2894584, "longitude": -118.5622893, "name": "John Doe", "country_code": "US", "province_code": "CA"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:08:43+00:00", "uuid": "dc4af780-1963-11ec-8001-06d0995b5ed3", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158940} -{"stream": "events", "data": {"object": "event", "id": "3mjvTV2pEEe", "statistic_id": "RDXsib", "timestamp": 1632067727, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147979714749:10576770891965:0", "$value": 27.0}, "datetime": "2021-09-19 16:08:47+00:00", "uuid": "dead5180-1963-11ec-8001-591dc6d24f9d", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158941} -{"stream": "events", "data": {"object": "event", "id": "3mjEbnWc7sQ", "statistic_id": "RDXsib", "timestamp": 1632067729, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147979911357:10576771121341:0", "$value": 27.0}, "datetime": "2021-09-19 16:08:49+00:00", "uuid": "dfde7e80-1963-11ec-8001-1db73dcb8ece", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158941} -{"stream": "events", "data": {"object": "event", "id": "3mjz6ajqMhN", "statistic_id": "RDXsib", "timestamp": 1632067731, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147980075197:10576771285181:0", "$value": 27.0}, "datetime": "2021-09-19 16:08:51+00:00", "uuid": "e10fab80-1963-11ec-8001-6f06055995c2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158941} -{"stream": "events", "data": {"object": "event", "id": "3mjDRB9UmyD", "statistic_id": "RDXsib", "timestamp": 1632067733, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796218302653, "Name": "Red & Silver Fishing Lure", "Variant Name": "Plastic", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Harris - Hamill", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4147980107965:10576771317949:0", "$value": 27.0}, "datetime": "2021-09-19 16:08:53+00:00", "uuid": "e240d880-1963-11ec-8001-4fd16cc028dc", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158942} -{"stream": "events", "data": {"object": "event", "id": "3mjvTVvGhi8", "statistic_id": "X3f6PC", "timestamp": 1632067747, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "4147979714749", "$value": 27.0, "$extra": {"id": 4147979714749, "admin_graphql_api_id": "gid://shopify/Order/4147979714749", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:08:18-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:17-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1118", "note": null, "note_attributes": [], "number": 118, "order_number": 1118, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f058fba395aef6e83c0732fc396ba976/authenticate?key=5b4b62b77d58b68dee4206fe5abfb18b", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:17-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f058fba395aef6e83c0732fc396ba976", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:08:18-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416513725, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416513725", "created_at": "2021-09-19T09:08:17-07:00", "location_id": 63590301885, "name": "#1118.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:08:17-07:00", "line_items": [{"id": 10576770891965, "admin_graphql_api_id": "gid://shopify/LineItem/10576770891965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576770891965, "admin_graphql_api_id": "gid://shopify/LineItem/10576770891965", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:09:07+00:00", "uuid": "ea991380-1963-11ec-8001-8ea15e08abd2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158942} -{"stream": "events", "data": {"object": "event", "id": "3mjy8DcEh7z", "statistic_id": "X3f6PC", "timestamp": 1632067750, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147979911357", "$value": 27.0, "$extra": {"id": 4147979911357, "admin_graphql_api_id": "gid://shopify/Order/4147979911357", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:08:20-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:19-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1119", "note": null, "note_attributes": [], "number": 119, "order_number": 1119, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6f1b457d74e46b813f885fd6704dab07/authenticate?key=13995ce1a88e2be5436add447d4cf3be", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:19-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6f1b457d74e46b813f885fd6704dab07", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:08:21-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416644797, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416644797", "created_at": "2021-09-19T09:08:20-07:00", "location_id": 63590301885, "name": "#1119.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:08:20-07:00", "line_items": [{"id": 10576771121341, "admin_graphql_api_id": "gid://shopify/LineItem/10576771121341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576771121341, "admin_graphql_api_id": "gid://shopify/LineItem/10576771121341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:09:10+00:00", "uuid": "ec62d700-1963-11ec-8001-14cb34f56ac4", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158943} -{"stream": "events", "data": {"object": "event", "id": "3mjDRsRSZuu", "statistic_id": "X3f6PC", "timestamp": 1632067752, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4147980075197", "$value": 27.0, "$extra": {"id": 4147980075197, "admin_graphql_api_id": "gid://shopify/Order/4147980075197", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-09-19T09:08:22-07:00", "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:21-07:00", "currency": "USD", "current_subtotal_price": "27.00", "current_subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "27.00", "current_total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1120", "note": null, "note_attributes": [], "number": 120, "order_number": 1120, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f0729ec0e4dbf2a142be43a50a4c165a/authenticate?key=c9f961b5090b86da4c82da542af4f2d9", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:21-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "f0729ec0e4dbf2a142be43a50a4c165a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "27.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-09-19T09:08:22-07:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416677565, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416677565", "created_at": "2021-09-19T09:08:22-07:00", "location_id": 63590301885, "name": "#1120.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-09-19T09:08:22-07:00", "line_items": [{"id": 10576771285181, "admin_graphql_api_id": "gid://shopify/LineItem/10576771285181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576771285181, "admin_graphql_api_id": "gid://shopify/LineItem/10576771285181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:09:12+00:00", "uuid": "ed940400-1963-11ec-8001-3cc6b435d5e7", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158944} -{"stream": "events", "data": {"object": "event", "id": "3mjDxF5B3sW", "statistic_id": "X3f6PC", "timestamp": 1632067753, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Red & Silver Fishing Lure"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 3737, "HasPartialFulfillments": false, "$event_id": "4147980107965", "$value": 27.0, "$extra": {"id": 4147980107965, "admin_graphql_api_id": "gid://shopify/Order/4147980107965", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte@airbyte.com", "created_at": "2021-09-19T09:08:23-07:00", "currency": "USD", "current_subtotal_price": "0.00", "current_subtotal_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "0.00", "current_total_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1121", "note": null, "note_attributes": [], "number": 121, "order_number": 1121, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6adf11e07ccb49b280ea4b9f53d64f12/authenticate?key=4cef2ff10ba4d18f31114df33933f81e", "original_total_duties_set": null, "payment_gateway_names": [], "phone": null, "presentment_currency": "USD", "processed_at": "2021-09-19T09:08:23-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "27.00", "subtotal_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": false, "test": false, "token": "6adf11e07ccb49b280ea4b9f53d64f12", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "27.00", "total_line_items_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "27.00", "total_price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_price_usd": "27.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-03-07T02:09:04-08:00", "user_id": null, "customer": {"id": 5565161144509, "email": "airbyte@airbyte.com", "accepts_marketing": false, "created_at": "2021-09-19T08:31:05-07:00", "updated_at": "2021-09-19T09:08:24-07:00", "first_name": null, "last_name": null, "orders_count": 92, "state": "disabled", "total_spent": "2484.00", "last_order_id": 4147980107965, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1121", "currency": "USD", "accepts_marketing_updated_at": "2021-09-19T08:31:05-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5565161144509"}, "discount_applications": [], "fulfillments": [{"id": 3693416710333, "admin_graphql_api_id": "gid://shopify/Fulfillment/3693416710333", "created_at": "2021-09-19T09:08:23-07:00", "location_id": 63590301885, "name": "#1121.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics US", "tracking_number": "123456", "tracking_numbers": ["123456"], "tracking_url": "https://track.amazon.com/tracking/123456", "tracking_urls": ["https://track.amazon.com/tracking/123456"], "updated_at": "2022-02-22T00:35:47-08:00", "line_items": [{"id": 10576771317949, "admin_graphql_api_id": "gid://shopify/LineItem/10576771317949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10576771317949, "admin_graphql_api_id": "gid://shopify/LineItem/10576771317949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796218302653, "title": "Red & Silver Fishing Lure", "handle": "red-silver-fishing-lure", "vendor": "Harris - Hamill", "tags": "developer-tools-generator", "body_html": "Half red, half silver fishing hook.", "product_type": "Industrial", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure.jpg?v=1624410569", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/red-silver-fishing-lure_x240.jpg?v=1624410569", "alt": null, "width": 3960, "height": 2640, "position": 1, "variant_ids": [], "id": 29301295546557, "created_at": "2021-06-22T18:09:29-07:00", "updated_at": "2021-06-22T18:09:29-07:00"}], "variant": {"sku": 40090580615357, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_terms": null, "refunds": [{"id": 845032358077, "admin_graphql_api_id": "gid://shopify/Refund/845032358077", "created_at": "2022-03-07T02:09:04-08:00", "note": null, "processed_at": "2022-03-07T02:09:04-08:00", "restock": true, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [], "transactions": [], "refund_line_items": [{"id": 352716947645, "line_item_id": 10576771317949, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 27.0, "subtotal_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 10576771317949, "admin_graphql_api_id": "gid://shopify/LineItem/10576771317949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 285, "name": "Red & Silver Fishing Lure - Plastic", "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796218302653, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Red & Silver Fishing Lure", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090580615357, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "Harris - Hamill", "tax_lines": [], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_address": {"first_name": "John", "address1": "San Francisco", "phone": "", "city": "San Francisco", "zip": "91326", "province": "California", "country": "United States", "last_name": "Doe", "address2": "10", "company": "Umbrella LLC", "latitude": 34.2894584, "longitude": -118.5622893, "name": "John Doe", "country_code": "US", "province_code": "CA"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-09-19 16:09:13+00:00", "uuid": "ee2c9a80-1963-11ec-8001-9acbc58c77b2", "person": {"object": "person", "id": "01G4CDT20J9T35W95VMF9C92MP", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "", "$email": "airbyte@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte@airbyte.com", "first_name": "", "last_name": "", "created": "2022-05-31 06:45:48", "updated": "2022-05-31 06:46:04"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158945} -{"stream": "events", "data": {"object": "event", "id": "QJdLXxLWF8", "statistic_id": "Tp8t7d", "timestamp": 1637075122, "event_name": "Subscribed to List", "event_properties": {"List": "Test AAA", "$event_id": "S8nmQ91637075122"}, "datetime": "2021-11-16 15:05:22+00:00", "uuid": "9eade500-46ee-11ec-8001-b5e21f4bedde", "person": {"object": "person", "id": "01FMMMFXPH5EY18XPVNPFVVM9A", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Eugene", "$title": "demon", "$phone_number": "", "$organization": "CIA", "$last_name": "K", "$email": "test@mail.com", "$timezone": "", "$id": "", "$source": -6, "email": "test@mail.com", "first_name": "Eugene", "last_name": "K", "created": "2021-11-16 15:05:22", "updated": "2021-11-16 15:05:22"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158946} -{"stream": "events", "data": {"object": "event", "id": "QJ9X6KN9R6", "statistic_id": "Tp8t7d", "timestamp": 1637075189, "event_name": "Subscribed to List", "event_properties": {"List": "Test5", "$event_id": "X7UeXn1637075189"}, "datetime": "2021-11-16 15:06:29+00:00", "uuid": "c69d4880-46ee-11ec-8001-c45d3d688bd7", "person": {"object": "person", "id": "01FMMMHZJXRDKNVT951M0J2S8C", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Eugene", "$title": "angel", "$phone_number": "", "$organization": "CIA", "$last_name": "K2", "$email": "mail@mail.com", "$timezone": "", "$id": "", "$source": -6, "email": "mail@mail.com", "first_name": "Eugene", "last_name": "K2", "created": "2021-11-16 15:06:29", "updated": "2021-11-16 15:06:29"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158946} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQu79y", "statistic_id": "R2WpFy", "timestamp": 1638939278, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3945529802941", "$value": 2279.9, "$extra": {"id": 3945529802941, "admin_graphql_api_id": "gid://shopify/Order/3945529802941", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:38-07:00", "currency": "USD", "current_subtotal_price": "1808.00", "current_subtotal_price_set": {"shop_money": {"amount": "1808.00", "currency_code": "USD"}, "presentment_money": {"amount": "1808.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "1866.40", "current_total_price_set": {"shop_money": {"amount": "1866.40", "currency_code": "USD"}, "presentment_money": {"amount": "1866.40", "currency_code": "USD"}}, "current_total_tax": "58.40", "current_total_tax_set": {"shop_money": {"amount": "58.40", "currency_code": "USD"}, "presentment_money": {"amount": "58.40", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1025", "note": null, "note_attributes": [], "number": 25, "order_number": 1025, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b8031f8453a0db6300e60109c56efcd1/authenticate?key=b1d625a28779e1a460613528f6f75d0f", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:38-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "b8031f8453a0db6300e60109c56efcd1", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-07T20:54:38-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837356733, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837356733", "created_at": "2021-07-08T02:42:38-07:00", "location_id": 63590301885, "name": "#1025.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-08T02:42:38-07:00", "line_items": [{"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 837898993853, "admin_graphql_api_id": "gid://shopify/Refund/837898993853", "created_at": "2021-12-07T20:54:37-08:00", "note": "Test order updated_at", "processed_at": "2021-12-07T20:54:37-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 196045734077, "amount": "465.60", "amount_set": {"shop_money": {"amount": "465.60", "currency_code": "USD"}, "presentment_money": {"amount": "465.60", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 837898993853, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5386423009469, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5386423009469", "amount": "1.00", "authorization": null, "created_at": "2021-12-07T20:54:37-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431869117, "processed_at": "2021-12-07T20:54:37-08:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 344055906493, "line_item_id": 10150322045117, "location_id": null, "quantity": 2, "restock_type": "no_restock", "subtotal": 452.0, "subtotal_set": {"shop_money": {"amount": "452.00", "currency_code": "USD"}, "presentment_money": {"amount": "452.00", "currency_code": "USD"}}, "total_tax": 14.6, "total_tax_set": {"shop_money": {"amount": "14.60", "currency_code": "USD"}, "presentment_money": {"amount": "14.60", "currency_code": "USD"}}, "line_item": {"id": 10150322045117, "admin_graphql_api_id": "gid://shopify/LineItem/10150322045117", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-12-08 04:54:38+00:00", "uuid": "f23dbb00-57e2-11ec-8001-57fe212ed2bc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158947} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzG2rzz", "statistic_id": "R2WpFy", "timestamp": 1639052034, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 482"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "HasPartialFulfillments": false, "$event_id": "3944277278909", "$value": 254.9, "$extra": {"id": 3944277278909, "admin_graphql_api_id": "gid://shopify/Order/3944277278909", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": "2021-07-07T08:54:42-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-07T08:54:41-07:00", "currency": "USD", "current_subtotal_price": "94.00", "current_subtotal_price_set": {"shop_money": {"amount": "94.00", "currency_code": "USD"}, "presentment_money": {"amount": "94.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "103.60", "current_total_price_set": {"shop_money": {"amount": "103.60", "currency_code": "USD"}, "presentment_money": {"amount": "103.60", "currency_code": "USD"}}, "current_total_tax": "9.60", "current_total_tax_set": {"shop_money": {"amount": "9.60", "currency_code": "USD"}, "presentment_money": {"amount": "9.60", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1018", "note": null, "note_attributes": [], "number": 18, "order_number": 1018, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/5c1166ff15a92366fd980ae495457f0f/authenticate?key=7b9c6edf066aa33378eb23bb93308ff4", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-07T08:54:41-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "235.00", "subtotal_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "24.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "5c1166ff15a92366fd980ae495457f0f", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "235.00", "total_line_items_price_set": {"shop_money": {"amount": "235.00", "currency_code": "USD"}, "presentment_money": {"amount": "235.00", "currency_code": "USD"}}, "total_outstanding": "234.00", "total_price": "254.90", "total_price_set": {"shop_money": {"amount": "254.90", "currency_code": "USD"}, "presentment_money": {"amount": "254.90", "currency_code": "USD"}}, "total_price_usd": "254.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2021-12-09T04:13:54-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3505568678077, "admin_graphql_api_id": "gid://shopify/Fulfillment/3505568678077", "created_at": "2021-07-07T08:54:41-07:00", "location_id": 63590301885, "name": "#1018.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2021-07-07T08:54:41-07:00", "line_items": [{"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": 47.0, "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 235.0}], "payment_terms": null, "refunds": [{"id": 838038356157, "admin_graphql_api_id": "gid://shopify/Refund/838038356157", "created_at": "2021-12-09T04:13:54-08:00", "note": "Reproducing issue", "processed_at": "2021-12-09T04:13:54-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 196150362301, "amount": "130.40", "amount_set": {"shop_money": {"amount": "130.40", "currency_code": "USD"}, "presentment_money": {"amount": "130.40", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 838038356157, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5389681295549, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5389681295549", "amount": "25.00", "authorization": null, "created_at": "2021-12-09T04:13:54-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 25.00 from manual gateway", "parent_id": 4944858644669, "processed_at": "2021-12-09T04:13:54-08:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 344218894525, "line_item_id": 10147937845437, "location_id": null, "quantity": 3, "restock_type": "no_restock", "subtotal": 141.0, "subtotal_set": {"shop_money": {"amount": "141.00", "currency_code": "USD"}, "presentment_money": {"amount": "141.00", "currency_code": "USD"}}, "total_tax": 14.4, "total_tax_set": {"shop_money": {"amount": "14.40", "currency_code": "USD"}, "presentment_money": {"amount": "14.40", "currency_code": "USD"}}, "line_item": {"id": 10147937845437, "admin_graphql_api_id": "gid://shopify/LineItem/10147937845437", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 482", "price": "47.00", "price_set": {"shop_money": {"amount": "47.00", "currency_code": "USD"}, "presentment_money": {"amount": "47.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 5, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 482", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "24.00", "price_set": {"shop_money": {"amount": "24.00", "currency_code": "USD"}, "presentment_money": {"amount": "24.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2021-12-09 12:13:54+00:00", "uuid": "7a0ded00-58e9-11ec-8001-899e5dbc91f9", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158948} -{"stream": "events", "data": {"object": "event", "id": "3mjEbnWc4zQ", "statistic_id": "R2WpFy", "timestamp": 1645520713, "event_name": "Refunded Order", "event_properties": {"Items": ["Test Order 196"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "5505221", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 5496, "HasPartialFulfillments": false, "$event_id": "3945529835709", "$value": 2279.9, "$extra": {"id": 3945529835709, "admin_graphql_api_id": "gid://shopify/Order/3945529835709", "app_id": 5505221, "browser_ip": null, "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": null, "checkout_token": null, "closed_at": null, "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2021-07-08T02:42:40-07:00", "currency": "USD", "current_subtotal_price": "1808.00", "current_subtotal_price_set": {"shop_money": {"amount": "1808.00", "currency_code": "USD"}, "presentment_money": {"amount": "1808.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "1866.40", "current_total_price_set": {"shop_money": {"amount": "1866.40", "currency_code": "USD"}, "presentment_money": {"amount": "1866.40", "currency_code": "USD"}}, "current_total_tax": "58.40", "current_total_tax_set": {"shop_money": {"amount": "58.40", "currency_code": "USD"}, "presentment_money": {"amount": "58.40", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1026", "note": null, "note_attributes": [], "number": 26, "order_number": 1026, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/fe9363a19b4720b42d66383d180c27f9/authenticate?key=7af240470595f35f311fc68fa69af05c", "original_total_duties_set": null, "payment_gateway_names": [""], "phone": null, "presentment_currency": "USD", "processed_at": "2021-07-08T02:42:40-07:00", "processing_method": "", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "5505221", "source_url": null, "subtotal_price": "2260.00", "subtotal_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "73.00", "rate": 0.06, "title": "State tax", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "channel_liable": null}], "taxes_included": false, "test": false, "token": "fe9363a19b4720b42d66383d180c27f9", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "2260.00", "total_line_items_price_set": {"shop_money": {"amount": "2260.00", "currency_code": "USD"}, "presentment_money": {"amount": "2260.00", "currency_code": "USD"}}, "total_outstanding": "2332.00", "total_price": "2279.90", "total_price_set": {"shop_money": {"amount": "2279.90", "currency_code": "USD"}, "presentment_money": {"amount": "2279.90", "currency_code": "USD"}}, "total_price_usd": "2279.90", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "19.90", "total_tax_set": {"shop_money": {"amount": "19.90", "currency_code": "USD"}, "presentment_money": {"amount": "19.90", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-02-22T01:05:13-08:00", "user_id": null, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3506837422269, "admin_graphql_api_id": "gid://shopify/Fulfillment/3506837422269", "created_at": "2021-07-08T02:42:40-07:00", "location_id": 63590301885, "name": "#1026.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics UK", "tracking_number": "1243557", "tracking_numbers": ["1243557"], "tracking_url": "https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530", "tracking_urls": ["https://www.amazon.co.uk/gp/help/customer/display.html?nodeId=201910530"], "updated_at": "2022-02-22T01:04:24-08:00", "line_items": [{"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": 226.0, "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10.0, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": [], "line_price": 2260.0}], "payment_terms": null, "refunds": [{"id": 812618088637, "admin_graphql_api_id": "gid://shopify/Refund/812618088637", "created_at": "2021-07-19T06:40:57-07:00", "note": "Test Refund", "processed_at": "2021-07-19T06:40:57-07:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 180577435837, "amount": "465.60", "amount_set": {"shop_money": {"amount": "465.60", "currency_code": "USD"}, "presentment_money": {"amount": "465.60", "currency_code": "USD"}}, "kind": "refund_discrepancy", "reason": "Refund discrepancy", "refund_id": 812618088637, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 4974344470717, "admin_graphql_api_id": "gid://shopify/OrderTransaction/4974344470717", "amount": "1.00", "authorization": null, "created_at": "2021-07-19T06:40:56-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "", "kind": "refund", "location_id": null, "message": "Refunded 1.00 from manual gateway", "parent_id": 4946431934653, "processed_at": "2021-07-19T06:40:56-07:00", "receipt": {}, "source_name": "1830279", "status": "success", "test": false, "user_id": 74861019325}], "refund_line_items": [{"id": 305405591741, "line_item_id": 10150322077885, "location_id": null, "quantity": 2, "restock_type": "no_restock", "subtotal": 452.0, "subtotal_set": {"shop_money": {"amount": "452.00", "currency_code": "USD"}, "presentment_money": {"amount": "452.00", "currency_code": "USD"}}, "total_tax": 14.6, "total_tax_set": {"shop_money": {"amount": "14.60", "currency_code": "USD"}, "presentment_money": {"amount": "14.60", "currency_code": "USD"}}, "line_item": {"id": 10150322077885, "admin_graphql_api_id": "gid://shopify/LineItem/10150322077885", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 200, "name": "Test Order 196", "price": "226.00", "price_set": {"shop_money": {"amount": "226.00", "currency_code": "USD"}, "presentment_money": {"amount": "226.00", "currency_code": "USD"}}, "product_exists": false, "product_id": null, "properties": [], "quantity": 10, "requires_shipping": true, "sku": null, "taxable": true, "title": "Test Order 196", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": null, "variant_inventory_management": null, "variant_title": null, "vendor": null, "tax_lines": [{"channel_liable": null, "price": "73.00", "price_set": {"shop_money": {"amount": "73.00", "currency_code": "USD"}, "presentment_money": {"amount": "73.00", "currency_code": "USD"}}, "rate": 0.06, "title": "State tax"}], "duties": [], "discount_allocations": []}}], "duties": []}], "shipping_address": {"first_name": "Airbyte", "address1": "san ", "phone": "", "city": "San Francisco", "zip": "91326", "province": "California", "country": "United States", "last_name": "Test", "address2": "2123", "company": "Airbyte", "latitude": 34.2928607, "longitude": -118.5703644, "name": "Airbyte Test", "country_code": "US", "province_code": "CA"}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2022-02-22 09:05:13+00:00", "uuid": "8b31aa80-93be-11ec-8001-c4ea1566bebd", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158949} -{"stream": "events", "data": {"object": "event", "id": "3mjwdMckjwp", "statistic_id": "SPnhc3", "timestamp": 1646034267, "event_name": "Checkout Started", "event_properties": {"Items": ["Yoga Mat Rolled"], "Collections": ["Home page"], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "2.61", "Source Name": "web", "$currency_code": "USD", "$event_id": "24010992844989", "$value": 102.83, "$extra": {"id": 4424598323389, "admin_graphql_api_id": "gid://shopify/Order/4424598323389", "app_id": 580111, "browser_ip": "82.193.127.107", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "1f9be99a6262001194ba66fc88a4a7e8", "checkout_id": 24010992844989, "checkout_token": "d0d77045b85950ed3f9788c96f001a11", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "82.193.127.107", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56"}, "closed_at": "2022-03-07T02:10:16-08:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-02-27T23:44:28-08:00", "currency": "USD", "current_subtotal_price": "84.39", "current_subtotal_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "current_total_discounts": "2.61", "current_total_discounts_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "84.39", "current_total_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "partially_refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1124", "note": null, "note_attributes": [], "number": 124, "order_number": 1124, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4b68c4eab10f7b535eacf55987610fe/authenticate?key=cfaf5ff2cab0707cd3cfed007975d708", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-02-27T23:44:27-08:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "84.39", "subtotal_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "d0d77045b85950ed3f9788c96f001a11", "total_discounts": "2.61", "total_discounts_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "total_line_items_price": "87.00", "total_line_items_price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "102.83", "total_price_set": {"shop_money": {"amount": "102.83", "currency_code": "USD"}, "presentment_money": {"amount": "102.83", "currency_code": "USD"}}, "total_price_usd": "102.83", "total_shipping_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 274, "updated_at": "2022-03-07T02:10:16-08:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-02-27T23:47:05-08:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7269786484925, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "San", "address2": "10", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94101", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 3953692311741, "admin_graphql_api_id": "gid://shopify/Fulfillment/3953692311741", "created_at": "2022-02-27T23:44:29-08:00", "location_id": 63590301885, "name": "#1124.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "TNT", "tracking_number": "36457736", "tracking_numbers": ["36457736"], "tracking_url": "https://www.tnt.com/express/en_us/site/tracking.html?searchType=con&cons=36457736", "tracking_urls": ["https://www.tnt.com/express/en_us/site/tracking.html?searchType=con&cons=36457736"], "updated_at": "2022-02-27T23:55:29-08:00", "line_items": [{"id": 11153523081405, "admin_graphql_api_id": "gid://shopify/LineItem/11153523081405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 274, "name": "Yoga Mat Rolled - Plastic", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "87.00", "price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796217811133, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Yoga Mat Rolled", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090579927229, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "O'Reilly - Grady", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.61", "amount_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11153523081405, "admin_graphql_api_id": "gid://shopify/LineItem/11153523081405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 274, "name": "Yoga Mat Rolled - Plastic", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 87.0, "price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796217811133, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Yoga Mat Rolled", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090579927229, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "O'Reilly - Grady", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.61", "amount_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 87.0, "product": {"id": 6796217811133, "title": "Yoga Mat Rolled", "handle": "yoga-mat-rolled", "vendor": "O'Reilly - Grady", "tags": "developer-tools-generator", "body_html": "Rolled up teal yoga mat on its side.", "product_type": "Shoes", "properties": {}, "images": [], "variant": {"sku": 40090579927229, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 845032423613, "admin_graphql_api_id": "gid://shopify/Refund/845032423613", "created_at": "2022-03-07T02:10:15-08:00", "note": "test", "processed_at": "2022-03-07T02:10:15-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 200933408957, "amount": "-18.44", "amount_set": {"shop_money": {"amount": "-18.44", "currency_code": "USD"}, "presentment_money": {"amount": "-18.44", "currency_code": "USD"}}, "kind": "shipping_refund", "reason": "Shipping refund", "refund_id": 845032423613, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5548849955005, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5548849955005", "amount": "18.44", "authorization": null, "created_at": "2022-03-07T02:10:15-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "parent_id": 5535956828349, "processed_at": "2022-03-07T02:10:15-08:00", "receipt": {"paid_amount": "18.44"}, "source_name": "1830279", "status": "success", "test": true, "user_id": 74861019325, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [], "duties": []}], "shipping_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3772225159357, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.44", "discounted_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "phone": null, "price": "18.44", "price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "http://airbyte-integration-test.myshopify.com/password", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4b68c4eab10f7b535eacf55987610fe/authenticate?key=cfaf5ff2cab0707cd3cfed007975d708", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4b68c4eab10f7b535eacf55987610fe/authenticate?key=cfaf5ff2cab0707cd3cfed007975d708"}}, "datetime": "2022-02-28 07:44:27+00:00", "uuid": "413b3f80-986a-11ec-8001-be61fbb67efb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158950} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQyGT", "statistic_id": "TspjNE", "timestamp": 1646034287, "event_name": "Placed Order", "event_properties": {"Items": ["Yoga Mat Rolled"], "Collections": ["Home page"], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "2.61", "Source Name": "web", "$currency_code": "USD", "$event_id": "4424598323389", "$value": 102.83, "$extra": {"id": 4424598323389, "admin_graphql_api_id": "gid://shopify/Order/4424598323389", "app_id": 580111, "browser_ip": "82.193.127.107", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "1f9be99a6262001194ba66fc88a4a7e8", "checkout_id": 24010992844989, "checkout_token": "d0d77045b85950ed3f9788c96f001a11", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "82.193.127.107", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56"}, "closed_at": "2022-03-07T02:10:16-08:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-02-27T23:44:28-08:00", "currency": "USD", "current_subtotal_price": "84.39", "current_subtotal_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "current_total_discounts": "2.61", "current_total_discounts_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "84.39", "current_total_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "partially_refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1124", "note": null, "note_attributes": [], "number": 124, "order_number": 1124, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4b68c4eab10f7b535eacf55987610fe/authenticate?key=cfaf5ff2cab0707cd3cfed007975d708", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-02-27T23:44:27-08:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "84.39", "subtotal_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "f4b68c4eab10f7b535eacf55987610fe", "total_discounts": "2.61", "total_discounts_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "total_line_items_price": "87.00", "total_line_items_price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "102.83", "total_price_set": {"shop_money": {"amount": "102.83", "currency_code": "USD"}, "presentment_money": {"amount": "102.83", "currency_code": "USD"}}, "total_price_usd": "102.83", "total_shipping_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 274, "updated_at": "2022-03-07T02:10:16-08:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-02-27T23:47:05-08:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7269786484925, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "San", "address2": "10", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94101", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 3953692311741, "admin_graphql_api_id": "gid://shopify/Fulfillment/3953692311741", "created_at": "2022-02-27T23:44:29-08:00", "location_id": 63590301885, "name": "#1124.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "TNT", "tracking_number": "36457736", "tracking_numbers": ["36457736"], "tracking_url": "https://www.tnt.com/express/en_us/site/tracking.html?searchType=con&cons=36457736", "tracking_urls": ["https://www.tnt.com/express/en_us/site/tracking.html?searchType=con&cons=36457736"], "updated_at": "2022-02-27T23:55:29-08:00", "line_items": [{"id": 11153523081405, "admin_graphql_api_id": "gid://shopify/LineItem/11153523081405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 274, "name": "Yoga Mat Rolled - Plastic", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "87.00", "price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796217811133, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Yoga Mat Rolled", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090579927229, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "O'Reilly - Grady", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.61", "amount_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11153523081405, "admin_graphql_api_id": "gid://shopify/LineItem/11153523081405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 274, "name": "Yoga Mat Rolled - Plastic", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 87.0, "price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796217811133, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Yoga Mat Rolled", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090579927229, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "O'Reilly - Grady", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.61", "amount_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 87.0, "product": {"id": 6796217811133, "title": "Yoga Mat Rolled", "handle": "yoga-mat-rolled", "vendor": "O'Reilly - Grady", "tags": "developer-tools-generator", "body_html": "Rolled up teal yoga mat on its side.", "product_type": "Shoes", "properties": {}, "images": [], "variant": {"sku": 40090579927229, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 845032423613, "admin_graphql_api_id": "gid://shopify/Refund/845032423613", "created_at": "2022-03-07T02:10:15-08:00", "note": "test", "processed_at": "2022-03-07T02:10:15-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 200933408957, "amount": "-18.44", "amount_set": {"shop_money": {"amount": "-18.44", "currency_code": "USD"}, "presentment_money": {"amount": "-18.44", "currency_code": "USD"}}, "kind": "shipping_refund", "reason": "Shipping refund", "refund_id": 845032423613, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5548849955005, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5548849955005", "amount": "18.44", "authorization": null, "created_at": "2022-03-07T02:10:15-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "parent_id": 5535956828349, "processed_at": "2022-03-07T02:10:15-08:00", "receipt": {"paid_amount": "18.44"}, "source_name": "1830279", "status": "success", "test": true, "user_id": 74861019325, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [], "duties": []}], "shipping_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3772225159357, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.44", "discounted_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "phone": null, "price": "18.44", "price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "http://airbyte-integration-test.myshopify.com/password"}}, "datetime": "2022-02-28 07:44:47+00:00", "uuid": "4d270180-986a-11ec-8001-a3b7134e13d2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158951} -{"stream": "events", "data": {"object": "event", "id": "3mjz64Xbmt5", "statistic_id": "RDXsib", "timestamp": 1646034297, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796217811133, "Name": "Yoga Mat Rolled", "Variant Name": "Plastic", "SKU": "", "Collections": ["Home page"], "Tags": ["developer-tools-generator"], "Vendor": "O'Reilly - Grady", "Variant Option: Title": "Plastic", "Quantity": 1, "$event_id": "4424598323389:11153523081405:0", "$value": 87.0}, "datetime": "2022-02-28 07:44:57+00:00", "uuid": "531ce280-986a-11ec-8001-8cd41852d7d0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158952} -{"stream": "events", "data": {"object": "event", "id": "3mjDRzG2AuW", "statistic_id": "X3f6PC", "timestamp": 1646034319, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Yoga Mat Rolled"], "Collections": ["Home page"], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "2.61", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4424598323389", "$value": 102.83, "$extra": {"id": 4424598323389, "admin_graphql_api_id": "gid://shopify/Order/4424598323389", "app_id": 580111, "browser_ip": "82.193.127.107", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "1f9be99a6262001194ba66fc88a4a7e8", "checkout_id": 24010992844989, "checkout_token": "d0d77045b85950ed3f9788c96f001a11", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "82.193.127.107", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56"}, "closed_at": "2022-03-07T02:10:16-08:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-02-27T23:44:28-08:00", "currency": "USD", "current_subtotal_price": "84.39", "current_subtotal_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "current_total_discounts": "2.61", "current_total_discounts_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "84.39", "current_total_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "partially_refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1124", "note": null, "note_attributes": [], "number": 124, "order_number": 1124, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/f4b68c4eab10f7b535eacf55987610fe/authenticate?key=cfaf5ff2cab0707cd3cfed007975d708", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-02-27T23:44:27-08:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "84.39", "subtotal_price_set": {"shop_money": {"amount": "84.39", "currency_code": "USD"}, "presentment_money": {"amount": "84.39", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "f4b68c4eab10f7b535eacf55987610fe", "total_discounts": "2.61", "total_discounts_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "total_line_items_price": "87.00", "total_line_items_price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "102.83", "total_price_set": {"shop_money": {"amount": "102.83", "currency_code": "USD"}, "presentment_money": {"amount": "102.83", "currency_code": "USD"}}, "total_price_usd": "102.83", "total_shipping_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 274, "updated_at": "2022-03-07T02:10:16-08:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-02-27T23:47:05-08:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7269786484925, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "San", "address2": "10", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94101", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 3953692311741, "admin_graphql_api_id": "gid://shopify/Fulfillment/3953692311741", "created_at": "2022-02-27T23:44:29-08:00", "location_id": 63590301885, "name": "#1124.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "TNT", "tracking_number": "36457736", "tracking_numbers": ["36457736"], "tracking_url": "https://www.tnt.com/express/en_us/site/tracking.html?searchType=con&cons=36457736", "tracking_urls": ["https://www.tnt.com/express/en_us/site/tracking.html?searchType=con&cons=36457736"], "updated_at": "2022-02-27T23:55:29-08:00", "line_items": [{"id": 11153523081405, "admin_graphql_api_id": "gid://shopify/LineItem/11153523081405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 274, "name": "Yoga Mat Rolled - Plastic", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "87.00", "price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796217811133, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Yoga Mat Rolled", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090579927229, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "O'Reilly - Grady", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.61", "amount_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11153523081405, "admin_graphql_api_id": "gid://shopify/LineItem/11153523081405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 274, "name": "Yoga Mat Rolled - Plastic", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 87.0, "price_set": {"shop_money": {"amount": "87.00", "currency_code": "USD"}, "presentment_money": {"amount": "87.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796217811133, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Yoga Mat Rolled", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090579927229, "variant_inventory_management": "shopify", "variant_title": "Plastic", "vendor": "O'Reilly - Grady", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.61", "amount_set": {"shop_money": {"amount": "2.61", "currency_code": "USD"}, "presentment_money": {"amount": "2.61", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 87.0, "product": {"id": 6796217811133, "title": "Yoga Mat Rolled", "handle": "yoga-mat-rolled", "vendor": "O'Reilly - Grady", "tags": "developer-tools-generator", "body_html": "Rolled up teal yoga mat on its side.", "product_type": "Shoes", "properties": {}, "images": [], "variant": {"sku": 40090579927229, "title": "Plastic", "options": {"Title": "Plastic"}, "images": []}, "variant_options": {"Title": "Plastic"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 845032423613, "admin_graphql_api_id": "gid://shopify/Refund/845032423613", "created_at": "2022-03-07T02:10:15-08:00", "note": "test", "processed_at": "2022-03-07T02:10:15-08:00", "restock": false, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [{"id": 200933408957, "amount": "-18.44", "amount_set": {"shop_money": {"amount": "-18.44", "currency_code": "USD"}, "presentment_money": {"amount": "-18.44", "currency_code": "USD"}}, "kind": "shipping_refund", "reason": "Shipping refund", "refund_id": 845032423613, "tax_amount": "0.00", "tax_amount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}}], "transactions": [{"id": 5548849955005, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5548849955005", "amount": "18.44", "authorization": null, "created_at": "2022-03-07T02:10:15-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "parent_id": 5535956828349, "processed_at": "2022-03-07T02:10:15-08:00", "receipt": {"paid_amount": "18.44"}, "source_name": "1830279", "status": "success", "test": true, "user_id": 74861019325, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [], "duties": []}], "shipping_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3772225159357, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.44", "discounted_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "phone": null, "price": "18.44", "price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "http://airbyte-integration-test.myshopify.com/password"}}, "datetime": "2022-02-28 07:45:19+00:00", "uuid": "6039d180-986a-11ec-8001-bd24e49840ed", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158953} -{"stream": "events", "data": {"object": "event", "id": "3mjwdLdKbVd", "statistic_id": "SPnhc3", "timestamp": 1646034424, "event_name": "Checkout Started", "event_properties": {"Items": ["Amber Beard Oil Bottle", "Anchor Bracelet Leather", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "7.65", "Source Name": "web", "$currency_code": "USD", "$event_id": "24080825876669", "$value": 265.79, "$extra": {"id": 4424599437501, "admin_graphql_api_id": "gid://shopify/Order/4424599437501", "app_id": 580111, "browser_ip": "82.193.127.107", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "671799f2f200f0f26fa8d32d511e03e9", "checkout_id": 24080825876669, "checkout_token": "8170ff81aa1f423a76c99de6c0e52303", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "82.193.127.107", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56"}, "closed_at": "2022-03-03T03:47:46-08:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-02-27T23:47:05-08:00", "currency": "USD", "current_subtotal_price": "101.85", "current_subtotal_price_set": {"shop_money": {"amount": "101.85", "currency_code": "USD"}, "presentment_money": {"amount": "101.85", "currency_code": "USD"}}, "current_total_discounts": "3.15", "current_total_discounts_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "120.29", "current_total_price_set": {"shop_money": {"amount": "120.29", "currency_code": "USD"}, "presentment_money": {"amount": "120.29", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "partially_refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1125", "note": null, "note_attributes": [], "number": 125, "order_number": 1125, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/691ecaf01a2efdfa5daae57d79184642/authenticate?key=ae33e2525a84167c6e8da7718d93a8f6", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-02-27T23:47:04-08:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "247.35", "subtotal_price_set": {"shop_money": {"amount": "247.35", "currency_code": "USD"}, "presentment_money": {"amount": "247.35", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "8170ff81aa1f423a76c99de6c0e52303", "total_discounts": "7.65", "total_discounts_set": {"shop_money": {"amount": "7.65", "currency_code": "USD"}, "presentment_money": {"amount": "7.65", "currency_code": "USD"}}, "total_line_items_price": "255.00", "total_line_items_price_set": {"shop_money": {"amount": "255.00", "currency_code": "USD"}, "presentment_money": {"amount": "255.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "265.79", "total_price_set": {"shop_money": {"amount": "265.79", "currency_code": "USD"}, "presentment_money": {"amount": "265.79", "currency_code": "USD"}}, "total_price_usd": "265.79", "total_shipping_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 660, "updated_at": "2022-03-03T03:47:46-08:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-02-27T23:47:05-08:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7269786484925, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "San", "address2": "10", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94101", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 3953697358013, "admin_graphql_api_id": "gid://shopify/Fulfillment/3953697358013", "created_at": "2022-02-27T23:47:07-08:00", "location_id": 63590301885, "name": "#1125.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics US", "tracking_number": "12345", "tracking_numbers": ["12345"], "tracking_url": "https://track.amazon.com/tracking/12345", "tracking_urls": ["https://track.amazon.com/tracking/12345"], "updated_at": "2022-02-27T23:49:13-08:00", "line_items": [{"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11153525309629, "admin_graphql_api_id": "gid://shopify/LineItem/11153525309629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 3, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.15", "amount_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 37.0, "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 37.0, "product": {"id": 6796229574845, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle", "vendor": "Lubowitz, Buckridge and Huels", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Outdoors", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_x240.jpg?v=1624410648", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301303410877, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604110013, "title": "Cotton", "options": {"Title": "Cotton"}, "images": []}, "variant_options": {"Title": "Cotton"}}}, {"id": 11153525309629, "admin_graphql_api_id": "gid://shopify/LineItem/11153525309629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.15", "amount_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 105.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}, {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 844778471613, "admin_graphql_api_id": "gid://shopify/Refund/844778471613", "created_at": "2022-03-03T03:47:46-08:00", "note": null, "processed_at": "2022-03-03T03:47:46-08:00", "restock": true, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [], "transactions": [{"id": 5541595152573, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5541595152573", "amount": "145.50", "authorization": null, "created_at": "2022-03-03T03:47:45-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "parent_id": 5535958532285, "processed_at": "2022-03-03T03:47:45-08:00", "receipt": {"paid_amount": "145.50"}, "source_name": "1830279", "status": "success", "test": true, "user_id": null, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [{"id": 352402899133, "line_item_id": 11153525276861, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 35.89, "subtotal_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}}, {"id": 352402931901, "line_item_id": 11153525342397, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 109.61, "subtotal_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}}], "duties": []}], "shipping_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3772226175165, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.44", "discounted_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "phone": null, "price": "18.44", "price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "http://airbyte-integration-test.myshopify.com/password", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/691ecaf01a2efdfa5daae57d79184642/authenticate?key=ae33e2525a84167c6e8da7718d93a8f6", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/691ecaf01a2efdfa5daae57d79184642/authenticate?key=ae33e2525a84167c6e8da7718d93a8f6"}}, "datetime": "2022-02-28 07:47:04+00:00", "uuid": "9ecf8c00-986a-11ec-8001-7957918790d8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158954} -{"stream": "events", "data": {"object": "event", "id": "3mjAFjqqpbw", "statistic_id": "TspjNE", "timestamp": 1646034444, "event_name": "Placed Order", "event_properties": {"Items": ["Amber Beard Oil Bottle", "Anchor Bracelet Leather", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "7.65", "Source Name": "web", "$currency_code": "USD", "$event_id": "4424599437501", "$value": 265.79, "$extra": {"id": 4424599437501, "admin_graphql_api_id": "gid://shopify/Order/4424599437501", "app_id": 580111, "browser_ip": "82.193.127.107", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "671799f2f200f0f26fa8d32d511e03e9", "checkout_id": 24080825876669, "checkout_token": "8170ff81aa1f423a76c99de6c0e52303", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "82.193.127.107", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56"}, "closed_at": "2022-03-03T03:47:46-08:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-02-27T23:47:05-08:00", "currency": "USD", "current_subtotal_price": "101.85", "current_subtotal_price_set": {"shop_money": {"amount": "101.85", "currency_code": "USD"}, "presentment_money": {"amount": "101.85", "currency_code": "USD"}}, "current_total_discounts": "3.15", "current_total_discounts_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "120.29", "current_total_price_set": {"shop_money": {"amount": "120.29", "currency_code": "USD"}, "presentment_money": {"amount": "120.29", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "partially_refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1125", "note": null, "note_attributes": [], "number": 125, "order_number": 1125, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/691ecaf01a2efdfa5daae57d79184642/authenticate?key=ae33e2525a84167c6e8da7718d93a8f6", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-02-27T23:47:04-08:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "247.35", "subtotal_price_set": {"shop_money": {"amount": "247.35", "currency_code": "USD"}, "presentment_money": {"amount": "247.35", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "691ecaf01a2efdfa5daae57d79184642", "total_discounts": "7.65", "total_discounts_set": {"shop_money": {"amount": "7.65", "currency_code": "USD"}, "presentment_money": {"amount": "7.65", "currency_code": "USD"}}, "total_line_items_price": "255.00", "total_line_items_price_set": {"shop_money": {"amount": "255.00", "currency_code": "USD"}, "presentment_money": {"amount": "255.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "265.79", "total_price_set": {"shop_money": {"amount": "265.79", "currency_code": "USD"}, "presentment_money": {"amount": "265.79", "currency_code": "USD"}}, "total_price_usd": "265.79", "total_shipping_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 660, "updated_at": "2022-03-03T03:47:46-08:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-02-27T23:47:05-08:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7269786484925, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "San", "address2": "10", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94101", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 3953697358013, "admin_graphql_api_id": "gid://shopify/Fulfillment/3953697358013", "created_at": "2022-02-27T23:47:07-08:00", "location_id": 63590301885, "name": "#1125.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics US", "tracking_number": "12345", "tracking_numbers": ["12345"], "tracking_url": "https://track.amazon.com/tracking/12345", "tracking_urls": ["https://track.amazon.com/tracking/12345"], "updated_at": "2022-02-27T23:49:13-08:00", "line_items": [{"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11153525309629, "admin_graphql_api_id": "gid://shopify/LineItem/11153525309629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 3, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.15", "amount_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 37.0, "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 37.0, "product": {"id": 6796229574845, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle", "vendor": "Lubowitz, Buckridge and Huels", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Outdoors", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_x240.jpg?v=1624410648", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301303410877, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604110013, "title": "Cotton", "options": {"Title": "Cotton"}, "images": []}, "variant_options": {"Title": "Cotton"}}}, {"id": 11153525309629, "admin_graphql_api_id": "gid://shopify/LineItem/11153525309629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.15", "amount_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 105.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}, {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 844778471613, "admin_graphql_api_id": "gid://shopify/Refund/844778471613", "created_at": "2022-03-03T03:47:46-08:00", "note": null, "processed_at": "2022-03-03T03:47:46-08:00", "restock": true, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [], "transactions": [{"id": 5541595152573, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5541595152573", "amount": "145.50", "authorization": null, "created_at": "2022-03-03T03:47:45-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "parent_id": 5535958532285, "processed_at": "2022-03-03T03:47:45-08:00", "receipt": {"paid_amount": "145.50"}, "source_name": "1830279", "status": "success", "test": true, "user_id": null, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [{"id": 352402899133, "line_item_id": 11153525276861, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 35.89, "subtotal_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}}, {"id": 352402931901, "line_item_id": 11153525342397, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 109.61, "subtotal_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}}], "duties": []}], "shipping_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3772226175165, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.44", "discounted_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "phone": null, "price": "18.44", "price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "http://airbyte-integration-test.myshopify.com/password"}}, "datetime": "2022-02-28 07:47:24+00:00", "uuid": "aabb4e00-986a-11ec-8001-0c2864fc9dae", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158957} -{"stream": "events", "data": {"object": "event", "id": "3mjAFknZwgp", "statistic_id": "RDXsib", "timestamp": 1646034454, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229443773, "Name": "Anchor Bracelet Leather", "Variant Name": "Concrete", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Kohler - Nolan", "Variant Option: Title": "Concrete", "Quantity": 1, "$event_id": "4424599437501:11153525309629:0", "$value": 35.0}, "datetime": "2022-02-28 07:47:34+00:00", "uuid": "b0b12f00-986a-11ec-8001-9221127732b6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158959} -{"stream": "events", "data": {"object": "event", "id": "3mjAFknZwgq", "statistic_id": "RDXsib", "timestamp": 1646034454, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229443773, "Name": "Anchor Bracelet Leather", "Variant Name": "Concrete", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Kohler - Nolan", "Variant Option: Title": "Concrete", "Quantity": 1, "$event_id": "4424599437501:11153525309629:1", "$value": 35.0}, "datetime": "2022-02-28 07:47:34+00:00", "uuid": "b0b12f00-986a-11ec-8001-7175dfee5a9a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158959} -{"stream": "events", "data": {"object": "event", "id": "3mjAFknZwgr", "statistic_id": "RDXsib", "timestamp": 1646034454, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229443773, "Name": "Anchor Bracelet Leather", "Variant Name": "Concrete", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Kohler - Nolan", "Variant Option: Title": "Concrete", "Quantity": 1, "$event_id": "4424599437501:11153525309629:2", "$value": 35.0}, "datetime": "2022-02-28 07:47:34+00:00", "uuid": "b0b12f00-986a-11ec-8001-5c5c77bec9b4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158959} -{"stream": "events", "data": {"object": "event", "id": "3mjBiXnD8rU", "statistic_id": "RDXsib", "timestamp": 1646034454, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229574845, "Name": "Amber Beard Oil Bottle", "Variant Name": "Cotton", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Lubowitz, Buckridge and Huels", "Variant Option: Title": "Cotton", "Quantity": 1, "$event_id": "4424599437501:11153525276861:0", "$value": 37.0}, "datetime": "2022-02-28 07:47:34+00:00", "uuid": "b0b12f00-986a-11ec-8001-2c123b9a9bc2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158960} -{"stream": "events", "data": {"object": "event", "id": "3mjCAhKzaNR", "statistic_id": "RDXsib", "timestamp": 1646034454, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "purple", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "purple", "Quantity": 1, "$event_id": "4424599437501:11153525342397:0", "$value": 113.0}, "datetime": "2022-02-28 07:47:34+00:00", "uuid": "b0b12f00-986a-11ec-8001-56a4204319f8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158960} -{"stream": "events", "data": {"object": "event", "id": "3mjDRtQu25B", "statistic_id": "X3f6PC", "timestamp": 1646034477, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Amber Beard Oil Bottle", "Anchor Bracelet Leather", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "7.65", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4424599437501", "$value": 265.79, "$extra": {"id": 4424599437501, "admin_graphql_api_id": "gid://shopify/Order/4424599437501", "app_id": 580111, "browser_ip": "82.193.127.107", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "671799f2f200f0f26fa8d32d511e03e9", "checkout_id": 24080825876669, "checkout_token": "8170ff81aa1f423a76c99de6c0e52303", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "82.193.127.107", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56"}, "closed_at": "2022-03-03T03:47:46-08:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-02-27T23:47:05-08:00", "currency": "USD", "current_subtotal_price": "101.85", "current_subtotal_price_set": {"shop_money": {"amount": "101.85", "currency_code": "USD"}, "presentment_money": {"amount": "101.85", "currency_code": "USD"}}, "current_total_discounts": "3.15", "current_total_discounts_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "120.29", "current_total_price_set": {"shop_money": {"amount": "120.29", "currency_code": "USD"}, "presentment_money": {"amount": "120.29", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "partially_refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1125", "note": null, "note_attributes": [], "number": 125, "order_number": 1125, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/691ecaf01a2efdfa5daae57d79184642/authenticate?key=ae33e2525a84167c6e8da7718d93a8f6", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-02-27T23:47:04-08:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "247.35", "subtotal_price_set": {"shop_money": {"amount": "247.35", "currency_code": "USD"}, "presentment_money": {"amount": "247.35", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "691ecaf01a2efdfa5daae57d79184642", "total_discounts": "7.65", "total_discounts_set": {"shop_money": {"amount": "7.65", "currency_code": "USD"}, "presentment_money": {"amount": "7.65", "currency_code": "USD"}}, "total_line_items_price": "255.00", "total_line_items_price_set": {"shop_money": {"amount": "255.00", "currency_code": "USD"}, "presentment_money": {"amount": "255.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "265.79", "total_price_set": {"shop_money": {"amount": "265.79", "currency_code": "USD"}, "presentment_money": {"amount": "265.79", "currency_code": "USD"}}, "total_price_usd": "265.79", "total_shipping_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 660, "updated_at": "2022-03-03T03:47:46-08:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-02-27T23:47:05-08:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7269786484925, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "San", "address2": "10", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94101", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 3953697358013, "admin_graphql_api_id": "gid://shopify/Fulfillment/3953697358013", "created_at": "2022-02-27T23:47:07-08:00", "location_id": 63590301885, "name": "#1125.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": "Amazon Logistics US", "tracking_number": "12345", "tracking_numbers": ["12345"], "tracking_url": "https://track.amazon.com/tracking/12345", "tracking_urls": ["https://track.amazon.com/tracking/12345"], "updated_at": "2022-02-27T23:49:13-08:00", "line_items": [{"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11153525309629, "admin_graphql_api_id": "gid://shopify/LineItem/11153525309629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 3, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.15", "amount_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 37.0, "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 37.0, "product": {"id": 6796229574845, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle", "vendor": "Lubowitz, Buckridge and Huels", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Outdoors", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_x240.jpg?v=1624410648", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301303410877, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604110013, "title": "Cotton", "options": {"Title": "Cotton"}, "images": []}, "variant_options": {"Title": "Cotton"}}}, {"id": 11153525309629, "admin_graphql_api_id": "gid://shopify/LineItem/11153525309629", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 3.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.15", "amount_set": {"shop_money": {"amount": "3.15", "currency_code": "USD"}, "presentment_money": {"amount": "3.15", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 105.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}, {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 844778471613, "admin_graphql_api_id": "gid://shopify/Refund/844778471613", "created_at": "2022-03-03T03:47:46-08:00", "note": null, "processed_at": "2022-03-03T03:47:46-08:00", "restock": true, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [], "transactions": [{"id": 5541595152573, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5541595152573", "amount": "145.50", "authorization": null, "created_at": "2022-03-03T03:47:45-08:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "parent_id": 5535958532285, "processed_at": "2022-03-03T03:47:45-08:00", "receipt": {"paid_amount": "145.50"}, "source_name": "1830279", "status": "success", "test": true, "user_id": null, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [{"id": 352402899133, "line_item_id": 11153525276861, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 35.89, "subtotal_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11153525276861, "admin_graphql_api_id": "gid://shopify/LineItem/11153525276861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}}, {"id": 352402931901, "line_item_id": 11153525342397, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 109.61, "subtotal_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11153525342397, "admin_graphql_api_id": "gid://shopify/LineItem/11153525342397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}}], "duties": []}], "shipping_address": {"first_name": "Iryna", "address1": "San", "phone": null, "city": "San Francisco", "zip": "94101", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "10", "company": null, "latitude": null, "longitude": null, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3772226175165, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.44", "discounted_price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "phone": null, "price": "18.44", "price_set": {"shop_money": {"amount": "18.44", "currency_code": "USD"}, "presentment_money": {"amount": "18.44", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "http://airbyte-integration-test.myshopify.com/password"}}, "datetime": "2022-02-28 07:47:57+00:00", "uuid": "be66b480-986a-11ec-8001-611e8a1bb5fc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158961} -{"stream": "events", "data": {"object": "event", "id": "3mjFsEpma3r", "statistic_id": "SPnhc3", "timestamp": 1647511214, "event_name": "Checkout Started", "event_properties": {"Items": ["4 Ounce Soy Candle"], "Collections": ["Home page"], "Item Count": 1, "tags": [], "ShippingRate": "custom", "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "24254363009213", "$value": 23.0, "$extra": {"id": 4446355226813, "admin_graphql_api_id": "gid://shopify/Order/4446355226813", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254363009213, "checkout_token": "ba187234cb2a5ba8b8d10e1dc026313a", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:00:17-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:00:15-07:00", "currency": "USD", "current_subtotal_price": "19.00", "current_subtotal_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "23.00", "current_total_price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1128", "note": null, "note_attributes": [], "number": 128, "order_number": 1128, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aa546a3df2a786a82821264f856a1200/authenticate?key=c02fc0d254c214aa2192459aefdeaaca", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:00:14-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "19.00", "subtotal_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "ba187234cb2a5ba8b8d10e1dc026313a", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "19.00", "total_line_items_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "23.00", "total_price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "total_price_usd": "23.00", "total_shipping_price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 112, "updated_at": "2022-03-17T03:00:17-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "San Francisco", "phone": null, "city": "San Francisco", "zip": "92345", "province": null, "country": null, "last_name": "Customer", "address2": "123", "company": "Test Company", "latitude": 34.3627106, "longitude": -117.2911248, "name": "Test Customer", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974519390397, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974519390397", "created_at": "2022-03-17T03:00:16-07:00", "location_id": 63590301885, "name": "#1128.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:00:16-07:00", "line_items": [{"id": 11196010823869, "admin_graphql_api_id": "gid://shopify/LineItem/11196010823869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "19.00", "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196010823869, "admin_graphql_api_id": "gid://shopify/LineItem/11196010823869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 19.0, "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 19.0, "product": {"id": 6796220989629, "title": "4 Ounce Soy Candle", "handle": "4-ounce-soy-candle", "vendor": "Hartmann Group", "tags": "developer-tools-generator", "body_html": "

    Small white soy candle in clear container on wooden table.

    \n

    Updated!

    ", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle.jpg?v=1624410587", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle_x240.jpg?v=1624410587", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301297316029, "created_at": "2021-06-22T18:09:47-07:00", "updated_at": "2021-06-22T18:09:47-07:00"}], "variant": {"sku": 40090585923773, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Test", "address1": "San Francisco", "phone": null, "city": "San Francisco", "zip": "92345", "province": null, "country": null, "last_name": "Customer", "address2": "123", "company": "Test Company", "latitude": 34.3627106, "longitude": -117.2911248, "name": "Test Customer", "country_code": null, "province_code": null}, "shipping_lines": [{"id": 3791179382973, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "4.00", "discounted_price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "phone": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Test Rate", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aa546a3df2a786a82821264f856a1200/authenticate?key=c02fc0d254c214aa2192459aefdeaaca", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aa546a3df2a786a82821264f856a1200/authenticate?key=c02fc0d254c214aa2192459aefdeaaca"}}, "datetime": "2022-03-17 10:00:14+00:00", "uuid": "0a3e8b00-a5d9-11ec-8001-d0203020c597", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158962} -{"stream": "events", "data": {"object": "event", "id": "3mjz69Q8gPC", "statistic_id": "TspjNE", "timestamp": 1647511234, "event_name": "Placed Order", "event_properties": {"Items": ["4 Ounce Soy Candle"], "Collections": ["Home page"], "Item Count": 1, "tags": [], "ShippingRate": "custom", "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "4446355226813", "$value": 23.0, "$extra": {"id": 4446355226813, "admin_graphql_api_id": "gid://shopify/Order/4446355226813", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254363009213, "checkout_token": "ba187234cb2a5ba8b8d10e1dc026313a", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:00:17-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:00:15-07:00", "currency": "USD", "current_subtotal_price": "19.00", "current_subtotal_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "23.00", "current_total_price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1128", "note": null, "note_attributes": [], "number": 128, "order_number": 1128, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aa546a3df2a786a82821264f856a1200/authenticate?key=c02fc0d254c214aa2192459aefdeaaca", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:00:14-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "19.00", "subtotal_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "aa546a3df2a786a82821264f856a1200", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "19.00", "total_line_items_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "23.00", "total_price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "total_price_usd": "23.00", "total_shipping_price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 112, "updated_at": "2022-03-17T03:00:17-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "San Francisco", "phone": null, "city": "San Francisco", "zip": "92345", "province": null, "country": null, "last_name": "Customer", "address2": "123", "company": "Test Company", "latitude": 34.3627106, "longitude": -117.2911248, "name": "Test Customer", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974519390397, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974519390397", "created_at": "2022-03-17T03:00:16-07:00", "location_id": 63590301885, "name": "#1128.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:00:16-07:00", "line_items": [{"id": 11196010823869, "admin_graphql_api_id": "gid://shopify/LineItem/11196010823869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "19.00", "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196010823869, "admin_graphql_api_id": "gid://shopify/LineItem/11196010823869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 19.0, "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 19.0, "product": {"id": 6796220989629, "title": "4 Ounce Soy Candle", "handle": "4-ounce-soy-candle", "vendor": "Hartmann Group", "tags": "developer-tools-generator", "body_html": "

    Small white soy candle in clear container on wooden table.

    \n

    Updated!

    ", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle.jpg?v=1624410587", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle_x240.jpg?v=1624410587", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301297316029, "created_at": "2021-06-22T18:09:47-07:00", "updated_at": "2021-06-22T18:09:47-07:00"}], "variant": {"sku": 40090585923773, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Test", "address1": "San Francisco", "phone": null, "city": "San Francisco", "zip": "92345", "province": null, "country": null, "last_name": "Customer", "address2": "123", "company": "Test Company", "latitude": 34.3627106, "longitude": -117.2911248, "name": "Test Customer", "country_code": null, "province_code": null}, "shipping_lines": [{"id": 3791179382973, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "4.00", "discounted_price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "phone": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Test Rate", "tax_lines": [], "discount_allocations": []}], "full_landing_site": ""}}, "datetime": "2022-03-17 10:00:34+00:00", "uuid": "162a4d00-a5d9-11ec-8001-7969a5b37686", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158963} -{"stream": "events", "data": {"object": "event", "id": "3mjBCJFCjCd", "statistic_id": "RDXsib", "timestamp": 1647511244, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796220989629, "Name": "4 Ounce Soy Candle", "Variant Name": "Metal", "SKU": "", "Collections": ["Home page"], "Tags": ["developer-tools-generator"], "Vendor": "Hartmann Group", "Variant Option: Title": "Metal", "Quantity": 1, "$event_id": "4446355226813:11196010823869:0", "$value": 19.0}, "datetime": "2022-03-17 10:00:44+00:00", "uuid": "1c202e00-a5d9-11ec-8001-45ffa509e1bb", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158964} -{"stream": "events", "data": {"object": "event", "id": "3mjAFe4aPBw", "statistic_id": "SPnhc3", "timestamp": 1647511245, "event_name": "Checkout Started", "event_properties": {"Items": ["Black & White Wrist Watches"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "24254368186557", "$value": 65.0, "$extra": {"id": 4446355882173, "admin_graphql_api_id": "gid://shopify/Order/4446355882173", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254368186557, "checkout_token": "f686bf436dec584badf309ea1e4c62b9", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:00:48-07:00", "confirmed": true, "contact_email": "jasmin.schiller@developer-tools.shopifyapps.com", "created_at": "2022-03-17T03:00:46-07:00", "currency": "USD", "current_subtotal_price": "65.00", "current_subtotal_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "65.00", "current_total_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1129", "note": null, "note_attributes": [], "number": 129, "order_number": 1129, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a87ec0d757303fb0ce8262e202bf8e2e/authenticate?key=440ae8a278cc5300b24b237b6431875c", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:00:45-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "65.00", "subtotal_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "f686bf436dec584badf309ea1e4c62b9", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "65.00", "total_line_items_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "65.00", "total_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "total_price_usd": "65.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 490, "updated_at": "2022-03-17T03:00:48-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Jasmin", "address1": "82644 Mayert Green", "phone": "522.452.8289 x71583", "city": "", "zip": "", "province": "Alberta", "country": "Canada", "last_name": "Schiller", "address2": "", "company": null, "latitude": 53.9332706, "longitude": -116.5765035, "name": "Jasmin Schiller", "country_code": "CA", "province_code": "AB"}, "customer": {"id": 5330782486717, "email": "jasmin.schiller@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:09-07:00", "updated_at": "2022-03-17T03:00:47-07:00", "first_name": "Jasmin", "last_name": "Schiller", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:09-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782486717", "default_address": {"id": 6564640293053, "customer_id": 5330782486717, "first_name": "Jasmin", "last_name": "Schiller", "company": null, "address1": "82644 Mayert Green", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "522.452.8289 x71583", "name": "Jasmin Schiller", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974519619773, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974519619773", "created_at": "2022-03-17T03:00:47-07:00", "location_id": 63590301885, "name": "#1129.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:00:47-07:00", "line_items": [{"id": 11196011544765, "admin_graphql_api_id": "gid://shopify/LineItem/11196011544765", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 490, "name": "Black & White Wrist Watches - Rubber", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "65.00", "price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796222791869, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black & White Wrist Watches", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090590019773, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Friesen LLC", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196011544765, "admin_graphql_api_id": "gid://shopify/LineItem/11196011544765", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 490, "name": "Black & White Wrist Watches - Rubber", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 65.0, "price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796222791869, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black & White Wrist Watches", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090590019773, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Friesen LLC", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 65.0, "product": {"id": 6796222791869, "title": "Black & White Wrist Watches", "handle": "black-white-wrist-watches", "vendor": "Friesen LLC", "tags": "developer-tools-generator", "body_html": "Black and white wrist watches with lined shadow across the faces.", "product_type": "Home", "properties": {}, "images": [], "variant": {"sku": 40090590019773, "title": "Rubber", "options": {"Title": "Rubber"}, "images": []}, "variant_options": {"Title": "Rubber"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Jasmin", "address1": "82644 Mayert Green", "phone": "522.452.8289 x71583", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Schiller", "address2": null, "company": null, "latitude": 56.130366, "longitude": -106.346771, "name": "Jasmin Schiller", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a87ec0d757303fb0ce8262e202bf8e2e/authenticate?key=440ae8a278cc5300b24b237b6431875c", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a87ec0d757303fb0ce8262e202bf8e2e/authenticate?key=440ae8a278cc5300b24b237b6431875c"}}, "datetime": "2022-03-17 10:00:45+00:00", "uuid": "1cb8c480-a5d9-11ec-8001-26019bed4e83", "person": {"object": "person", "id": "01G4CDT0819VNY12K2E7BGFE05", "$address1": "82644 Mayert Green", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Alberta", "$zip": "", "$first_name": "Jasmin", "$title": "", "$phone_number": "+15224528289", "$organization": "", "$last_name": "Schiller", "$email": "jasmin.schiller@developer-tools.shopifyapps.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "first_name": "Jasmin", "last_name": "Schiller", "created": "2022-05-31 06:45:46", "updated": "2022-05-31 06:45:47"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158965} -{"stream": "events", "data": {"object": "event", "id": "3mjAFdySbSL", "statistic_id": "TspjNE", "timestamp": 1647511265, "event_name": "Placed Order", "event_properties": {"Items": ["Black & White Wrist Watches"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "4446355882173", "$value": 65.0, "$extra": {"id": 4446355882173, "admin_graphql_api_id": "gid://shopify/Order/4446355882173", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254368186557, "checkout_token": "f686bf436dec584badf309ea1e4c62b9", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:00:48-07:00", "confirmed": true, "contact_email": "jasmin.schiller@developer-tools.shopifyapps.com", "created_at": "2022-03-17T03:00:46-07:00", "currency": "USD", "current_subtotal_price": "65.00", "current_subtotal_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "65.00", "current_total_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1129", "note": null, "note_attributes": [], "number": 129, "order_number": 1129, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a87ec0d757303fb0ce8262e202bf8e2e/authenticate?key=440ae8a278cc5300b24b237b6431875c", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:00:45-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "65.00", "subtotal_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "a87ec0d757303fb0ce8262e202bf8e2e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "65.00", "total_line_items_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "65.00", "total_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "total_price_usd": "65.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 490, "updated_at": "2022-03-17T03:00:48-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Jasmin", "address1": "82644 Mayert Green", "phone": "522.452.8289 x71583", "city": "", "zip": "", "province": "Alberta", "country": "Canada", "last_name": "Schiller", "address2": "", "company": null, "latitude": 53.9332706, "longitude": -116.5765035, "name": "Jasmin Schiller", "country_code": "CA", "province_code": "AB"}, "customer": {"id": 5330782486717, "email": "jasmin.schiller@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:09-07:00", "updated_at": "2022-03-17T03:00:47-07:00", "first_name": "Jasmin", "last_name": "Schiller", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:09-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782486717", "default_address": {"id": 6564640293053, "customer_id": 5330782486717, "first_name": "Jasmin", "last_name": "Schiller", "company": null, "address1": "82644 Mayert Green", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "522.452.8289 x71583", "name": "Jasmin Schiller", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974519619773, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974519619773", "created_at": "2022-03-17T03:00:47-07:00", "location_id": 63590301885, "name": "#1129.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:00:47-07:00", "line_items": [{"id": 11196011544765, "admin_graphql_api_id": "gid://shopify/LineItem/11196011544765", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 490, "name": "Black & White Wrist Watches - Rubber", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "65.00", "price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796222791869, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black & White Wrist Watches", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090590019773, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Friesen LLC", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196011544765, "admin_graphql_api_id": "gid://shopify/LineItem/11196011544765", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 490, "name": "Black & White Wrist Watches - Rubber", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 65.0, "price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796222791869, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black & White Wrist Watches", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090590019773, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Friesen LLC", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 65.0, "product": {"id": 6796222791869, "title": "Black & White Wrist Watches", "handle": "black-white-wrist-watches", "vendor": "Friesen LLC", "tags": "developer-tools-generator", "body_html": "Black and white wrist watches with lined shadow across the faces.", "product_type": "Home", "properties": {}, "images": [], "variant": {"sku": 40090590019773, "title": "Rubber", "options": {"Title": "Rubber"}, "images": []}, "variant_options": {"Title": "Rubber"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Jasmin", "address1": "82644 Mayert Green", "phone": "522.452.8289 x71583", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Schiller", "address2": null, "company": null, "latitude": 56.130366, "longitude": -106.346771, "name": "Jasmin Schiller", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2022-03-17 10:01:05+00:00", "uuid": "28a48680-a5d9-11ec-8001-f83852d6cb95", "person": {"object": "person", "id": "01G4CDT0819VNY12K2E7BGFE05", "$address1": "82644 Mayert Green", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Alberta", "$zip": "", "$first_name": "Jasmin", "$title": "", "$phone_number": "+15224528289", "$organization": "", "$last_name": "Schiller", "$email": "jasmin.schiller@developer-tools.shopifyapps.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "first_name": "Jasmin", "last_name": "Schiller", "created": "2022-05-31 06:45:46", "updated": "2022-05-31 06:45:47"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158966} -{"stream": "events", "data": {"object": "event", "id": "3mjxNNuB568", "statistic_id": "X3f6PC", "timestamp": 1647511266, "event_name": "Fulfilled Order", "event_properties": {"Items": ["4 Ounce Soy Candle"], "Collections": ["Home page"], "Item Count": 1, "tags": [], "ShippingRate": "custom", "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4446355226813", "$value": 23.0, "$extra": {"id": 4446355226813, "admin_graphql_api_id": "gid://shopify/Order/4446355226813", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254363009213, "checkout_token": "ba187234cb2a5ba8b8d10e1dc026313a", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:00:17-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:00:15-07:00", "currency": "USD", "current_subtotal_price": "19.00", "current_subtotal_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "23.00", "current_total_price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1128", "note": null, "note_attributes": [], "number": 128, "order_number": 1128, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/aa546a3df2a786a82821264f856a1200/authenticate?key=c02fc0d254c214aa2192459aefdeaaca", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:00:14-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "19.00", "subtotal_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "aa546a3df2a786a82821264f856a1200", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "19.00", "total_line_items_price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "23.00", "total_price_set": {"shop_money": {"amount": "23.00", "currency_code": "USD"}, "presentment_money": {"amount": "23.00", "currency_code": "USD"}}, "total_price_usd": "23.00", "total_shipping_price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 112, "updated_at": "2022-03-17T03:00:17-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "San Francisco", "phone": null, "city": "San Francisco", "zip": "92345", "province": null, "country": null, "last_name": "Customer", "address2": "123", "company": "Test Company", "latitude": 34.3627106, "longitude": -117.2911248, "name": "Test Customer", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974519390397, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974519390397", "created_at": "2022-03-17T03:00:16-07:00", "location_id": 63590301885, "name": "#1128.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:00:16-07:00", "line_items": [{"id": 11196010823869, "admin_graphql_api_id": "gid://shopify/LineItem/11196010823869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "19.00", "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196010823869, "admin_graphql_api_id": "gid://shopify/LineItem/11196010823869", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 19.0, "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 19.0, "product": {"id": 6796220989629, "title": "4 Ounce Soy Candle", "handle": "4-ounce-soy-candle", "vendor": "Hartmann Group", "tags": "developer-tools-generator", "body_html": "

    Small white soy candle in clear container on wooden table.

    \n

    Updated!

    ", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle.jpg?v=1624410587", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle_x240.jpg?v=1624410587", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301297316029, "created_at": "2021-06-22T18:09:47-07:00", "updated_at": "2021-06-22T18:09:47-07:00"}], "variant": {"sku": 40090585923773, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Test", "address1": "San Francisco", "phone": null, "city": "San Francisco", "zip": "92345", "province": null, "country": null, "last_name": "Customer", "address2": "123", "company": "Test Company", "latitude": 34.3627106, "longitude": -117.2911248, "name": "Test Customer", "country_code": null, "province_code": null}, "shipping_lines": [{"id": 3791179382973, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "4.00", "discounted_price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "phone": null, "price": "4.00", "price_set": {"shop_money": {"amount": "4.00", "currency_code": "USD"}, "presentment_money": {"amount": "4.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Test Rate", "tax_lines": [], "discount_allocations": []}], "full_landing_site": ""}}, "datetime": "2022-03-17 10:01:06+00:00", "uuid": "293d1d00-a5d9-11ec-8001-b32c0405d3e6", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158967} -{"stream": "events", "data": {"object": "event", "id": "3mjDRye8Qqw", "statistic_id": "RDXsib", "timestamp": 1647511275, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796222791869, "Name": "Black & White Wrist Watches", "Variant Name": "Rubber", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Friesen LLC", "Variant Option: Title": "Rubber", "Quantity": 1, "$event_id": "4446355882173:11196011544765:0", "$value": 65.0}, "datetime": "2022-03-17 10:01:15+00:00", "uuid": "2e9a6780-a5d9-11ec-8001-c8ef87b4e5bd", "person": {"object": "person", "id": "01G4CDT0819VNY12K2E7BGFE05", "$address1": "82644 Mayert Green", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Alberta", "$zip": "", "$first_name": "Jasmin", "$title": "", "$phone_number": "+15224528289", "$organization": "", "$last_name": "Schiller", "$email": "jasmin.schiller@developer-tools.shopifyapps.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "first_name": "Jasmin", "last_name": "Schiller", "created": "2022-05-31 06:45:46", "updated": "2022-05-31 06:45:47"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158968} -{"stream": "events", "data": {"object": "event", "id": "3mjBWARz5as", "statistic_id": "X3f6PC", "timestamp": 1647511297, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Black & White Wrist Watches"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4446355882173", "$value": 65.0, "$extra": {"id": 4446355882173, "admin_graphql_api_id": "gid://shopify/Order/4446355882173", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254368186557, "checkout_token": "f686bf436dec584badf309ea1e4c62b9", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:00:48-07:00", "confirmed": true, "contact_email": "jasmin.schiller@developer-tools.shopifyapps.com", "created_at": "2022-03-17T03:00:46-07:00", "currency": "USD", "current_subtotal_price": "65.00", "current_subtotal_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "65.00", "current_total_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": null, "name": "#1129", "note": null, "note_attributes": [], "number": 129, "order_number": 1129, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a87ec0d757303fb0ce8262e202bf8e2e/authenticate?key=440ae8a278cc5300b24b237b6431875c", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:00:45-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "65.00", "subtotal_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "a87ec0d757303fb0ce8262e202bf8e2e", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "65.00", "total_line_items_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "65.00", "total_price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "total_price_usd": "65.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 490, "updated_at": "2022-03-17T03:00:48-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Jasmin", "address1": "82644 Mayert Green", "phone": "522.452.8289 x71583", "city": "", "zip": "", "province": "Alberta", "country": "Canada", "last_name": "Schiller", "address2": "", "company": null, "latitude": 53.9332706, "longitude": -116.5765035, "name": "Jasmin Schiller", "country_code": "CA", "province_code": "AB"}, "customer": {"id": 5330782486717, "email": "jasmin.schiller@developer-tools.shopifyapps.com", "accepts_marketing": false, "created_at": "2021-06-22T23:05:09-07:00", "updated_at": "2022-03-17T03:00:47-07:00", "first_name": "Jasmin", "last_name": "Schiller", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "developer-tools-generator", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-06-22T23:05:09-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5330782486717", "default_address": {"id": 6564640293053, "customer_id": 5330782486717, "first_name": "Jasmin", "last_name": "Schiller", "company": null, "address1": "82644 Mayert Green", "address2": null, "city": null, "province": null, "country": "Canada", "zip": null, "phone": "522.452.8289 x71583", "name": "Jasmin Schiller", "province_code": null, "country_code": "CA", "country_name": "Canada", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974519619773, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974519619773", "created_at": "2022-03-17T03:00:47-07:00", "location_id": 63590301885, "name": "#1129.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:00:47-07:00", "line_items": [{"id": 11196011544765, "admin_graphql_api_id": "gid://shopify/LineItem/11196011544765", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 490, "name": "Black & White Wrist Watches - Rubber", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "65.00", "price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796222791869, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black & White Wrist Watches", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090590019773, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Friesen LLC", "tax_lines": [], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196011544765, "admin_graphql_api_id": "gid://shopify/LineItem/11196011544765", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 490, "name": "Black & White Wrist Watches - Rubber", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 65.0, "price_set": {"shop_money": {"amount": "65.00", "currency_code": "USD"}, "presentment_money": {"amount": "65.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796222791869, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black & White Wrist Watches", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090590019773, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Friesen LLC", "tax_lines": [], "duties": [], "discount_allocations": [], "line_price": 65.0, "product": {"id": 6796222791869, "title": "Black & White Wrist Watches", "handle": "black-white-wrist-watches", "vendor": "Friesen LLC", "tags": "developer-tools-generator", "body_html": "Black and white wrist watches with lined shadow across the faces.", "product_type": "Home", "properties": {}, "images": [], "variant": {"sku": 40090590019773, "title": "Rubber", "options": {"Title": "Rubber"}, "images": []}, "variant_options": {"Title": "Rubber"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Jasmin", "address1": "82644 Mayert Green", "phone": "522.452.8289 x71583", "city": null, "zip": null, "province": null, "country": "Canada", "last_name": "Schiller", "address2": null, "company": null, "latitude": 56.130366, "longitude": -106.346771, "name": "Jasmin Schiller", "country_code": "CA", "province_code": null}, "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2022-03-17 10:01:37+00:00", "uuid": "3bb75680-a5d9-11ec-8001-8d83e087779e", "person": {"object": "person", "id": "01G4CDT0819VNY12K2E7BGFE05", "$address1": "82644 Mayert Green", "$address2": "", "$city": "", "$country": "Canada", "$latitude": "", "$longitude": "", "$region": "Alberta", "$zip": "", "$first_name": "Jasmin", "$title": "", "$phone_number": "+15224528289", "$organization": "", "$last_name": "Schiller", "$email": "jasmin.schiller@developer-tools.shopifyapps.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": ["developer-tools-generator"], "email": "jasmin.schiller@developer-tools.shopifyapps.com", "first_name": "Jasmin", "last_name": "Schiller", "created": "2022-05-31 06:45:46", "updated": "2022-05-31 06:45:47"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158969} -{"stream": "events", "data": {"object": "event", "id": "3mjzpTbVET5", "statistic_id": "SPnhc3", "timestamp": 1647511438, "event_name": "Checkout Started", "event_properties": {"Items": ["8 Ounce Soy Candle", "Blue And White Skate Shoes"], "Collections": [], "Item Count": 2, "tags": [], "ShippingRate": "custom", "Discount Codes": ["Test discount"], "Total Discounts": "0.50", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "24254391156925", "$value": 140.5, "$extra": {"id": 4446359781565, "admin_graphql_api_id": "gid://shopify/Order/4446359781565", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254391156925, "checkout_token": "30b6672dc2bd0662044d310c280e034c", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:04:05-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:04:00-07:00", "currency": "USD", "current_subtotal_price": "140.50", "current_subtotal_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "current_total_discounts": "0.50", "current_total_discounts_set": {"shop_money": {"amount": "0.50", "currency_code": "USD"}, "presentment_money": {"amount": "0.50", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "140.50", "current_total_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "current_total_tax": "23.42", "current_total_tax_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [{"code": "Test discount", "amount": "0.50", "type": "fixed_amount"}], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1130", "note": null, "note_attributes": [], "number": 130, "order_number": 1130, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ee03e4bce0517ed5e23d50846cf96c2c/authenticate?key=30c9de2978d4ef1fdee94d1944194880", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:03:58-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "140.50", "subtotal_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "23.42", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "30b6672dc2bd0662044d310c280e034c", "total_discounts": "0.50", "total_discounts_set": {"shop_money": {"amount": "0.50", "currency_code": "USD"}, "presentment_money": {"amount": "0.50", "currency_code": "USD"}}, "total_line_items_price": "141.00", "total_line_items_price_set": {"shop_money": {"amount": "141.00", "currency_code": "USD"}, "presentment_money": {"amount": "141.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "140.50", "total_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "total_price_usd": "140.50", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "23.42", "total_tax_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 63, "updated_at": "2022-03-17T03:04:05-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "manual", "value": "0.5", "value_type": "fixed_amount", "allocation_method": "across", "target_selection": "all", "title": "Test discount", "description": "Test discount"}], "fulfillments": [{"id": 3974520537277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520537277", "created_at": "2022-03-17T03:04:04-07:00", "location_id": 63590301885, "name": "#1130.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:04-07:00", "line_items": [{"id": 11196017180861, "admin_graphql_api_id": "gid://shopify/LineItem/11196017180861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "102.00", "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [{"channel_liable": false, "price": "16.94", "price_set": {"shop_money": {"amount": "16.94", "currency_code": "USD"}, "presentment_money": {"amount": "16.94", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.37", "amount_set": {"shop_money": {"amount": "0.37", "currency_code": "USD"}, "presentment_money": {"amount": "0.37", "currency_code": "USD"}}, "discount_application_index": 0}]}]}, {"id": 3974520570045, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520570045", "created_at": "2022-03-17T03:04:04-07:00", "location_id": 63590301885, "name": "#1130.2", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:04-07:00", "line_items": [{"id": 11196017246397, "admin_graphql_api_id": "gid://shopify/LineItem/11196017246397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [{"channel_liable": false, "price": "6.48", "price_set": {"shop_money": {"amount": "6.48", "currency_code": "USD"}, "presentment_money": {"amount": "6.48", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.13", "amount_set": {"shop_money": {"amount": "0.13", "currency_code": "USD"}, "presentment_money": {"amount": "0.13", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11196017180861, "admin_graphql_api_id": "gid://shopify/LineItem/11196017180861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [{"channel_liable": false, "price": "16.94", "price_set": {"shop_money": {"amount": "16.94", "currency_code": "USD"}, "presentment_money": {"amount": "16.94", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.37", "amount_set": {"shop_money": {"amount": "0.37", "currency_code": "USD"}, "presentment_money": {"amount": "0.37", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 11196017246397, "admin_graphql_api_id": "gid://shopify/LineItem/11196017246397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 39.0, "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [{"channel_liable": false, "price": "6.48", "price_set": {"shop_money": {"amount": "6.48", "currency_code": "USD"}, "presentment_money": {"amount": "6.48", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.13", "amount_set": {"shop_money": {"amount": "0.13", "currency_code": "USD"}, "presentment_money": {"amount": "0.13", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 39.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614005949, "title": "orchid", "options": {"Title": "orchid"}, "images": []}, "variant_options": {"Title": "orchid"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [{"id": 3791183773885, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "0.00", "discounted_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "phone": null, "price": "0.00", "price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Free shipping", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ee03e4bce0517ed5e23d50846cf96c2c/authenticate?key=30c9de2978d4ef1fdee94d1944194880", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ee03e4bce0517ed5e23d50846cf96c2c/authenticate?key=30c9de2978d4ef1fdee94d1944194880"}}, "datetime": "2022-03-17 10:03:58+00:00", "uuid": "8fc23b00-a5d9-11ec-8001-b1423e757480", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158970} -{"stream": "events", "data": {"object": "event", "id": "3mjyswmc82v", "statistic_id": "TspjNE", "timestamp": 1647511458, "event_name": "Placed Order", "event_properties": {"Items": ["8 Ounce Soy Candle", "Blue And White Skate Shoes"], "Collections": [], "Item Count": 2, "tags": [], "ShippingRate": "custom", "Discount Codes": ["Test discount"], "Total Discounts": "0.50", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "4446359781565", "$value": 140.5, "$extra": {"id": 4446359781565, "admin_graphql_api_id": "gid://shopify/Order/4446359781565", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254391156925, "checkout_token": "30b6672dc2bd0662044d310c280e034c", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:04:05-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:04:00-07:00", "currency": "USD", "current_subtotal_price": "140.50", "current_subtotal_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "current_total_discounts": "0.50", "current_total_discounts_set": {"shop_money": {"amount": "0.50", "currency_code": "USD"}, "presentment_money": {"amount": "0.50", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "140.50", "current_total_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "current_total_tax": "23.42", "current_total_tax_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [{"code": "Test discount", "amount": "0.50", "type": "fixed_amount"}], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1130", "note": null, "note_attributes": [], "number": 130, "order_number": 1130, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ee03e4bce0517ed5e23d50846cf96c2c/authenticate?key=30c9de2978d4ef1fdee94d1944194880", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:03:58-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "140.50", "subtotal_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "23.42", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "ee03e4bce0517ed5e23d50846cf96c2c", "total_discounts": "0.50", "total_discounts_set": {"shop_money": {"amount": "0.50", "currency_code": "USD"}, "presentment_money": {"amount": "0.50", "currency_code": "USD"}}, "total_line_items_price": "141.00", "total_line_items_price_set": {"shop_money": {"amount": "141.00", "currency_code": "USD"}, "presentment_money": {"amount": "141.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "140.50", "total_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "total_price_usd": "140.50", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "23.42", "total_tax_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 63, "updated_at": "2022-03-17T03:04:05-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "manual", "value": "0.5", "value_type": "fixed_amount", "allocation_method": "across", "target_selection": "all", "title": "Test discount", "description": "Test discount"}], "fulfillments": [{"id": 3974520537277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520537277", "created_at": "2022-03-17T03:04:04-07:00", "location_id": 63590301885, "name": "#1130.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:04-07:00", "line_items": [{"id": 11196017180861, "admin_graphql_api_id": "gid://shopify/LineItem/11196017180861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "102.00", "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [{"channel_liable": false, "price": "16.94", "price_set": {"shop_money": {"amount": "16.94", "currency_code": "USD"}, "presentment_money": {"amount": "16.94", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.37", "amount_set": {"shop_money": {"amount": "0.37", "currency_code": "USD"}, "presentment_money": {"amount": "0.37", "currency_code": "USD"}}, "discount_application_index": 0}]}]}, {"id": 3974520570045, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520570045", "created_at": "2022-03-17T03:04:04-07:00", "location_id": 63590301885, "name": "#1130.2", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:04-07:00", "line_items": [{"id": 11196017246397, "admin_graphql_api_id": "gid://shopify/LineItem/11196017246397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [{"channel_liable": false, "price": "6.48", "price_set": {"shop_money": {"amount": "6.48", "currency_code": "USD"}, "presentment_money": {"amount": "6.48", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.13", "amount_set": {"shop_money": {"amount": "0.13", "currency_code": "USD"}, "presentment_money": {"amount": "0.13", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11196017180861, "admin_graphql_api_id": "gid://shopify/LineItem/11196017180861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [{"channel_liable": false, "price": "16.94", "price_set": {"shop_money": {"amount": "16.94", "currency_code": "USD"}, "presentment_money": {"amount": "16.94", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.37", "amount_set": {"shop_money": {"amount": "0.37", "currency_code": "USD"}, "presentment_money": {"amount": "0.37", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 11196017246397, "admin_graphql_api_id": "gid://shopify/LineItem/11196017246397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 39.0, "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [{"channel_liable": false, "price": "6.48", "price_set": {"shop_money": {"amount": "6.48", "currency_code": "USD"}, "presentment_money": {"amount": "6.48", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.13", "amount_set": {"shop_money": {"amount": "0.13", "currency_code": "USD"}, "presentment_money": {"amount": "0.13", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 39.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614005949, "title": "orchid", "options": {"Title": "orchid"}, "images": []}, "variant_options": {"Title": "orchid"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [{"id": 3791183773885, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "0.00", "discounted_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "phone": null, "price": "0.00", "price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Free shipping", "tax_lines": [], "discount_allocations": []}], "full_landing_site": ""}}, "datetime": "2022-03-17 10:04:18+00:00", "uuid": "9badfd00-a5d9-11ec-8001-d645602891ee", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158971} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHKea", "statistic_id": "RDXsib", "timestamp": 1647511468, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796234555581, "Name": "Blue And White Skate Shoes", "Variant Name": "orchid", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Ullrich, Kris and Dicki", "Variant Option: Title": "orchid", "Quantity": 1, "$event_id": "4446359781565:11196017246397:0", "$value": 39.0}, "datetime": "2022-03-17 10:04:28+00:00", "uuid": "a1a3de00-a5d9-11ec-8001-689fac2fa8a8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158972} -{"stream": "events", "data": {"object": "event", "id": "3mjEbh6DDms", "statistic_id": "RDXsib", "timestamp": 1647511468, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "Wooden", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "Wooden", "Quantity": 1, "$event_id": "4446359781565:11196017180861:0", "$value": 102.0}, "datetime": "2022-03-17 10:04:28+00:00", "uuid": "a1a3de00-a5d9-11ec-8001-6c3ab95dabb8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158973} -{"stream": "events", "data": {"object": "event", "id": "3mjwRkhBJUt", "statistic_id": "SPnhc3", "timestamp": 1647511473, "event_name": "Checkout Started", "event_properties": {"Items": ["Black And White Stripe Tee", "Black And White Stripe Tee"], "Collections": ["Test Collection"], "Item Count": 2, "tags": [], "ShippingRate": "custom", "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "24254399971517", "$value": 153.0, "$extra": {"id": 4446360731837, "admin_graphql_api_id": "gid://shopify/Order/4446360731837", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254399971517, "checkout_token": "dbd2ac8ffc98cf1107924f7ce8672eb0", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:04:40-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:04:34-07:00", "currency": "USD", "current_subtotal_price": "153.00", "current_subtotal_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "153.00", "current_total_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "current_total_tax": "25.50", "current_total_tax_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1131", "note": null, "note_attributes": [], "number": 131, "order_number": 1131, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e12a8aee2e6a67b24ecfb078509f05df/authenticate?key=ec9128d3e348d2568ac66dd7c3d293c5", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:04:33-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "153.00", "subtotal_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "25.50", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "dbd2ac8ffc98cf1107924f7ce8672eb0", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "153.00", "total_line_items_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "153.00", "total_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "total_price_usd": "153.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "25.50", "total_tax_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 684, "updated_at": "2022-03-17T03:04:40-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974520996029, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520996029", "created_at": "2022-03-17T03:04:39-07:00", "location_id": 63590301885, "name": "#1131.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:39-07:00", "line_items": [{"id": 11196019507389, "admin_graphql_api_id": "gid://shopify/LineItem/11196019507389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 466, "name": "Black And White Stripe Tee - white", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "70.00", "price_set": {"shop_money": {"amount": "70.00", "currency_code": "USD"}, "presentment_money": {"amount": "70.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583761085, "variant_inventory_management": "shopify", "variant_title": "white", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "11.67", "price_set": {"shop_money": {"amount": "11.67", "currency_code": "USD"}, "presentment_money": {"amount": "11.67", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}, {"id": 11196019540157, "admin_graphql_api_id": "gid://shopify/LineItem/11196019540157", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 218, "name": "Black And White Stripe Tee - Soft", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "83.00", "price_set": {"shop_money": {"amount": "83.00", "currency_code": "USD"}, "presentment_money": {"amount": "83.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583793853, "variant_inventory_management": "shopify", "variant_title": "Soft", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "13.83", "price_set": {"shop_money": {"amount": "13.83", "currency_code": "USD"}, "presentment_money": {"amount": "13.83", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196019507389, "admin_graphql_api_id": "gid://shopify/LineItem/11196019507389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 466, "name": "Black And White Stripe Tee - white", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 70.0, "price_set": {"shop_money": {"amount": "70.00", "currency_code": "USD"}, "presentment_money": {"amount": "70.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583761085, "variant_inventory_management": "shopify", "variant_title": "white", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "11.67", "price_set": {"shop_money": {"amount": "11.67", "currency_code": "USD"}, "presentment_money": {"amount": "11.67", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 70.0, "product": {"id": 6796219875517, "title": "Black And White Stripe Tee", "handle": "black-and-white-stripe-tee", "vendor": "Lehner and Sons", "tags": "developer-tools-generator", "body_html": "A front view of a woman's black and white striped t-shirt; loose-fitting, with a crew neck. Beach or dinner? This top can do both.", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090583761085, "title": "white", "options": {"Title": "white"}, "images": []}, "variant_options": {"Title": "white"}}}, {"id": 11196019540157, "admin_graphql_api_id": "gid://shopify/LineItem/11196019540157", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 218, "name": "Black And White Stripe Tee - Soft", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 83.0, "price_set": {"shop_money": {"amount": "83.00", "currency_code": "USD"}, "presentment_money": {"amount": "83.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583793853, "variant_inventory_management": "shopify", "variant_title": "Soft", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "13.83", "price_set": {"shop_money": {"amount": "13.83", "currency_code": "USD"}, "presentment_money": {"amount": "13.83", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 83.0, "product": {"id": 6796219875517, "title": "Black And White Stripe Tee", "handle": "black-and-white-stripe-tee", "vendor": "Lehner and Sons", "tags": "developer-tools-generator", "body_html": "A front view of a woman's black and white striped t-shirt; loose-fitting, with a crew neck. Beach or dinner? This top can do both.", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090583793853, "title": "Soft", "options": {"Title": "Soft"}, "images": []}, "variant_options": {"Title": "Soft"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [{"id": 3791184691389, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "0.00", "discounted_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "phone": null, "price": "0.00", "price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Free shipping", "tax_lines": [], "discount_allocations": []}], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e12a8aee2e6a67b24ecfb078509f05df/authenticate?key=ec9128d3e348d2568ac66dd7c3d293c5", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e12a8aee2e6a67b24ecfb078509f05df/authenticate?key=ec9128d3e348d2568ac66dd7c3d293c5"}}, "datetime": "2022-03-17 10:04:33+00:00", "uuid": "a49ece80-a5d9-11ec-8001-3216702987e0", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158973} -{"stream": "events", "data": {"object": "event", "id": "3mjuCE4xe3n", "statistic_id": "TspjNE", "timestamp": 1647511493, "event_name": "Placed Order", "event_properties": {"Items": ["Black And White Stripe Tee", "Black And White Stripe Tee"], "Collections": ["Test Collection"], "Item Count": 2, "tags": [], "ShippingRate": "custom", "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "4446360731837", "$value": 153.0, "$extra": {"id": 4446360731837, "admin_graphql_api_id": "gid://shopify/Order/4446360731837", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254399971517, "checkout_token": "dbd2ac8ffc98cf1107924f7ce8672eb0", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:04:40-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:04:34-07:00", "currency": "USD", "current_subtotal_price": "153.00", "current_subtotal_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "153.00", "current_total_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "current_total_tax": "25.50", "current_total_tax_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1131", "note": null, "note_attributes": [], "number": 131, "order_number": 1131, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e12a8aee2e6a67b24ecfb078509f05df/authenticate?key=ec9128d3e348d2568ac66dd7c3d293c5", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:04:33-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "153.00", "subtotal_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "25.50", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "e12a8aee2e6a67b24ecfb078509f05df", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "153.00", "total_line_items_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "153.00", "total_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "total_price_usd": "153.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "25.50", "total_tax_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 684, "updated_at": "2022-03-17T03:04:40-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974520996029, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520996029", "created_at": "2022-03-17T03:04:39-07:00", "location_id": 63590301885, "name": "#1131.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:39-07:00", "line_items": [{"id": 11196019507389, "admin_graphql_api_id": "gid://shopify/LineItem/11196019507389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 466, "name": "Black And White Stripe Tee - white", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "70.00", "price_set": {"shop_money": {"amount": "70.00", "currency_code": "USD"}, "presentment_money": {"amount": "70.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583761085, "variant_inventory_management": "shopify", "variant_title": "white", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "11.67", "price_set": {"shop_money": {"amount": "11.67", "currency_code": "USD"}, "presentment_money": {"amount": "11.67", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}, {"id": 11196019540157, "admin_graphql_api_id": "gid://shopify/LineItem/11196019540157", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 218, "name": "Black And White Stripe Tee - Soft", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "83.00", "price_set": {"shop_money": {"amount": "83.00", "currency_code": "USD"}, "presentment_money": {"amount": "83.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583793853, "variant_inventory_management": "shopify", "variant_title": "Soft", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "13.83", "price_set": {"shop_money": {"amount": "13.83", "currency_code": "USD"}, "presentment_money": {"amount": "13.83", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196019507389, "admin_graphql_api_id": "gid://shopify/LineItem/11196019507389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 466, "name": "Black And White Stripe Tee - white", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 70.0, "price_set": {"shop_money": {"amount": "70.00", "currency_code": "USD"}, "presentment_money": {"amount": "70.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583761085, "variant_inventory_management": "shopify", "variant_title": "white", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "11.67", "price_set": {"shop_money": {"amount": "11.67", "currency_code": "USD"}, "presentment_money": {"amount": "11.67", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 70.0, "product": {"id": 6796219875517, "title": "Black And White Stripe Tee", "handle": "black-and-white-stripe-tee", "vendor": "Lehner and Sons", "tags": "developer-tools-generator", "body_html": "A front view of a woman's black and white striped t-shirt; loose-fitting, with a crew neck. Beach or dinner? This top can do both.", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090583761085, "title": "white", "options": {"Title": "white"}, "images": []}, "variant_options": {"Title": "white"}}}, {"id": 11196019540157, "admin_graphql_api_id": "gid://shopify/LineItem/11196019540157", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 218, "name": "Black And White Stripe Tee - Soft", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 83.0, "price_set": {"shop_money": {"amount": "83.00", "currency_code": "USD"}, "presentment_money": {"amount": "83.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583793853, "variant_inventory_management": "shopify", "variant_title": "Soft", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "13.83", "price_set": {"shop_money": {"amount": "13.83", "currency_code": "USD"}, "presentment_money": {"amount": "13.83", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 83.0, "product": {"id": 6796219875517, "title": "Black And White Stripe Tee", "handle": "black-and-white-stripe-tee", "vendor": "Lehner and Sons", "tags": "developer-tools-generator", "body_html": "A front view of a woman's black and white striped t-shirt; loose-fitting, with a crew neck. Beach or dinner? This top can do both.", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090583793853, "title": "Soft", "options": {"Title": "Soft"}, "images": []}, "variant_options": {"Title": "Soft"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [{"id": 3791184691389, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "0.00", "discounted_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "phone": null, "price": "0.00", "price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Free shipping", "tax_lines": [], "discount_allocations": []}], "full_landing_site": ""}}, "datetime": "2022-03-17 10:04:53+00:00", "uuid": "b08a9080-a5d9-11ec-8001-2b766edc8cd8", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158974} -{"stream": "events", "data": {"object": "event", "id": "3mjz68RxfCz", "statistic_id": "X3f6PC", "timestamp": 1647511494, "event_name": "Fulfilled Order", "event_properties": {"Items": ["8 Ounce Soy Candle", "Blue And White Skate Shoes"], "Collections": [], "Item Count": 2, "tags": [], "ShippingRate": "custom", "Discount Codes": ["Test discount"], "Total Discounts": "0.50", "Source Name": "shopify_draft_order", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4446359781565", "$value": 140.5, "$extra": {"id": 4446359781565, "admin_graphql_api_id": "gid://shopify/Order/4446359781565", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254391156925, "checkout_token": "30b6672dc2bd0662044d310c280e034c", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:04:05-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:04:00-07:00", "currency": "USD", "current_subtotal_price": "140.50", "current_subtotal_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "current_total_discounts": "0.50", "current_total_discounts_set": {"shop_money": {"amount": "0.50", "currency_code": "USD"}, "presentment_money": {"amount": "0.50", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "140.50", "current_total_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "current_total_tax": "23.42", "current_total_tax_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [{"code": "Test discount", "amount": "0.50", "type": "fixed_amount"}], "email": "airbyte-test@airbyte.com", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1130", "note": null, "note_attributes": [], "number": 130, "order_number": 1130, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/ee03e4bce0517ed5e23d50846cf96c2c/authenticate?key=30c9de2978d4ef1fdee94d1944194880", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:03:58-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "140.50", "subtotal_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "23.42", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "ee03e4bce0517ed5e23d50846cf96c2c", "total_discounts": "0.50", "total_discounts_set": {"shop_money": {"amount": "0.50", "currency_code": "USD"}, "presentment_money": {"amount": "0.50", "currency_code": "USD"}}, "total_line_items_price": "141.00", "total_line_items_price_set": {"shop_money": {"amount": "141.00", "currency_code": "USD"}, "presentment_money": {"amount": "141.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "140.50", "total_price_set": {"shop_money": {"amount": "140.50", "currency_code": "USD"}, "presentment_money": {"amount": "140.50", "currency_code": "USD"}}, "total_price_usd": "140.50", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "23.42", "total_tax_set": {"shop_money": {"amount": "23.42", "currency_code": "USD"}, "presentment_money": {"amount": "23.42", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 63, "updated_at": "2022-03-17T03:04:05-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "manual", "value": "0.5", "value_type": "fixed_amount", "allocation_method": "across", "target_selection": "all", "title": "Test discount", "description": "Test discount"}], "fulfillments": [{"id": 3974520537277, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520537277", "created_at": "2022-03-17T03:04:04-07:00", "location_id": 63590301885, "name": "#1130.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:04-07:00", "line_items": [{"id": 11196017180861, "admin_graphql_api_id": "gid://shopify/LineItem/11196017180861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "102.00", "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [{"channel_liable": false, "price": "16.94", "price_set": {"shop_money": {"amount": "16.94", "currency_code": "USD"}, "presentment_money": {"amount": "16.94", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.37", "amount_set": {"shop_money": {"amount": "0.37", "currency_code": "USD"}, "presentment_money": {"amount": "0.37", "currency_code": "USD"}}, "discount_application_index": 0}]}]}, {"id": 3974520570045, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520570045", "created_at": "2022-03-17T03:04:04-07:00", "location_id": 63590301885, "name": "#1130.2", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:04-07:00", "line_items": [{"id": 11196017246397, "admin_graphql_api_id": "gid://shopify/LineItem/11196017246397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "39.00", "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [{"channel_liable": false, "price": "6.48", "price_set": {"shop_money": {"amount": "6.48", "currency_code": "USD"}, "presentment_money": {"amount": "6.48", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.13", "amount_set": {"shop_money": {"amount": "0.13", "currency_code": "USD"}, "presentment_money": {"amount": "0.13", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11196017180861, "admin_graphql_api_id": "gid://shopify/LineItem/11196017180861", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [{"channel_liable": false, "price": "16.94", "price_set": {"shop_money": {"amount": "16.94", "currency_code": "USD"}, "presentment_money": {"amount": "16.94", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.37", "amount_set": {"shop_money": {"amount": "0.37", "currency_code": "USD"}, "presentment_money": {"amount": "0.37", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 11196017246397, "admin_graphql_api_id": "gid://shopify/LineItem/11196017246397", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Blue And White Skate Shoes - orchid", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 39.0, "price_set": {"shop_money": {"amount": "39.00", "currency_code": "USD"}, "presentment_money": {"amount": "39.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796234555581, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Blue And White Skate Shoes", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090614005949, "variant_inventory_management": "shopify", "variant_title": "orchid", "vendor": "Ullrich, Kris and Dicki", "tax_lines": [{"channel_liable": false, "price": "6.48", "price_set": {"shop_money": {"amount": "6.48", "currency_code": "USD"}, "presentment_money": {"amount": "6.48", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [{"amount": "0.13", "amount_set": {"shop_money": {"amount": "0.13", "currency_code": "USD"}, "presentment_money": {"amount": "0.13", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 39.0, "product": {"id": 6796234555581, "title": "Blue And White Skate Shoes", "handle": "blue-and-white-skate-shoes-1", "vendor": "Ullrich, Kris and Dicki", "tags": "developer-tools-generator", "body_html": "A pair of navy blue skate shoes on a dark wooden platform in front of a white painted brick wall. Shot from the back.", "product_type": "Books", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224.jpg?v=1624410684", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/blue-and-white-skate-shoes_6fa6174f-8c5a-454c-92ed-8852a0aa3224_x240.jpg?v=1624410684", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301307015357, "created_at": "2021-06-22T18:11:24-07:00", "updated_at": "2021-06-22T18:11:24-07:00"}], "variant": {"sku": 40090614005949, "title": "orchid", "options": {"Title": "orchid"}, "images": []}, "variant_options": {"Title": "orchid"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [{"id": 3791183773885, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "0.00", "discounted_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "phone": null, "price": "0.00", "price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Free shipping", "tax_lines": [], "discount_allocations": []}], "full_landing_site": ""}}, "datetime": "2022-03-17 10:04:54+00:00", "uuid": "b1232700-a5d9-11ec-8001-dcf189ec0cef", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158976} -{"stream": "events", "data": {"object": "event", "id": "3mjwxBUpFcH", "statistic_id": "RDXsib", "timestamp": 1647511503, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796219875517, "Name": "Black And White Stripe Tee", "Variant Name": "Soft", "SKU": "", "Collections": ["Test Collection"], "Tags": ["developer-tools-generator"], "Vendor": "Lehner and Sons", "Variant Option: Title": "Soft", "Quantity": 1, "$event_id": "4446360731837:11196019540157:0", "$value": 83.0}, "datetime": "2022-03-17 10:05:03+00:00", "uuid": "b6807180-a5d9-11ec-8001-bb6941ebd1d1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158977} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQinf", "statistic_id": "RDXsib", "timestamp": 1647511503, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796219875517, "Name": "Black And White Stripe Tee", "Variant Name": "white", "SKU": "", "Collections": ["Test Collection"], "Tags": ["developer-tools-generator"], "Vendor": "Lehner and Sons", "Variant Option: Title": "white", "Quantity": 1, "$event_id": "4446360731837:11196019507389:0", "$value": 70.0}, "datetime": "2022-03-17 10:05:03+00:00", "uuid": "b6807180-a5d9-11ec-8001-c6fa0a06c5ea", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158978} -{"stream": "events", "data": {"object": "event", "id": "3mjwdDpBZsS", "statistic_id": "SPnhc3", "timestamp": 1647511505, "event_name": "Checkout Started", "event_properties": {"Items": ["All Black Sneaker Right Foot", "4 Ounce Soy Candle"], "Collections": ["Home page"], "Item Count": 2, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "24254404001981", "$value": 46.0, "$extra": {"id": 4446361583805, "admin_graphql_api_id": "gid://shopify/Order/4446361583805", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254404001981, "checkout_token": "51b3cf8cc1a3d339d550283d0bcd7847", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:05:09-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:05:06-07:00", "currency": "USD", "current_subtotal_price": "46.00", "current_subtotal_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "46.00", "current_total_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "current_total_tax": "7.67", "current_total_tax_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1132", "note": null, "note_attributes": [], "number": 132, "order_number": 1132, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/686b5f94a0ef7a716913bed3b0042d53/authenticate?key=2f1a314592703ad48d1a6b18717e9cb3", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:05:05-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "46.00", "subtotal_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "7.67", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "51b3cf8cc1a3d339d550283d0bcd7847", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "46.00", "total_line_items_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "46.00", "total_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "total_price_usd": "46.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "7.67", "total_tax_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 112, "updated_at": "2022-03-17T03:05:09-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974521323709, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974521323709", "created_at": "2022-03-17T03:05:08-07:00", "location_id": 63590301885, "name": "#1132.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:05:08-07:00", "line_items": [{"id": 11196020981949, "admin_graphql_api_id": "gid://shopify/LineItem/11196020981949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "19.00", "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [{"channel_liable": false, "price": "3.17", "price_set": {"shop_money": {"amount": "3.17", "currency_code": "USD"}, "presentment_money": {"amount": "3.17", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}, {"id": 3974521389245, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974521389245", "created_at": "2022-03-17T03:05:08-07:00", "location_id": 63590301885, "name": "#1132.2", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:05:08-07:00", "line_items": [{"id": 11196020949181, "admin_graphql_api_id": "gid://shopify/LineItem/11196020949181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - Rubber", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597916861, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Becker - Moore", "tax_lines": [{"channel_liable": false, "price": "4.50", "price_set": {"shop_money": {"amount": "4.50", "currency_code": "USD"}, "presentment_money": {"amount": "4.50", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196020949181, "admin_graphql_api_id": "gid://shopify/LineItem/11196020949181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - Rubber", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597916861, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Becker - Moore", "tax_lines": [{"channel_liable": false, "price": "4.50", "price_set": {"shop_money": {"amount": "4.50", "currency_code": "USD"}, "presentment_money": {"amount": "4.50", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597916861, "title": "Rubber", "options": {"Title": "Rubber"}, "images": []}, "variant_options": {"Title": "Rubber"}}}, {"id": 11196020981949, "admin_graphql_api_id": "gid://shopify/LineItem/11196020981949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 19.0, "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [{"channel_liable": false, "price": "3.17", "price_set": {"shop_money": {"amount": "3.17", "currency_code": "USD"}, "presentment_money": {"amount": "3.17", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 19.0, "product": {"id": 6796220989629, "title": "4 Ounce Soy Candle", "handle": "4-ounce-soy-candle", "vendor": "Hartmann Group", "tags": "developer-tools-generator", "body_html": "

    Small white soy candle in clear container on wooden table.

    \n

    Updated!

    ", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle.jpg?v=1624410587", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle_x240.jpg?v=1624410587", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301297316029, "created_at": "2021-06-22T18:09:47-07:00", "updated_at": "2021-06-22T18:09:47-07:00"}], "variant": {"sku": 40090585923773, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": "", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/686b5f94a0ef7a716913bed3b0042d53/authenticate?key=2f1a314592703ad48d1a6b18717e9cb3", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/686b5f94a0ef7a716913bed3b0042d53/authenticate?key=2f1a314592703ad48d1a6b18717e9cb3"}}, "datetime": "2022-03-17 10:05:05+00:00", "uuid": "b7b19e80-a5d9-11ec-8001-d3b52d2297bf", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158978} -{"stream": "events", "data": {"object": "event", "id": "3mjAFdyRWas", "statistic_id": "TspjNE", "timestamp": 1647511525, "event_name": "Placed Order", "event_properties": {"Items": ["All Black Sneaker Right Foot", "4 Ounce Soy Candle"], "Collections": ["Home page"], "Item Count": 2, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "$event_id": "4446361583805", "$value": 46.0, "$extra": {"id": 4446361583805, "admin_graphql_api_id": "gid://shopify/Order/4446361583805", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254404001981, "checkout_token": "51b3cf8cc1a3d339d550283d0bcd7847", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:05:09-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:05:06-07:00", "currency": "USD", "current_subtotal_price": "46.00", "current_subtotal_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "46.00", "current_total_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "current_total_tax": "7.67", "current_total_tax_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1132", "note": null, "note_attributes": [], "number": 132, "order_number": 1132, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/686b5f94a0ef7a716913bed3b0042d53/authenticate?key=2f1a314592703ad48d1a6b18717e9cb3", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:05:05-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "46.00", "subtotal_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "7.67", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "686b5f94a0ef7a716913bed3b0042d53", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "46.00", "total_line_items_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "46.00", "total_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "total_price_usd": "46.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "7.67", "total_tax_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 112, "updated_at": "2022-03-17T03:05:09-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974521323709, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974521323709", "created_at": "2022-03-17T03:05:08-07:00", "location_id": 63590301885, "name": "#1132.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:05:08-07:00", "line_items": [{"id": 11196020981949, "admin_graphql_api_id": "gid://shopify/LineItem/11196020981949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "19.00", "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [{"channel_liable": false, "price": "3.17", "price_set": {"shop_money": {"amount": "3.17", "currency_code": "USD"}, "presentment_money": {"amount": "3.17", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}, {"id": 3974521389245, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974521389245", "created_at": "2022-03-17T03:05:08-07:00", "location_id": 63590301885, "name": "#1132.2", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:05:08-07:00", "line_items": [{"id": 11196020949181, "admin_graphql_api_id": "gid://shopify/LineItem/11196020949181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - Rubber", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597916861, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Becker - Moore", "tax_lines": [{"channel_liable": false, "price": "4.50", "price_set": {"shop_money": {"amount": "4.50", "currency_code": "USD"}, "presentment_money": {"amount": "4.50", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196020949181, "admin_graphql_api_id": "gid://shopify/LineItem/11196020949181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - Rubber", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597916861, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Becker - Moore", "tax_lines": [{"channel_liable": false, "price": "4.50", "price_set": {"shop_money": {"amount": "4.50", "currency_code": "USD"}, "presentment_money": {"amount": "4.50", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597916861, "title": "Rubber", "options": {"Title": "Rubber"}, "images": []}, "variant_options": {"Title": "Rubber"}}}, {"id": 11196020981949, "admin_graphql_api_id": "gid://shopify/LineItem/11196020981949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 19.0, "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [{"channel_liable": false, "price": "3.17", "price_set": {"shop_money": {"amount": "3.17", "currency_code": "USD"}, "presentment_money": {"amount": "3.17", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 19.0, "product": {"id": 6796220989629, "title": "4 Ounce Soy Candle", "handle": "4-ounce-soy-candle", "vendor": "Hartmann Group", "tags": "developer-tools-generator", "body_html": "

    Small white soy candle in clear container on wooden table.

    \n

    Updated!

    ", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle.jpg?v=1624410587", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle_x240.jpg?v=1624410587", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301297316029, "created_at": "2021-06-22T18:09:47-07:00", "updated_at": "2021-06-22T18:09:47-07:00"}], "variant": {"sku": 40090585923773, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2022-03-17 10:05:25+00:00", "uuid": "c39d6080-a5d9-11ec-8001-ccee1882a6d1", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158979} -{"stream": "events", "data": {"object": "event", "id": "3mjBiUWaaBJ", "statistic_id": "X3f6PC", "timestamp": 1647511529, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Black And White Stripe Tee", "Black And White Stripe Tee"], "Collections": ["Test Collection"], "Item Count": 2, "tags": [], "ShippingRate": "custom", "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4446360731837", "$value": 153.0, "$extra": {"id": 4446360731837, "admin_graphql_api_id": "gid://shopify/Order/4446360731837", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254399971517, "checkout_token": "dbd2ac8ffc98cf1107924f7ce8672eb0", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:04:40-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:04:34-07:00", "currency": "USD", "current_subtotal_price": "153.00", "current_subtotal_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "153.00", "current_total_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "current_total_tax": "25.50", "current_total_tax_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1131", "note": null, "note_attributes": [], "number": 131, "order_number": 1131, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e12a8aee2e6a67b24ecfb078509f05df/authenticate?key=ec9128d3e348d2568ac66dd7c3d293c5", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:04:33-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "153.00", "subtotal_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "25.50", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "e12a8aee2e6a67b24ecfb078509f05df", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "153.00", "total_line_items_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "153.00", "total_price_set": {"shop_money": {"amount": "153.00", "currency_code": "USD"}, "presentment_money": {"amount": "153.00", "currency_code": "USD"}}, "total_price_usd": "153.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "25.50", "total_tax_set": {"shop_money": {"amount": "25.50", "currency_code": "USD"}, "presentment_money": {"amount": "25.50", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 684, "updated_at": "2022-03-17T03:04:40-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974520996029, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974520996029", "created_at": "2022-03-17T03:04:39-07:00", "location_id": 63590301885, "name": "#1131.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:04:39-07:00", "line_items": [{"id": 11196019507389, "admin_graphql_api_id": "gid://shopify/LineItem/11196019507389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 466, "name": "Black And White Stripe Tee - white", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "70.00", "price_set": {"shop_money": {"amount": "70.00", "currency_code": "USD"}, "presentment_money": {"amount": "70.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583761085, "variant_inventory_management": "shopify", "variant_title": "white", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "11.67", "price_set": {"shop_money": {"amount": "11.67", "currency_code": "USD"}, "presentment_money": {"amount": "11.67", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}, {"id": 11196019540157, "admin_graphql_api_id": "gid://shopify/LineItem/11196019540157", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 218, "name": "Black And White Stripe Tee - Soft", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "83.00", "price_set": {"shop_money": {"amount": "83.00", "currency_code": "USD"}, "presentment_money": {"amount": "83.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583793853, "variant_inventory_management": "shopify", "variant_title": "Soft", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "13.83", "price_set": {"shop_money": {"amount": "13.83", "currency_code": "USD"}, "presentment_money": {"amount": "13.83", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196019507389, "admin_graphql_api_id": "gid://shopify/LineItem/11196019507389", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 466, "name": "Black And White Stripe Tee - white", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 70.0, "price_set": {"shop_money": {"amount": "70.00", "currency_code": "USD"}, "presentment_money": {"amount": "70.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583761085, "variant_inventory_management": "shopify", "variant_title": "white", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "11.67", "price_set": {"shop_money": {"amount": "11.67", "currency_code": "USD"}, "presentment_money": {"amount": "11.67", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 70.0, "product": {"id": 6796219875517, "title": "Black And White Stripe Tee", "handle": "black-and-white-stripe-tee", "vendor": "Lehner and Sons", "tags": "developer-tools-generator", "body_html": "A front view of a woman's black and white striped t-shirt; loose-fitting, with a crew neck. Beach or dinner? This top can do both.", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090583761085, "title": "white", "options": {"Title": "white"}, "images": []}, "variant_options": {"Title": "white"}}}, {"id": 11196019540157, "admin_graphql_api_id": "gid://shopify/LineItem/11196019540157", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 218, "name": "Black And White Stripe Tee - Soft", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 83.0, "price_set": {"shop_money": {"amount": "83.00", "currency_code": "USD"}, "presentment_money": {"amount": "83.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796219875517, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Black And White Stripe Tee", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090583793853, "variant_inventory_management": "shopify", "variant_title": "Soft", "vendor": "Lehner and Sons", "tax_lines": [{"channel_liable": false, "price": "13.83", "price_set": {"shop_money": {"amount": "13.83", "currency_code": "USD"}, "presentment_money": {"amount": "13.83", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 83.0, "product": {"id": 6796219875517, "title": "Black And White Stripe Tee", "handle": "black-and-white-stripe-tee", "vendor": "Lehner and Sons", "tags": "developer-tools-generator", "body_html": "A front view of a woman's black and white striped t-shirt; loose-fitting, with a crew neck. Beach or dinner? This top can do both.", "product_type": "Beauty", "properties": {}, "images": [], "variant": {"sku": 40090583793853, "title": "Soft", "options": {"Title": "Soft"}, "images": []}, "variant_options": {"Title": "Soft"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [{"id": 3791184691389, "carrier_identifier": null, "code": "custom", "delivery_category": null, "discounted_price": "0.00", "discounted_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "phone": null, "price": "0.00", "price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Free shipping", "tax_lines": [], "discount_allocations": []}], "full_landing_site": ""}}, "datetime": "2022-03-17 10:05:29+00:00", "uuid": "c5ffba80-a5d9-11ec-8001-5b0f24a62cf6", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158981} -{"stream": "events", "data": {"object": "event", "id": "3mjwdKJsNch", "statistic_id": "RDXsib", "timestamp": 1647511535, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796220989629, "Name": "4 Ounce Soy Candle", "Variant Name": "Metal", "SKU": "", "Collections": ["Home page"], "Tags": ["developer-tools-generator"], "Vendor": "Hartmann Group", "Variant Option: Title": "Metal", "Quantity": 1, "$event_id": "4446361583805:11196020981949:0", "$value": 19.0}, "datetime": "2022-03-17 10:05:35+00:00", "uuid": "c9934180-a5d9-11ec-8001-e05ed3b557c0", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158983} -{"stream": "events", "data": {"object": "event", "id": "3mjDRxJQr5i", "statistic_id": "RDXsib", "timestamp": 1647511535, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796226560189, "Name": "All Black Sneaker Right Foot", "Variant Name": "Rubber", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Becker - Moore", "Variant Option: Title": "Rubber", "Quantity": 1, "$event_id": "4446361583805:11196020949181:0", "$value": 27.0}, "datetime": "2022-03-17 10:05:35+00:00", "uuid": "c9934180-a5d9-11ec-8001-b4a5ffb54c8c", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158983} -{"stream": "events", "data": {"object": "event", "id": "3mjz6aNHCeE", "statistic_id": "X3f6PC", "timestamp": 1647511558, "event_name": "Fulfilled Order", "event_properties": {"Items": ["All Black Sneaker Right Foot", "4 Ounce Soy Candle"], "Collections": ["Home page"], "Item Count": 2, "tags": [], "Discount Codes": [], "Total Discounts": "0.00", "Source Name": "shopify_draft_order", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4446361583805", "$value": 46.0, "$extra": {"id": 4446361583805, "admin_graphql_api_id": "gid://shopify/Order/4446361583805", "app_id": 1354745, "browser_ip": "31.11.129.15", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 24254404001981, "checkout_token": "51b3cf8cc1a3d339d550283d0bcd7847", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": null, "browser_ip": "31.11.129.15", "browser_width": null, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39"}, "closed_at": "2022-03-17T03:05:09-07:00", "confirmed": true, "contact_email": "airbyte-test@airbyte.com", "created_at": "2022-03-17T03:05:06-07:00", "currency": "USD", "current_subtotal_price": "46.00", "current_subtotal_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "46.00", "current_total_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "current_total_tax": "7.67", "current_total_tax_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "customer_locale": null, "device_id": null, "discount_codes": [], "email": "", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": null, "landing_site_ref": null, "location_id": 63590301885, "name": "#1132", "note": null, "note_attributes": [], "number": 132, "order_number": 1132, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/686b5f94a0ef7a716913bed3b0042d53/authenticate?key=2f1a314592703ad48d1a6b18717e9cb3", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-03-17T03:05:05-07:00", "processing_method": "direct", "reference": null, "referring_site": null, "source_identifier": null, "source_name": "shopify_draft_order", "source_url": null, "subtotal_price": "46.00", "subtotal_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "tags": "", "tax_lines": [{"price": "7.67", "rate": 0.2, "title": "PDV", "price_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "channel_liable": false}], "taxes_included": true, "test": true, "token": "686b5f94a0ef7a716913bed3b0042d53", "total_discounts": "0.00", "total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_line_items_price": "46.00", "total_line_items_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "46.00", "total_price_set": {"shop_money": {"amount": "46.00", "currency_code": "USD"}, "presentment_money": {"amount": "46.00", "currency_code": "USD"}}, "total_price_usd": "46.00", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "7.67", "total_tax_set": {"shop_money": {"amount": "7.67", "currency_code": "USD"}, "presentment_money": {"amount": "7.67", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 112, "updated_at": "2022-03-17T03:05:09-07:00", "user_id": 74861019325, "billing_address": {"first_name": "Test", "address1": "", "phone": "", "city": "", "zip": "", "province": "", "country": "", "last_name": "Test", "address2": "", "company": null, "latitude": null, "longitude": null, "name": "Test Test", "country_code": null, "province_code": null}, "customer": {"id": 5359724789949, "email": "airbyte-test@airbyte.com", "accepts_marketing": false, "created_at": "2021-07-07T08:51:53-07:00", "updated_at": "2022-03-17T03:04:03-07:00", "first_name": "Test", "last_name": "Customer", "orders_count": 13, "state": "disabled", "total_spent": "1646.00", "last_order_id": 3945529835709, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": "#1026", "currency": "USD", "accepts_marketing_updated_at": "2021-07-07T08:51:54-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5359724789949", "default_address": {"id": 7306119610557, "customer_id": 5359724789949, "first_name": "Test", "last_name": "Test", "company": null, "address1": "", "address2": "", "city": "", "province": "", "country": "", "zip": "", "phone": "", "name": "Test Test", "province_code": null, "country_code": null, "country_name": "", "default": true}}, "discount_applications": [], "fulfillments": [{"id": 3974521323709, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974521323709", "created_at": "2022-03-17T03:05:08-07:00", "location_id": 63590301885, "name": "#1132.1", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:05:08-07:00", "line_items": [{"id": 11196020981949, "admin_graphql_api_id": "gid://shopify/LineItem/11196020981949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "19.00", "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [{"channel_liable": false, "price": "3.17", "price_set": {"shop_money": {"amount": "3.17", "currency_code": "USD"}, "presentment_money": {"amount": "3.17", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}, {"id": 3974521389245, "admin_graphql_api_id": "gid://shopify/Fulfillment/3974521389245", "created_at": "2022-03-17T03:05:08-07:00", "location_id": 63590301885, "name": "#1132.2", "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-03-17T03:05:08-07:00", "line_items": [{"id": 11196020949181, "admin_graphql_api_id": "gid://shopify/LineItem/11196020949181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - Rubber", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "27.00", "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597916861, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Becker - Moore", "tax_lines": [{"channel_liable": false, "price": "4.50", "price_set": {"shop_money": {"amount": "4.50", "currency_code": "USD"}, "presentment_money": {"amount": "4.50", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": []}]}], "line_items": [{"id": 11196020949181, "admin_graphql_api_id": "gid://shopify/LineItem/11196020949181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - Rubber", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 27.0, "price_set": {"shop_money": {"amount": "27.00", "currency_code": "USD"}, "presentment_money": {"amount": "27.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597916861, "variant_inventory_management": "shopify", "variant_title": "Rubber", "vendor": "Becker - Moore", "tax_lines": [{"channel_liable": false, "price": "4.50", "price_set": {"shop_money": {"amount": "4.50", "currency_code": "USD"}, "presentment_money": {"amount": "4.50", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 27.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597916861, "title": "Rubber", "options": {"Title": "Rubber"}, "images": []}, "variant_options": {"Title": "Rubber"}}}, {"id": 11196020981949, "admin_graphql_api_id": "gid://shopify/LineItem/11196020981949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 112, "name": "4 Ounce Soy Candle - Metal", "origin_location": {"id": 3000230707389, "country_code": "UA", "province_code": "", "name": "Heroiv UPA 72", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 19.0, "price_set": {"shop_money": {"amount": "19.00", "currency_code": "USD"}, "presentment_money": {"amount": "19.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796220989629, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "4 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090585923773, "variant_inventory_management": "shopify", "variant_title": "Metal", "vendor": "Hartmann Group", "tax_lines": [{"channel_liable": false, "price": "3.17", "price_set": {"shop_money": {"amount": "3.17", "currency_code": "USD"}, "presentment_money": {"amount": "3.17", "currency_code": "USD"}}, "rate": 0.2, "title": "PDV"}], "duties": [], "discount_allocations": [], "line_price": 19.0, "product": {"id": 6796220989629, "title": "4 Ounce Soy Candle", "handle": "4-ounce-soy-candle", "vendor": "Hartmann Group", "tags": "developer-tools-generator", "body_html": "

    Small white soy candle in clear container on wooden table.

    \n

    Updated!

    ", "product_type": "Baby", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle.jpg?v=1624410587", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/4-ounce-soy-candle_x240.jpg?v=1624410587", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301297316029, "created_at": "2021-06-22T18:09:47-07:00", "updated_at": "2021-06-22T18:09:47-07:00"}], "variant": {"sku": 40090585923773, "title": "Metal", "options": {"Title": "Metal"}, "images": []}, "variant_options": {"Title": "Metal"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "full_landing_site": ""}}, "datetime": "2022-03-17 10:05:58+00:00", "uuid": "d748c700-a5d9-11ec-8001-3daf94d71ffc", "person": {"object": "person", "id": "01G4CDT06J4QWW5Z8DQ575BSVY", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$first_name": "Test", "$title": "", "$phone_number": "", "$organization": "Test Company", "$last_name": "Customer", "$email": "airbyte-test@airbyte.com", "$timezone": "", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "airbyte-test@airbyte.com", "first_name": "Test", "last_name": "Customer", "created": "2022-05-31 06:45:46", "updated": "2022-06-15 13:23:34"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158983} -{"stream": "events", "data": {"object": "event", "id": "3py63zGBLUV", "statistic_id": "SPnhc3", "timestamp": 1655293603, "event_name": "Checkout Started", "event_properties": {"Items": ["Anchor Bracelet Leather"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "1.05", "Source Name": "web", "$currency_code": "USD", "$event_id": "25048192712893", "$value": 33.95, "$extra": {"referring_site": "", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1", "token": "7d5a94bef8f62b1b6567ed39cbe1ad5c", "webhook_id": "bbe8dcdb-094e-4416-92c5-93e8f890ecf8", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/7d5a94bef8f62b1b6567ed39cbe1ad5c/recover?key=9df45cc57054bca65a62f774bf7cd974", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/7d5a94bef8f62b1b6567ed39cbe1ad5c/recover?key=9df45cc57054bca65a62f774bf7cd974", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "1.05", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "3324c2a7abb8bc0b829ff76772a58ad0", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 116, "origin_location_id": 3007664259261, "presentment_title": "Anchor Bracelet Leather", "presentment_variant_title": "Concrete", "product_id": 6796229443773, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "Anchor Bracelet Leather", "variant_id": 40090603716797, "variant_title": "Concrete", "variant_price": "35.00", "vendor": "Kohler - Nolan", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 35.0, "price": 35.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}]}}, "datetime": "2022-06-15 11:46:43+00:00", "uuid": "d3901380-eca0-11ec-8001-f2dc29074dab", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158985} -{"stream": "events", "data": {"object": "event", "id": "3py7iJjCWL3", "statistic_id": "TspjNE", "timestamp": 1655293884, "event_name": "Placed Order", "event_properties": {"Items": ["Anchor Bracelet Leather"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "1.05", "Source Name": "web", "$currency_code": "USD", "$event_id": "4554804756669", "$value": 52.57, "$extra": {"id": 4554804756669, "admin_graphql_api_id": "gid://shopify/Order/4554804756669", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "b1508c1323140fe564f3950d5ce3c814", "checkout_id": 25048192712893, "checkout_token": "7d5a94bef8f62b1b6567ed39cbe1ad5c", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T04:51:07-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T04:51:05-07:00", "currency": "USD", "current_subtotal_price": "33.95", "current_subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "current_total_discounts": "1.05", "current_total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "52.57", "current_total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/?_ab=0&_fd=0&_sc=1", "landing_site_ref": null, "location_id": null, "name": "#1134", "note": null, "note_attributes": [], "number": 134, "order_number": 1134, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/1322d3f1b2c93618202951c5991adeef/authenticate?key=5fa975470dee80bffeeead2b4787bdf8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T04:51:04-07:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "33.95", "subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "1322d3f1b2c93618202951c5991adeef", "total_discounts": "1.05", "total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "total_line_items_price": "35.00", "total_line_items_price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "52.57", "total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "total_price_usd": "52.57", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 116, "updated_at": "2022-06-15T04:51:08-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T04:51:06-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075774935229, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075774935229", "created_at": "2022-06-15T04:51:07-07:00", "location_id": 63590301885, "name": "#1134.1", "order_id": 4554804756669, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T04:51:07-07:00", "line_items": [{"id": 11406093779133, "admin_graphql_api_id": "gid://shopify/LineItem/11406093779133", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406093779133, "admin_graphql_api_id": "gid://shopify/LineItem/11406093779133", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 35.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881058205885, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "728dbf22-625c-4348-a5f5-58796c44e7be", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1"}}, "datetime": "2022-06-15 11:51:24+00:00", "uuid": "7b0d4600-eca1-11ec-8001-38af7fa15383", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158985} -{"stream": "events", "data": {"object": "event", "id": "3py9dFcU9ct", "statistic_id": "RDXsib", "timestamp": 1655293894, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229443773, "Name": "Anchor Bracelet Leather", "Variant Name": "Concrete", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Kohler - Nolan", "Variant Option: Title": "Concrete", "Quantity": 1, "$event_id": "4554804756669:11406093779133:0", "$value": 35.0}, "datetime": "2022-06-15 11:51:34+00:00", "uuid": "81032700-eca1-11ec-8001-7e64bbdf38cd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158986} -{"stream": "events", "data": {"object": "event", "id": "3py8A9zweRa", "statistic_id": "X3f6PC", "timestamp": 1655293917, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Anchor Bracelet Leather"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "1.05", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4554804756669", "$value": 52.57, "$extra": {"id": 4554804756669, "admin_graphql_api_id": "gid://shopify/Order/4554804756669", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "b1508c1323140fe564f3950d5ce3c814", "checkout_id": 25048192712893, "checkout_token": "7d5a94bef8f62b1b6567ed39cbe1ad5c", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T04:51:07-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T04:51:05-07:00", "currency": "USD", "current_subtotal_price": "33.95", "current_subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "current_total_discounts": "1.05", "current_total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "52.57", "current_total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/?_ab=0&_fd=0&_sc=1", "landing_site_ref": null, "location_id": null, "name": "#1134", "note": null, "note_attributes": [], "number": 134, "order_number": 1134, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/1322d3f1b2c93618202951c5991adeef/authenticate?key=5fa975470dee80bffeeead2b4787bdf8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T04:51:04-07:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "33.95", "subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "1322d3f1b2c93618202951c5991adeef", "total_discounts": "1.05", "total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "total_line_items_price": "35.00", "total_line_items_price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "52.57", "total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "total_price_usd": "52.57", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 116, "updated_at": "2022-06-15T04:51:08-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T04:51:06-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075774935229, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075774935229", "created_at": "2022-06-15T04:51:07-07:00", "location_id": 63590301885, "name": "#1134.1", "order_id": 4554804756669, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T04:51:07-07:00", "line_items": [{"id": 11406093779133, "admin_graphql_api_id": "gid://shopify/LineItem/11406093779133", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406093779133, "admin_graphql_api_id": "gid://shopify/LineItem/11406093779133", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 35.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881058205885, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "728dbf22-625c-4348-a5f5-58796c44e7be", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1"}}, "datetime": "2022-06-15 11:51:57+00:00", "uuid": "8eb8ac80-eca1-11ec-8001-6bfda94dd785", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158987} -{"stream": "events", "data": {"object": "event", "id": "3pyfAgD67Tc", "statistic_id": "SPnhc3", "timestamp": 1655294958, "event_name": "Checkout Started", "event_properties": {"Items": ["Anchor Bracelet Leather"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "1.05", "Source Name": "web", "$currency_code": "USD", "$event_id": "25048378441917", "$value": 33.95, "$extra": {"referring_site": "https://airbyte-integration-test.myshopify.com/products/anchor-bracelet-leather", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json", "token": "a57117420c7dfc1e827fbe670b1ac9c3", "webhook_id": "17050824-8655-4274-a618-3251f2c34d6e", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/a57117420c7dfc1e827fbe670b1ac9c3/recover?key=09a10265e650d8c99b8f14170b70339c", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/a57117420c7dfc1e827fbe670b1ac9c3/recover?key=09a10265e650d8c99b8f14170b70339c", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "1.05", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "9ea8ce3961ba286c15774c002fbc18bb", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 116, "origin_location_id": 3007664259261, "presentment_title": "Anchor Bracelet Leather", "presentment_variant_title": "Concrete", "product_id": 6796229443773, "properties": null, "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "Anchor Bracelet Leather", "variant_id": 40090603716797, "variant_title": "Concrete", "variant_price": "35.00", "vendor": "Kohler - Nolan", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": 0, "compare_at_price": null, "line_price": 35.0, "price": 35.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}]}}, "datetime": "2022-06-15 12:09:18+00:00", "uuid": "fb34ab00-eca3-11ec-8001-03f65f6b07f1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158988} -{"stream": "events", "data": {"object": "event", "id": "3pydFkJq5Ks", "statistic_id": "TspjNE", "timestamp": 1655295004, "event_name": "Placed Order", "event_properties": {"Items": ["Anchor Bracelet Leather"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "1.05", "Source Name": "web", "$currency_code": "USD", "$event_id": "4554817142973", "$value": 52.57, "$extra": {"id": 4554817142973, "admin_graphql_api_id": "gid://shopify/Order/4554817142973", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048378441917, "checkout_token": "a57117420c7dfc1e827fbe670b1ac9c3", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T05:09:47-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T05:09:45-07:00", "currency": "USD", "current_subtotal_price": "33.95", "current_subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "current_total_discounts": "1.05", "current_total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "52.57", "current_total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1135", "note": null, "note_attributes": [], "number": 135, "order_number": 1135, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c42145a4eaf9ce5d2e58233c8af720d4/authenticate?key=417251b12058fa039d3d74f7c088bf10", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T05:09:44-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/anchor-bracelet-leather", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "33.95", "subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "c42145a4eaf9ce5d2e58233c8af720d4", "total_discounts": "1.05", "total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "total_line_items_price": "35.00", "total_line_items_price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "52.57", "total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "total_price_usd": "52.57", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 116, "updated_at": "2022-06-15T05:09:47-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T05:09:46-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075785814205, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075785814205", "created_at": "2022-06-15T05:09:46-07:00", "location_id": 63590301885, "name": "#1135.1", "order_id": 4554817142973, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T05:09:46-07:00", "line_items": [{"id": 11406117896381, "admin_graphql_api_id": "gid://shopify/LineItem/11406117896381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406117896381, "admin_graphql_api_id": "gid://shopify/LineItem/11406117896381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 35.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881069314237, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "944f9aef-6f5a-4dde-8f03-50289e03245d", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 12:10:04+00:00", "uuid": "169fb600-eca4-11ec-8001-7cb15ac3c8b9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158988} -{"stream": "events", "data": {"object": "event", "id": "3pydFmdGwk6", "statistic_id": "RDXsib", "timestamp": 1655295014, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229443773, "Name": "Anchor Bracelet Leather", "Variant Name": "Concrete", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Kohler - Nolan", "Variant Option: Title": "Concrete", "Quantity": 1, "$event_id": "4554817142973:11406117896381:0", "$value": 35.0}, "datetime": "2022-06-15 12:10:14+00:00", "uuid": "1c959700-eca4-11ec-8001-d28adc6f29fd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367158989} -{"stream": "events", "data": {"object": "event", "id": "3pyfgvk6FWd", "statistic_id": "X3f6PC", "timestamp": 1655295036, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Anchor Bracelet Leather"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "1.05", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4554817142973", "$value": 52.57, "$extra": {"id": 4554817142973, "admin_graphql_api_id": "gid://shopify/Order/4554817142973", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048378441917, "checkout_token": "a57117420c7dfc1e827fbe670b1ac9c3", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T05:09:47-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T05:09:45-07:00", "currency": "USD", "current_subtotal_price": "33.95", "current_subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "current_total_discounts": "1.05", "current_total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "52.57", "current_total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1135", "note": null, "note_attributes": [], "number": 135, "order_number": 1135, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/c42145a4eaf9ce5d2e58233c8af720d4/authenticate?key=417251b12058fa039d3d74f7c088bf10", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T05:09:44-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/anchor-bracelet-leather", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "33.95", "subtotal_price_set": {"shop_money": {"amount": "33.95", "currency_code": "USD"}, "presentment_money": {"amount": "33.95", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "c42145a4eaf9ce5d2e58233c8af720d4", "total_discounts": "1.05", "total_discounts_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "total_line_items_price": "35.00", "total_line_items_price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "52.57", "total_price_set": {"shop_money": {"amount": "52.57", "currency_code": "USD"}, "presentment_money": {"amount": "52.57", "currency_code": "USD"}}, "total_price_usd": "52.57", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 116, "updated_at": "2022-06-15T05:09:47-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T05:09:46-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075785814205, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075785814205", "created_at": "2022-06-15T05:09:46-07:00", "location_id": 63590301885, "name": "#1135.1", "order_id": 4554817142973, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T05:09:46-07:00", "line_items": [{"id": 11406117896381, "admin_graphql_api_id": "gid://shopify/LineItem/11406117896381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "35.00", "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406117896381, "admin_graphql_api_id": "gid://shopify/LineItem/11406117896381", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 116, "name": "Anchor Bracelet Leather - Concrete", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 35.0, "price_set": {"shop_money": {"amount": "35.00", "currency_code": "USD"}, "presentment_money": {"amount": "35.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229443773, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603716797, "variant_inventory_management": "shopify", "variant_title": "Concrete", "vendor": "Kohler - Nolan", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.05", "amount_set": {"shop_money": {"amount": "1.05", "currency_code": "USD"}, "presentment_money": {"amount": "1.05", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 35.0, "product": {"id": 6796229443773, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather", "vendor": "Kohler - Nolan", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Electronics", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather.jpg?v=1624410647", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_x240.jpg?v=1624410647", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301303279805, "created_at": "2021-06-22T18:10:47-07:00", "updated_at": "2021-06-22T18:10:47-07:00"}], "variant": {"sku": 40090603716797, "title": "Concrete", "options": {"Title": "Concrete"}, "images": []}, "variant_options": {"Title": "Concrete"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881069314237, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "944f9aef-6f5a-4dde-8f03-50289e03245d", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 12:10:36+00:00", "uuid": "29b28600-eca4-11ec-8001-7bc5e951c0c4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159409} -{"stream": "events", "data": {"object": "event", "id": "3pyfgvk6G7y", "statistic_id": "SPnhc3", "timestamp": 1655295395, "event_name": "Checkout Started", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "$event_id": "25048437719229", "$value": 57.23, "$extra": {"referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json", "token": "cf5d16a0a0688905bd551c6dec591506", "webhook_id": "f5b72c6f-48c2-4fea-b5cf-992437dc2807", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/cf5d16a0a0688905bd551c6dec591506/recover?key=b553390f770b42d05d805b8156ff4f54", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/cf5d16a0a0688905bd551c6dec591506/recover?key=b553390f770b42d05d805b8156ff4f54", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "1.77", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "4d4c5e2fda4069b37a289ed485bb9b4d", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 0, "origin_location_id": 3007664259261, "presentment_title": "All Black Sneaker Right Foot", "presentment_variant_title": "ivory", "product_id": 6796226560189, "properties": null, "quantity": 1.0, "requires_shipping": false, "sku": "", "tax_lines": [], "taxable": true, "title": "All Black Sneaker Right Foot", "variant_id": 40090597884093, "variant_title": "ivory", "variant_price": "59.00", "vendor": "Becker - Moore", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": 0, "compare_at_price": null, "line_price": 59.0, "price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}]}}, "datetime": "2022-06-15 12:16:35+00:00", "uuid": "ffad9380-eca4-11ec-8001-08fd790534b2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159412} -{"stream": "events", "data": {"object": "event", "id": "3pykmDzdtJ5", "statistic_id": "TspjNE", "timestamp": 1655295433, "event_name": "Placed Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "$event_id": "4554821468349", "$value": 57.23, "$extra": {"id": 4554821468349, "admin_graphql_api_id": "gid://shopify/Order/4554821468349", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048437719229, "checkout_token": "cf5d16a0a0688905bd551c6dec591506", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T05:16:55-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T05:16:53-07:00", "currency": "USD", "current_subtotal_price": "57.23", "current_subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_discounts": "1.77", "current_total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.23", "current_total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1136", "note": null, "note_attributes": [], "number": 136, "order_number": 1136, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e4f98630ea44a884e33e700203ce2130/authenticate?key=edf087d6ae55a4541bf1375432f6a4b8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T05:16:53-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "e4f98630ea44a884e33e700203ce2130", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-15T05:16:55-07:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T05:16:54-07:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492285137085, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075788501181, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075788501181", "created_at": "2022-06-15T05:16:55-07:00", "location_id": 63590301885, "name": "#1136.1", "order_id": 4554821468349, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T05:16:55-07:00", "line_items": [{"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "webhook_id": "d1b29992-cf98-48da-b7af-7484284a0857", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 12:17:13+00:00", "uuid": "1653ea80-eca5-11ec-8001-e233f72de2c9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159412} -{"stream": "events", "data": {"object": "event", "id": "3pydFfna3N5", "statistic_id": "RDXsib", "timestamp": 1655295443, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796226560189, "Name": "All Black Sneaker Right Foot", "Variant Name": "ivory", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Becker - Moore", "Variant Option: Title": "ivory", "Quantity": 1, "$event_id": "4554821468349:11406125564093:0", "$value": 59.0}, "datetime": "2022-06-15 12:17:23+00:00", "uuid": "1c49cb80-eca5-11ec-8001-70b5e5880384", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159414} -{"stream": "events", "data": {"object": "event", "id": "3pykmGuYzAM", "statistic_id": "X3f6PC", "timestamp": 1655295465, "event_name": "Fulfilled Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4554821468349", "$value": 57.23, "$extra": {"id": 4554821468349, "admin_graphql_api_id": "gid://shopify/Order/4554821468349", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048437719229, "checkout_token": "cf5d16a0a0688905bd551c6dec591506", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T05:16:55-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T05:16:53-07:00", "currency": "USD", "current_subtotal_price": "57.23", "current_subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_discounts": "1.77", "current_total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.23", "current_total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1136", "note": null, "note_attributes": [], "number": 136, "order_number": 1136, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e4f98630ea44a884e33e700203ce2130/authenticate?key=edf087d6ae55a4541bf1375432f6a4b8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T05:16:53-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "e4f98630ea44a884e33e700203ce2130", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-15T05:16:55-07:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T05:16:54-07:00", "first_name": "Iryna", "last_name": "Grankova", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492285137085, "customer_id": 5362027233469, "first_name": "Iryna", "last_name": "Grankova", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Iryna Grankova", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075788501181, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075788501181", "created_at": "2022-06-15T05:16:55-07:00", "location_id": 63590301885, "name": "#1136.1", "order_id": 4554821468349, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T05:16:55-07:00", "line_items": [{"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "webhook_id": "1b002c71-ac6e-49fd-9402-ecbd0e5dcebc", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 12:17:45+00:00", "uuid": "2966ba80-eca5-11ec-8001-1e82586b5fd1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159415} -{"stream": "events", "data": {"object": "event", "id": "3pyG6QEZBFZ", "statistic_id": "SPnhc3", "timestamp": 1655299097, "event_name": "Checkout Started", "event_properties": {"Items": ["Amber Beard Oil Bottle"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "1.11", "Source Name": "web", "$currency_code": "USD", "$event_id": "25048907317437", "$value": 35.89, "$extra": {"referring_site": "https://airbyte-integration-test.myshopify.com/products/amber-beard-oil-bottle", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json", "token": "e8fa46467215eab1d1a7028bb3e79407", "webhook_id": "69baa764-5026-467d-97ec-e28f48843196", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/e8fa46467215eab1d1a7028bb3e79407/recover?key=361ec0d1926bac028203450374fc1ea0", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/e8fa46467215eab1d1a7028bb3e79407/recover?key=361ec0d1926bac028203450374fc1ea0", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "1.11", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "1a36e046c8bc8c7a9f4e7fbbe405047f", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 185, "origin_location_id": 3007664259261, "presentment_title": "Amber Beard Oil Bottle", "presentment_variant_title": "Cotton", "product_id": 6796229574845, "properties": null, "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "Amber Beard Oil Bottle", "variant_id": 40090604110013, "variant_title": "Cotton", "variant_price": "37.00", "vendor": "Lubowitz, Buckridge and Huels", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": 0, "compare_at_price": null, "line_price": 37.0, "price": 37.0, "product": {"id": 6796229574845, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle", "vendor": "Lubowitz, Buckridge and Huels", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Outdoors", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_x240.jpg?v=1624410648", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301303410877, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604110013, "title": "Cotton", "options": {"Title": "Cotton"}, "images": []}, "variant_options": {"Title": "Cotton"}}}]}}, "datetime": "2022-06-15 13:18:17+00:00", "uuid": "9e3df280-ecad-11ec-8001-91df35040891", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159417} -{"stream": "events", "data": {"object": "event", "id": "3pyBX5gCXU8", "statistic_id": "TspjNE", "timestamp": 1655299154, "event_name": "Placed Order", "event_properties": {"Items": ["Amber Beard Oil Bottle"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "1.11", "Source Name": "web", "$currency_code": "USD", "$event_id": "4554863542461", "$value": 54.51, "$extra": {"id": 4554863542461, "admin_graphql_api_id": "gid://shopify/Order/4554863542461", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048907317437, "checkout_token": "e8fa46467215eab1d1a7028bb3e79407", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T06:18:57-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T06:18:55-07:00", "currency": "USD", "current_subtotal_price": "35.89", "current_subtotal_price_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "current_total_discounts": "1.11", "current_total_discounts_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "54.51", "current_total_price_set": {"shop_money": {"amount": "54.51", "currency_code": "USD"}, "presentment_money": {"amount": "54.51", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1137", "note": null, "note_attributes": [], "number": 137, "order_number": 1137, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b4ec8cd615bb9fcfd18d79256dded91b/authenticate?key=4b2960a7da17d0d721b343b0db60bedf", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T06:18:54-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/amber-beard-oil-bottle", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "35.89", "subtotal_price_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "b4ec8cd615bb9fcfd18d79256dded91b", "total_discounts": "1.11", "total_discounts_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "total_line_items_price": "37.00", "total_line_items_price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "54.51", "total_price_set": {"shop_money": {"amount": "54.51", "currency_code": "USD"}, "presentment_money": {"amount": "54.51", "currency_code": "USD"}}, "total_price_usd": "54.51", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 185, "updated_at": "2022-06-15T06:18:57-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T06:18:56-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075842371773, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075842371773", "created_at": "2022-06-15T06:18:57-07:00", "location_id": 63590301885, "name": "#1137.1", "order_id": 4554863542461, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T06:18:57-07:00", "line_items": [{"id": 11406207156413, "admin_graphql_api_id": "gid://shopify/LineItem/11406207156413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406207156413, "admin_graphql_api_id": "gid://shopify/LineItem/11406207156413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 37.0, "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 37.0, "product": {"id": 6796229574845, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle", "vendor": "Lubowitz, Buckridge and Huels", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Outdoors", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_x240.jpg?v=1624410648", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301303410877, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604110013, "title": "Cotton", "options": {"Title": "Cotton"}, "images": []}, "variant_options": {"Title": "Cotton"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881109815485, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "2bd2c7e0-c9ed-47db-b5c2-5120804e950b", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 13:19:14+00:00", "uuid": "c0377500-ecad-11ec-8001-08647d80b9c7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159419} -{"stream": "events", "data": {"object": "event", "id": "3pyAZy9SfFf", "statistic_id": "RDXsib", "timestamp": 1655299164, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229574845, "Name": "Amber Beard Oil Bottle", "Variant Name": "Cotton", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Lubowitz, Buckridge and Huels", "Variant Option: Title": "Cotton", "Quantity": 1, "$event_id": "4554863542461:11406207156413:0", "$value": 37.0}, "datetime": "2022-06-15 13:19:24+00:00", "uuid": "c62d5600-ecad-11ec-8001-e7e9b2cfa7f4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159421} -{"stream": "events", "data": {"object": "event", "id": "3pyF9nu2nkG", "statistic_id": "X3f6PC", "timestamp": 1655299187, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Amber Beard Oil Bottle"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "1.11", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4554863542461", "$value": 54.51, "$extra": {"id": 4554863542461, "admin_graphql_api_id": "gid://shopify/Order/4554863542461", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048907317437, "checkout_token": "e8fa46467215eab1d1a7028bb3e79407", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T06:18:57-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T06:18:55-07:00", "currency": "USD", "current_subtotal_price": "35.89", "current_subtotal_price_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "current_total_discounts": "1.11", "current_total_discounts_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "54.51", "current_total_price_set": {"shop_money": {"amount": "54.51", "currency_code": "USD"}, "presentment_money": {"amount": "54.51", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1137", "note": null, "note_attributes": [], "number": 137, "order_number": 1137, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/b4ec8cd615bb9fcfd18d79256dded91b/authenticate?key=4b2960a7da17d0d721b343b0db60bedf", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T06:18:54-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/amber-beard-oil-bottle", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "35.89", "subtotal_price_set": {"shop_money": {"amount": "35.89", "currency_code": "USD"}, "presentment_money": {"amount": "35.89", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "b4ec8cd615bb9fcfd18d79256dded91b", "total_discounts": "1.11", "total_discounts_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "total_line_items_price": "37.00", "total_line_items_price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "54.51", "total_price_set": {"shop_money": {"amount": "54.51", "currency_code": "USD"}, "presentment_money": {"amount": "54.51", "currency_code": "USD"}}, "total_price_usd": "54.51", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 185, "updated_at": "2022-06-15T06:18:57-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T06:18:56-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075842371773, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075842371773", "created_at": "2022-06-15T06:18:57-07:00", "location_id": 63590301885, "name": "#1137.1", "order_id": 4554863542461, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T06:18:57-07:00", "line_items": [{"id": 11406207156413, "admin_graphql_api_id": "gid://shopify/LineItem/11406207156413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "37.00", "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406207156413, "admin_graphql_api_id": "gid://shopify/LineItem/11406207156413", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 185, "name": "Amber Beard Oil Bottle - Cotton", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 37.0, "price_set": {"shop_money": {"amount": "37.00", "currency_code": "USD"}, "presentment_money": {"amount": "37.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229574845, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604110013, "variant_inventory_management": "shopify", "variant_title": "Cotton", "vendor": "Lubowitz, Buckridge and Huels", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.11", "amount_set": {"shop_money": {"amount": "1.11", "currency_code": "USD"}, "presentment_money": {"amount": "1.11", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 37.0, "product": {"id": 6796229574845, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle", "vendor": "Lubowitz, Buckridge and Huels", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Outdoors", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_x240.jpg?v=1624410648", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301303410877, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604110013, "title": "Cotton", "options": {"Title": "Cotton"}, "images": []}, "variant_options": {"Title": "Cotton"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881109815485, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "2bd2c7e0-c9ed-47db-b5c2-5120804e950b", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 13:19:47+00:00", "uuid": "d3e2db80-ecad-11ec-8001-44957936e899", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159421} -{"stream": "events", "data": {"object": "event", "id": "3pyG6SCbQ2M", "statistic_id": "SPnhc3", "timestamp": 1655299189, "event_name": "Checkout Started", "event_properties": {"Items": ["Amber Beard Oil Bottle", "8 Ounce Soy Candle", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "Discount Codes": [], "Total Discounts": "9.96", "Source Name": "web", "$currency_code": "USD", "$event_id": "25048917868733", "$value": 322.04, "$extra": {"referring_site": "", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1", "token": "0d4a7e03b4f41897dea29c6fce5323b6", "webhook_id": "9e365eb2-c138-46b4-a8ec-8438579a6ca8", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/0d4a7e03b4f41897dea29c6fce5323b6/recover?key=c34c1fe5d6ea1de8544374af4c5cd67f", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/0d4a7e03b4f41897dea29c6fce5323b6/recover?key=c34c1fe5d6ea1de8544374af4c5cd67f", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "3.51", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "bdff230dc1a9f23f605b29b9e8281edd", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 319, "origin_location_id": 3007664259261, "presentment_title": "Amber Beard Oil Bottle", "presentment_variant_title": "turquoise", "product_id": 6796232851645, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "Amber Beard Oil Bottle", "variant_id": 40090610663613, "variant_title": "turquoise", "variant_price": "117.00", "vendor": "Breitenberg - Okuneva", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 117.0, "price": 117.0, "product": {"id": 6796232851645, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle-1", "vendor": "Breitenberg - Okuneva", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Home", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_776c81ea-07bc-4d6d-8c8b-769035dff699.jpg?v=1624410672", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_776c81ea-07bc-4d6d-8c8b-769035dff699_x240.jpg?v=1624410672", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301305508029, "created_at": "2021-06-22T18:11:12-07:00", "updated_at": "2021-06-22T18:11:12-07:00"}], "variant": {"sku": 40090610663613, "title": "turquoise", "options": {"Title": "turquoise"}, "images": []}, "variant_options": {"Title": "turquoise"}}}, {"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "3.06", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "0fc2415af35ff059955ef313c43fced8", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 63, "origin_location_id": 3007664259261, "presentment_title": "8 Ounce Soy Candle", "presentment_variant_title": "Wooden", "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "8 Ounce Soy Candle", "variant_id": 40090604011709, "variant_title": "Wooden", "variant_price": "102.00", "vendor": "Bosco Inc", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 102.0, "price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "3.39", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "61e98b7b185f44590ab53ed410aa8492", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 127, "origin_location_id": 3007664259261, "presentment_title": "8 Ounce Soy Candle", "presentment_variant_title": "purple", "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "8 Ounce Soy Candle", "variant_id": 40090603946173, "variant_title": "purple", "variant_price": "113.00", "vendor": "Bosco Inc", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 113.0, "price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}]}}, "datetime": "2022-06-15 13:19:49+00:00", "uuid": "d5140880-ecad-11ec-8001-b2cb97275efc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159423} -{"stream": "events", "data": {"object": "event", "id": "3pyFLZWWH8V", "statistic_id": "TspjNE", "timestamp": 1655299232, "event_name": "Placed Order", "event_properties": {"Items": ["Amber Beard Oil Bottle", "8 Ounce Soy Candle", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "9.96", "Source Name": "web", "$currency_code": "USD", "$event_id": "4554864656573", "$value": 340.66, "$extra": {"id": 4554864656573, "admin_graphql_api_id": "gid://shopify/Order/4554864656573", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "d673e302f46c11faba291d9bad815c03", "checkout_id": 25048917868733, "checkout_token": "0d4a7e03b4f41897dea29c6fce5323b6", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": null, "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T06:20:14-07:00", "currency": "USD", "current_subtotal_price": "322.04", "current_subtotal_price_set": {"shop_money": {"amount": "322.04", "currency_code": "USD"}, "presentment_money": {"amount": "322.04", "currency_code": "USD"}}, "current_total_discounts": "9.96", "current_total_discounts_set": {"shop_money": {"amount": "9.96", "currency_code": "USD"}, "presentment_money": {"amount": "9.96", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "340.66", "current_total_price_set": {"shop_money": {"amount": "340.66", "currency_code": "USD"}, "presentment_money": {"amount": "340.66", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/?_ab=0&_fd=0&_sc=1", "landing_site_ref": null, "location_id": null, "name": "#1138", "note": null, "note_attributes": [], "number": 138, "order_number": 1138, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/03d4039f05e26d5330c1e94c255c4e81/authenticate?key=c894f99e6a3063e07d3e32eef1f1ffc8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T06:20:12-07:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "322.04", "subtotal_price_set": {"shop_money": {"amount": "322.04", "currency_code": "USD"}, "presentment_money": {"amount": "322.04", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "03d4039f05e26d5330c1e94c255c4e81", "total_discounts": "9.96", "total_discounts_set": {"shop_money": {"amount": "9.96", "currency_code": "USD"}, "presentment_money": {"amount": "9.96", "currency_code": "USD"}}, "total_line_items_price": "332.00", "total_line_items_price_set": {"shop_money": {"amount": "332.00", "currency_code": "USD"}, "presentment_money": {"amount": "332.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "340.66", "total_price_set": {"shop_money": {"amount": "340.66", "currency_code": "USD"}, "presentment_money": {"amount": "340.66", "currency_code": "USD"}}, "total_price_usd": "340.66", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 509, "updated_at": "2022-06-15T06:20:16-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T06:20:15-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075843190973, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075843190973", "created_at": "2022-06-15T06:20:15-07:00", "location_id": 63590301885, "name": "#1138.1", "order_id": 4554864656573, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T06:20:15-07:00", "line_items": [{"id": 11406209384637, "admin_graphql_api_id": "gid://shopify/LineItem/11406209384637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 319, "name": "Amber Beard Oil Bottle - turquoise", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "117.00", "price_set": {"shop_money": {"amount": "117.00", "currency_code": "USD"}, "presentment_money": {"amount": "117.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796232851645, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090610663613, "variant_inventory_management": "shopify", "variant_title": "turquoise", "vendor": "Breitenberg - Okuneva", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.51", "amount_set": {"shop_money": {"amount": "3.51", "currency_code": "USD"}, "presentment_money": {"amount": "3.51", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11406209417405, "admin_graphql_api_id": "gid://shopify/LineItem/11406209417405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "102.00", "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.06", "amount_set": {"shop_money": {"amount": "3.06", "currency_code": "USD"}, "presentment_money": {"amount": "3.06", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11406209450173, "admin_graphql_api_id": "gid://shopify/LineItem/11406209450173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406209384637, "admin_graphql_api_id": "gid://shopify/LineItem/11406209384637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 319, "name": "Amber Beard Oil Bottle - turquoise", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 117.0, "price_set": {"shop_money": {"amount": "117.00", "currency_code": "USD"}, "presentment_money": {"amount": "117.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796232851645, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090610663613, "variant_inventory_management": "shopify", "variant_title": "turquoise", "vendor": "Breitenberg - Okuneva", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.51", "amount_set": {"shop_money": {"amount": "3.51", "currency_code": "USD"}, "presentment_money": {"amount": "3.51", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 117.0, "product": {"id": 6796232851645, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle-1", "vendor": "Breitenberg - Okuneva", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Home", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_776c81ea-07bc-4d6d-8c8b-769035dff699.jpg?v=1624410672", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_776c81ea-07bc-4d6d-8c8b-769035dff699_x240.jpg?v=1624410672", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301305508029, "created_at": "2021-06-22T18:11:12-07:00", "updated_at": "2021-06-22T18:11:12-07:00"}], "variant": {"sku": 40090610663613, "title": "turquoise", "options": {"Title": "turquoise"}, "images": []}, "variant_options": {"Title": "turquoise"}}}, {"id": 11406209417405, "admin_graphql_api_id": "gid://shopify/LineItem/11406209417405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.06", "amount_set": {"shop_money": {"amount": "3.06", "currency_code": "USD"}, "presentment_money": {"amount": "3.06", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 11406209450173, "admin_graphql_api_id": "gid://shopify/LineItem/11406209450173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881110765757, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "a548cc17-5de8-4673-95e7-d2899025df85", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1"}}, "datetime": "2022-06-15 13:20:32+00:00", "uuid": "eeb55000-ecad-11ec-8001-54c4027bdf9a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159425} -{"stream": "events", "data": {"object": "event", "id": "3pyF9pVUczD", "statistic_id": "RDXsib", "timestamp": 1655299242, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796232851645, "Name": "Amber Beard Oil Bottle", "Variant Name": "turquoise", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Breitenberg - Okuneva", "Variant Option: Title": "turquoise", "Quantity": 1, "$event_id": "4554864656573:11406209384637:0", "$value": 117.0}, "datetime": "2022-06-15 13:20:42+00:00", "uuid": "f4ab3100-ecad-11ec-8001-3bffd4663bb7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159428} -{"stream": "events", "data": {"object": "event", "id": "3pyFt8Nr62K", "statistic_id": "RDXsib", "timestamp": 1655299242, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "purple", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "purple", "Quantity": 1, "$event_id": "4554864656573:11406209450173:0", "$value": 113.0}, "datetime": "2022-06-15 13:20:42+00:00", "uuid": "f4ab3100-ecad-11ec-8001-81b3588486b1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159428} -{"stream": "events", "data": {"object": "event", "id": "3pyGJu9VPKf", "statistic_id": "RDXsib", "timestamp": 1655299242, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "Wooden", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "Wooden", "Quantity": 1, "$event_id": "4554864656573:11406209417405:0", "$value": 102.0}, "datetime": "2022-06-15 13:20:42+00:00", "uuid": "f4ab3100-ecad-11ec-8001-ea898885fb85", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159429} -{"stream": "events", "data": {"object": "event", "id": "3pyF9kwQkxF", "statistic_id": "X3f6PC", "timestamp": 1655299265, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Amber Beard Oil Bottle", "8 Ounce Soy Candle", "8 Ounce Soy Candle"], "Collections": [], "Item Count": 3, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "9.96", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4554864656573", "$value": 340.66, "$extra": {"id": 4554864656573, "admin_graphql_api_id": "gid://shopify/Order/4554864656573", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "d673e302f46c11faba291d9bad815c03", "checkout_id": 25048917868733, "checkout_token": "0d4a7e03b4f41897dea29c6fce5323b6", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": null, "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T06:20:14-07:00", "currency": "USD", "current_subtotal_price": "322.04", "current_subtotal_price_set": {"shop_money": {"amount": "322.04", "currency_code": "USD"}, "presentment_money": {"amount": "322.04", "currency_code": "USD"}}, "current_total_discounts": "9.96", "current_total_discounts_set": {"shop_money": {"amount": "9.96", "currency_code": "USD"}, "presentment_money": {"amount": "9.96", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "340.66", "current_total_price_set": {"shop_money": {"amount": "340.66", "currency_code": "USD"}, "presentment_money": {"amount": "340.66", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/?_ab=0&_fd=0&_sc=1", "landing_site_ref": null, "location_id": null, "name": "#1138", "note": null, "note_attributes": [], "number": 138, "order_number": 1138, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/03d4039f05e26d5330c1e94c255c4e81/authenticate?key=c894f99e6a3063e07d3e32eef1f1ffc8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T06:20:12-07:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "322.04", "subtotal_price_set": {"shop_money": {"amount": "322.04", "currency_code": "USD"}, "presentment_money": {"amount": "322.04", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "03d4039f05e26d5330c1e94c255c4e81", "total_discounts": "9.96", "total_discounts_set": {"shop_money": {"amount": "9.96", "currency_code": "USD"}, "presentment_money": {"amount": "9.96", "currency_code": "USD"}}, "total_line_items_price": "332.00", "total_line_items_price_set": {"shop_money": {"amount": "332.00", "currency_code": "USD"}, "presentment_money": {"amount": "332.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "340.66", "total_price_set": {"shop_money": {"amount": "340.66", "currency_code": "USD"}, "presentment_money": {"amount": "340.66", "currency_code": "USD"}}, "total_price_usd": "340.66", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 509, "updated_at": "2022-06-15T06:20:16-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T06:20:15-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075843190973, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075843190973", "created_at": "2022-06-15T06:20:15-07:00", "location_id": 63590301885, "name": "#1138.1", "order_id": 4554864656573, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T06:20:15-07:00", "line_items": [{"id": 11406209384637, "admin_graphql_api_id": "gid://shopify/LineItem/11406209384637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 319, "name": "Amber Beard Oil Bottle - turquoise", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "117.00", "price_set": {"shop_money": {"amount": "117.00", "currency_code": "USD"}, "presentment_money": {"amount": "117.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796232851645, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090610663613, "variant_inventory_management": "shopify", "variant_title": "turquoise", "vendor": "Breitenberg - Okuneva", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.51", "amount_set": {"shop_money": {"amount": "3.51", "currency_code": "USD"}, "presentment_money": {"amount": "3.51", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11406209417405, "admin_graphql_api_id": "gid://shopify/LineItem/11406209417405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "102.00", "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.06", "amount_set": {"shop_money": {"amount": "3.06", "currency_code": "USD"}, "presentment_money": {"amount": "3.06", "currency_code": "USD"}}, "discount_application_index": 0}]}, {"id": 11406209450173, "admin_graphql_api_id": "gid://shopify/LineItem/11406209450173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406209384637, "admin_graphql_api_id": "gid://shopify/LineItem/11406209384637", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 319, "name": "Amber Beard Oil Bottle - turquoise", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 117.0, "price_set": {"shop_money": {"amount": "117.00", "currency_code": "USD"}, "presentment_money": {"amount": "117.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796232851645, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Amber Beard Oil Bottle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090610663613, "variant_inventory_management": "shopify", "variant_title": "turquoise", "vendor": "Breitenberg - Okuneva", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.51", "amount_set": {"shop_money": {"amount": "3.51", "currency_code": "USD"}, "presentment_money": {"amount": "3.51", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 117.0, "product": {"id": 6796232851645, "title": "Amber Beard Oil Bottle", "handle": "amber-beard-oil-bottle-1", "vendor": "Breitenberg - Okuneva", "tags": "developer-tools-generator", "body_html": "Back side of beard oil bottle.", "product_type": "Home", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_776c81ea-07bc-4d6d-8c8b-769035dff699.jpg?v=1624410672", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/amber-beard-oil-bottle_776c81ea-07bc-4d6d-8c8b-769035dff699_x240.jpg?v=1624410672", "alt": null, "width": 5572, "height": 3715, "position": 1, "variant_ids": [], "id": 29301305508029, "created_at": "2021-06-22T18:11:12-07:00", "updated_at": "2021-06-22T18:11:12-07:00"}], "variant": {"sku": 40090610663613, "title": "turquoise", "options": {"Title": "turquoise"}, "images": []}, "variant_options": {"Title": "turquoise"}}}, {"id": 11406209417405, "admin_graphql_api_id": "gid://shopify/LineItem/11406209417405", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 63, "name": "8 Ounce Soy Candle - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 102.0, "price_set": {"shop_money": {"amount": "102.00", "currency_code": "USD"}, "presentment_money": {"amount": "102.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604011709, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.06", "amount_set": {"shop_money": {"amount": "3.06", "currency_code": "USD"}, "presentment_money": {"amount": "3.06", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 102.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090604011709, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}, {"id": 11406209450173, "admin_graphql_api_id": "gid://shopify/LineItem/11406209450173", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3881110765757, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "a548cc17-5de8-4673-95e7-d2899025df85", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1"}}, "datetime": "2022-06-15 13:21:05+00:00", "uuid": "0260b680-ecae-11ec-8001-19a5af19b7bf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159429} -{"stream": "events", "data": {"object": "event", "id": "3pyFt8NqYHW", "statistic_id": "R2WpFy", "timestamp": 1655299543, "event_name": "Refunded Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4554821468349", "$value": 57.23, "$extra": {"id": 4554821468349, "admin_graphql_api_id": "gid://shopify/Order/4554821468349", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25048437719229, "checkout_token": "cf5d16a0a0688905bd551c6dec591506", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-15T06:25:43-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-15T05:16:53-07:00", "currency": "USD", "current_subtotal_price": "0.00", "current_subtotal_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_discounts": "0.00", "current_total_discounts_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "0.00", "current_total_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "refunded", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1136", "note": null, "note_attributes": [], "number": 136, "order_number": 1136, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/e4f98630ea44a884e33e700203ce2130/authenticate?key=edf087d6ae55a4541bf1375432f6a4b8", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-15T05:16:53-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "e4f98630ea44a884e33e700203ce2130", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-15T06:25:43-07:00", "user_id": null, "billing_address": {"first_name": "Iryna", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Grankova", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Iryna Grankova", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-15T06:20:15-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4075788501181, "admin_graphql_api_id": "gid://shopify/Fulfillment/4075788501181", "created_at": "2022-06-15T05:16:55-07:00", "location_id": 63590301885, "name": "#1136.1", "order_id": 4554821468349, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-15T05:16:55-07:00", "line_items": [{"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [{"id": 852809646269, "admin_graphql_api_id": "gid://shopify/Refund/852809646269", "created_at": "2022-06-15T06:25:43-07:00", "note": null, "order_id": 4554821468349, "processed_at": "2022-06-15T06:25:43-07:00", "restock": true, "total_duties_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "user_id": 74861019325, "order_adjustments": [], "transactions": [{"id": 5721170968765, "admin_graphql_api_id": "gid://shopify/OrderTransaction/5721170968765", "amount": "57.23", "authorization": null, "created_at": "2022-06-15T06:25:42-07:00", "currency": "USD", "device_id": null, "error_code": null, "gateway": "bogus", "kind": "refund", "location_id": null, "message": "Bogus Gateway: Forced success", "order_id": 4554821468349, "parent_id": 5721110872253, "processed_at": "2022-06-15T06:25:42-07:00", "receipt": {"paid_amount": "57.23"}, "source_name": "1830279", "status": "success", "test": true, "user_id": null, "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}}], "refund_line_items": [{"id": 363131404477, "line_item_id": 11406125564093, "location_id": 63590301885, "quantity": 1, "restock_type": "return", "subtotal": 57.23, "subtotal_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_tax": 0.0, "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "line_item": {"id": 11406125564093, "admin_graphql_api_id": "gid://shopify/LineItem/11406125564093", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}}], "duties": []}], "shipping_lines": [], "webhook_id": "49f90188-31f8-4674-9a4a-9480bef8f146", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-15 13:25:43+00:00", "uuid": "a8142580-ecae-11ec-8001-306116dc5da7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159432} -{"stream": "events", "data": {"object": "event", "id": "3h2CtCQX6MS", "statistic_id": "WKHXf4", "timestamp": 1655308018, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214430495373716522434701569534115992170"}, "datetime": "2022-06-15 15:46:58+00:00", "uuid": "63928500-ecc2-11ec-8001-743495aaaedd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159434} -{"stream": "events", "data": {"object": "event", "id": "3h2BQsTCgNs", "statistic_id": "Yy9QKx", "timestamp": 1655308021, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308021"}, "datetime": "2022-06-15 15:47:01+00:00", "uuid": "655c4880-ecc2-11ec-8001-38fe003fe3e7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159435} -{"stream": "events", "data": {"object": "event", "id": "3h2B7QpQcgk", "statistic_id": "Yy9QKx", "timestamp": 1655308022, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308022"}, "datetime": "2022-06-15 15:47:02+00:00", "uuid": "65f4df00-ecc2-11ec-8001-f5c5d81ecbc5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159435} -{"stream": "events", "data": {"object": "event", "id": "3h2CfJtbqpR", "statistic_id": "Yy9QKx", "timestamp": 1655308025, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308025"}, "datetime": "2022-06-15 15:47:05+00:00", "uuid": "67bea280-ecc2-11ec-8001-ce4645a6b4f4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159436} -{"stream": "events", "data": {"object": "event", "id": "3h2BAEiSycG", "statistic_id": "Yy9QKx", "timestamp": 1655308027, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308027"}, "datetime": "2022-06-15 15:47:07+00:00", "uuid": "68efcf80-ecc2-11ec-8001-e14ef51a03ef", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159436} -{"stream": "events", "data": {"object": "event", "id": "3h2AUQ8mbMi", "statistic_id": "Yy9QKx", "timestamp": 1655308029, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308029"}, "datetime": "2022-06-15 15:47:09+00:00", "uuid": "6a20fc80-ecc2-11ec-8001-9c5daaf8eea4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159437} -{"stream": "events", "data": {"object": "event", "id": "3h2AQYwsrKx", "statistic_id": "Yy9QKx", "timestamp": 1655308031, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308031"}, "datetime": "2022-06-15 15:47:11+00:00", "uuid": "6b522980-ecc2-11ec-8001-ea6f8167e6a8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159437} -{"stream": "events", "data": {"object": "event", "id": "3h2CvuTE7Ts", "statistic_id": "Yy9QKx", "timestamp": 1655308032, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308032"}, "datetime": "2022-06-15 15:47:12+00:00", "uuid": "6beac000-ecc2-11ec-8001-7a37e798c58e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159438} -{"stream": "events", "data": {"object": "event", "id": "3h2CvuTE7Tt", "statistic_id": "Yy9QKx", "timestamp": 1655308033, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308033"}, "datetime": "2022-06-15 15:47:13+00:00", "uuid": "6c835680-ecc2-11ec-8001-f4562d9f58b9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159438} -{"stream": "events", "data": {"object": "event", "id": "3h2CCzFJ7fk", "statistic_id": "Yy9QKx", "timestamp": 1655308035, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308035"}, "datetime": "2022-06-15 15:47:15+00:00", "uuid": "6db48380-ecc2-11ec-8001-6ccb9747b487", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159439} -{"stream": "events", "data": {"object": "event", "id": "3h2ChGqPMVN", "statistic_id": "Yy9QKx", "timestamp": 1655308039, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655308039"}, "datetime": "2022-06-15 15:47:19+00:00", "uuid": "7016dd80-ecc2-11ec-8001-3fcfd9607583", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159439} -{"stream": "events", "data": {"object": "event", "id": "3h2J27yvcWL", "statistic_id": "Yy9QKx", "timestamp": 1655309249, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655309249"}, "datetime": "2022-06-15 16:07:29+00:00", "uuid": "414e3680-ecc5-11ec-8001-e72a2fb9aee1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159440} -{"stream": "events", "data": {"object": "event", "id": "3h2JzPWci2m", "statistic_id": "Yy9QKx", "timestamp": 1655309251, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655309251"}, "datetime": "2022-06-15 16:07:31+00:00", "uuid": "427f6380-ecc5-11ec-8001-1d3564331a92", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159440} -{"stream": "events", "data": {"object": "event", "id": "3h2J7yBW6B4", "statistic_id": "WKHXf4", "timestamp": 1655309443, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214430498780527510548068086056505856618"}, "datetime": "2022-06-15 16:10:43+00:00", "uuid": "b4f04380-ecc5-11ec-8001-09a3345d19d1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159441} -{"stream": "events", "data": {"object": "event", "id": "3h2KwtWX9mM", "statistic_id": "Yy9QKx", "timestamp": 1655309447, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309447"}, "datetime": "2022-06-15 16:10:47+00:00", "uuid": "b7529d80-ecc5-11ec-8001-296b645585d2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159442} -{"stream": "events", "data": {"object": "event", "id": "3h2HfLzWGAg", "statistic_id": "Yy9QKx", "timestamp": 1655309448, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309448"}, "datetime": "2022-06-15 16:10:48+00:00", "uuid": "b7eb3400-ecc5-11ec-8001-38957b521da9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159442} -{"stream": "events", "data": {"object": "event", "id": "3h2JQ3XF2VT", "statistic_id": "Yy9QKx", "timestamp": 1655309449, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309449"}, "datetime": "2022-06-15 16:10:49+00:00", "uuid": "b883ca80-ecc5-11ec-8001-1e782b2a52c9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159443} -{"stream": "events", "data": {"object": "event", "id": "3h2JPaV69ha", "statistic_id": "Yy9QKx", "timestamp": 1655309453, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309453"}, "datetime": "2022-06-15 16:10:53+00:00", "uuid": "bae62480-ecc5-11ec-8001-9c3d4156fefe", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159443} -{"stream": "events", "data": {"object": "event", "id": "3h2JzavzUU3", "statistic_id": "Yy9QKx", "timestamp": 1655309457, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655309457"}, "datetime": "2022-06-15 16:10:57+00:00", "uuid": "bd487e80-ecc5-11ec-8001-eb3831021db7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159444} -{"stream": "events", "data": {"object": "event", "id": "3h2JvbKCLh5", "statistic_id": "Yy9QKx", "timestamp": 1655309458, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309458"}, "datetime": "2022-06-15 16:10:58+00:00", "uuid": "bde11500-ecc5-11ec-8001-8f02957bd6c4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159444} -{"stream": "events", "data": {"object": "event", "id": "3h2HR3AxJdT", "statistic_id": "Yy9QKx", "timestamp": 1655309459, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309459"}, "datetime": "2022-06-15 16:10:59+00:00", "uuid": "be79ab80-ecc5-11ec-8001-d6c70170bfb1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159444} -{"stream": "events", "data": {"object": "event", "id": "3h2H9JkHYNj", "statistic_id": "Yy9QKx", "timestamp": 1655309460, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309460"}, "datetime": "2022-06-15 16:11:00+00:00", "uuid": "bf124200-ecc5-11ec-8001-e538fb55d586", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159445} -{"stream": "events", "data": {"object": "event", "id": "3h2HcGjCppz", "statistic_id": "Yy9QKx", "timestamp": 1655309461, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309461"}, "datetime": "2022-06-15 16:11:01+00:00", "uuid": "bfaad880-ecc5-11ec-8001-26b6a5627cf9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159445} -{"stream": "events", "data": {"object": "event", "id": "3h2KafuTZ3u", "statistic_id": "Yy9QKx", "timestamp": 1655309574, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655309574"}, "datetime": "2022-06-15 16:12:54+00:00", "uuid": "03054700-ecc6-11ec-8001-648faec476ea", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159445} -{"stream": "events", "data": {"object": "event", "id": "3h2LxQ8Ux7s", "statistic_id": "Yy9QKx", "timestamp": 1655309713, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309713"}, "datetime": "2022-06-15 16:15:13+00:00", "uuid": "55defe80-ecc6-11ec-8001-a9ddd14b52f6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159445} -{"stream": "events", "data": {"object": "event", "id": "3h2JLtfYH4G", "statistic_id": "Yy9QKx", "timestamp": 1655309714, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655309714"}, "datetime": "2022-06-15 16:15:14+00:00", "uuid": "56779500-ecc6-11ec-8001-02f92f0910c0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159446} -{"stream": "events", "data": {"object": "event", "id": "3h2JARTLK3a", "statistic_id": "WKHXf4", "timestamp": 1655309811, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214430499414352810662182786804857459306"}, "datetime": "2022-06-15 16:16:51+00:00", "uuid": "90489b80-ecc6-11ec-8001-1bda8024e8da", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159446} -{"stream": "events", "data": {"object": "event", "id": "3h2KhfB7KpH", "statistic_id": "Yy9QKx", "timestamp": 1655309815, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309815"}, "datetime": "2022-06-15 16:16:55+00:00", "uuid": "92aaf580-ecc6-11ec-8001-b2862fda309d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159447} -{"stream": "events", "data": {"object": "event", "id": "3h2LqSJwWYc", "statistic_id": "Yy9QKx", "timestamp": 1655309816, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309816"}, "datetime": "2022-06-15 16:16:56+00:00", "uuid": "93438c00-ecc6-11ec-8001-62c4d7b444c5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159447} -{"stream": "events", "data": {"object": "event", "id": "3h2LvErBRg6", "statistic_id": "Yy9QKx", "timestamp": 1655309817, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309817"}, "datetime": "2022-06-15 16:16:57+00:00", "uuid": "93dc2280-ecc6-11ec-8001-001230e10b9d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159448} -{"stream": "events", "data": {"object": "event", "id": "3h2LkwfnYjJ", "statistic_id": "Yy9QKx", "timestamp": 1655309818, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309818"}, "datetime": "2022-06-15 16:16:58+00:00", "uuid": "9474b900-ecc6-11ec-8001-759297833cea", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159448} -{"stream": "events", "data": {"object": "event", "id": "3h2LfpumLmd", "statistic_id": "Yy9QKx", "timestamp": 1655309820, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309820"}, "datetime": "2022-06-15 16:17:00+00:00", "uuid": "95a5e600-ecc6-11ec-8001-01f6d154bfcc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159448} -{"stream": "events", "data": {"object": "event", "id": "3h2JHxHyPsi", "statistic_id": "Yy9QKx", "timestamp": 1655309825, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309825"}, "datetime": "2022-06-15 16:17:05+00:00", "uuid": "98a0d680-ecc6-11ec-8001-811043cfd9cf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159449} -{"stream": "events", "data": {"object": "event", "id": "3h2LqG7ZBVR", "statistic_id": "Yy9QKx", "timestamp": 1655309826, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309826"}, "datetime": "2022-06-15 16:17:06+00:00", "uuid": "99396d00-ecc6-11ec-8001-26d4ff9e50f5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159449} -{"stream": "events", "data": {"object": "event", "id": "3h2KxKLKrQA", "statistic_id": "Yy9QKx", "timestamp": 1655309827, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309827"}, "datetime": "2022-06-15 16:17:07+00:00", "uuid": "99d20380-ecc6-11ec-8001-5dcc65cf5197", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159449} -{"stream": "events", "data": {"object": "event", "id": "3h2KPXP2Dvz", "statistic_id": "Yy9QKx", "timestamp": 1655309874, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655309874"}, "datetime": "2022-06-15 16:17:54+00:00", "uuid": "b5d5a500-ecc6-11ec-8001-80988dfe349a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159450} -{"stream": "events", "data": {"object": "event", "id": "3h2LzVMiXGe", "statistic_id": "Yy9QKx", "timestamp": 1655309874, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655309874"}, "datetime": "2022-06-15 16:17:54+00:00", "uuid": "b5d5a500-ecc6-11ec-8001-8ae6fa1f48f8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159450} -{"stream": "events", "data": {"object": "event", "id": "3h2MsJeJuau", "statistic_id": "Yy9QKx", "timestamp": 1655309875, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655309875"}, "datetime": "2022-06-15 16:17:55+00:00", "uuid": "b66e3b80-ecc6-11ec-8001-21e81c9a4bf2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159450} -{"stream": "events", "data": {"object": "event", "id": "3h2Nm3ebbtV", "statistic_id": "Yy9QKx", "timestamp": 1655310306, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655310306"}, "datetime": "2022-06-15 16:25:06+00:00", "uuid": "b7539d00-ecc7-11ec-8001-7a911e2083ca", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159451} -{"stream": "events", "data": {"object": "event", "id": "3h2PhxFR787", "statistic_id": "Yy9QKx", "timestamp": 1655310306, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655310306"}, "datetime": "2022-06-15 16:25:06+00:00", "uuid": "b7539d00-ecc7-11ec-8001-9042118e0281", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159451} -{"stream": "events", "data": {"object": "event", "id": "3h2UmrSYdDN", "statistic_id": "Yy9QKx", "timestamp": 1655311807, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655311807"}, "datetime": "2022-06-15 16:50:07+00:00", "uuid": "35fe0980-eccb-11ec-8001-2569b0379caf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159451} -{"stream": "events", "data": {"object": "event", "id": "3h2ViXHPS3z", "statistic_id": "Yy9QKx", "timestamp": 1655311810, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655311810"}, "datetime": "2022-06-15 16:50:10+00:00", "uuid": "37c7cd00-eccb-11ec-8001-6e434effaaf2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159451} -{"stream": "events", "data": {"object": "event", "id": "3h2VJ4rd7TG", "statistic_id": "Yy9QKx", "timestamp": 1655311841, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655311841"}, "datetime": "2022-06-15 16:50:41+00:00", "uuid": "4a420680-eccb-11ec-8001-cb5d9cc2b1ee", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159452} -{"stream": "events", "data": {"object": "event", "id": "3h2VMPuVxvE", "statistic_id": "Yy9QKx", "timestamp": 1655312104, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655312104"}, "datetime": "2022-06-15 16:55:04+00:00", "uuid": "e704a400-eccb-11ec-8001-7594970b31d4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159452} -{"stream": "events", "data": {"object": "event", "id": "3h2YqC9nw6U", "statistic_id": "Yy9QKx", "timestamp": 1655312104, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655312104"}, "datetime": "2022-06-15 16:55:04+00:00", "uuid": "e704a400-eccb-11ec-8001-a1857fa7a7b6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159452} -{"stream": "events", "data": {"object": "event", "id": "3h2YtfU7Ta3", "statistic_id": "Yy9QKx", "timestamp": 1655312104, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655312104"}, "datetime": "2022-06-15 16:55:04+00:00", "uuid": "e704a400-eccb-11ec-8001-85b8fd59719d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159452} -{"stream": "events", "data": {"object": "event", "id": "3h2X9HmHiXu", "statistic_id": "Yy9QKx", "timestamp": 1655312113, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655312113"}, "datetime": "2022-06-15 16:55:13+00:00", "uuid": "ec61ee80-eccb-11ec-8001-a2a0180b65e0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159453} -{"stream": "events", "data": {"object": "event", "id": "3h2XgUvu6gz", "statistic_id": "Yy9QKx", "timestamp": 1655312113, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655312113"}, "datetime": "2022-06-15 16:55:13+00:00", "uuid": "ec61ee80-eccb-11ec-8001-9f9d6b69e4e7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159453} -{"stream": "events", "data": {"object": "event", "id": "3h2Yn7DvY4N", "statistic_id": "Yy9QKx", "timestamp": 1655312113, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655312113"}, "datetime": "2022-06-15 16:55:13+00:00", "uuid": "ec61ee80-eccb-11ec-8001-8d63589f64a7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159453} -{"stream": "events", "data": {"object": "event", "id": "3h379yC6SZK", "statistic_id": "WKHXf4", "timestamp": 1655313532, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214430506227974786888915819849637188202"}, "datetime": "2022-06-15 17:18:52+00:00", "uuid": "3a2c2600-eccf-11ec-8001-a65e80871dbb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159453} -{"stream": "events", "data": {"object": "event", "id": "3h35Sk9gKEX", "statistic_id": "Yy9QKx", "timestamp": 1655313535, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313535"}, "datetime": "2022-06-15 17:18:55+00:00", "uuid": "3bf5e980-eccf-11ec-8001-57c912d9ea90", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159454} -{"stream": "events", "data": {"object": "event", "id": "3h377pDKrJ7", "statistic_id": "Yy9QKx", "timestamp": 1655313536, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313536"}, "datetime": "2022-06-15 17:18:56+00:00", "uuid": "3c8e8000-eccf-11ec-8001-66f8563e63a6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159454} -{"stream": "events", "data": {"object": "event", "id": "3h37mqfv58h", "statistic_id": "Yy9QKx", "timestamp": 1655313537, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313537"}, "datetime": "2022-06-15 17:18:57+00:00", "uuid": "3d271680-eccf-11ec-8001-aa183deea288", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159454} -{"stream": "events", "data": {"object": "event", "id": "3h35ck5aWpj", "statistic_id": "Yy9QKx", "timestamp": 1655313545, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313545"}, "datetime": "2022-06-15 17:19:05+00:00", "uuid": "41ebca80-eccf-11ec-8001-ac71d1c5338a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159455} -{"stream": "events", "data": {"object": "event", "id": "3h359xeTWsh", "statistic_id": "Yy9QKx", "timestamp": 1655313546, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313546"}, "datetime": "2022-06-15 17:19:06+00:00", "uuid": "42846100-eccf-11ec-8001-f068e1110abc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159455} -{"stream": "events", "data": {"object": "event", "id": "3h35v2JsDuS", "statistic_id": "Yy9QKx", "timestamp": 1655313547, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313547"}, "datetime": "2022-06-15 17:19:07+00:00", "uuid": "431cf780-eccf-11ec-8001-87da32168fe3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159455} -{"stream": "events", "data": {"object": "event", "id": "3h35yVjxmgG", "statistic_id": "WKHXf4", "timestamp": 1655313652, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214430506465659274431708832630269039210"}, "datetime": "2022-06-15 17:20:52+00:00", "uuid": "81b2b200-eccf-11ec-8001-e84e613a8b8d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159455} -{"stream": "events", "data": {"object": "event", "id": "3h35C2fprc3", "statistic_id": "Yy9QKx", "timestamp": 1655313654, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655313654"}, "datetime": "2022-06-15 17:20:54+00:00", "uuid": "82e3df00-eccf-11ec-8001-dffee5f4f290", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159455} -{"stream": "events", "data": {"object": "event", "id": "3h37uChAmDr", "statistic_id": "Yy9QKx", "timestamp": 1655313655, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655313655"}, "datetime": "2022-06-15 17:20:55+00:00", "uuid": "837c7580-eccf-11ec-8001-598c93fdb4a8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159456} -{"stream": "events", "data": {"object": "event", "id": "3h37Z2HgPpW", "statistic_id": "Yy9QKx", "timestamp": 1655313657, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655313657"}, "datetime": "2022-06-15 17:20:57+00:00", "uuid": "84ada280-eccf-11ec-8001-19fe249c0bbd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159456} -{"stream": "events", "data": {"object": "event", "id": "3h36rbP9M7f", "statistic_id": "Yy9QKx", "timestamp": 1655313665, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655313665"}, "datetime": "2022-06-15 17:21:05+00:00", "uuid": "89725680-eccf-11ec-8001-99a5b6f59d90", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159456} -{"stream": "events", "data": {"object": "event", "id": "3h35U9jBmTM", "statistic_id": "Yy9QKx", "timestamp": 1655313666, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655313666"}, "datetime": "2022-06-15 17:21:06+00:00", "uuid": "8a0aed00-eccf-11ec-8001-91c6c721d7b3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159457} -{"stream": "events", "data": {"object": "event", "id": "3h38uvUqcc4", "statistic_id": "Yy9QKx", "timestamp": 1655313873, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655313873"}, "datetime": "2022-06-15 17:24:33+00:00", "uuid": "056c9e80-ecd0-11ec-8001-6e9a6c662deb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159457} -{"stream": "events", "data": {"object": "event", "id": "3h38RJYYaZs", "statistic_id": "Yy9QKx", "timestamp": 1655314371, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655314371"}, "datetime": "2022-06-15 17:32:51+00:00", "uuid": "2e416380-ecd1-11ec-8001-195b3721e2e6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159458} -{"stream": "events", "data": {"object": "event", "id": "3h3afk6zFRz", "statistic_id": "Yy9QKx", "timestamp": 1655314371, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655314371"}, "datetime": "2022-06-15 17:32:51+00:00", "uuid": "2e416380-ecd1-11ec-8001-20299a1d54e1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159458} -{"stream": "events", "data": {"object": "event", "id": "3h3ayZMFfVT", "statistic_id": "Yy9QKx", "timestamp": 1655314371, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655314371"}, "datetime": "2022-06-15 17:32:51+00:00", "uuid": "2e416380-ecd1-11ec-8001-a201ac1884f0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159458} -{"stream": "events", "data": {"object": "event", "id": "3h3b5ZFmn9u", "statistic_id": "Yy9QKx", "timestamp": 1655314371, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655314371"}, "datetime": "2022-06-15 17:32:51+00:00", "uuid": "2e416380-ecd1-11ec-8001-852f399db098", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159459} -{"stream": "events", "data": {"object": "event", "id": "3h3b78hWjFG", "statistic_id": "Yy9QKx", "timestamp": 1655314371, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655314371"}, "datetime": "2022-06-15 17:32:51+00:00", "uuid": "2e416380-ecd1-11ec-8001-8800e3cff6b7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159459} -{"stream": "events", "data": {"object": "event", "id": "3h3isku3hTj", "statistic_id": "Yy9QKx", "timestamp": 1655315900, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655315900"}, "datetime": "2022-06-15 17:58:20+00:00", "uuid": "bd9c4600-ecd4-11ec-8001-cf482420eee3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159459} -{"stream": "events", "data": {"object": "event", "id": "3pBqT2a8zRn", "statistic_id": "Yy9QKx", "timestamp": 1655328676, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655328676"}, "datetime": "2022-06-15 21:31:16+00:00", "uuid": "7cb32a00-ecf2-11ec-8001-37483ad3bcb7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159459} -{"stream": "events", "data": {"object": "event", "id": "3pBrcVfPQqB", "statistic_id": "Yy9QKx", "timestamp": 1655328679, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655328679"}, "datetime": "2022-06-15 21:31:19+00:00", "uuid": "7e7ced80-ecf2-11ec-8001-4ab56bc7b58c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159460} -{"stream": "events", "data": {"object": "event", "id": "3pDxBUbui7g", "statistic_id": "Yy9QKx", "timestamp": 1655350356, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655350356"}, "datetime": "2022-06-16 03:32:36+00:00", "uuid": "f6fca200-ed24-11ec-8001-11b8134b9fb7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159460} -{"stream": "events", "data": {"object": "event", "id": "3pDwkCJjFPZ", "statistic_id": "Yy9QKx", "timestamp": 1655350357, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655350357"}, "datetime": "2022-06-16 03:32:37+00:00", "uuid": "f7953880-ed24-11ec-8001-41cfe82224fb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159460} -{"stream": "events", "data": {"object": "event", "id": "3pEWSunWn6G", "statistic_id": "Yy9QKx", "timestamp": 1655365485, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655365485"}, "datetime": "2022-06-16 07:44:45+00:00", "uuid": "3092d480-ed48-11ec-8001-f33a5dbe79ed", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159460} -{"stream": "events", "data": {"object": "event", "id": "3pEVV498YH3", "statistic_id": "Yy9QKx", "timestamp": 1655365488, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655365488"}, "datetime": "2022-06-16 07:44:48+00:00", "uuid": "325c9800-ed48-11ec-8001-79ff6286fafa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159461} -{"stream": "events", "data": {"object": "event", "id": "3pEXPVBkvKi", "statistic_id": "Yy9QKx", "timestamp": 1655365489, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655365489"}, "datetime": "2022-06-16 07:44:49+00:00", "uuid": "32f52e80-ed48-11ec-8001-00d4f83615c9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159461} -{"stream": "events", "data": {"object": "event", "id": "3pEXwcfxiJ4", "statistic_id": "Yy9QKx", "timestamp": 1655365490, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1655365490"}, "datetime": "2022-06-16 07:44:50+00:00", "uuid": "338dc500-ed48-11ec-8001-3f5ca192b9f2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159461} -{"stream": "events", "data": {"object": "event", "id": "3pEXwbLeNUR", "statistic_id": "Yy9QKx", "timestamp": 1655365491, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655365491"}, "datetime": "2022-06-16 07:44:51+00:00", "uuid": "34265b80-ed48-11ec-8001-c0941b0bfea5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159461} -{"stream": "events", "data": {"object": "event", "id": "3pEXweFZYiV", "statistic_id": "Yy9QKx", "timestamp": 1655365492, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655365492"}, "datetime": "2022-06-16 07:44:52+00:00", "uuid": "34bef200-ed48-11ec-8001-453f37a8dc83", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159462} -{"stream": "events", "data": {"object": "event", "id": "3pF5SBswv2b", "statistic_id": "SPnhc3", "timestamp": 1655367064, "event_name": "Checkout Started", "event_properties": {"Items": ["Back Of Watermelon Enamel Pin", "Anchor Bracelet Leather"], "Collections": [], "Item Count": 2, "Discount Codes": [], "Total Discounts": "5.70", "Source Name": "web", "$currency_code": "USD", "$event_id": "25058292367549", "$value": 184.3, "$extra": {"referring_site": "", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1", "token": "39ac55bdc2ad0a615e6fff6da95f6e0b", "webhook_id": "de08b2f6-292b-48bb-9cf2-a3741de85b21", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/39ac55bdc2ad0a615e6fff6da95f6e0b/recover?key=6ac03b4a87317f8996bbf11ce754f323", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/39ac55bdc2ad0a615e6fff6da95f6e0b/recover?key=6ac03b4a87317f8996bbf11ce754f323", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "3.00", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "400509e3be9f1429df9497db1f03c98f", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 0, "origin_location_id": 3007664259261, "presentment_title": "Back Of Watermelon Enamel Pin", "presentment_variant_title": "mint green", "product_id": 6796229935293, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "tax_lines": [], "taxable": true, "title": "Back Of Watermelon Enamel Pin", "variant_id": 40090604994749, "variant_title": "mint green", "variant_price": "100.00", "vendor": "Murazik and Sons", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 100.0, "price": 100.0, "product": {"id": 6796229935293, "title": "Back Of Watermelon Enamel Pin", "handle": "back-of-watermelon-enamel-pin", "vendor": "Murazik and Sons", "tags": "developer-tools-generator", "body_html": "Back of watermelon pin.", "product_type": "Shoes", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/back-of-watermelon-pin__1.jpg?v=1624410651", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/back-of-watermelon-pin__1_x240.jpg?v=1624410651", "alt": null, "width": 2500, "height": 2500, "position": 1, "variant_ids": [], "id": 29301303673021, "created_at": "2021-06-22T18:10:51-07:00", "updated_at": "2021-06-22T18:10:51-07:00"}], "variant": {"sku": 40090604994749, "title": "mint green", "options": {"Title": "mint green"}, "images": []}, "variant_options": {"Title": "mint green"}}}, {"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "2.70", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "cb1bdd1edfccb1b4fb0bcfe833b448a7", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 367, "origin_location_id": 3007664259261, "presentment_title": "Anchor Bracelet Leather", "presentment_variant_title": "Wooden", "product_id": 6796231835837, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "Anchor Bracelet Leather", "variant_id": 40090609090749, "variant_title": "Wooden", "variant_price": "90.00", "vendor": "O'Hara - Gutmann", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 90.0, "price": 90.0, "product": {"id": 6796231835837, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather-1", "vendor": "O'Hara - Gutmann", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_429381b9-0ea0-43e8-9c3c-15768fe9b68d.jpg?v=1624410665", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_429381b9-0ea0-43e8-9c3c-15768fe9b68d_x240.jpg?v=1624410665", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301304885437, "created_at": "2021-06-22T18:11:05-07:00", "updated_at": "2021-06-22T18:11:05-07:00"}], "variant": {"sku": 40090609090749, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}]}, "$attribution": {"$attributed_event_id": "3pEXweFZYiV", "$send_ts": 1655313530.0, "$message": "SfFuN7", "$flow": "YfYbWb"}}, "datetime": "2022-06-16 08:11:04+00:00", "uuid": "ddbb1c00-ed4b-11ec-8001-88781c4c6682", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159462} -{"stream": "events", "data": {"object": "event", "id": "3pF6wqFkVL4", "statistic_id": "TspjNE", "timestamp": 1655367113, "event_name": "Placed Order", "event_properties": {"Items": ["Back Of Watermelon Enamel Pin", "Anchor Bracelet Leather"], "Collections": [], "Item Count": 2, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "5.70", "Source Name": "web", "$currency_code": "USD", "$event_id": "4557016793277", "$value": 202.92, "$extra": {"id": 4557016793277, "admin_graphql_api_id": "gid://shopify/Order/4557016793277", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "af94773207c3b0d1029cc9e6fbb9496a", "checkout_id": 25058292367549, "checkout_token": "39ac55bdc2ad0a615e6fff6da95f6e0b", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-16T01:11:36-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-16T01:11:33-07:00", "currency": "USD", "current_subtotal_price": "184.30", "current_subtotal_price_set": {"shop_money": {"amount": "184.30", "currency_code": "USD"}, "presentment_money": {"amount": "184.30", "currency_code": "USD"}}, "current_total_discounts": "5.70", "current_total_discounts_set": {"shop_money": {"amount": "5.70", "currency_code": "USD"}, "presentment_money": {"amount": "5.70", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "202.92", "current_total_price_set": {"shop_money": {"amount": "202.92", "currency_code": "USD"}, "presentment_money": {"amount": "202.92", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/?_ab=0&_fd=0&_sc=1", "landing_site_ref": null, "location_id": null, "name": "#1139", "note": null, "note_attributes": [], "number": 139, "order_number": 1139, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a93866a511e84a729484cffdad7bbc96/authenticate?key=6eaf4caad8291de6abd6e3e4bc52990b", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-16T01:11:33-07:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "184.30", "subtotal_price_set": {"shop_money": {"amount": "184.30", "currency_code": "USD"}, "presentment_money": {"amount": "184.30", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "a93866a511e84a729484cffdad7bbc96", "total_discounts": "5.70", "total_discounts_set": {"shop_money": {"amount": "5.70", "currency_code": "USD"}, "presentment_money": {"amount": "5.70", "currency_code": "USD"}}, "total_line_items_price": "190.00", "total_line_items_price_set": {"shop_money": {"amount": "190.00", "currency_code": "USD"}, "presentment_money": {"amount": "190.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "202.92", "total_price_set": {"shop_money": {"amount": "202.92", "currency_code": "USD"}, "presentment_money": {"amount": "202.92", "currency_code": "USD"}}, "total_price_usd": "202.92", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 367, "updated_at": "2022-06-16T01:11:36-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-16T01:11:34-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4076916179133, "admin_graphql_api_id": "gid://shopify/Fulfillment/4076916179133", "created_at": "2022-06-16T01:11:35-07:00", "location_id": 63590301885, "name": "#1139.1", "order_id": 4557016793277, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-16T01:11:35-07:00", "line_items": [{"id": 11410270453949, "admin_graphql_api_id": "gid://shopify/LineItem/11410270453949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 367, "name": "Anchor Bracelet Leather - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "90.00", "price_set": {"shop_money": {"amount": "90.00", "currency_code": "USD"}, "presentment_money": {"amount": "90.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796231835837, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090609090749, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "O'Hara - Gutmann", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.70", "amount_set": {"shop_money": {"amount": "2.70", "currency_code": "USD"}, "presentment_money": {"amount": "2.70", "currency_code": "USD"}}, "discount_application_index": 0}]}]}, {"id": 4076916211901, "admin_graphql_api_id": "gid://shopify/Fulfillment/4076916211901", "created_at": "2022-06-16T01:11:35-07:00", "location_id": 63590301885, "name": "#1139.2", "order_id": 4557016793277, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-16T01:11:35-07:00", "line_items": [{"id": 11410270421181, "admin_graphql_api_id": "gid://shopify/LineItem/11410270421181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Back Of Watermelon Enamel Pin - mint green", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "100.00", "price_set": {"shop_money": {"amount": "100.00", "currency_code": "USD"}, "presentment_money": {"amount": "100.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229935293, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "Back Of Watermelon Enamel Pin", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604994749, "variant_inventory_management": "shopify", "variant_title": "mint green", "vendor": "Murazik and Sons", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.00", "amount_set": {"shop_money": {"amount": "3.00", "currency_code": "USD"}, "presentment_money": {"amount": "3.00", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11410270421181, "admin_graphql_api_id": "gid://shopify/LineItem/11410270421181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Back Of Watermelon Enamel Pin - mint green", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 100.0, "price_set": {"shop_money": {"amount": "100.00", "currency_code": "USD"}, "presentment_money": {"amount": "100.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229935293, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Back Of Watermelon Enamel Pin", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604994749, "variant_inventory_management": "shopify", "variant_title": "mint green", "vendor": "Murazik and Sons", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.00", "amount_set": {"shop_money": {"amount": "3.00", "currency_code": "USD"}, "presentment_money": {"amount": "3.00", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 100.0, "product": {"id": 6796229935293, "title": "Back Of Watermelon Enamel Pin", "handle": "back-of-watermelon-enamel-pin", "vendor": "Murazik and Sons", "tags": "developer-tools-generator", "body_html": "Back of watermelon pin.", "product_type": "Shoes", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/back-of-watermelon-pin__1.jpg?v=1624410651", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/back-of-watermelon-pin__1_x240.jpg?v=1624410651", "alt": null, "width": 2500, "height": 2500, "position": 1, "variant_ids": [], "id": 29301303673021, "created_at": "2021-06-22T18:10:51-07:00", "updated_at": "2021-06-22T18:10:51-07:00"}], "variant": {"sku": 40090604994749, "title": "mint green", "options": {"Title": "mint green"}, "images": []}, "variant_options": {"Title": "mint green"}}}, {"id": 11410270453949, "admin_graphql_api_id": "gid://shopify/LineItem/11410270453949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 367, "name": "Anchor Bracelet Leather - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 90.0, "price_set": {"shop_money": {"amount": "90.00", "currency_code": "USD"}, "presentment_money": {"amount": "90.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796231835837, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090609090749, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "O'Hara - Gutmann", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.70", "amount_set": {"shop_money": {"amount": "2.70", "currency_code": "USD"}, "presentment_money": {"amount": "2.70", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 90.0, "product": {"id": 6796231835837, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather-1", "vendor": "O'Hara - Gutmann", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_429381b9-0ea0-43e8-9c3c-15768fe9b68d.jpg?v=1624410665", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_429381b9-0ea0-43e8-9c3c-15768fe9b68d_x240.jpg?v=1624410665", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301304885437, "created_at": "2021-06-22T18:11:05-07:00", "updated_at": "2021-06-22T18:11:05-07:00"}], "variant": {"sku": 40090609090749, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3883014226109, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "2c0f9195-7356-485d-8d95-8d4614bcfbf9", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1"}, "$attribution": {"$attributed_event_id": "3pEXweFZYiV", "$send_ts": 1655313530.0, "$message": "SfFuN7", "$flow": "YfYbWb"}}, "datetime": "2022-06-16 08:11:53+00:00", "uuid": "faefea80-ed4b-11ec-8001-6f0fee858cb2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159463} -{"stream": "events", "data": {"object": "event", "id": "3pF6Q85YAnG", "statistic_id": "RDXsib", "timestamp": 1655367123, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796231835837, "Name": "Anchor Bracelet Leather", "Variant Name": "Wooden", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "O'Hara - Gutmann", "Variant Option: Title": "Wooden", "Quantity": 1, "$event_id": "4557016793277:11410270453949:0", "$value": 90.0, "$attribution": {"$attributed_event_id": "3pEXweFZYiV", "$send_ts": 1655313530.0, "$message": "SfFuN7", "$flow": "YfYbWb"}}, "datetime": "2022-06-16 08:12:03+00:00", "uuid": "00e5cb80-ed4c-11ec-8001-6279d9f3e7e9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159464} -{"stream": "events", "data": {"object": "event", "id": "3pF7tMw6xDM", "statistic_id": "RDXsib", "timestamp": 1655367123, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229935293, "Name": "Back Of Watermelon Enamel Pin", "Variant Name": "mint green", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Murazik and Sons", "Variant Option: Title": "mint green", "Quantity": 1, "$event_id": "4557016793277:11410270421181:0", "$value": 100.0, "$attribution": {"$attributed_event_id": "3pEXweFZYiV", "$send_ts": 1655313530.0, "$message": "SfFuN7", "$flow": "YfYbWb"}}, "datetime": "2022-06-16 08:12:03+00:00", "uuid": "00e5cb80-ed4c-11ec-8001-cf580a3f0494", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159464} -{"stream": "events", "data": {"object": "event", "id": "3pF6QdVwXiS", "statistic_id": "X3f6PC", "timestamp": 1655367145, "event_name": "Fulfilled Order", "event_properties": {"Items": ["Back Of Watermelon Enamel Pin", "Anchor Bracelet Leather"], "Collections": [], "Item Count": 2, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "5.70", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4557016793277", "$value": 202.92, "$extra": {"id": 4557016793277, "admin_graphql_api_id": "gid://shopify/Order/4557016793277", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "af94773207c3b0d1029cc9e6fbb9496a", "checkout_id": 25058292367549, "checkout_token": "39ac55bdc2ad0a615e6fff6da95f6e0b", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36 Edg/101.0.1210.53"}, "closed_at": "2022-06-16T01:11:36-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-16T01:11:33-07:00", "currency": "USD", "current_subtotal_price": "184.30", "current_subtotal_price_set": {"shop_money": {"amount": "184.30", "currency_code": "USD"}, "presentment_money": {"amount": "184.30", "currency_code": "USD"}}, "current_total_discounts": "5.70", "current_total_discounts_set": {"shop_money": {"amount": "5.70", "currency_code": "USD"}, "presentment_money": {"amount": "5.70", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "202.92", "current_total_price_set": {"shop_money": {"amount": "202.92", "currency_code": "USD"}, "presentment_money": {"amount": "202.92", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/?_ab=0&_fd=0&_sc=1", "landing_site_ref": null, "location_id": null, "name": "#1139", "note": null, "note_attributes": [], "number": 139, "order_number": 1139, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/a93866a511e84a729484cffdad7bbc96/authenticate?key=6eaf4caad8291de6abd6e3e4bc52990b", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-16T01:11:33-07:00", "processing_method": "direct", "reference": null, "referring_site": "", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "184.30", "subtotal_price_set": {"shop_money": {"amount": "184.30", "currency_code": "USD"}, "presentment_money": {"amount": "184.30", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "a93866a511e84a729484cffdad7bbc96", "total_discounts": "5.70", "total_discounts_set": {"shop_money": {"amount": "5.70", "currency_code": "USD"}, "presentment_money": {"amount": "5.70", "currency_code": "USD"}}, "total_line_items_price": "190.00", "total_line_items_price_set": {"shop_money": {"amount": "190.00", "currency_code": "USD"}, "presentment_money": {"amount": "190.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "202.92", "total_price_set": {"shop_money": {"amount": "202.92", "currency_code": "USD"}, "presentment_money": {"amount": "202.92", "currency_code": "USD"}}, "total_price_usd": "202.92", "total_shipping_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 367, "updated_at": "2022-06-16T01:11:36-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-16T01:11:34-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4076916179133, "admin_graphql_api_id": "gid://shopify/Fulfillment/4076916179133", "created_at": "2022-06-16T01:11:35-07:00", "location_id": 63590301885, "name": "#1139.1", "order_id": 4557016793277, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-16T01:11:35-07:00", "line_items": [{"id": 11410270453949, "admin_graphql_api_id": "gid://shopify/LineItem/11410270453949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 367, "name": "Anchor Bracelet Leather - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "90.00", "price_set": {"shop_money": {"amount": "90.00", "currency_code": "USD"}, "presentment_money": {"amount": "90.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796231835837, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090609090749, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "O'Hara - Gutmann", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.70", "amount_set": {"shop_money": {"amount": "2.70", "currency_code": "USD"}, "presentment_money": {"amount": "2.70", "currency_code": "USD"}}, "discount_application_index": 0}]}]}, {"id": 4076916211901, "admin_graphql_api_id": "gid://shopify/Fulfillment/4076916211901", "created_at": "2022-06-16T01:11:35-07:00", "location_id": 63590301885, "name": "#1139.2", "order_id": 4557016793277, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-16T01:11:35-07:00", "line_items": [{"id": 11410270421181, "admin_graphql_api_id": "gid://shopify/LineItem/11410270421181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Back Of Watermelon Enamel Pin - mint green", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "100.00", "price_set": {"shop_money": {"amount": "100.00", "currency_code": "USD"}, "presentment_money": {"amount": "100.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229935293, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "Back Of Watermelon Enamel Pin", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604994749, "variant_inventory_management": "shopify", "variant_title": "mint green", "vendor": "Murazik and Sons", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.00", "amount_set": {"shop_money": {"amount": "3.00", "currency_code": "USD"}, "presentment_money": {"amount": "3.00", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11410270421181, "admin_graphql_api_id": "gid://shopify/LineItem/11410270421181", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "Back Of Watermelon Enamel Pin - mint green", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 100.0, "price_set": {"shop_money": {"amount": "100.00", "currency_code": "USD"}, "presentment_money": {"amount": "100.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229935293, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "Back Of Watermelon Enamel Pin", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090604994749, "variant_inventory_management": "shopify", "variant_title": "mint green", "vendor": "Murazik and Sons", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.00", "amount_set": {"shop_money": {"amount": "3.00", "currency_code": "USD"}, "presentment_money": {"amount": "3.00", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 100.0, "product": {"id": 6796229935293, "title": "Back Of Watermelon Enamel Pin", "handle": "back-of-watermelon-enamel-pin", "vendor": "Murazik and Sons", "tags": "developer-tools-generator", "body_html": "Back of watermelon pin.", "product_type": "Shoes", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/back-of-watermelon-pin__1.jpg?v=1624410651", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/back-of-watermelon-pin__1_x240.jpg?v=1624410651", "alt": null, "width": 2500, "height": 2500, "position": 1, "variant_ids": [], "id": 29301303673021, "created_at": "2021-06-22T18:10:51-07:00", "updated_at": "2021-06-22T18:10:51-07:00"}], "variant": {"sku": 40090604994749, "title": "mint green", "options": {"Title": "mint green"}, "images": []}, "variant_options": {"Title": "mint green"}}}, {"id": 11410270453949, "admin_graphql_api_id": "gid://shopify/LineItem/11410270453949", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 367, "name": "Anchor Bracelet Leather - Wooden", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 90.0, "price_set": {"shop_money": {"amount": "90.00", "currency_code": "USD"}, "presentment_money": {"amount": "90.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796231835837, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "Anchor Bracelet Leather", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090609090749, "variant_inventory_management": "shopify", "variant_title": "Wooden", "vendor": "O'Hara - Gutmann", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "2.70", "amount_set": {"shop_money": {"amount": "2.70", "currency_code": "USD"}, "presentment_money": {"amount": "2.70", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 90.0, "product": {"id": 6796231835837, "title": "Anchor Bracelet Leather", "handle": "anchor-bracelet-leather-1", "vendor": "O'Hara - Gutmann", "tags": "developer-tools-generator", "body_html": "Black leather bracelet with gold anchor.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_429381b9-0ea0-43e8-9c3c-15768fe9b68d.jpg?v=1624410665", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/anchor-bracelet-leather_429381b9-0ea0-43e8-9c3c-15768fe9b68d_x240.jpg?v=1624410665", "alt": null, "width": 5241, "height": 3499, "position": 1, "variant_ids": [], "id": 29301304885437, "created_at": "2021-06-22T18:11:05-07:00", "updated_at": "2021-06-22T18:11:05-07:00"}], "variant": {"sku": 40090609090749, "title": "Wooden", "options": {"Title": "Wooden"}, "images": []}, "variant_options": {"Title": "Wooden"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3883014226109, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.62", "discounted_price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "phone": null, "price": "18.62", "price_set": {"shop_money": {"amount": "18.62", "currency_code": "USD"}, "presentment_money": {"amount": "18.62", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "2c0f9195-7356-485d-8d95-8d4614bcfbf9", "full_landing_site": "http://airbyte-integration-test.myshopify.com/?_ab=0&_fd=0&_sc=1"}, "$attribution": {"$attributed_event_id": "3pEXweFZYiV", "$send_ts": 1655313530.0, "$message": "SfFuN7", "$flow": "YfYbWb"}}, "datetime": "2022-06-16 08:12:25+00:00", "uuid": "0e02ba80-ed4c-11ec-8001-09e4a45c3ff3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$organization": "", "$title": "", "$last_name": "Team", "$email": "integration-test@airbyte.io", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367159465} -{"stream": "events", "data": {"object": "event", "id": "3pFqEqfFXtz", "statistic_id": "Yy9QKx", "timestamp": 1655370526, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1655370526"}, "datetime": "2022-06-16 09:08:46+00:00", "uuid": "ed3e6300-ed53-11ec-8001-3e73a96766cf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159820} -{"stream": "events", "data": {"object": "event", "id": "3pFqY98ctQT", "statistic_id": "Yy9QKx", "timestamp": 1655370526, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655370526"}, "datetime": "2022-06-16 09:08:46+00:00", "uuid": "ed3e6300-ed53-11ec-8001-53d4368964c0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159821} -{"stream": "events", "data": {"object": "event", "id": "3pFrhYNQQdk", "statistic_id": "Yy9QKx", "timestamp": 1655370526, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655370526"}, "datetime": "2022-06-16 09:08:46+00:00", "uuid": "ed3e6300-ed53-11ec-8001-d23471c464b0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159821} -{"stream": "events", "data": {"object": "event", "id": "3pFqEiq9xjq", "statistic_id": "Yy9QKx", "timestamp": 1655370547, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655370547"}, "datetime": "2022-06-16 09:09:07+00:00", "uuid": "f9c2bb80-ed53-11ec-8001-17c306a357a9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159821} -{"stream": "events", "data": {"object": "event", "id": "3pFqYbyEQR9", "statistic_id": "Yy9QKx", "timestamp": 1655370547, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655370547"}, "datetime": "2022-06-16 09:09:07+00:00", "uuid": "f9c2bb80-ed53-11ec-8001-abb15bf9efce", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159821} -{"stream": "events", "data": {"object": "event", "id": "3pGamxC8r6b", "statistic_id": "WKHXf4", "timestamp": 1655380032, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430515814582451114900668668455178858"}, "datetime": "2022-06-16 11:47:12+00:00", "uuid": "0f42e000-ed6a-11ec-8001-aefca352da9d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159822} -{"stream": "events", "data": {"object": "event", "id": "3pGhZqYKTqU", "statistic_id": "Yy9QKx", "timestamp": 1655380034, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380034"}, "datetime": "2022-06-16 11:47:14+00:00", "uuid": "10740d00-ed6a-11ec-8001-43dd03eea1c2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159822} -{"stream": "events", "data": {"object": "event", "id": "3pGmbEhM6TG", "statistic_id": "Yy9QKx", "timestamp": 1655380035, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380035"}, "datetime": "2022-06-16 11:47:15+00:00", "uuid": "110ca380-ed6a-11ec-8001-d781e7a8f1ba", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159822} -{"stream": "events", "data": {"object": "event", "id": "3pGjApQxRue", "statistic_id": "Yy9QKx", "timestamp": 1655380036, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380036"}, "datetime": "2022-06-16 11:47:16+00:00", "uuid": "11a53a00-ed6a-11ec-8001-ba196198f6d0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159822} -{"stream": "events", "data": {"object": "event", "id": "3pGhZq2aDa2", "statistic_id": "Yy9QKx", "timestamp": 1655380037, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380037"}, "datetime": "2022-06-16 11:47:17+00:00", "uuid": "123dd080-ed6a-11ec-8001-4e417ce7f2c5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159823} -{"stream": "events", "data": {"object": "event", "id": "3pGmbxWWahK", "statistic_id": "Yy9QKx", "timestamp": 1655380038, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380038"}, "datetime": "2022-06-16 11:47:18+00:00", "uuid": "12d66700-ed6a-11ec-8001-75184c0b9595", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159823} -{"stream": "events", "data": {"object": "event", "id": "3pGjUe5iwWk", "statistic_id": "Yy9QKx", "timestamp": 1655380039, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380039"}, "datetime": "2022-06-16 11:47:19+00:00", "uuid": "136efd80-ed6a-11ec-8001-4f65d3ec0bdb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159824} -{"stream": "events", "data": {"object": "event", "id": "3pGhFwnkxT6", "statistic_id": "Yy9QKx", "timestamp": 1655380045, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380045"}, "datetime": "2022-06-16 11:47:25+00:00", "uuid": "17028480-ed6a-11ec-8001-73e41400bfa3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159824} -{"stream": "events", "data": {"object": "event", "id": "3pGhZkB6Vr8", "statistic_id": "Yy9QKx", "timestamp": 1655380046, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380046"}, "datetime": "2022-06-16 11:47:26+00:00", "uuid": "179b1b00-ed6a-11ec-8001-b2476488929e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159824} -{"stream": "events", "data": {"object": "event", "id": "3pGhZj9djVJ", "statistic_id": "Yy9QKx", "timestamp": 1655380047, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380047"}, "datetime": "2022-06-16 11:47:27+00:00", "uuid": "1833b180-ed6a-11ec-8001-a59276221086", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159824} -{"stream": "events", "data": {"object": "event", "id": "3pGmvvv8Kn5", "statistic_id": "Yy9QKx", "timestamp": 1655380057, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380057"}, "datetime": "2022-06-16 11:47:37+00:00", "uuid": "1e299280-ed6a-11ec-8001-dd45f0adaca0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159824} -{"stream": "events", "data": {"object": "event", "id": "3pGkxS3xdwj", "statistic_id": "Yy9QKx", "timestamp": 1655380065, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380065"}, "datetime": "2022-06-16 11:47:45+00:00", "uuid": "22ee4680-ed6a-11ec-8001-8337b7168c85", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159825} -{"stream": "events", "data": {"object": "event", "id": "3pGq45RkcpV", "statistic_id": "Yy9QKx", "timestamp": 1655380648, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655380648"}, "datetime": "2022-06-16 11:57:28+00:00", "uuid": "7e6d0400-ed6b-11ec-8001-9473705ac396", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159825} -{"stream": "events", "data": {"object": "event", "id": "3pGrD79AUr7", "statistic_id": "Yy9QKx", "timestamp": 1655381155, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655381155"}, "datetime": "2022-06-16 12:05:55+00:00", "uuid": "ac9f1380-ed6c-11ec-8001-ba881c56f682", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159825} -{"stream": "events", "data": {"object": "event", "id": "3pGuPmrdvAM", "statistic_id": "WKHXf4", "timestamp": 1655381410, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430522469748102313105026526147007082"}, "datetime": "2022-06-16 12:10:10+00:00", "uuid": "449cfd00-ed6d-11ec-8001-52e44f7baaa8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159825} -{"stream": "events", "data": {"object": "event", "id": "3pGubHsZaMj", "statistic_id": "Yy9QKx", "timestamp": 1655381412, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381412"}, "datetime": "2022-06-16 12:10:12+00:00", "uuid": "45ce2a00-ed6d-11ec-8001-40a16444d1aa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159826} -{"stream": "events", "data": {"object": "event", "id": "3pGtRVbq52k", "statistic_id": "Yy9QKx", "timestamp": 1655381413, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381413"}, "datetime": "2022-06-16 12:10:13+00:00", "uuid": "4666c080-ed6d-11ec-8001-e87ee751eaa8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159826} -{"stream": "events", "data": {"object": "event", "id": "3pGtRVbq52j", "statistic_id": "Yy9QKx", "timestamp": 1655381414, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381414"}, "datetime": "2022-06-16 12:10:14+00:00", "uuid": "46ff5700-ed6d-11ec-8001-8e07d57be693", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159826} -{"stream": "events", "data": {"object": "event", "id": "3pGtybPAYHr", "statistic_id": "Yy9QKx", "timestamp": 1655381418, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381418"}, "datetime": "2022-06-16 12:10:18+00:00", "uuid": "4961b100-ed6d-11ec-8001-a5a4d7c3b9ed", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159827} -{"stream": "events", "data": {"object": "event", "id": "3pGty2yYUja", "statistic_id": "SxR9Bt", "timestamp": 1655381419, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "URL": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/a57117420c7dfc1e827fbe670b1ac9c3/recover?key=09a10265e650d8c99b8f14170b70339c", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381419"}, "datetime": "2022-06-16 12:10:19+00:00", "uuid": "49fa4780-ed6d-11ec-8001-a7ad4e484592", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159827} -{"stream": "events", "data": {"object": "event", "id": "3pGubMmmnEx", "statistic_id": "Yy9QKx", "timestamp": 1655381423, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381423"}, "datetime": "2022-06-16 12:10:23+00:00", "uuid": "4c5ca180-ed6d-11ec-8001-117a56b025fb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159827} -{"stream": "events", "data": {"object": "event", "id": "3pGubNjWqct", "statistic_id": "Yy9QKx", "timestamp": 1655381424, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381424"}, "datetime": "2022-06-16 12:10:24+00:00", "uuid": "4cf53800-ed6d-11ec-8001-3a091c697ab2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159828} -{"stream": "events", "data": {"object": "event", "id": "3pGtRYAueSC", "statistic_id": "Yy9QKx", "timestamp": 1655381427, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381427"}, "datetime": "2022-06-16 12:10:27+00:00", "uuid": "4ebefb80-ed6d-11ec-8001-3478ee2192df", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159828} -{"stream": "events", "data": {"object": "event", "id": "3pGv9d7RY4F", "statistic_id": "WKHXf4", "timestamp": 1655381512, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214430581732413662982829546497021858410"}, "datetime": "2022-06-16 12:11:52+00:00", "uuid": "8168f400-ed6d-11ec-8001-726c8a74edfb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159828} -{"stream": "events", "data": {"object": "event", "id": "3pGtRY7bQQk", "statistic_id": "Yy9QKx", "timestamp": 1655381514, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381514"}, "datetime": "2022-06-16 12:11:54+00:00", "uuid": "829a2100-ed6d-11ec-8001-de205f5f2a9c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159828} -{"stream": "events", "data": {"object": "event", "id": "3pGtehGuqkk", "statistic_id": "Yy9QKx", "timestamp": 1655381515, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381515"}, "datetime": "2022-06-16 12:11:55+00:00", "uuid": "8332b780-ed6d-11ec-8001-a7d82d1fabe9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159829} -{"stream": "events", "data": {"object": "event", "id": "3pGty8qxbje", "statistic_id": "Yy9QKx", "timestamp": 1655381516, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381516"}, "datetime": "2022-06-16 12:11:56+00:00", "uuid": "83cb4e00-ed6d-11ec-8001-fbd63a3240ad", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159830} -{"stream": "events", "data": {"object": "event", "id": "3pGubLnLjTR", "statistic_id": "Yy9QKx", "timestamp": 1655381518, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381518"}, "datetime": "2022-06-16 12:11:58+00:00", "uuid": "84fc7b00-ed6d-11ec-8001-99a4c43770e8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159830} -{"stream": "events", "data": {"object": "event", "id": "3pGuPtL5fQ8", "statistic_id": "Yy9QKx", "timestamp": 1655381518, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381518"}, "datetime": "2022-06-16 12:11:58+00:00", "uuid": "84fc7b00-ed6d-11ec-8001-c38cd7689084", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159830} -{"stream": "events", "data": {"object": "event", "id": "3pGvt6KFU5S", "statistic_id": "Yy9QKx", "timestamp": 1655381519, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381519"}, "datetime": "2022-06-16 12:11:59+00:00", "uuid": "85951180-ed6d-11ec-8001-be6e744be0f2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159831} -{"stream": "events", "data": {"object": "event", "id": "3pGvt6KFU5T", "statistic_id": "Yy9QKx", "timestamp": 1655381522, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381522"}, "datetime": "2022-06-16 12:12:02+00:00", "uuid": "875ed500-ed6d-11ec-8001-3cfc4858658e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159831} -{"stream": "events", "data": {"object": "event", "id": "3pGty8TQfBQ", "statistic_id": "Yy9QKx", "timestamp": 1655381524, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381524"}, "datetime": "2022-06-16 12:12:04+00:00", "uuid": "88900200-ed6d-11ec-8001-b6cbee2f76e6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159832} -{"stream": "events", "data": {"object": "event", "id": "3pGuPuJDpiX", "statistic_id": "Yy9QKx", "timestamp": 1655381525, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381525"}, "datetime": "2022-06-16 12:12:05+00:00", "uuid": "89289880-ed6d-11ec-8001-16e2c6a47ad5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159832} -{"stream": "events", "data": {"object": "event", "id": "3pGubFZ7ip4", "statistic_id": "Yy9QKx", "timestamp": 1655381526, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381526"}, "datetime": "2022-06-16 12:12:06+00:00", "uuid": "89c12f00-ed6d-11ec-8001-5ab6eea0a5cb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159832} -{"stream": "events", "data": {"object": "event", "id": "3pGubMQDTvd", "statistic_id": "Yy9QKx", "timestamp": 1655381527, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381527"}, "datetime": "2022-06-16 12:12:07+00:00", "uuid": "8a59c580-ed6d-11ec-8001-ec5f1c576399", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159832} -{"stream": "events", "data": {"object": "event", "id": "3pGtejDEH7x", "statistic_id": "SxR9Bt", "timestamp": 1655381539, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "URL": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/a57117420c7dfc1e827fbe670b1ac9c3/recover?key=09a10265e650d8c99b8f14170b70339c", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381539"}, "datetime": "2022-06-16 12:12:19+00:00", "uuid": "9180d380-ed6d-11ec-8001-6e9683caf9a9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159833} -{"stream": "events", "data": {"object": "event", "id": "3pGtRWDig32", "statistic_id": "Yy9QKx", "timestamp": 1655381593, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381593"}, "datetime": "2022-06-16 12:13:13+00:00", "uuid": "b1b09280-ed6d-11ec-8001-00f45df91fa3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159833} -{"stream": "events", "data": {"object": "event", "id": "3pGuvABxaYk", "statistic_id": "Yy9QKx", "timestamp": 1655381594, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381594"}, "datetime": "2022-06-16 12:13:14+00:00", "uuid": "b2492900-ed6d-11ec-8001-b6f3331525c7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159834} -{"stream": "events", "data": {"object": "event", "id": "3pGtRYAur9D", "statistic_id": "Yy9QKx", "timestamp": 1655381597, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381597"}, "datetime": "2022-06-16 12:13:17+00:00", "uuid": "b412ec80-ed6d-11ec-8001-7c18194c14c3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159834} -{"stream": "events", "data": {"object": "event", "id": "3pGtem7ye9v", "statistic_id": "SxR9Bt", "timestamp": 1655381601, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "URL": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/a57117420c7dfc1e827fbe670b1ac9c3/recover?key=09a10265e650d8c99b8f14170b70339c", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381601"}, "datetime": "2022-06-16 12:13:21+00:00", "uuid": "b6754680-ed6d-11ec-8001-17ee002b6caa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159834} -{"stream": "events", "data": {"object": "event", "id": "3pGty8TQfFD", "statistic_id": "Yy9QKx", "timestamp": 1655381668, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381668"}, "datetime": "2022-06-16 12:14:28+00:00", "uuid": "de64aa00-ed6d-11ec-8001-37cb2199f38f", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159834} -{"stream": "events", "data": {"object": "event", "id": "3pGv9baFyaH", "statistic_id": "Yy9QKx", "timestamp": 1655381760, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1655381760"}, "datetime": "2022-06-16 12:16:00+00:00", "uuid": "153ac000-ed6e-11ec-8001-1958b16ad8b2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159835} -{"stream": "events", "data": {"object": "event", "id": "3pGvLNDBC9K", "statistic_id": "Yy9QKx", "timestamp": 1655381762, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655381762"}, "datetime": "2022-06-16 12:16:02+00:00", "uuid": "166bed00-ed6e-11ec-8001-1d52b36765f9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159835} -{"stream": "events", "data": {"object": "event", "id": "3pGwqsBQApF", "statistic_id": "WKHXf4", "timestamp": 1655381832, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430523103573402427219727274498609770"}, "datetime": "2022-06-16 12:17:12+00:00", "uuid": "40251400-ed6e-11ec-8001-650493c453ed", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159835} -{"stream": "events", "data": {"object": "event", "id": "3pGuPqQifA8", "statistic_id": "Yy9QKx", "timestamp": 1655381836, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381836"}, "datetime": "2022-06-16 12:17:16+00:00", "uuid": "42876e00-ed6e-11ec-8001-29aa68473fb9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159835} -{"stream": "events", "data": {"object": "event", "id": "3pGx483WBGu", "statistic_id": "Yy9QKx", "timestamp": 1655381837, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381837"}, "datetime": "2022-06-16 12:17:17+00:00", "uuid": "43200480-ed6e-11ec-8001-9bc49a19bf98", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159835} -{"stream": "events", "data": {"object": "event", "id": "3pGvt5hNicf", "statistic_id": "Yy9QKx", "timestamp": 1655381838, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381838"}, "datetime": "2022-06-16 12:17:18+00:00", "uuid": "43b89b00-ed6e-11ec-8001-488e97d95edc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159836} -{"stream": "events", "data": {"object": "event", "id": "3pGx4enMXyU", "statistic_id": "Yy9QKx", "timestamp": 1655381846, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381846"}, "datetime": "2022-06-16 12:17:26+00:00", "uuid": "487d4f00-ed6e-11ec-8001-7f7f6f5a91f6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159836} -{"stream": "events", "data": {"object": "event", "id": "3pGx4fQFnx5", "statistic_id": "Yy9QKx", "timestamp": 1655381847, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381847"}, "datetime": "2022-06-16 12:17:27+00:00", "uuid": "4915e580-ed6e-11ec-8001-75383d5c7f8e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159837} -{"stream": "events", "data": {"object": "event", "id": "3pGx4enMXyV", "statistic_id": "Yy9QKx", "timestamp": 1655381848, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381848"}, "datetime": "2022-06-16 12:17:28+00:00", "uuid": "49ae7c00-ed6e-11ec-8001-4a8e4504fbf8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159837} -{"stream": "events", "data": {"object": "event", "id": "3pGx4bt2MAe", "statistic_id": "Yy9QKx", "timestamp": 1655381896, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655381896"}, "datetime": "2022-06-16 12:18:16+00:00", "uuid": "664ab400-ed6e-11ec-8001-6d8a903106c7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159837} -{"stream": "events", "data": {"object": "event", "id": "3pGx4eS6kJE", "statistic_id": "Yy9QKx", "timestamp": 1655381897, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655381897"}, "datetime": "2022-06-16 12:18:17+00:00", "uuid": "66e34a80-ed6e-11ec-8001-b55eb8b79482", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159837} -{"stream": "events", "data": {"object": "event", "id": "3pGzUDt4B6a", "statistic_id": "Yy9QKx", "timestamp": 1655382831, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655382831"}, "datetime": "2022-06-16 12:33:51+00:00", "uuid": "93986180-ed70-11ec-8001-c98790581efb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367159838} -{"stream": "events", "data": {"object": "event", "id": "3pGBbWn7ieT", "statistic_id": "Yy9QKx", "timestamp": 1655382831, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655382831"}, "datetime": "2022-06-16 12:33:51+00:00", "uuid": "93986180-ed70-11ec-8001-24b57e7108d9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159838} -{"stream": "events", "data": {"object": "event", "id": "3pGBbWn7ieU", "statistic_id": "Yy9QKx", "timestamp": 1655382831, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655382831"}, "datetime": "2022-06-16 12:33:51+00:00", "uuid": "93986180-ed70-11ec-8001-053088f1e6bc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159838} -{"stream": "events", "data": {"object": "event", "id": "3pGAemkXWUm", "statistic_id": "Yy9QKx", "timestamp": 1655382847, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655382847"}, "datetime": "2022-06-16 12:34:07+00:00", "uuid": "9d21c980-ed70-11ec-8001-9523f3724286", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159838} -{"stream": "events", "data": {"object": "event", "id": "3pGNXrSw4TF", "statistic_id": "Yy9QKx", "timestamp": 1655385228, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655385228"}, "datetime": "2022-06-16 13:13:48+00:00", "uuid": "28518e00-ed76-11ec-8001-9b34f337acd1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159839} -{"stream": "events", "data": {"object": "event", "id": "3pGPhg7hU48", "statistic_id": "Yy9QKx", "timestamp": 1655385228, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655385228"}, "datetime": "2022-06-16 13:13:48+00:00", "uuid": "28518e00-ed76-11ec-8001-d37caa818df8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159839} -{"stream": "events", "data": {"object": "event", "id": "3pGPhi4tAHe", "statistic_id": "Yy9QKx", "timestamp": 1655385228, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655385228"}, "datetime": "2022-06-16 13:13:48+00:00", "uuid": "28518e00-ed76-11ec-8001-a4ecb4ce56d1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159839} -{"stream": "events", "data": {"object": "event", "id": "3pGTqW9nWFK", "statistic_id": "WKHXf4", "timestamp": 1655385551, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430526668840715569114918983976374890"}, "datetime": "2022-06-16 13:19:11+00:00", "uuid": "e8d77180-ed76-11ec-8001-6bd383a459c2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159839} -{"stream": "events", "data": {"object": "event", "id": "3pGRw3GAFJi", "statistic_id": "Yy9QKx", "timestamp": 1655385553, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385553"}, "datetime": "2022-06-16 13:19:13+00:00", "uuid": "ea089e80-ed76-11ec-8001-686016becbd7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159840} -{"stream": "events", "data": {"object": "event", "id": "3pGRPTpfhMq", "statistic_id": "Yy9QKx", "timestamp": 1655385554, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385554"}, "datetime": "2022-06-16 13:19:14+00:00", "uuid": "eaa13500-ed76-11ec-8001-52aafe3ecddf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159840} -{"stream": "events", "data": {"object": "event", "id": "3pGRcdtQ6rS", "statistic_id": "Yy9QKx", "timestamp": 1655385555, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385555"}, "datetime": "2022-06-16 13:19:15+00:00", "uuid": "eb39cb80-ed76-11ec-8001-c380148184c5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159840} -{"stream": "events", "data": {"object": "event", "id": "3pGRcay4EHZ", "statistic_id": "Yy9QKx", "timestamp": 1655385558, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385558"}, "datetime": "2022-06-16 13:19:18+00:00", "uuid": "ed038f00-ed76-11ec-8001-240fb3af2586", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159840} -{"stream": "events", "data": {"object": "event", "id": "3pGRw6CnbBa", "statistic_id": "Yy9QKx", "timestamp": 1655385558, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655385558"}, "datetime": "2022-06-16 13:19:18+00:00", "uuid": "ed038f00-ed76-11ec-8001-e784bfd8219d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159841} -{"stream": "events", "data": {"object": "event", "id": "3pGRw7AWH64", "statistic_id": "Yy9QKx", "timestamp": 1655385559, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385559"}, "datetime": "2022-06-16 13:19:19+00:00", "uuid": "ed9c2580-ed76-11ec-8001-ac1ac11ba087", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159841} -{"stream": "events", "data": {"object": "event", "id": "3pGRw94QK9s", "statistic_id": "Yy9QKx", "timestamp": 1655385564, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385564"}, "datetime": "2022-06-16 13:19:24+00:00", "uuid": "f0971600-ed76-11ec-8001-b74339e4b2b5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159842} -{"stream": "events", "data": {"object": "event", "id": "3pGRcikM9CW", "statistic_id": "Yy9QKx", "timestamp": 1655385565, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385565"}, "datetime": "2022-06-16 13:19:25+00:00", "uuid": "f12fac80-ed76-11ec-8001-d48de8b277d3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159842} -{"stream": "events", "data": {"object": "event", "id": "3pGRvZKqjvt", "statistic_id": "Yy9QKx", "timestamp": 1655385566, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385566"}, "datetime": "2022-06-16 13:19:26+00:00", "uuid": "f1c84300-ed76-11ec-8001-cbbfbc4711d4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159842} -{"stream": "events", "data": {"object": "event", "id": "3pGS9Ga889b", "statistic_id": "WKHXf4", "timestamp": 1655385612, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430526748068878083379256577520325226"}, "datetime": "2022-06-16 13:20:12+00:00", "uuid": "0d334e00-ed77-11ec-8001-774a85e2c6cd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159843} -{"stream": "events", "data": {"object": "event", "id": "3pGRw8zxVcK", "statistic_id": "Yy9QKx", "timestamp": 1655385617, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385617"}, "datetime": "2022-06-16 13:20:17+00:00", "uuid": "102e3e80-ed77-11ec-8001-9f1a526840ee", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159843} -{"stream": "events", "data": {"object": "event", "id": "3pGTr6UGs8S", "statistic_id": "Yy9QKx", "timestamp": 1655385617, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655385617"}, "datetime": "2022-06-16 13:20:17+00:00", "uuid": "102e3e80-ed77-11ec-8001-e4c6f074f596", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159843} -{"stream": "events", "data": {"object": "event", "id": "3pGTr6UGs8T", "statistic_id": "Yy9QKx", "timestamp": 1655385618, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385618"}, "datetime": "2022-06-16 13:20:18+00:00", "uuid": "10c6d500-ed77-11ec-8001-0227010e648c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159844} -{"stream": "events", "data": {"object": "event", "id": "3pGRPNwh3LJ", "statistic_id": "Yy9QKx", "timestamp": 1655385620, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385620"}, "datetime": "2022-06-16 13:20:20+00:00", "uuid": "11f80200-ed77-11ec-8001-e2f7b7e2f1fe", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159844} -{"stream": "events", "data": {"object": "event", "id": "3pGRw5DLCaJ", "statistic_id": "Yy9QKx", "timestamp": 1655385623, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655385623"}, "datetime": "2022-06-16 13:20:23+00:00", "uuid": "13c1c580-ed77-11ec-8001-27ba433b68b0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159844} -{"stream": "events", "data": {"object": "event", "id": "3pGRPWNib5w", "statistic_id": "Yy9QKx", "timestamp": 1655385625, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385625"}, "datetime": "2022-06-16 13:20:25+00:00", "uuid": "14f2f280-ed77-11ec-8001-277f233a5eb5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159845} -{"stream": "events", "data": {"object": "event", "id": "3pGRw8zxMRW", "statistic_id": "Yy9QKx", "timestamp": 1655385626, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385626"}, "datetime": "2022-06-16 13:20:26+00:00", "uuid": "158b8900-ed77-11ec-8001-b7e16c9fffb7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159845} -{"stream": "events", "data": {"object": "event", "id": "3pGTr6rqcYs", "statistic_id": "Yy9QKx", "timestamp": 1655385627, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385627"}, "datetime": "2022-06-16 13:20:27+00:00", "uuid": "16241f80-ed77-11ec-8001-2a26dd819283", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159845} -{"stream": "events", "data": {"object": "event", "id": "3pGT7fHkRS8", "statistic_id": "Yy9QKx", "timestamp": 1655385628, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385628"}, "datetime": "2022-06-16 13:20:28+00:00", "uuid": "16bcb600-ed77-11ec-8001-cb70435772bf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159846} -{"stream": "events", "data": {"object": "event", "id": "3pGTr3vCMHh", "statistic_id": "Yy9QKx", "timestamp": 1655385797, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655385797"}, "datetime": "2022-06-16 13:23:17+00:00", "uuid": "7b781080-ed77-11ec-8001-414d472546d2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159846} -{"stream": "events", "data": {"object": "event", "id": "3pGXyEBYvpy", "statistic_id": "Yy9QKx", "timestamp": 1655386884, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655386884"}, "datetime": "2022-06-16 13:41:24+00:00", "uuid": "035f1a00-ed7a-11ec-8001-2353c567e1bb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159846} -{"stream": "events", "data": {"object": "event", "id": "3pGZtCtQUSf", "statistic_id": "Yy9QKx", "timestamp": 1655386885, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655386885"}, "datetime": "2022-06-16 13:41:25+00:00", "uuid": "03f7b080-ed7a-11ec-8001-5d274e48638f", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159846} -{"stream": "events", "data": {"object": "event", "id": "3pGZtGRvZKK", "statistic_id": "Yy9QKx", "timestamp": 1655387133, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655387133"}, "datetime": "2022-06-16 13:45:33+00:00", "uuid": "97c97c80-ed7a-11ec-8001-8375baf7d4a5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159846} -{"stream": "events", "data": {"object": "event", "id": "3pHmcSYHbKq", "statistic_id": "Yy9QKx", "timestamp": 1655390972, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655390972"}, "datetime": "2022-06-16 14:49:32+00:00", "uuid": "88026600-ed83-11ec-8001-aeaade118e97", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159847} -{"stream": "events", "data": {"object": "event", "id": "3pJ5iNPG7ZX", "statistic_id": "Yy9QKx", "timestamp": 1655398585, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655398585"}, "datetime": "2022-06-16 16:56:25+00:00", "uuid": "41b60280-ed95-11ec-8001-8fab1a1189ac", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159847} -{"stream": "events", "data": {"object": "event", "id": "3pJ5iRgaCmY", "statistic_id": "Yy9QKx", "timestamp": 1655398585, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655398585"}, "datetime": "2022-06-16 16:56:25+00:00", "uuid": "41b60280-ed95-11ec-8001-2403a1a3a9eb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159848} -{"stream": "events", "data": {"object": "event", "id": "3pJ5iVDPsJ5", "statistic_id": "Yy9QKx", "timestamp": 1655398585, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655398585"}, "datetime": "2022-06-16 16:56:25+00:00", "uuid": "41b60280-ed95-11ec-8001-f33e218210ad", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159848} -{"stream": "events", "data": {"object": "event", "id": "3pLQjY6uqSH", "statistic_id": "Yy9QKx", "timestamp": 1655427335, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655427335"}, "datetime": "2022-06-17 00:55:35+00:00", "uuid": "320bdd80-edd8-11ec-8001-8aceabcc8986", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159848} -{"stream": "events", "data": {"object": "event", "id": "3pPj7P4LT55", "statistic_id": "WKHXf4", "timestamp": 1655453530, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430604787808954633751786218311406186"}, "datetime": "2022-06-17 08:12:10+00:00", "uuid": "2f7ba900-ee15-11ec-8001-d1580c6fbdba", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159849} -{"stream": "events", "data": {"object": "event", "id": "3pPgzcKpkGV", "statistic_id": "Yy9QKx", "timestamp": 1655453532, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453532"}, "datetime": "2022-06-17 08:12:12+00:00", "uuid": "30acd600-ee15-11ec-8001-953a791147da", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159849} -{"stream": "events", "data": {"object": "event", "id": "3pPgSXz66iL", "statistic_id": "Yy9QKx", "timestamp": 1655453533, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453533"}, "datetime": "2022-06-17 08:12:13+00:00", "uuid": "31456c80-ee15-11ec-8001-16420aa21f98", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159849} -{"stream": "events", "data": {"object": "event", "id": "3pPiaqL9PFs", "statistic_id": "Yy9QKx", "timestamp": 1655453534, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453534"}, "datetime": "2022-06-17 08:12:14+00:00", "uuid": "31de0300-ee15-11ec-8001-55588cad0681", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159849} -{"stream": "events", "data": {"object": "event", "id": "3pPhQuFQA7t", "statistic_id": "Yy9QKx", "timestamp": 1655453537, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453537"}, "datetime": "2022-06-17 08:12:17+00:00", "uuid": "33a7c680-ee15-11ec-8001-de330adb328b", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159850} -{"stream": "events", "data": {"object": "event", "id": "3pPhcQejxpu", "statistic_id": "Yy9QKx", "timestamp": 1655453540, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453540"}, "datetime": "2022-06-17 08:12:20+00:00", "uuid": "35718a00-ee15-11ec-8001-41d8f65dec8c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159850} -{"stream": "events", "data": {"object": "event", "id": "3pPhcTDpbiJ", "statistic_id": "SxR9Bt", "timestamp": 1655453541, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "URL": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/39ac55bdc2ad0a615e6fff6da95f6e0b/recover?key=6ac03b4a87317f8996bbf11ce754f323", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453541"}, "datetime": "2022-06-17 08:12:21+00:00", "uuid": "360a2080-ee15-11ec-8001-58cd509a4f80", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$first_name": "Airbyte", "$title": "", "$organization": "", "$email": "integration-test@airbyte.io", "$phone_number": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367159850} -{"stream": "events", "data": {"object": "event", "id": "3pPhwEWnznn", "statistic_id": "Yy9QKx", "timestamp": 1655453541, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453541"}, "datetime": "2022-06-17 08:12:21+00:00", "uuid": "360a2080-ee15-11ec-8001-9a6aa701e9db", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160184} -{"stream": "events", "data": {"object": "event", "id": "3pPhQsJEDJD", "statistic_id": "Yy9QKx", "timestamp": 1655453543, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453543"}, "datetime": "2022-06-17 08:12:23+00:00", "uuid": "373b4d80-ee15-11ec-8001-19d37e32e583", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160185} -{"stream": "events", "data": {"object": "event", "id": "3pPgT2Y9Zae", "statistic_id": "Yy9QKx", "timestamp": 1655453544, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453544"}, "datetime": "2022-06-17 08:12:24+00:00", "uuid": "37d3e400-ee15-11ec-8001-d6827253fb8b", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160185} -{"stream": "events", "data": {"object": "event", "id": "3pPgzfFaL6n", "statistic_id": "Yy9QKx", "timestamp": 1655453545, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453545"}, "datetime": "2022-06-17 08:12:25+00:00", "uuid": "386c7a80-ee15-11ec-8001-75b072e09299", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160185} -{"stream": "events", "data": {"object": "event", "id": "3pPhwHS9PSi", "statistic_id": "Yy9QKx", "timestamp": 1655453643, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453643"}, "datetime": "2022-06-17 08:14:03+00:00", "uuid": "72d61780-ee15-11ec-8001-73f57c8125d5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160186} -{"stream": "events", "data": {"object": "event", "id": "3pPhQyAcYdr", "statistic_id": "SxR9Bt", "timestamp": 1655453648, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "URL": "https://airbyte.io/products/back-of-watermelon-enamel-pin", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453648"}, "datetime": "2022-06-17 08:14:08+00:00", "uuid": "75d10800-ee15-11ec-8001-25e71701f2dc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160187} -{"stream": "events", "data": {"object": "event", "id": "3pPhwLMUXFV", "statistic_id": "SxR9Bt", "timestamp": 1655453666, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "URL": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/39ac55bdc2ad0a615e6fff6da95f6e0b/recover?key=6ac03b4a87317f8996bbf11ce754f323", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655453666"}, "datetime": "2022-06-17 08:14:26+00:00", "uuid": "808b9d00-ee15-11ec-8001-88266993cbcc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160187} -{"stream": "events", "data": {"object": "event", "id": "3pPsrQftzxB", "statistic_id": "Yy9QKx", "timestamp": 1655455316, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655455316"}, "datetime": "2022-06-17 08:41:56+00:00", "uuid": "5805a200-ee19-11ec-8001-3a96250d5ece", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160187} -{"stream": "events", "data": {"object": "event", "id": "3pPsrTbeFKW", "statistic_id": "Yy9QKx", "timestamp": 1655455318, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655455318"}, "datetime": "2022-06-17 08:41:58+00:00", "uuid": "5936cf00-ee19-11ec-8001-eb033870edd0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160188} -{"stream": "events", "data": {"object": "event", "id": "3pPDTt6CwPc", "statistic_id": "Yy9QKx", "timestamp": 1655457635, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655457635"}, "datetime": "2022-06-17 09:20:35+00:00", "uuid": "be40f380-ee1e-11ec-8001-dccb6adef6b0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160188} -{"stream": "events", "data": {"object": "event", "id": "3pQ58Kc6snh", "statistic_id": "Yy9QKx", "timestamp": 1655461788, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655461788"}, "datetime": "2022-06-17 10:29:48+00:00", "uuid": "69a27600-ee28-11ec-8001-58e9fcab75e0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160189} -{"stream": "events", "data": {"object": "event", "id": "3pQ7Zi55Qt8", "statistic_id": "Yy9QKx", "timestamp": 1655462218, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655462218"}, "datetime": "2022-06-17 10:36:58+00:00", "uuid": "69ef4100-ee29-11ec-8001-bc801e73a5e1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160189} -{"stream": "events", "data": {"object": "event", "id": "3pQFPG5U6vs", "statistic_id": "Yy9QKx", "timestamp": 1655468214, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655468214"}, "datetime": "2022-06-17 12:16:54+00:00", "uuid": "5fd43f00-ee37-11ec-8001-19ecd6811bb0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160190} -{"stream": "events", "data": {"object": "event", "id": "3pVEEYuaeqQ", "statistic_id": "Yy9QKx", "timestamp": 1655520535, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655520535"}, "datetime": "2022-06-18 02:48:55+00:00", "uuid": "31934580-eeb1-11ec-8001-d8435e06f2d4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160191} -{"stream": "events", "data": {"object": "event", "id": "3pVCsasm7Y5", "statistic_id": "Yy9QKx", "timestamp": 1655520541, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655520541"}, "datetime": "2022-06-18 02:49:01+00:00", "uuid": "3526cc80-eeb1-11ec-8001-7a60b349f58e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160192} -{"stream": "events", "data": {"object": "event", "id": "3pVEm4pR426", "statistic_id": "Yy9QKx", "timestamp": 1655520541, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655520541"}, "datetime": "2022-06-18 02:49:01+00:00", "uuid": "3526cc80-eeb1-11ec-8001-87f9ad294df1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160195} -{"stream": "events", "data": {"object": "event", "id": "3pVEm6QjCuW", "statistic_id": "Yy9QKx", "timestamp": 1655520541, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655520541"}, "datetime": "2022-06-18 02:49:01+00:00", "uuid": "3526cc80-eeb1-11ec-8001-e6c218d6c5d3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160195} -{"stream": "events", "data": {"object": "event", "id": "3pVDpF5NW5D", "statistic_id": "Yy9QKx", "timestamp": 1655520542, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655520542"}, "datetime": "2022-06-18 02:49:02+00:00", "uuid": "35bf6300-eeb1-11ec-8001-d2efa977c9b1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160196} -{"stream": "events", "data": {"object": "event", "id": "3qrxFd3ewc2", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-fc56a4e37fe4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160196} -{"stream": "events", "data": {"object": "event", "id": "3qrxFd3ewc3", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-5a8ec58e0de3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160197} -{"stream": "events", "data": {"object": "event", "id": "3qrxFd3ewc5", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-ab5dac4ee3b7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160197} -{"stream": "events", "data": {"object": "event", "id": "3qrxFd3ewc6", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-67106017d5ca", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160198} -{"stream": "events", "data": {"object": "event", "id": "3qryiLBN3yq", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-8b677d3e0ea5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160198} -{"stream": "events", "data": {"object": "event", "id": "3qryCznEU58", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-035d1d2ce8b0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160198} -{"stream": "events", "data": {"object": "event", "id": "3qryWpBrtsY", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-433a60dc7cb2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160199} -{"stream": "events", "data": {"object": "event", "id": "3qrAdG3bLvs", "statistic_id": "Yy9QKx", "timestamp": 1655814992, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1655814992"}, "datetime": "2022-06-21 12:36:32+00:00", "uuid": "c79fc800-f15e-11ec-8001-5b0820e259fa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160200} -{"stream": "events", "data": {"object": "event", "id": "3qryCznEYdf", "statistic_id": "Yy9QKx", "timestamp": 1655815027, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1655815027"}, "datetime": "2022-06-21 12:37:07+00:00", "uuid": "dc7c5b80-f15e-11ec-8001-0a6f83b4a7c1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160200} -{"stream": "events", "data": {"object": "event", "id": "3qyCBzJJSbS", "statistic_id": "SPnhc3", "timestamp": 1655889870, "event_name": "Checkout Started", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "$event_id": "25114920845501", "$value": 57.23, "$extra": {"referring_site": "https://airbyte-integration-test.myshopify.com/", "full_landing_site": "http://airbyte-integration-test.myshopify.com/password", "token": "00e66e0ec8153d3760308883f53d7c77", "webhook_id": "f4270074-1bc9-4fed-9acd-b12ce5c3c940", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/00e66e0ec8153d3760308883f53d7c77/recover?key=6a8cd9d1296132857904c09bd916d1f1", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/00e66e0ec8153d3760308883f53d7c77/recover?key=6a8cd9d1296132857904c09bd916d1f1", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "1.77", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "34fe0068a7b9e8025cd6f06196c5cfdd", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 0, "origin_location_id": 3007664259261, "presentment_title": "All Black Sneaker Right Foot", "presentment_variant_title": "ivory", "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "tax_lines": [], "taxable": true, "title": "All Black Sneaker Right Foot", "variant_id": 40090597884093, "variant_title": "ivory", "variant_price": "59.00", "vendor": "Becker - Moore", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": null, "compare_at_price": null, "line_price": 59.0, "price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}]}}, "datetime": "2022-06-22 09:24:30+00:00", "uuid": "1e63db00-f20d-11ec-8001-4bd066caaaee", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160200} -{"stream": "events", "data": {"object": "event", "id": "3qyCBwNXFr3", "statistic_id": "TspjNE", "timestamp": 1655890495, "event_name": "Placed Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "$event_id": "4563707330749", "$value": 57.23, "$extra": {"id": 4563707330749, "admin_graphql_api_id": "gid://shopify/Order/4563707330749", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "0b58b0d104c7428fdba88142bdefa5be", "checkout_id": 25114920845501, "checkout_token": "00e66e0ec8153d3760308883f53d7c77", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}, "closed_at": "2022-06-22T02:34:38-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-22T02:34:36-07:00", "currency": "USD", "current_subtotal_price": "57.23", "current_subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_discounts": "1.77", "current_total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.23", "current_total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1140", "note": null, "note_attributes": [], "number": 140, "order_number": 1140, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6637617c89e2f0771dbfa172aecc1aaa/authenticate?key=1ee98f0ef8d096c2a4aa7bde67c78f89", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-22T02:34:35-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "6637617c89e2f0771dbfa172aecc1aaa", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-22T02:34:38-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-22T02:34:37-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4083217170621, "admin_graphql_api_id": "gid://shopify/Fulfillment/4083217170621", "created_at": "2022-06-22T02:34:38-07:00", "location_id": 63590301885, "name": "#1140.1", "order_id": 4563707330749, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-22T02:34:38-07:00", "line_items": [{"id": 11424204849341, "admin_graphql_api_id": "gid://shopify/LineItem/11424204849341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11424204849341, "admin_graphql_api_id": "gid://shopify/LineItem/11424204849341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "webhook_id": "28493764-fe82-4e0b-b2aa-5fef8118aee8", "full_landing_site": "http://airbyte-integration-test.myshopify.com/password"}}, "datetime": "2022-06-22 09:34:55+00:00", "uuid": "92eb4980-f20e-11ec-8001-ac7f87af1cd1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160201} -{"stream": "events", "data": {"object": "event", "id": "3qyCVkyrAFv", "statistic_id": "RDXsib", "timestamp": 1655890505, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796226560189, "Name": "All Black Sneaker Right Foot", "Variant Name": "ivory", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Becker - Moore", "Variant Option: Title": "ivory", "Quantity": 1, "$event_id": "4563707330749:11424204849341:0", "$value": 59.0}, "datetime": "2022-06-22 09:35:05+00:00", "uuid": "98e12a80-f20e-11ec-8001-626fdc26c1ef", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160202} -{"stream": "events", "data": {"object": "event", "id": "3qyEQjSbD8i", "statistic_id": "X3f6PC", "timestamp": 1655890528, "event_name": "Fulfilled Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4563707330749", "$value": 57.23, "$extra": {"id": 4563707330749, "admin_graphql_api_id": "gid://shopify/Order/4563707330749", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": "0b58b0d104c7428fdba88142bdefa5be", "checkout_id": 25114920845501, "checkout_token": "00e66e0ec8153d3760308883f53d7c77", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}, "closed_at": "2022-06-22T02:34:38-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-22T02:34:36-07:00", "currency": "USD", "current_subtotal_price": "57.23", "current_subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_discounts": "1.77", "current_total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.23", "current_total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en-US", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/password", "landing_site_ref": null, "location_id": null, "name": "#1140", "note": null, "note_attributes": [], "number": 140, "order_number": 1140, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/6637617c89e2f0771dbfa172aecc1aaa/authenticate?key=1ee98f0ef8d096c2a4aa7bde67c78f89", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-22T02:34:35-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "6637617c89e2f0771dbfa172aecc1aaa", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-22T02:34:38-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-22T02:34:37-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4083217170621, "admin_graphql_api_id": "gid://shopify/Fulfillment/4083217170621", "created_at": "2022-06-22T02:34:38-07:00", "location_id": 63590301885, "name": "#1140.1", "order_id": 4563707330749, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-22T02:34:38-07:00", "line_items": [{"id": 11424204849341, "admin_graphql_api_id": "gid://shopify/LineItem/11424204849341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11424204849341, "admin_graphql_api_id": "gid://shopify/LineItem/11424204849341", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "webhook_id": "28493764-fe82-4e0b-b2aa-5fef8118aee8", "full_landing_site": "http://airbyte-integration-test.myshopify.com/password"}}, "datetime": "2022-06-22 09:35:28+00:00", "uuid": "a696b000-f20e-11ec-8001-3f45d90578f7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160202} -{"stream": "events", "data": {"object": "event", "id": "3qyHGNmw54G", "statistic_id": "SPnhc3", "timestamp": 1655891402, "event_name": "Checkout Started", "event_properties": {"Items": ["8 Ounce Soy Candle"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "3.39", "Source Name": "web", "$currency_code": "USD", "$event_id": "25115112669373", "$value": 109.61, "$extra": {"referring_site": "https://airbyte-integration-test.myshopify.com/products/8-ounce-soy-candle", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json", "token": "8988df67e8a6e2b7bf023a552637e307", "webhook_id": "cf9d4d37-620b-4b02-adcb-c2a89059678b", "webhook_topic": "checkouts/update", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/8988df67e8a6e2b7bf023a552637e307/recover?key=7c63931e1b806f6dd564419121bf39cf", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/8988df67e8a6e2b7bf023a552637e307/recover?key=7c63931e1b806f6dd564419121bf39cf", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "3.39", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "4c2cd97ab93b08ad92bb21153595a086", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 127, "origin_location_id": 3007664259261, "presentment_title": "8 Ounce Soy Candle", "presentment_variant_title": "purple", "product_id": 6796229509309, "properties": null, "quantity": 1.0, "requires_shipping": true, "sku": "", "tax_lines": [], "taxable": true, "title": "8 Ounce Soy Candle", "variant_id": 40090603946173, "variant_title": "purple", "variant_price": "113.00", "vendor": "Bosco Inc", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": 0, "compare_at_price": null, "line_price": 113.0, "price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}]}}, "datetime": "2022-06-22 09:50:02+00:00", "uuid": "af888100-f210-11ec-8001-34aceb530dca", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160203} -{"stream": "events", "data": {"object": "event", "id": "3qyHGJt9QRa", "statistic_id": "TspjNE", "timestamp": 1655891448, "event_name": "Placed Order", "event_properties": {"Items": ["8 Ounce Soy Candle"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "3.39", "Source Name": "web", "$currency_code": "USD", "$event_id": "4563717849277", "$value": 128.3, "$extra": {"id": 4563717849277, "admin_graphql_api_id": "gid://shopify/Order/4563717849277", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25115112669373, "checkout_token": "8988df67e8a6e2b7bf023a552637e307", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}, "closed_at": "2022-06-22T02:50:31-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-22T02:50:29-07:00", "currency": "USD", "current_subtotal_price": "109.61", "current_subtotal_price_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "current_total_discounts": "3.39", "current_total_discounts_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "128.30", "current_total_price_set": {"shop_money": {"amount": "128.30", "currency_code": "USD"}, "presentment_money": {"amount": "128.30", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1141", "note": null, "note_attributes": [], "number": 141, "order_number": 1141, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/99f1f43b4471f537d203c67b67515ee4/authenticate?key=dfbabb5f307e352c49daf9756affbfb1", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-22T02:50:28-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/8-ounce-soy-candle", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "109.61", "subtotal_price_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "99f1f43b4471f537d203c67b67515ee4", "total_discounts": "3.39", "total_discounts_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "total_line_items_price": "113.00", "total_line_items_price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "128.30", "total_price_set": {"shop_money": {"amount": "128.30", "currency_code": "USD"}, "presentment_money": {"amount": "128.30", "currency_code": "USD"}}, "total_price_usd": "128.30", "total_shipping_price_set": {"shop_money": {"amount": "18.69", "currency_code": "USD"}, "presentment_money": {"amount": "18.69", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 127, "updated_at": "2022-06-22T02:50:31-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-22T02:50:30-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4083225919677, "admin_graphql_api_id": "gid://shopify/Fulfillment/4083225919677", "created_at": "2022-06-22T02:50:31-07:00", "location_id": 63590301885, "name": "#1141.1", "order_id": 4563717849277, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-22T02:50:31-07:00", "line_items": [{"id": 11424222740669, "admin_graphql_api_id": "gid://shopify/LineItem/11424222740669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11424222740669, "admin_graphql_api_id": "gid://shopify/LineItem/11424222740669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3888550412477, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.69", "discounted_price_set": {"shop_money": {"amount": "18.69", "currency_code": "USD"}, "presentment_money": {"amount": "18.69", "currency_code": "USD"}}, "phone": null, "price": "18.69", "price_set": {"shop_money": {"amount": "18.69", "currency_code": "USD"}, "presentment_money": {"amount": "18.69", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "0daa8e5c-7bf2-42ea-a3a1-96da50605e45", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-22 09:50:48+00:00", "uuid": "caf38c00-f210-11ec-8001-3bfb15a066aa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160204} -{"stream": "events", "data": {"object": "event", "id": "3qyNakzzWw5", "statistic_id": "RDXsib", "timestamp": 1655891458, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796229509309, "Name": "8 Ounce Soy Candle", "Variant Name": "purple", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Bosco Inc", "Variant Option: Title": "purple", "Quantity": 1, "$event_id": "4563717849277:11424222740669:0", "$value": 113.0}, "datetime": "2022-06-22 09:50:58+00:00", "uuid": "d0e96d00-f210-11ec-8001-c19b9d2beebe", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160206} -{"stream": "events", "data": {"object": "event", "id": "3qyHGGvXFKq", "statistic_id": "X3f6PC", "timestamp": 1655891481, "event_name": "Fulfilled Order", "event_properties": {"Items": ["8 Ounce Soy Candle"], "Collections": [], "Item Count": 1, "tags": [], "ShippingRate": "Standard", "Discount Codes": [], "Total Discounts": "3.39", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4563717849277", "$value": 128.3, "$extra": {"id": 4563717849277, "admin_graphql_api_id": "gid://shopify/Order/4563717849277", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25115112669373, "checkout_token": "8988df67e8a6e2b7bf023a552637e307", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}, "closed_at": "2022-06-22T02:50:31-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-22T02:50:29-07:00", "currency": "USD", "current_subtotal_price": "109.61", "current_subtotal_price_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "current_total_discounts": "3.39", "current_total_discounts_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "128.30", "current_total_price_set": {"shop_money": {"amount": "128.30", "currency_code": "USD"}, "presentment_money": {"amount": "128.30", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1141", "note": null, "note_attributes": [], "number": 141, "order_number": 1141, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/99f1f43b4471f537d203c67b67515ee4/authenticate?key=dfbabb5f307e352c49daf9756affbfb1", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-22T02:50:28-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/8-ounce-soy-candle", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "109.61", "subtotal_price_set": {"shop_money": {"amount": "109.61", "currency_code": "USD"}, "presentment_money": {"amount": "109.61", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "99f1f43b4471f537d203c67b67515ee4", "total_discounts": "3.39", "total_discounts_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "total_line_items_price": "113.00", "total_line_items_price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "128.30", "total_price_set": {"shop_money": {"amount": "128.30", "currency_code": "USD"}, "presentment_money": {"amount": "128.30", "currency_code": "USD"}}, "total_price_usd": "128.30", "total_shipping_price_set": {"shop_money": {"amount": "18.69", "currency_code": "USD"}, "presentment_money": {"amount": "18.69", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 127, "updated_at": "2022-06-22T02:50:31-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-22T02:50:30-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4083225919677, "admin_graphql_api_id": "gid://shopify/Fulfillment/4083225919677", "created_at": "2022-06-22T02:50:31-07:00", "location_id": 63590301885, "name": "#1141.1", "order_id": 4563717849277, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-22T02:50:31-07:00", "line_items": [{"id": 11424222740669, "admin_graphql_api_id": "gid://shopify/LineItem/11424222740669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "113.00", "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11424222740669, "admin_graphql_api_id": "gid://shopify/LineItem/11424222740669", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 127, "name": "8 Ounce Soy Candle - purple", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 113.0, "price_set": {"shop_money": {"amount": "113.00", "currency_code": "USD"}, "presentment_money": {"amount": "113.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796229509309, "properties": [], "quantity": 1.0, "requires_shipping": true, "sku": "", "taxable": true, "title": "8 Ounce Soy Candle", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090603946173, "variant_inventory_management": "shopify", "variant_title": "purple", "vendor": "Bosco Inc", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "3.39", "amount_set": {"shop_money": {"amount": "3.39", "currency_code": "USD"}, "presentment_money": {"amount": "3.39", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 113.0, "product": {"id": 6796229509309, "title": "8 Ounce Soy Candle", "handle": "8-ounce-soy-candle", "vendor": "Bosco Inc", "tags": "developer-tools-generator", "body_html": "Close up of white soy candle in clear container on brown wooden table.", "product_type": "Sports", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle.jpg?v=1624410648", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/8-ounce-soy-candle_x240.jpg?v=1624410648", "alt": null, "width": 2200, "height": 1467, "position": 1, "variant_ids": [], "id": 29301303345341, "created_at": "2021-06-22T18:10:48-07:00", "updated_at": "2021-06-22T18:10:48-07:00"}], "variant": {"sku": 40090603946173, "title": "purple", "options": {"Title": "purple"}, "images": []}, "variant_options": {"Title": "purple"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "shipping_lines": [{"id": 3888550412477, "carrier_identifier": null, "code": "Standard", "delivery_category": null, "discounted_price": "18.69", "discounted_price_set": {"shop_money": {"amount": "18.69", "currency_code": "USD"}, "presentment_money": {"amount": "18.69", "currency_code": "USD"}}, "phone": null, "price": "18.69", "price_set": {"shop_money": {"amount": "18.69", "currency_code": "USD"}, "presentment_money": {"amount": "18.69", "currency_code": "USD"}}, "requested_fulfillment_service_id": null, "source": "shopify", "title": "Standard", "tax_lines": [], "discount_allocations": []}], "webhook_id": "0daa8e5c-7bf2-42ea-a3a1-96da50605e45", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-22 09:51:21+00:00", "uuid": "de9ef280-f210-11ec-8001-f1dc0dea82c5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160206} -{"stream": "events", "data": {"object": "event", "id": "3qz8C9q5VEw", "statistic_id": "SPnhc3", "timestamp": 1655894962, "event_name": "Checkout Started", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "580111", "$currency_code": "USD", "$event_id": "25115562803389", "$value": 57.23, "$extra": {"referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json", "token": "dc0479c4519ac34439bcf51d792329b3", "webhook_id": "457b54df-3305-4d6e-ac29-659466f7ee4e", "webhook_topic": "checkouts/create", "responsive_checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/dc0479c4519ac34439bcf51d792329b3/recover?key=d8011c42358e3ce2eba0c8688f50a456", "checkout_url": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/dc0479c4519ac34439bcf51d792329b3/recover?key=d8011c42358e3ce2eba0c8688f50a456", "note_attributes": [], "line_items": [{"applied_discounts": [], "discount_allocations": [{"id": null, "amount": "1.77", "description": "eeeee", "created_at": null, "application_type": "automatic"}], "key": "4d4c5e2fda4069b37a289ed485bb9b4d", "destination_location_id": null, "fulfillment_service": "manual", "gift_card": false, "grams": 0, "origin_location_id": 3007664259261, "presentment_title": "All Black Sneaker Right Foot", "presentment_variant_title": "ivory", "product_id": 6796226560189, "properties": null, "quantity": 1.0, "requires_shipping": false, "sku": "", "tax_lines": [{"position": 1, "price": "9.54", "rate": 0.2, "title": "PDV", "source": "Shopify", "compare_at": 0.2, "zone": "country", "channel_liable": false, "identifier": null}], "taxable": true, "title": "All Black Sneaker Right Foot", "variant_id": 40090597884093, "variant_title": "ivory", "variant_price": "59.00", "vendor": "Becker - Moore", "user_id": null, "unit_price_measurement": {"measured_type": null, "quantity_value": null, "quantity_unit": null, "reference_value": null, "reference_unit": null}, "rank": 0, "compare_at_price": null, "line_price": 59.0, "price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}]}}, "datetime": "2022-06-22 10:49:22+00:00", "uuid": "f9756500-f218-11ec-8001-6c6303a3d6ba", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160207} -{"stream": "events", "data": {"object": "event", "id": "3qz9fNQceWs", "statistic_id": "TspjNE", "timestamp": 1655895031, "event_name": "Placed Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "$event_id": "4563761987773", "$value": 57.23, "$extra": {"id": 4563761987773, "admin_graphql_api_id": "gid://shopify/Order/4563761987773", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25115562803389, "checkout_token": "dc0479c4519ac34439bcf51d792329b3", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}, "closed_at": "2022-06-22T03:50:14-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-22T03:50:12-07:00", "currency": "USD", "current_subtotal_price": "57.23", "current_subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_discounts": "1.77", "current_total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.23", "current_total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1142", "note": null, "note_attributes": [], "number": 142, "order_number": 1142, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d454421b21b1ff3884b88bafac903b3b/authenticate?key=952c5c25bc551990df6e6215b1ddef25", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-22T03:50:11-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "d454421b21b1ff3884b88bafac903b3b", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-22T03:50:14-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-22T03:50:13-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4083246596285, "admin_graphql_api_id": "gid://shopify/Fulfillment/4083246596285", "created_at": "2022-06-22T03:50:14-07:00", "location_id": 63590301885, "name": "#1142.1", "order_id": 4563761987773, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-22T03:50:14-07:00", "line_items": [{"id": 11424310362301, "admin_graphql_api_id": "gid://shopify/LineItem/11424310362301", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11424310362301, "admin_graphql_api_id": "gid://shopify/LineItem/11424310362301", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "webhook_id": "19f7b18a-eb11-4269-bc3b-f10c5daf90ea", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-22 10:50:31+00:00", "uuid": "2295f580-f219-11ec-8001-0c00e33774b7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160208} -{"stream": "events", "data": {"object": "event", "id": "3qz65DUPRxN", "statistic_id": "RDXsib", "timestamp": 1655895041, "event_name": "Ordered Product", "event_properties": {"ProductID": 6796226560189, "Name": "All Black Sneaker Right Foot", "Variant Name": "ivory", "SKU": "", "Collections": [], "Tags": ["developer-tools-generator"], "Vendor": "Becker - Moore", "Variant Option: Title": "ivory", "Quantity": 1, "$event_id": "4563761987773:11424310362301:0", "$value": 59.0}, "datetime": "2022-06-22 10:50:41+00:00", "uuid": "288bd680-f219-11ec-8001-63829a9f1db3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160209} -{"stream": "events", "data": {"object": "event", "id": "3qz3QJwLPKr", "statistic_id": "X3f6PC", "timestamp": 1655895064, "event_name": "Fulfilled Order", "event_properties": {"Items": ["All Black Sneaker Right Foot"], "Collections": [], "Item Count": 1, "tags": [], "Discount Codes": [], "Total Discounts": "1.77", "Source Name": "web", "$currency_code": "USD", "FulfillmentStatus": "fulfilled", "FulfillmentHours": 1, "HasPartialFulfillments": false, "$event_id": "4563761987773", "$value": 57.23, "$extra": {"id": 4563761987773, "admin_graphql_api_id": "gid://shopify/Order/4563761987773", "app_id": 580111, "browser_ip": "176.113.167.23", "buyer_accepts_marketing": false, "cancel_reason": null, "cancelled_at": null, "cart_token": null, "checkout_id": 25115562803389, "checkout_token": "dc0479c4519ac34439bcf51d792329b3", "client_details": {"accept_language": "en-US,en;q=0.9,uk;q=0.8", "browser_height": 754, "browser_ip": "176.113.167.23", "browser_width": 1519, "session_hash": null, "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.124 Safari/537.36 Edg/102.0.1245.44"}, "closed_at": "2022-06-22T03:50:14-07:00", "confirmed": true, "contact_email": "integration-test@airbyte.io", "created_at": "2022-06-22T03:50:12-07:00", "currency": "USD", "current_subtotal_price": "57.23", "current_subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_discounts": "1.77", "current_total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "current_total_duties_set": null, "current_total_price": "57.23", "current_total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "current_total_tax": "0.00", "current_total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "customer_locale": "en", "device_id": null, "discount_codes": [], "email": "integration-test@airbyte.io", "estimated_taxes": false, "financial_status": "paid", "fulfillment_status": "fulfilled", "gateway": "bogus", "landing_site": "/wallets/checkouts.json", "landing_site_ref": null, "location_id": null, "name": "#1142", "note": null, "note_attributes": [], "number": 142, "order_number": 1142, "order_status_url": "https://airbyte-integration-test.myshopify.com/58033176765/orders/d454421b21b1ff3884b88bafac903b3b/authenticate?key=952c5c25bc551990df6e6215b1ddef25", "original_total_duties_set": null, "payment_gateway_names": ["bogus"], "phone": null, "presentment_currency": "USD", "processed_at": "2022-06-22T03:50:11-07:00", "processing_method": "direct", "reference": null, "referring_site": "https://airbyte-integration-test.myshopify.com/products/all-black-sneaker-right-foot", "source_identifier": null, "source_name": "web", "source_url": null, "subtotal_price": "57.23", "subtotal_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "tags": "", "tax_lines": [], "taxes_included": true, "test": true, "token": "d454421b21b1ff3884b88bafac903b3b", "total_discounts": "1.77", "total_discounts_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "total_line_items_price": "59.00", "total_line_items_price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "total_outstanding": "0.00", "total_price": "57.23", "total_price_set": {"shop_money": {"amount": "57.23", "currency_code": "USD"}, "presentment_money": {"amount": "57.23", "currency_code": "USD"}}, "total_price_usd": "57.23", "total_shipping_price_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tax": "0.00", "total_tax_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "total_tip_received": "0.00", "total_weight": 0, "updated_at": "2022-06-22T03:50:14-07:00", "user_id": null, "billing_address": {"first_name": "Airbyte", "address1": "2261 Market Street", "phone": null, "city": "San Francisco", "zip": "94114", "province": "California", "country": "United States", "last_name": "Team", "address2": "4381", "company": null, "latitude": 37.7647751, "longitude": -122.4320369, "name": "Airbyte Team", "country_code": "US", "province_code": "CA"}, "customer": {"id": 5362027233469, "email": "integration-test@airbyte.io", "accepts_marketing": false, "created_at": "2021-07-08T05:41:47-07:00", "updated_at": "2022-06-22T03:50:13-07:00", "first_name": "Airbyte", "last_name": "Team", "orders_count": 0, "state": "disabled", "total_spent": "0.00", "last_order_id": null, "note": null, "verified_email": true, "multipass_identifier": null, "tax_exempt": false, "phone": null, "tags": "", "last_order_name": null, "currency": "USD", "accepts_marketing_updated_at": "2021-07-08T05:41:47-07:00", "marketing_opt_in_level": null, "tax_exemptions": [], "sms_marketing_consent": null, "admin_graphql_api_id": "gid://shopify/Customer/5362027233469", "default_address": {"id": 7492260823229, "customer_id": 5362027233469, "first_name": "Airbyte", "last_name": "Team", "company": null, "address1": "2261 Market Street", "address2": "4381", "city": "San Francisco", "province": "California", "country": "United States", "zip": "94114", "phone": null, "name": "Airbyte Team", "province_code": "CA", "country_code": "US", "country_name": "United States", "default": true}}, "discount_applications": [{"target_type": "line_item", "type": "automatic", "value": "3.0", "value_type": "percentage", "allocation_method": "across", "target_selection": "all", "title": "eeeee"}], "fulfillments": [{"id": 4083246596285, "admin_graphql_api_id": "gid://shopify/Fulfillment/4083246596285", "created_at": "2022-06-22T03:50:14-07:00", "location_id": 63590301885, "name": "#1142.1", "order_id": 4563761987773, "origin_address": {}, "receipt": {}, "service": "manual", "shipment_status": null, "status": "success", "tracking_company": null, "tracking_number": null, "tracking_numbers": [], "tracking_url": null, "tracking_urls": [], "updated_at": "2022-06-22T03:50:14-07:00", "line_items": [{"id": 11424310362301, "admin_graphql_api_id": "gid://shopify/LineItem/11424310362301", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": "59.00", "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}]}]}], "line_items": [{"id": 11424310362301, "admin_graphql_api_id": "gid://shopify/LineItem/11424310362301", "fulfillable_quantity": 0, "fulfillment_service": "manual", "fulfillment_status": "fulfilled", "gift_card": false, "grams": 0, "name": "All Black Sneaker Right Foot - ivory", "origin_location": {"id": 3007664259261, "country_code": "UA", "province_code": "", "name": "airbyte integration test", "address1": "Heroiv UPA 72", "address2": "", "city": "Lviv", "zip": "30100"}, "price": 59.0, "price_set": {"shop_money": {"amount": "59.00", "currency_code": "USD"}, "presentment_money": {"amount": "59.00", "currency_code": "USD"}}, "product_exists": true, "product_id": 6796226560189, "properties": [], "quantity": 1.0, "requires_shipping": false, "sku": "", "taxable": true, "title": "All Black Sneaker Right Foot", "total_discount": "0.00", "total_discount_set": {"shop_money": {"amount": "0.00", "currency_code": "USD"}, "presentment_money": {"amount": "0.00", "currency_code": "USD"}}, "variant_id": 40090597884093, "variant_inventory_management": "shopify", "variant_title": "ivory", "vendor": "Becker - Moore", "tax_lines": [], "duties": [], "discount_allocations": [{"amount": "1.77", "amount_set": {"shop_money": {"amount": "1.77", "currency_code": "USD"}, "presentment_money": {"amount": "1.77", "currency_code": "USD"}}, "discount_application_index": 0}], "line_price": 59.0, "product": {"id": 6796226560189, "title": "All Black Sneaker Right Foot", "handle": "all-black-sneaker-right-foot", "vendor": "Becker - Moore", "tags": "developer-tools-generator", "body_html": "The right foot of an all black sneaker on a dark wooden platform in front of a white painted brick wall.", "product_type": "Automotive", "properties": {}, "images": [{"src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot.jpg?v=1624410627", "thumb_src": "https://cdn.shopify.com/s/files/1/0580/3317/6765/products/all-black-sneaker-right-foot_x240.jpg?v=1624410627", "alt": null, "width": 3840, "height": 2560, "position": 1, "variant_ids": [], "id": 29301300723901, "created_at": "2021-06-22T18:10:27-07:00", "updated_at": "2021-06-22T18:10:27-07:00"}], "variant": {"sku": 40090597884093, "title": "ivory", "options": {"Title": "ivory"}, "images": []}, "variant_options": {"Title": "ivory"}}}], "payment_details": {"credit_card_bin": "1", "avs_result_code": null, "cvv_result_code": null, "credit_card_number": "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 1", "credit_card_company": "Bogus"}, "payment_terms": null, "refunds": [], "shipping_lines": [], "webhook_id": "43dada16-ee0f-4577-add6-eb23749f7118", "full_landing_site": "http://airbyte-integration-test.myshopify.com/wallets/checkouts.json"}}, "datetime": "2022-06-22 10:51:04+00:00", "uuid": "36415c00-f219-11ec-8001-a2fdd51064df", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": null, "flow_message_id": null, "campaign_id": null}, "emitted_at": 1663367160209} -{"stream": "events", "data": {"object": "event", "id": "3qzW9eUJJUm", "statistic_id": "WKHXf4", "timestamp": 1655904290, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214431373855582480597676806749437317738"}, "datetime": "2022-06-22 13:24:50+00:00", "uuid": "b1613d00-f22e-11ec-8001-a58a70b8fdff", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160210} -{"stream": "events", "data": {"object": "event", "id": "3qA2ALDvmyj", "statistic_id": "Yy9QKx", "timestamp": 1655904293, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904293"}, "datetime": "2022-06-22 13:24:53+00:00", "uuid": "b32b0080-f22e-11ec-8001-63f0e62ab994", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160210} -{"stream": "events", "data": {"object": "event", "id": "3qzY4aPqGnw", "statistic_id": "Yy9QKx", "timestamp": 1655904294, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904294"}, "datetime": "2022-06-22 13:24:54+00:00", "uuid": "b3c39700-f22e-11ec-8001-39463e561b8b", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160211} -{"stream": "events", "data": {"object": "event", "id": "3qzZDhXdszw", "statistic_id": "Yy9QKx", "timestamp": 1655904295, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904295"}, "datetime": "2022-06-22 13:24:55+00:00", "uuid": "b45c2d80-f22e-11ec-8001-6c4c1a50ff82", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160211} -{"stream": "events", "data": {"object": "event", "id": "3qA2AQ4yZ9A", "statistic_id": "Yy9QKx", "timestamp": 1655904296, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904296"}, "datetime": "2022-06-22 13:24:56+00:00", "uuid": "b4f4c400-f22e-11ec-8001-e97fd663d294", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160211} -{"stream": "events", "data": {"object": "event", "id": "3qzYFTEArLz", "statistic_id": "Yy9QKx", "timestamp": 1655904299, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904299"}, "datetime": "2022-06-22 13:24:59+00:00", "uuid": "b6be8780-f22e-11ec-8001-134593876f98", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160211} -{"stream": "events", "data": {"object": "event", "id": "3qzZjtJsSPN", "statistic_id": "Yy9QKx", "timestamp": 1655904300, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904300"}, "datetime": "2022-06-22 13:25:00+00:00", "uuid": "b7571e00-f22e-11ec-8001-4f6925a754d8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160211} -{"stream": "events", "data": {"object": "event", "id": "3qzYZDYYSL4", "statistic_id": "Yy9QKx", "timestamp": 1655904302, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904302"}, "datetime": "2022-06-22 13:25:02+00:00", "uuid": "b8884b00-f22e-11ec-8001-0e15c4309d92", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160212} -{"stream": "events", "data": {"object": "event", "id": "3qzZjvckA46", "statistic_id": "Yy9QKx", "timestamp": 1655904304, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904304"}, "datetime": "2022-06-22 13:25:04+00:00", "uuid": "b9b97800-f22e-11ec-8001-80565aa1109a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160212} -{"stream": "events", "data": {"object": "event", "id": "3qzZXb7KbZe", "statistic_id": "Yy9QKx", "timestamp": 1655904305, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904305"}, "datetime": "2022-06-22 13:25:05+00:00", "uuid": "ba520e80-f22e-11ec-8001-86d61b42f2b5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160212} -{"stream": "events", "data": {"object": "event", "id": "3qzZDmRzBUB", "statistic_id": "Yy9QKx", "timestamp": 1655904306, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904306"}, "datetime": "2022-06-22 13:25:06+00:00", "uuid": "baeaa500-f22e-11ec-8001-812fe9a11bc0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160212} -{"stream": "events", "data": {"object": "event", "id": "3qA3etxveNe", "statistic_id": "Yy9QKx", "timestamp": 1655904413, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904413"}, "datetime": "2022-06-22 13:26:53+00:00", "uuid": "fab18c80-f22e-11ec-8001-c8c647010dad", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160212} -{"stream": "events", "data": {"object": "event", "id": "3qA3etxveNM", "statistic_id": "Yy9QKx", "timestamp": 1655904420, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904420"}, "datetime": "2022-06-22 13:27:00+00:00", "uuid": "feddaa00-f22e-11ec-8001-d8f19f1d028d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160213} -{"stream": "events", "data": {"object": "event", "id": "3qzZjz6GEk4", "statistic_id": "Yy9QKx", "timestamp": 1655904423, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904423"}, "datetime": "2022-06-22 13:27:03+00:00", "uuid": "00a76d80-f22f-11ec-8001-40de25e8ab92", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160213} -{"stream": "events", "data": {"object": "event", "id": "3qA2AJGj5dj", "statistic_id": "Yy9QKx", "timestamp": 1655904462, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904462"}, "datetime": "2022-06-22 13:27:42+00:00", "uuid": "17e65b00-f22f-11ec-8001-798bfeab80da", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160213} -{"stream": "events", "data": {"object": "event", "id": "3qA3ymbjHwb", "statistic_id": "Yy9QKx", "timestamp": 1655904592, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655904592"}, "datetime": "2022-06-22 13:29:52+00:00", "uuid": "6562c800-f22f-11ec-8001-be59b6ddc8e2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160213} -{"stream": "events", "data": {"object": "event", "id": "3qA7ZKAESYB", "statistic_id": "WKHXf4", "timestamp": 1655905852, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214431377816990606310893686426634834538"}, "datetime": "2022-06-22 13:50:52+00:00", "uuid": "54678600-f232-11ec-8001-26fc848cc7bb", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160214} -{"stream": "events", "data": {"object": "event", "id": "3qAaSc8Pd3E", "statistic_id": "Yy9QKx", "timestamp": 1655905855, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905855"}, "datetime": "2022-06-22 13:50:55+00:00", "uuid": "56314980-f232-11ec-8001-d614f4c9a0a7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160214} -{"stream": "events", "data": {"object": "event", "id": "3qAaSitEfHg", "statistic_id": "Yy9QKx", "timestamp": 1655905856, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905856"}, "datetime": "2022-06-22 13:50:56+00:00", "uuid": "56c9e000-f232-11ec-8001-3f8b4bfc03ce", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160214} -{"stream": "events", "data": {"object": "event", "id": "3qAaywc5Gbd", "statistic_id": "Yy9QKx", "timestamp": 1655905857, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905857"}, "datetime": "2022-06-22 13:50:57+00:00", "uuid": "57627680-f232-11ec-8001-2751bc3aefee", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160214} -{"stream": "events", "data": {"object": "event", "id": "3qAaSiWWypr", "statistic_id": "Yy9QKx", "timestamp": 1655905858, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905858"}, "datetime": "2022-06-22 13:50:58+00:00", "uuid": "57fb0d00-f232-11ec-8001-69e31a00b3d2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160215} -{"stream": "events", "data": {"object": "event", "id": "3qAayqkxaUd", "statistic_id": "Yy9QKx", "timestamp": 1655905859, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905859"}, "datetime": "2022-06-22 13:50:59+00:00", "uuid": "5893a380-f232-11ec-8001-588d679102d3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160215} -{"stream": "events", "data": {"object": "event", "id": "3qAaSd7q8xv", "statistic_id": "Yy9QKx", "timestamp": 1655905866, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905866"}, "datetime": "2022-06-22 13:51:06+00:00", "uuid": "5cbfc100-f232-11ec-8001-702bea55c4e7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160215} -{"stream": "events", "data": {"object": "event", "id": "3qAaSbDwCtq", "statistic_id": "Yy9QKx", "timestamp": 1655905867, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655905867"}, "datetime": "2022-06-22 13:51:07+00:00", "uuid": "5d585780-f232-11ec-8001-bbe23a238189", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160216} -{"stream": "events", "data": {"object": "event", "id": "3qAc9DjST6S", "statistic_id": "Yy9QKx", "timestamp": 1655906154, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655906154"}, "datetime": "2022-06-22 13:55:54+00:00", "uuid": "08691100-f233-11ec-8001-5178f33bb3a4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160216} -{"stream": "events", "data": {"object": "event", "id": "3qAe4zHRBAX", "statistic_id": "Yy9QKx", "timestamp": 1655906466, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655906466"}, "datetime": "2022-06-22 14:01:06+00:00", "uuid": "c2607d00-f233-11ec-8001-5114ed4478f3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160216} -{"stream": "events", "data": {"object": "event", "id": "3qAdqTNsPLU", "statistic_id": "Yy9QKx", "timestamp": 1655906467, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655906467"}, "datetime": "2022-06-22 14:01:07+00:00", "uuid": "c2f91380-f233-11ec-8001-d0b48e26c8e8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160216} -{"stream": "events", "data": {"object": "event", "id": "3qAenm2R6EM", "statistic_id": "Yy9QKx", "timestamp": 1655906767, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655906767"}, "datetime": "2022-06-22 14:06:07+00:00", "uuid": "75c97180-f234-11ec-8001-cf1e82aab9ab", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160217} -{"stream": "events", "data": {"object": "event", "id": "3qAkr5ivub8", "statistic_id": "Yy9QKx", "timestamp": 1655907789, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655907789"}, "datetime": "2022-06-22 14:23:09+00:00", "uuid": "d6f24480-f236-11ec-8001-b4f6fb4e94d3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160217} -{"stream": "events", "data": {"object": "event", "id": "3qAkJS5nemp", "statistic_id": "Yy9QKx", "timestamp": 1655907790, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655907790"}, "datetime": "2022-06-22 14:23:10+00:00", "uuid": "d78adb00-f236-11ec-8001-ed6680d4cfb7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160217} -{"stream": "events", "data": {"object": "event", "id": "3qAnXBHwRvy", "statistic_id": "Yy9QKx", "timestamp": 1655908311, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655908311"}, "datetime": "2022-06-22 14:31:51+00:00", "uuid": "0e152580-f238-11ec-8001-9fa7e9f60ba5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160217} -{"stream": "events", "data": {"object": "event", "id": "3qApBjA7wQL", "statistic_id": "Yy9QKx", "timestamp": 1655908312, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655908312"}, "datetime": "2022-06-22 14:31:52+00:00", "uuid": "0eadbc00-f238-11ec-8001-e69d53b780e7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160218} -{"stream": "events", "data": {"object": "event", "id": "3qApV3u3hcA", "statistic_id": "WKHXf4", "timestamp": 1655909391, "event_name": "Received Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "$ESP": 0, "$event_id": "SfFuN7:214431387482826433051142872838996775530"}, "datetime": "2022-06-22 14:49:51+00:00", "uuid": "91d01180-f23a-11ec-8001-40728cbdb1bc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160218} -{"stream": "events", "data": {"object": "event", "id": "3qAunAEGj42", "statistic_id": "Yy9QKx", "timestamp": 1655909394, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909394"}, "datetime": "2022-06-22 14:49:54+00:00", "uuid": "9399d500-f23a-11ec-8001-7cd54ac6c7ee", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160219} -{"stream": "events", "data": {"object": "event", "id": "3qAu4Tg5aNY", "statistic_id": "Yy9QKx", "timestamp": 1655909395, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909395"}, "datetime": "2022-06-22 14:49:55+00:00", "uuid": "94326b80-f23a-11ec-8001-82dbfefe2cff", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160219} -{"stream": "events", "data": {"object": "event", "id": "3qAuGtjVxqF", "statistic_id": "Yy9QKx", "timestamp": 1655909396, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909396"}, "datetime": "2022-06-22 14:49:56+00:00", "uuid": "94cb0200-f23a-11ec-8001-924b7a70e3f2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160219} -{"stream": "events", "data": {"object": "event", "id": "3qAvDXXpS96", "statistic_id": "Yy9QKx", "timestamp": 1655909397, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909397"}, "datetime": "2022-06-22 14:49:57+00:00", "uuid": "95639880-f23a-11ec-8001-275182898cab", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160219} -{"stream": "events", "data": {"object": "event", "id": "3qAu4RiT5Qr", "statistic_id": "Yy9QKx", "timestamp": 1655909405, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909405"}, "datetime": "2022-06-22 14:50:05+00:00", "uuid": "9a284c80-f23a-11ec-8001-fe9d7c20f5b5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160220} -{"stream": "events", "data": {"object": "event", "id": "3qAv2mWkY3M", "statistic_id": "Yy9QKx", "timestamp": 1655909406, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909406"}, "datetime": "2022-06-22 14:50:06+00:00", "uuid": "9ac0e300-f23a-11ec-8001-c1204d9a7b9e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160220} -{"stream": "events", "data": {"object": "event", "id": "3qAu4Ktkzwc", "statistic_id": "Yy9QKx", "timestamp": 1655909407, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909407"}, "datetime": "2022-06-22 14:50:07+00:00", "uuid": "9b597980-f23a-11ec-8001-77c645b9dec2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160220} -{"stream": "events", "data": {"object": "event", "id": "3qAvk5kYcKF", "statistic_id": "Yy9QKx", "timestamp": 1655909408, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909408"}, "datetime": "2022-06-22 14:50:08+00:00", "uuid": "9bf21000-f23a-11ec-8001-a8f6a58642b1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160220} -{"stream": "events", "data": {"object": "event", "id": "3qAvDV3ChPN", "statistic_id": "Yy9QKx", "timestamp": 1655909409, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909409"}, "datetime": "2022-06-22 14:50:09+00:00", "uuid": "9c8aa680-f23a-11ec-8001-c16d0bbcb09d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160221} -{"stream": "events", "data": {"object": "event", "id": "3qAv2kutmC4", "statistic_id": "Yy9QKx", "timestamp": 1655909420, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655909420"}, "datetime": "2022-06-22 14:50:20+00:00", "uuid": "a3191e00-f23a-11ec-8001-f317183506e5", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160221} -{"stream": "events", "data": {"object": "event", "id": "3qAAK6JCj7D", "statistic_id": "Yy9QKx", "timestamp": 1655910308, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655910308"}, "datetime": "2022-06-22 15:05:08+00:00", "uuid": "b4632a00-f23c-11ec-8001-f55d56cab0a2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160221} -{"stream": "events", "data": {"object": "event", "id": "3qAzMBz4Gkz", "statistic_id": "Yy9QKx", "timestamp": 1655910309, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655910309"}, "datetime": "2022-06-22 15:05:09+00:00", "uuid": "b4fbc080-f23c-11ec-8001-58c95114e499", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160221} -{"stream": "events", "data": {"object": "event", "id": "3qAA7Az8JgN", "statistic_id": "Yy9QKx", "timestamp": 1655910309, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655910309"}, "datetime": "2022-06-22 15:05:09+00:00", "uuid": "b4fbc080-f23c-11ec-8001-2be97f75f6a8", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160222} -{"stream": "events", "data": {"object": "event", "id": "3qAza7SQJcD", "statistic_id": "Yy9QKx", "timestamp": 1655910312, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655910312"}, "datetime": "2022-06-22 15:05:12+00:00", "uuid": "b6c58400-f23c-11ec-8001-c5be3d5e8aa9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160222} -{"stream": "events", "data": {"object": "event", "id": "3qAzMC4mbzj", "statistic_id": "Yy9QKx", "timestamp": 1655910379, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1655910379"}, "datetime": "2022-06-22 15:06:19+00:00", "uuid": "deb4e780-f23c-11ec-8001-828bded523d9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160222} -{"stream": "events", "data": {"object": "event", "id": "3qAA7wbtJr8", "statistic_id": "Yy9QKx", "timestamp": 1655910379, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655910379"}, "datetime": "2022-06-22 15:06:19+00:00", "uuid": "deb4e780-f23c-11ec-8001-e5aebc9819b0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160223} -{"stream": "events", "data": {"object": "event", "id": "3qAAKc6T57t", "statistic_id": "Yy9QKx", "timestamp": 1655910379, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655910379"}, "datetime": "2022-06-22 15:06:19+00:00", "uuid": "deb4e780-f23c-11ec-8001-0075bdaabd93", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160223} -{"stream": "events", "data": {"object": "event", "id": "3qAEz3qWF2v", "statistic_id": "Yy9QKx", "timestamp": 1655911242, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655911242"}, "datetime": "2022-06-22 15:20:42+00:00", "uuid": "e1184100-f23e-11ec-8001-46a9780e9199", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160223} -{"stream": "events", "data": {"object": "event", "id": "3qFXye3gYJd", "statistic_id": "Yy9QKx", "timestamp": 1655967594, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1655967594"}, "datetime": "2022-06-23 06:59:54+00:00", "uuid": "15811100-f2c2-11ec-8001-9f2ef72cd5a2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160224} -{"stream": "events", "data": {"object": "event", "id": "3qFZ9r5su9s", "statistic_id": "Yy9QKx", "timestamp": 1655967596, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": false, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1655967596"}, "datetime": "2022-06-23 06:59:56+00:00", "uuid": "16b23e00-f2c2-11ec-8001-f2fcd15b9489", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160225} -{"stream": "events", "data": {"object": "event", "id": "3qGLKrU6M3R", "statistic_id": "WKHXf4", "timestamp": 1655976310, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214431410617449887216329450153830273642"}, "datetime": "2022-06-23 09:25:10+00:00", "uuid": "60a51f00-f2d6-11ec-8001-674fdd1f02a9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160225} -{"stream": "events", "data": {"object": "event", "id": "3qGNYbyf49q", "statistic_id": "Yy9QKx", "timestamp": 1655976313, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976313"}, "datetime": "2022-06-23 09:25:13+00:00", "uuid": "626ee280-f2d6-11ec-8001-4f195b22f596", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160225} -{"stream": "events", "data": {"object": "event", "id": "3qGNEmNM3Fe", "statistic_id": "Yy9QKx", "timestamp": 1655976314, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976314"}, "datetime": "2022-06-23 09:25:14+00:00", "uuid": "63077900-f2d6-11ec-8001-3ee29a9400c6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160226} -{"stream": "events", "data": {"object": "event", "id": "3qGN2NecY5P", "statistic_id": "Yy9QKx", "timestamp": 1655976315, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976315"}, "datetime": "2022-06-23 09:25:15+00:00", "uuid": "63a00f80-f2d6-11ec-8001-192acccccbc9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160226} -{"stream": "events", "data": {"object": "event", "id": "3qGL7NrazHJ", "statistic_id": "Yy9QKx", "timestamp": 1655976319, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976319"}, "datetime": "2022-06-23 09:25:19+00:00", "uuid": "66026980-f2d6-11ec-8001-9425f087bf8b", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160226} -{"stream": "events", "data": {"object": "event", "id": "3qGL7SjwTGG", "statistic_id": "Yy9QKx", "timestamp": 1655976324, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976324"}, "datetime": "2022-06-23 09:25:24+00:00", "uuid": "68fd5a00-f2d6-11ec-8001-67640778a9d7", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160227} -{"stream": "events", "data": {"object": "event", "id": "3qGPBZgnxCP", "statistic_id": "Yy9QKx", "timestamp": 1655976325, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976325"}, "datetime": "2022-06-23 09:25:25+00:00", "uuid": "6995f080-f2d6-11ec-8001-bfbc397f76be", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160227} -{"stream": "events", "data": {"object": "event", "id": "3qGQfwTLcvX", "statistic_id": "Yy9QKx", "timestamp": 1655976507, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976507"}, "datetime": "2022-06-23 09:28:27+00:00", "uuid": "d610ef80-f2d6-11ec-8001-e555c20d4cf2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$email": "integration-test@airbyte.io", "$first_name": "Airbyte", "$title": "", "$phone_number": "", "$organization": "", "$last_name": "Team", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160227} -{"stream": "events", "data": {"object": "event", "id": "3qGNYbyf4gJ", "statistic_id": "Yy9QKx", "timestamp": 1655976641, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976641"}, "datetime": "2022-06-23 09:30:41+00:00", "uuid": "25efb680-f2d7-11ec-8001-92e79aad6cc0", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160624} -{"stream": "events", "data": {"object": "event", "id": "3qGSauKCD8c", "statistic_id": "Yy9QKx", "timestamp": 1655976644, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655976644"}, "datetime": "2022-06-23 09:30:44+00:00", "uuid": "27b97a00-f2d7-11ec-8001-ec58b7982bc3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160624} -{"stream": "events", "data": {"object": "event", "id": "3qGSNbak4jZ", "statistic_id": "WKHXf4", "timestamp": 1655977810, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214431413390435575215581265927868535402"}, "datetime": "2022-06-23 09:50:10+00:00", "uuid": "deb6f500-f2d9-11ec-8001-ae6758b99285", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160625} -{"stream": "events", "data": {"object": "event", "id": "3qGXThVyPm3", "statistic_id": "Yy9QKx", "timestamp": 1655977813, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977813"}, "datetime": "2022-06-23 09:50:13+00:00", "uuid": "e080b880-f2d9-11ec-8001-fc1f3cab4188", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160625} -{"stream": "events", "data": {"object": "event", "id": "3qGXTrJhmKg", "statistic_id": "Yy9QKx", "timestamp": 1655977814, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977814"}, "datetime": "2022-06-23 09:50:14+00:00", "uuid": "e1194f00-f2d9-11ec-8001-586477c6a7fa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160626} -{"stream": "events", "data": {"object": "event", "id": "3qGYd9Cd46x", "statistic_id": "Yy9QKx", "timestamp": 1655977815, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977815"}, "datetime": "2022-06-23 09:50:15+00:00", "uuid": "e1b1e580-f2d9-11ec-8001-027fcb686a98", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160626} -{"stream": "events", "data": {"object": "event", "id": "3qGXTtcb2rP", "statistic_id": "Yy9QKx", "timestamp": 1655977816, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977816"}, "datetime": "2022-06-23 09:50:16+00:00", "uuid": "e24a7c00-f2d9-11ec-8001-fafaf2bb599e", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160627} -{"stream": "events", "data": {"object": "event", "id": "3qGWifEH2yX", "statistic_id": "SxR9Bt", "timestamp": 1655977819, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "URL": "https://airbyte.io/", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977819"}, "datetime": "2022-06-23 09:50:19+00:00", "uuid": "e4143f80-f2d9-11ec-8001-fc96fbeada99", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160627} -{"stream": "events", "data": {"object": "event", "id": "3qGVkKZQ8LT", "statistic_id": "Yy9QKx", "timestamp": 1655977821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977821"}, "datetime": "2022-06-23 09:50:21+00:00", "uuid": "e5456c80-f2d9-11ec-8001-1612b36d5392", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160628} -{"stream": "events", "data": {"object": "event", "id": "3qGYdhqVJt3", "statistic_id": "Yy9QKx", "timestamp": 1655977824, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977824"}, "datetime": "2022-06-23 09:50:24+00:00", "uuid": "e70f3000-f2d9-11ec-8001-bb0b16b7cfac", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160628} -{"stream": "events", "data": {"object": "event", "id": "3qGXfMgL7Bx", "statistic_id": "Yy9QKx", "timestamp": 1655977825, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977825"}, "datetime": "2022-06-23 09:50:25+00:00", "uuid": "e7a7c680-f2d9-11ec-8001-6fa286f53298", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160629} -{"stream": "events", "data": {"object": "event", "id": "3qGYQRY6DM7", "statistic_id": "SxR9Bt", "timestamp": 1655977825, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "URL": "https://airbyte-integration-test.myshopify.com/58033176765/checkouts/8988df67e8a6e2b7bf023a552637e307/recover?key=7c63931e1b806f6dd564419121bf39cf", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977825"}, "datetime": "2022-06-23 09:50:25+00:00", "uuid": "e7a7c680-f2d9-11ec-8001-345986f990ac", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160629} -{"stream": "events", "data": {"object": "event", "id": "3qGVEAeAFBD", "statistic_id": "Yy9QKx", "timestamp": 1655977837, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655977837"}, "datetime": "2022-06-23 09:50:37+00:00", "uuid": "eeced480-f2d9-11ec-8001-3765d7946eb6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160630} -{"stream": "events", "data": {"object": "event", "id": "3qGXTrJhmMa", "statistic_id": "Yy9QKx", "timestamp": 1655978018, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655978018"}, "datetime": "2022-06-23 09:53:38+00:00", "uuid": "5ab13d00-f2da-11ec-8001-0a91ae948181", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160631} -{"stream": "events", "data": {"object": "event", "id": "3qH2s43F5zM", "statistic_id": "Yy9QKx", "timestamp": 1655978139, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1655978139"}, "datetime": "2022-06-23 09:55:39+00:00", "uuid": "a2d05f80-f2da-11ec-8001-bfe3c709348c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160631} -{"stream": "events", "data": {"object": "event", "id": "3qH2s43F5zT", "statistic_id": "Yy9QKx", "timestamp": 1655978139, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655978139"}, "datetime": "2022-06-23 09:55:39+00:00", "uuid": "a2d05f80-f2da-11ec-8001-f4776f0fdea6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160632} -{"stream": "events", "data": {"object": "event", "id": "3qHdTFRrb22", "statistic_id": "WKHXf4", "timestamp": 1655981413, "event_name": "Received Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214431423610868539555680815495038128746"}, "datetime": "2022-06-23 10:50:13+00:00", "uuid": "42452080-f2e2-11ec-8001-2361c5f70584", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160633} -{"stream": "events", "data": {"object": "event", "id": "3qHjWjg7Tqz", "statistic_id": "Yy9QKx", "timestamp": 1655981415, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981415"}, "datetime": "2022-06-23 10:50:15+00:00", "uuid": "43764d80-f2e2-11ec-8001-31cb460651c4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160633} -{"stream": "events", "data": {"object": "event", "id": "3qHhHzBXEnD", "statistic_id": "Yy9QKx", "timestamp": 1655981416, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981416"}, "datetime": "2022-06-23 10:50:16+00:00", "uuid": "440ee400-f2e2-11ec-8001-5a39fc7429d2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160634} -{"stream": "events", "data": {"object": "event", "id": "3qHjCvZWnMq", "statistic_id": "Yy9QKx", "timestamp": 1655981417, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981417"}, "datetime": "2022-06-23 10:50:17+00:00", "uuid": "44a77a80-f2e2-11ec-8001-ca51ac5039d1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160634} -{"stream": "events", "data": {"object": "event", "id": "3qHhpJqBYFa", "statistic_id": "SxR9Bt", "timestamp": 1655981421, "event_name": "Clicked Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "URL": "https://www.klaviyo.com/?utm_medium=freebie&utm_source=brand&utm_term=RjQ4wJ", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981421"}, "datetime": "2022-06-23 10:50:21+00:00", "uuid": "4709d480-f2e2-11ec-8001-bd7bcebf1faf", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160635} -{"stream": "events", "data": {"object": "event", "id": "3qHhHFtw5gq", "statistic_id": "Yy9QKx", "timestamp": 1655981426, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981426"}, "datetime": "2022-06-23 10:50:26+00:00", "uuid": "4a04c500-f2e2-11ec-8001-bf26e79f0aec", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160635} -{"stream": "events", "data": {"object": "event", "id": "3qHgL6spnwA", "statistic_id": "Yy9QKx", "timestamp": 1655981427, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981427"}, "datetime": "2022-06-23 10:50:27+00:00", "uuid": "4a9d5b80-f2e2-11ec-8001-92f265bf22dc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160636} -{"stream": "events", "data": {"object": "event", "id": "3qHjiLaQvKz", "statistic_id": "Yy9QKx", "timestamp": 1655981428, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Browser", "Client OS Family": "Windows", "Client OS": "Windows", "Client Name": "Chrome", "Client Canonical": "Chrome", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981428"}, "datetime": "2022-06-23 10:50:28+00:00", "uuid": "4b35f200-f2e2-11ec-8001-a4c9066578c6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160637} -{"stream": "events", "data": {"object": "event", "id": "3qHh5WCjSMB", "statistic_id": "Yy9QKx", "timestamp": 1655981581, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": "Other", "Client OS Family": "Linux", "Client OS": "Linux", "Client Name": "Gmail image proxy", "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655981581"}, "datetime": "2022-06-23 10:53:01+00:00", "uuid": "a667e480-f2e2-11ec-8001-e8a8ed6e94e1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160637} -{"stream": "events", "data": {"object": "event", "id": "3qHtU7fb5zs", "statistic_id": "Yy9QKx", "timestamp": 1655983192, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655983192"}, "datetime": "2022-06-23 11:19:52+00:00", "uuid": "66a2fc00-f2e6-11ec-8001-c486a940f38d", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160638} -{"stream": "events", "data": {"object": "event", "id": "3qHsiSfPSzd", "statistic_id": "Yy9QKx", "timestamp": 1655983194, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1655983194"}, "datetime": "2022-06-23 11:19:54+00:00", "uuid": "67d42900-f2e6-11ec-8001-72818fc98fe1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160639} -{"stream": "events", "data": {"object": "event", "id": "3qHxq2Yery8", "statistic_id": "Yy9QKx", "timestamp": 1655983676, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": false, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655983676"}, "datetime": "2022-06-23 11:27:56+00:00", "uuid": "871f8600-f2e7-11ec-8001-ec142bfe40fa", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160639} -{"stream": "events", "data": {"object": "event", "id": "3qHzWDLUyjb", "statistic_id": "Yy9QKx", "timestamp": 1655984544, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1655984544"}, "datetime": "2022-06-23 11:42:24+00:00", "uuid": "8c7dd000-f2e9-11ec-8001-2ff0494a839a", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160640} -{"stream": "events", "data": {"object": "event", "id": "3qKWn63wGWA", "statistic_id": "Yy9QKx", "timestamp": 1656009356, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655909389:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214431387482826433051142872838996775530:1656009356"}, "datetime": "2022-06-23 18:35:56+00:00", "uuid": "51988e00-f323-11ec-8001-02bb6b0e69f2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160640} -{"stream": "events", "data": {"object": "event", "id": "3qKWnaUuezH", "statistic_id": "Yy9QKx", "timestamp": 1656009356, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655904289:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214431373855582480597676806749437317738:1656009356"}, "datetime": "2022-06-23 18:35:56+00:00", "uuid": "51988e00-f323-11ec-8001-4a3d26f227bc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160641} -{"stream": "events", "data": {"object": "event", "id": "3qKXjzd6MSY", "statistic_id": "Yy9QKx", "timestamp": 1656009356, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655905850:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214431377816990606310893686426634834538:1656009356"}, "datetime": "2022-06-23 18:35:56+00:00", "uuid": "51988e00-f323-11ec-8001-2aa38484208f", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160641} -{"stream": "events", "data": {"object": "event", "id": "3qKXDk2MNtg", "statistic_id": "Yy9QKx", "timestamp": 1656009356, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655981410:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214431423610868539555680815495038128746:1656009356"}, "datetime": "2022-06-23 18:35:56+00:00", "uuid": "51988e00-f323-11ec-8001-98595b25d5fc", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160642} -{"stream": "events", "data": {"object": "event", "id": "3qKXDvhq2vU", "statistic_id": "Yy9QKx", "timestamp": 1656009356, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655977809:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214431413390435575215581265927868535402:1656009356"}, "datetime": "2022-06-23 18:35:56+00:00", "uuid": "51988e00-f323-11ec-8001-88331621ee8b", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160642} -{"stream": "events", "data": {"object": "event", "id": "3qKXXixzwuE", "statistic_id": "Yy9QKx", "timestamp": 1656009356, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655976309:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214431410617449887216329450153830273642:1656009356"}, "datetime": "2022-06-23 18:35:56+00:00", "uuid": "51988e00-f323-11ec-8001-672aa7430cac", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160643} -{"stream": "events", "data": {"object": "event", "id": "3qL3M8pKWkA", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-1cb50cb78ac4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160643} -{"stream": "events", "data": {"object": "event", "id": "3qL4qNjaF5J", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-8b54ca73918c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160644} -{"stream": "events", "data": {"object": "event", "id": "3qL4qQgkQac", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-be2a0d8fc9c2", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160644} -{"stream": "events", "data": {"object": "event", "id": "3qL5ZSxcCWE", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-5099f62da1c6", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160644} -{"stream": "events", "data": {"object": "event", "id": "3qL6jFMndGD", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-aeb9cf2ecb92", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160645} -{"stream": "events", "data": {"object": "event", "id": "3qL6jGKXmVM", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-96802fa69cc1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160645} -{"stream": "events", "data": {"object": "event", "id": "3qL6DwvrjuV", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-994916660080", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160645} -{"stream": "events", "data": {"object": "event", "id": "3qL6XjgiqM3", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-737c248a8cfe", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160646} -{"stream": "events", "data": {"object": "event", "id": "3qL6XpDX88V", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-3ab063422db4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160646} -{"stream": "events", "data": {"object": "event", "id": "3qL6Xr7R3tg", "statistic_id": "Yy9QKx", "timestamp": 1656010798, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1656010798"}, "datetime": "2022-06-23 18:59:58+00:00", "uuid": "ad184b00-f326-11ec-8001-00c547d90ed9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160647} -{"stream": "events", "data": {"object": "event", "id": "3qL5ZSxcCWF", "statistic_id": "Yy9QKx", "timestamp": 1656010799, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1656010799"}, "datetime": "2022-06-23 18:59:59+00:00", "uuid": "adb0e180-f326-11ec-8001-44d9c0e8efbe", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160647} -{"stream": "events", "data": {"object": "event", "id": "3qL6XkJbZBz", "statistic_id": "Yy9QKx", "timestamp": 1656010799, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1656010799"}, "datetime": "2022-06-23 18:59:59+00:00", "uuid": "adb0e180-f326-11ec-8001-2bbbb68a42fd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160647} -{"stream": "events", "data": {"object": "event", "id": "3zwpXcHJUZC", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381510:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430581732413662982829546497021858410:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-4b34fa0192e1", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160648} -{"stream": "events", "data": {"object": "event", "id": "3zwqh7kadsB", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655453529:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430604787808954633751786218311406186:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-455a41a067dd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160648} -{"stream": "events", "data": {"object": "event", "id": "3zwqUGnBFr6", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655308016:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430495373716522434701569534115992170:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-b444cf6bd08c", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160649} -{"stream": "events", "data": {"object": "event", "id": "3zwrynMjhND", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385610:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526748068878083379256577520325226:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-90c6f855ac89", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160649} -{"stream": "events", "data": {"object": "event", "id": "3zwsvNxPMtd", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309810:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430499414352810662182786804857459306:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-ee60f14ff381", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160650} -{"stream": "events", "data": {"object": "event", "id": "3zwsvNxPMte", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313650:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430506465659274431708832630269039210:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-24a36682bfcd", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160650} -{"stream": "events", "data": {"object": "event", "id": "3zwt9pAhMHu", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655380029:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430515814582451114900668668455178858:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-1c7c32b140c4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160650} -{"stream": "events", "data": {"object": "event", "id": "3zwt9qySTpV", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655313530:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430506227974786888915819849637188202:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-51091ab9d3b9", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160651} -{"stream": "events", "data": {"object": "event", "id": "3zwt9qySTq8", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655385550:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430526668840715569114918983976374890:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-92ed017ed3c4", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160651} -{"stream": "events", "data": {"object": "event", "id": "3zwt9sw54x6", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "It looks like you left something behind...", "Campaign Name": "Abandoned Cart: Email 1", "$message": "SfFuN7", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655309440:SfFuN7", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "SfFuN7", "$ESP": 0, "machine_open": true, "$event_id": "SfFuN7:214430498780527510548068086056505856618:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-549dabd427a3", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "SfFuN7", "campaign_id": null}, "emitted_at": 1663367160652} -{"stream": "events", "data": {"object": "event", "id": "3zwttdP42ks", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381830:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430523103573402427219727274498609770:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-bd8fad124789", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160652} -{"stream": "events", "data": {"object": "event", "id": "3zwtM6XzGUj", "statistic_id": "Yy9QKx", "timestamp": 1661186821, "event_name": "Opened Email", "event_properties": {"Subject": "Your cart is about to expire.", "Campaign Name": "Abandoned Cart: Email 2", "$message": "VQxfP5", "Email Domain": "airbyte.io", "$flow": "YfYbWb", "$_cohort$message_send_cohort": "1655381409:VQxfP5", "Client Type": null, "Client OS Family": null, "Client OS": null, "Client Name": null, "$message_interaction": "VQxfP5", "$ESP": 0, "machine_open": true, "$event_id": "VQxfP5:214430522469748102313105026526147007082:1661186821"}, "datetime": "2022-08-22 16:47:01+00:00", "uuid": "0b37b080-223a-11ed-8001-d0a28f921a94", "person": {"object": "person", "id": "01F23YG492HT91MKG2R285HN5W", "$address1": "2261 Market Street", "$address2": "4381", "$city": "San Francisco", "$country": "United States", "$latitude": 50.5183, "$longitude": 30.5088, "$region": "California", "$zip": "94114", "$title": "", "$email": "integration-test@airbyte.io", "$organization": "", "$last_name": "Team", "$first_name": "Airbyte", "$phone_number": "", "$timezone": "America/Los_Angeles", "$id": "", "Accepts Marketing": false, "Shopify Tags": [], "email": "integration-test@airbyte.io", "first_name": "Airbyte", "last_name": "Team", "created": "2021-03-31 10:50:36", "updated": "2022-08-22 16:47:06"}, "flow_id": "YfYbWb", "flow_message_id": "VQxfP5", "campaign_id": null}, "emitted_at": 1663367160652} +{"stream": "events", "data": {"object": "event", "id": "3qvdbYg3", "statistic_id": "VFFb4u", "timestamp": 1621295008, "event_name": "Clicked Email", "event_properties": { "$event_id": "1621295008" }, "datetime": "2021-05-17 23:43:28+00:00", "uuid": "adc8d000-b769-11eb-8001-28a6687f81c3", "person": { "object": "person", "id": "01F5YBDQE9W7WDSH9KK398CAYX", "$address1": "", "$address2": "", "$city": "", "$country": "", "$latitude": "", "$longitude": "", "$region": "", "$zip": "", "$last_name": "", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.{seed}@airbyte.io", "$first_name": "", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.{seed}@airbyte.io", "first_name": "", "last_name": "", "created": "2021-05-17 23:43:50", "updated": "2021-05-17 23:43:50" }, "flow_id": null, "flow_message_id": null, "campaign_id": null }, "emitted_at": 1663367160652} +{"stream": "events", "data": {"object": "event", "id": "3qvdgpzF", "statistic_id": "VFFb4u", "timestamp": 1621295124, "event_name": "Clicked Email", "event_properties": { "$event_id": "1621295124" }, "datetime": "2021-05-17 23:45:24+00:00", "uuid": "f2ed0200-b769-11eb-8001-76152f6b1c82", "person": { "object": "person", "id": "01F5YBGKW1SQN453RM293PHH37", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 0", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.0@airbyte.io", "$first_name": "First Name 0", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.0@airbyte.io", "first_name": "First Name 0", "last_name": "Last Name 0", "created": "2021-05-17 23:45:24", "updated": "2021-05-17 23:45:25" }, "flow_id": null, "flow_message_id": null, "campaign_id": null }, "emitted_at": 1663367160652} +{"stream": "events", "data": {"object": "event", "id": "3qvdgr5Z", "statistic_id": "VFFb4u", "timestamp": 1621295124, "event_name": "Clicked Email", "event_properties": { "$event_id": "1621295124" }, "datetime": "2021-05-17 23:45:24+00:00", "uuid": "f2ed0200-b769-11eb-8001-b642ddab48ad", "person": { "object": "person", "id": "01F5YBGM7J4YD4P6EYK5Q87BG4", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 1", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.1@airbyte.io", "$first_name": "First Name 1", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.1@airbyte.io", "first_name": "First Name 1", "last_name": "Last Name 1", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:26" }, "flow_id": null, "flow_message_id": null, "campaign_id": null }, "emitted_at": 1663367160652} +{"stream": "events", "data": {"object": "event", "id": "3qvdgBgK", "statistic_id": "VFFb4u", "timestamp": 1621295124, "event_name": "Clicked Email", "event_properties": { "$event_id": "1621295124" }, "datetime": "2021-05-17 23:45:24+00:00", "uuid": "f2ed0200-b769-11eb-8001-2006a2b2b6e7", "person": { "object": "person", "id": "01F5YBGMK62AJR0955G7NW6EP7", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 2", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.2@airbyte.io", "$first_name": "First Name 2", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.2@airbyte.io", "first_name": "First Name 2", "last_name": "Last Name 2", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:38" }, "flow_id": null, "flow_message_id": null, "campaign_id": null }, "emitted_at": 1663367160652} +{"stream": "events", "data": {"object": "event", "id": "3qvdgs9P", "statistic_id": "VFFb4u", "timestamp": 1621295125, "event_name": "Clicked Email", "event_properties": { "$event_id": "1621295125" }, "datetime": "2021-05-17 23:45:25+00:00", "uuid": "f3859880-b769-11eb-8001-f6a061424b91", "person": { "object": "person", "id": "01F5YBGMK62AJR0955G7NW6EP7", "$address1": "", "$address2": "", "$city": "Springfield", "$country": "", "$latitude": "", "$longitude": "", "$region": "Illinois", "$zip": "", "$last_name": "Last Name 2", "$title": "", "$organization": "", "$phone_number": "", "$email": "some.email.that.dont.exist.2@airbyte.io", "$first_name": "First Name 2", "$timezone": "", "$id": "", "email": "some.email.that.dont.exist.2@airbyte.io", "first_name": "First Name 2", "last_name": "Last Name 2", "created": "2021-05-17 23:45:25", "updated": "2021-05-17 23:45:38" }, "flow_id": null, "flow_message_id": null, "campaign_id": null }, "emitted_at": 1663367160652} {"stream": "flows", "data": {"object": "flow", "id": "VueJfU", "name": "Happy Birthday Email - Standard", "status": "manual", "archived": false, "trigger": {"type": "Date Based", "filter": null}, "customer_filter": null, "created": "2022-05-31T07:01:40+00:00", "updated": "2022-05-31T07:01:41+00:00"}, "emitted_at": 1663367161059} {"stream": "flows", "data": {"object": "flow", "id": "U5LCpF", "name": "Welcome Series - Customer v. Non-Customer", "status": "live", "archived": false, "trigger": {"type": "Added to List", "filter": null, "list": {"id": "RnsiHB", "name": "Newsletter"}}, "customer_filter": null, "created": "2022-05-31T07:00:28+00:00", "updated": "2022-05-31T07:00:29+00:00"}, "emitted_at": 1663367161065} {"stream": "flows", "data": {"object": "flow", "id": "VcNt87", "name": "Welcome Series - Standard (Email & SMS)", "status": "manual", "archived": false, "trigger": {"type": "Added to List", "filter": null, "list": {"id": "RnsiHB", "name": "Newsletter"}}, "customer_filter": null, "created": "2022-05-31T06:58:03+00:00", "updated": "2022-05-31T06:58:04+00:00"}, "emitted_at": 1663367161067} diff --git a/airbyte-integrations/connectors/source-marketo/integration_tests/expected_records.txt b/airbyte-integrations/connectors/source-marketo/integration_tests/expected_records.txt index 594941380aac..8dad84fbc48b 100644 --- a/airbyte-integrations/connectors/source-marketo/integration_tests/expected_records.txt +++ b/airbyte-integrations/connectors/source-marketo/integration_tests/expected_records.txt @@ -51,7 +51,7 @@ {"stream": "activity_types", "data": {"id": 145, "name": "Push Lead to Marketo", "primaryAttribute": {"name": "Program ID", "dataType": "integer"}, "attributes": [{"name": "Reason", "dataType": "string"}, {"name": "Source", "dataType": "string"}, {"name": "API Method Name", "dataType": "string"}, {"name": "Modifying User", "dataType": "string"}, {"name": "Request Id", "dataType": "string"}]}, "emitted_at": 1661177367609} {"stream": "activity_types", "data": {"id": 155, "name": "Execute Campaign", "description": "Invoke an executable campaign", "primaryAttribute": {"name": "Campaign ID", "dataType": "integer"}, "attributes": [{"name": "Qualified", "dataType": "boolean"}, {"name": "Token Context Campaign ID", "dataType": "integer"}, {"name": "Used Parent Campaign Token Context", "dataType": "boolean"}]}, "emitted_at": 1661177367609} {"stream": "activity_types", "data": {"id": 157, "name": "Reply to Sales Email", "description": "User replies to a sales email", "primaryAttribute": {"name": "Artifact ID", "dataType": "integer"}, "attributes": [{"name": "Campaign Run ID", "dataType": "integer"}, {"name": "Has Predictive", "dataType": "boolean"}, {"name": "Marketo Sales Person ID", "dataType": "integer"}, {"name": "Sales Campaign Name", "dataType": "string"}, {"name": "Sales Campaign URL", "dataType": "string"}, {"name": "Sales Email URL", "dataType": "string"}, {"name": "Sales Template Name", "dataType": "string"}, {"name": "Sales Template URL", "dataType": "string"}, {"name": "Sent by", "dataType": "string"}, {"name": "Source", "dataType": "string"}, {"name": "Template ID", "dataType": "integer"}]}, "emitted_at": 1661177367609} -{"stream": "activity_types", "data": {"id": 158, "name": "Dialogue Engaged", "description": "Lead engages with Dynamic Chat dialogue", "primaryAttribute": {"name": "Dialogue Id", "dataType": "integer"}, "attributes": [{"name": "Page URL", "dataType": "string"}, {"name": "Status", "dataType": "string"}]}, "emitted_at": 1661177367609} +{"stream": "activity_types", "data": {"id": 158, "name": "Dialogue Engaged", "description": "Lead engages with Dynamic Chat dialogue", "primaryAttribute": {"name": "Dialogue Id", "dataType": "integer"}, "attributes": [{"name": "Chat Transcript", "dataType": "string"}, {"name": "Page URL", "dataType": "string"}, {"name": "Status", "dataType": "string"}]}, "emitted_at": 1661177367609} {"stream": "activity_types", "data": {"id": 159, "name": "Document Interacted With", "description": "Lead interacts with a document in Dynamic Chat dialogue", "primaryAttribute": {"name": "Document Id", "dataType": "integer"}, "attributes": [{"name": "Dialogue Id", "dataType": "integer"}, {"name": "Document Downloaded", "dataType": "boolean"}, {"name": "Document Opened", "dataType": "boolean"}, {"name": "Document URL", "dataType": "string"}, {"name": "Search Terms", "dataType": "string"}]}, "emitted_at": 1661177367610} {"stream": "activity_types", "data": {"id": 160, "name": "Dialogue Appointment Scheduled", "description": "Lead schedules an appointment in Dynamic Chat dialogue", "primaryAttribute": {"name": "Agent Id", "dataType": "integer"}, "attributes": [{"name": "Dialogue Id", "dataType": "integer"}, {"name": "Page URL", "dataType": "string"}, {"name": "Scheduled For", "dataType": "datetime"}, {"name": "Status", "dataType": "string"}]}, "emitted_at": 1661177367610} {"stream": "activity_types", "data": {"id": 161, "name": "Dialogue Goal Reached", "description": "Lead reaches a goal in Dynamic Chat dialogue", "primaryAttribute": {"name": "Dialogue Id", "dataType": "integer"}, "attributes": [{"name": "Goal Name", "dataType": "string"}, {"name": "Page URL", "dataType": "string"}]}, "emitted_at": 1661177367610} From 40896d00c0ac33d8a2bfe9144626fc5ead9de006 Mon Sep 17 00:00:00 2001 From: Tyler B <104733644+TBernstein4@users.noreply.github.com> Date: Tue, 18 Oct 2022 09:47:39 -0500 Subject: [PATCH 160/498] Re-direct Postgres guide to active page, current link is 404'd (#18103) The current "Refer to this guide..." link goes to a 404 page on postgres. The new link i put in is the equivalent of the old site. It can be compared using the wayback machine: https://web.archive.org/web/20220121223117/https://jdbc.postgresql.org/documentation/head/connect.html --- docs/integrations/destinations/postgres.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/destinations/postgres.md b/docs/integrations/destinations/postgres.md index 99e33f2d0a10..1aa587411e1a 100644 --- a/docs/integrations/destinations/postgres.md +++ b/docs/integrations/destinations/postgres.md @@ -21,7 +21,7 @@ You'll need the following information to configure the Postgres destination: * **Database** - The database name. The default is to connect to a database with the same name as the user name. * **JDBC URL Params** (optional) -[Refer to this guide for more details](https://jdbc.postgresql.org/documentation/head/connect.html) +[Refer to this guide for more details](https://jdbc.postgresql.org/documentation/use/#connecting-to-the-database) #### Configure Network Access From 4db2364a67567fabfb1fc7d9d9a80ef0de496306 Mon Sep 17 00:00:00 2001 From: Augustin Date: Tue, 18 Oct 2022 17:40:39 +0200 Subject: [PATCH 161/498] source-e2e-test: update changelog with 0.1.2 (#18108) --- docs/integrations/sources/e2e-test.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/integrations/sources/e2e-test.md b/docs/integrations/sources/e2e-test.md index dcbcd8c2ca4d..ab2a0a8f185c 100644 --- a/docs/integrations/sources/e2e-test.md +++ b/docs/integrations/sources/e2e-test.md @@ -68,5 +68,6 @@ The OSS and Cloud variants have the same version number. The Cloud variant was i | 2.0.0 | 2021-02-01 | [\#9954](https://github.com/airbytehq/airbyte/pull/9954) | Remove legacy modes. Use more efficient Json generator. | | 1.0.1 | 2021-01-29 | [\#9745](https://github.com/airbytehq/airbyte/pull/9745) | Integrate with Sentry. | | 1.0.0 | 2021-01-23 | [\#9720](https://github.com/airbytehq/airbyte/pull/9720) | Add new continuous feed mode that supports arbitrary catalog specification. Initial release to cloud. | +| 0.1.2 | 2022-10-18 | [\#18100](https://github.com/airbytehq/airbyte/pull/18100) | Set supported sync mode on streams | | 0.1.1 | 2021-12-16 | [\#8217](https://github.com/airbytehq/airbyte/pull/8217) | Fix sleep time in infinite feed mode. | | 0.1.0 | 2021-07-23 | [\#3290](https://github.com/airbytehq/airbyte/pull/3290) [\#4939](https://github.com/airbytehq/airbyte/pull/4939) | Initial release. | From 7cb01a76e69564f699313e1da898e44e078c2fdb Mon Sep 17 00:00:00 2001 From: "JC (Jonathan Chen)" Date: Tue, 18 Oct 2022 09:08:43 -0700 Subject: [PATCH 162/498] docs: clarify language (#18090) --- docs/cloud/core-concepts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cloud/core-concepts.md b/docs/cloud/core-concepts.md index d64315393db4..c53c3189712a 100644 --- a/docs/cloud/core-concepts.md +++ b/docs/cloud/core-concepts.md @@ -14,7 +14,7 @@ A destination is a data warehouse, data lake, database, or an analytics tool whe ## Connector -An Airbyte component which pulls data from, or pushes data to, a source or destination. +An Airbyte component which pulls data from a source or pushes data to a destination. ## Connection From 510daa4b0d91861029b8954f3fbf1ef96faabb1c Mon Sep 17 00:00:00 2001 From: midavadim Date: Tue, 18 Oct 2022 19:12:55 +0300 Subject: [PATCH 163/498] increased timeout for sat tests (#18114) increase timeout for sat tests --- .../source-tiktok-marketing/acceptance-test-config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml b/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml index 8a39ffa837c9..971be94ebdb6 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml @@ -68,23 +68,23 @@ tests: incremental: - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_basic.json" - timeout_seconds: 2400 + timeout_seconds: 3600 future_state_path: "integration_tests/abnormal_state.json" - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_reports_daily.json" - timeout_seconds: 2400 + timeout_seconds: 3600 future_state_path: "integration_tests/abnormal_state.json" # LIFETIME granularity: does not support incremental sync full_refresh: - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_basic.json" - timeout_seconds: 2400 + timeout_seconds: 3600 ignored_fields: # Important: sometimes some streams does not return the same records in subsequent syncs "ad_groups": ["dayparting", "enable_search_result", "display_mode", "schedule_infos", "feed_type", "status" ] - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_reports_daily.json" - timeout_seconds: 2400 + timeout_seconds: 3600 - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_reports_lifetime.json" - timeout_seconds: 2400 \ No newline at end of file + timeout_seconds: 3600 \ No newline at end of file From 8c3162aeba345667510f1f8d538eca26fa18339f Mon Sep 17 00:00:00 2001 From: Yevhen Sukhomud Date: Tue, 18 Oct 2022 23:36:32 +0700 Subject: [PATCH 164/498] 11679 BigQuery-Denormalized Destination: improve code coverage (#17827) * 11679 BigQuery-Denormalized Destination improve code coverage --- .../build.gradle | 3 + .../bigquery/JsonSchemaFormat.java | 4 - ...ltBigQueryDenormalizedRecordFormatter.java | 14 +- .../arrayformater/DefaultArrayFormatter.java | 28 +- .../arrayformater/LegacyArrayFormatter.java | 22 +- .../formatter/util/FormatterUtil.java | 34 ++ .../BigQueryDenormalizedDestinationTest.java | 389 ++++++++++++++++++ .../BigQueryDenormalizedUtilsTest.java | 267 ------------ .../bigquery/JsonSchemaFormatTest.java | 31 ++ .../bigquery/JsonSchemaTypeTest.java | 25 ++ ...gQueryDenormalizedRecordFormatterTest.java | 288 +++++++++++++ ...gQueryDenormalizedRecordFormatterTest.java | 70 ++++ .../DefaultArrayFormatterTest.java | 98 +++++ .../LegacyArrayFormatterTest.java | 90 ++++ .../formatter/util/FormatterUtilTest.java | 54 +++ .../BigQueryDenormalizedTestSchemaUtils.java | 8 + ...stBigQueryDenormalizedRecordFormatter.java | 29 -- ...csBigQueryDenormalizedRecordFormatter.java | 25 -- .../schemas/expectedSchemaArraysLegacy.json | 105 +++++ .../test/resources/schemas/schemaAllOf.json | 16 + 20 files changed, 1224 insertions(+), 376 deletions(-) create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtil.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedDestinationTest.java delete mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedUtilsTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormatTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaTypeTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/GcsBigQueryDenormalizedRecordFormatterTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java delete mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestBigQueryDenormalizedRecordFormatter.java delete mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestGcsBigQueryDenormalizedRecordFormatter.java create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/expectedSchemaArraysLegacy.json create mode 100644 airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/schemaAllOf.json diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/build.gradle b/airbyte-integrations/connectors/destination-bigquery-denormalized/build.gradle index 88c4b75901c8..8f7e0786c3ee 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/build.gradle +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/build.gradle @@ -21,6 +21,9 @@ dependencies { implementation project(':airbyte-integrations:connectors:destination-gcs') implementation group: 'org.apache.parquet', name: 'parquet-avro', version: '1.12.0' + testImplementation 'org.hamcrest:hamcrest-all:1.3' + testImplementation 'org.mockito:mockito-inline:4.7.0' + integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-bigquery-denormalized') integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-normalization').airbyteDocker.outputs) diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormat.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormat.java index c8b8402ef062..bd309187e279 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormat.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormat.java @@ -59,10 +59,6 @@ public static JsonSchemaFormat fromJsonSchemaFormat(final @Nonnull String jsonSc } } - public String getJsonSchemaFormat() { - return jsonSchemaFormat; - } - public StandardSQLTypeName getBigQueryType() { return bigQueryType; } diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatter.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatter.java index 06cd6288c2a4..5f101e6f9f9f 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatter.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatter.java @@ -4,6 +4,8 @@ package io.airbyte.integrations.destination.bigquery.formatter; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.TYPE_FIELD; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -46,7 +48,6 @@ public class DefaultBigQueryDenormalizedRecordFormatter extends DefaultBigQueryR private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBigQueryDenormalizedRecordFormatter.class); public static final String PROPERTIES_FIELD = "properties"; - public static final String TYPE_FIELD = "type"; private static final String ALL_OF_FIELD = "allOf"; private static final String ANY_OF_FIELD = "anyOf"; private static final String FORMAT_FIELD = "format"; @@ -81,17 +82,6 @@ protected JsonNode formatJsonSchema(final JsonNode jsonSchema) { return modifiedJsonSchema; } - private JsonNode formatAllOfAndAnyOfFields(final StandardNameTransformer namingResolver, final JsonNode jsonSchema) { - LOGGER.info("getSchemaFields : " + jsonSchema + " namingResolver " + namingResolver); - final JsonNode modifiedSchema = jsonSchema.deepCopy(); - Preconditions.checkArgument(modifiedSchema.isObject() && modifiedSchema.has(PROPERTIES_FIELD)); - ObjectNode properties = (ObjectNode) modifiedSchema.get(PROPERTIES_FIELD); - Jsons.keys(properties).stream() - .peek(addToRefList(properties)) - .forEach(key -> properties.replace(key, getFileDefinition(properties.get(key)))); - return modifiedSchema; - } - @Override public JsonNode formatRecord(final AirbyteRecordMessage recordMessage) { // Bigquery represents TIMESTAMP to the microsecond precision, so we convert to microseconds then diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatter.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatter.java index b7ae89fee5a0..0ea7d77b2e33 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatter.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatter.java @@ -5,22 +5,21 @@ package io.airbyte.integrations.destination.bigquery.formatter.arrayformater; import static io.airbyte.integrations.destination.bigquery.formatter.DefaultBigQueryDenormalizedRecordFormatter.PROPERTIES_FIELD; -import static io.airbyte.integrations.destination.bigquery.formatter.DefaultBigQueryDenormalizedRecordFormatter.TYPE_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.ARRAY_ITEMS_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.NESTED_ARRAY_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.TYPE_FIELD; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; public class DefaultArrayFormatter implements ArrayFormatter { - public static final String NESTED_ARRAY_FIELD = "big_query_array"; - public static final String ARRAY_ITEMS_FIELD = "items"; - @Override public void populateEmptyArrays(final JsonNode node) { findArrays(node).forEach(jsonNode -> { @@ -35,7 +34,7 @@ public void populateEmptyArrays(final JsonNode node) { public void surroundArraysByObjects(final JsonNode node) { findArrays(node).forEach( jsonNode -> { - if (isAirbyteArray(jsonNode.get(ARRAY_ITEMS_FIELD))) { + if (FormatterUtil.isAirbyteArray(jsonNode.get(ARRAY_ITEMS_FIELD))) { final ObjectNode arrayNode = jsonNode.get(ARRAY_ITEMS_FIELD).deepCopy(); final ObjectNode originalNode = (ObjectNode) jsonNode; @@ -58,26 +57,11 @@ public JsonNode formatArrayItems(List arrayItems) { protected List findArrays(final JsonNode node) { if (node != null) { return node.findParents(TYPE_FIELD).stream() - .filter(this::isAirbyteArray) + .filter(FormatterUtil::isAirbyteArray) .collect(Collectors.toList()); } else { return Collections.emptyList(); } } - protected boolean isAirbyteArray(final JsonNode node) { - final JsonNode type = node.get(TYPE_FIELD); - if (type.isArray()) { - final ArrayNode typeNode = (ArrayNode) type; - for (final JsonNode arrayTypeNode : typeNode) { - if (arrayTypeNode.isTextual() && arrayTypeNode.textValue().equals("array")) { - return true; - } - } - } else if (type.isTextual()) { - return node.asText().equals("array"); - } - return false; - } - } diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatter.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatter.java index d36649780a44..1fd017ec7af6 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatter.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatter.java @@ -5,13 +5,15 @@ package io.airbyte.integrations.destination.bigquery.formatter.arrayformater; import static io.airbyte.integrations.destination.bigquery.formatter.DefaultBigQueryDenormalizedRecordFormatter.PROPERTIES_FIELD; -import static io.airbyte.integrations.destination.bigquery.formatter.DefaultBigQueryDenormalizedRecordFormatter.TYPE_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.ARRAY_ITEMS_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.NESTED_ARRAY_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.TYPE_FIELD; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; +import io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -37,21 +39,7 @@ public void surroundArraysByObjects(final JsonNode node) { protected List findArrays(final JsonNode node) { if (node != null) { return node.findParents(TYPE_FIELD).stream() - .filter( - jsonNode -> { - final JsonNode type = jsonNode.get(TYPE_FIELD); - if (type.isArray()) { - final ArrayNode typeNode = (ArrayNode) type; - for (final JsonNode arrayTypeNode : typeNode) { - if (arrayTypeNode.isTextual() && arrayTypeNode.textValue().equals("array")) { - return true; - } - } - } else if (type.isTextual()) { - return jsonNode.asText().equals("array"); - } - return false; - }) + .filter(FormatterUtil::isAirbyteArray) .collect(Collectors.toList()); } else { return Collections.emptyList(); diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtil.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtil.java new file mode 100644 index 000000000000..8aaa1cf5f87c --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/main/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtil.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery.formatter.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class FormatterUtil { + + public static final String NESTED_ARRAY_FIELD = "big_query_array"; + public static final String ARRAY_ITEMS_FIELD = "items"; + public static final String TYPE_FIELD = "type"; + + public static boolean isAirbyteArray(final JsonNode jsonSchemaNode) { + if (jsonSchemaNode == null || jsonSchemaNode.get("type") == null) { + return false; + } + final JsonNode type = jsonSchemaNode.get("type"); + if (type.isArray()) { + final ArrayNode typeNode = (ArrayNode) type; + for (final JsonNode arrayTypeNode : typeNode) { + if (arrayTypeNode.isTextual() && arrayTypeNode.textValue().equals("array")) { + return true; + } + } + } else if (type.isTextual()) { + return jsonSchemaNode.asText().equals("array"); + } + return false; + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedDestinationTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedDestinationTest.java new file mode 100644 index 000000000000..5b36c7bc1b31 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedDestinationTest.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery; + +import static com.google.cloud.bigquery.Field.Mode.REPEATED; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchema; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.cloud.bigquery.BigQuery; +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.FieldList; +import com.google.cloud.bigquery.LegacySQLTypeName; +import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardSQLTypeName; +import com.google.cloud.bigquery.Table; +import com.google.cloud.bigquery.TableDefinition; +import io.airbyte.integrations.base.AirbyteStreamNameNamespacePair; +import io.airbyte.integrations.destination.bigquery.formatter.BigQueryRecordFormatter; +import io.airbyte.integrations.destination.bigquery.formatter.DefaultBigQueryDenormalizedRecordFormatter; +import io.airbyte.integrations.destination.bigquery.formatter.GcsBigQueryDenormalizedRecordFormatter; +import io.airbyte.integrations.destination.bigquery.formatter.arrayformater.LegacyArrayFormatter; +import io.airbyte.integrations.destination.bigquery.uploader.AbstractBigQueryUploader; +import io.airbyte.integrations.destination.bigquery.uploader.BigQueryDirectUploader; +import io.airbyte.integrations.destination.bigquery.uploader.BigQueryUploaderFactory; +import io.airbyte.integrations.destination.bigquery.uploader.UploaderType; +import io.airbyte.integrations.destination.bigquery.uploader.config.UploaderConfig; +import io.airbyte.protocol.models.AirbyteStream; +import io.airbyte.protocol.models.ConfiguredAirbyteStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class BigQueryDenormalizedDestinationTest { + + @Mock + UploaderConfig uploaderConfigMock; + @Mock + ConfiguredAirbyteStream configuredStreamMock; + @Mock + AirbyteStream airbyteStreamMock; + @Mock + DefaultBigQueryDenormalizedRecordFormatter bigQueryRecordFormatterMock; + @Mock + BigQuery bigQueryMock; + + MockedStatic uploaderFactoryMock; + + @InjectMocks + BigQueryDenormalizedDestination bqdd; + + final ObjectMapper mapper = new ObjectMapper(); + + @BeforeEach + void init() { + uploaderFactoryMock = Mockito.mockStatic(BigQueryUploaderFactory.class, Mockito.CALLS_REAL_METHODS); + uploaderFactoryMock.when(() -> BigQueryUploaderFactory.getUploader(any(UploaderConfig.class))).thenReturn(mock(BigQueryDirectUploader.class)); + } + + @AfterEach + public void teardown() { + uploaderFactoryMock.close(); + } + + @Test + void getFormatterMap() { + final JsonNode jsonNodeSchema = getSchema(); + final Map formatterMap = bqdd.getFormatterMap(jsonNodeSchema); + assertEquals(2, formatterMap.size()); + assertTrue(formatterMap.containsKey(UploaderType.AVRO)); + assertTrue(formatterMap.containsKey(UploaderType.STANDARD)); + assertThat(formatterMap.get(UploaderType.AVRO), instanceOf(GcsBigQueryDenormalizedRecordFormatter.class)); + assertThat(formatterMap.get(UploaderType.STANDARD), instanceOf(DefaultBigQueryDenormalizedRecordFormatter.class)); + } + + @Test + void isDefaultAirbyteTmpTableSchema() { + assertFalse(bqdd.isDefaultAirbyteTmpTableSchema()); + } + + @Test + void getRecordFormatterCreator() { + final BigQuerySQLNameTransformer nameTransformerMock = mock(BigQuerySQLNameTransformer.class); + final BigQueryRecordFormatter resultFormatter = bqdd.getRecordFormatterCreator(nameTransformerMock) + .apply(mapper.createObjectNode()); + + assertThat(resultFormatter, instanceOf(GcsBigQueryDenormalizedRecordFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_compareSchemas_expectedIsNotNullExistingIsNull() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final String streamName = "stream_name"; + final String nameSpace = "name_space"; + final Table tableMock = mock(Table.class); + final Schema schemaMock = mock(Schema.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + + mockBigqueryStream(); + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn(nameSpace); + when(airbyteStreamMock.getName()).thenReturn(streamName); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + // expected schema is not null + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(schemaMock); + // existing schema is null + when(tableDefinitionMock.getSchema()).thenReturn(null); + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // should use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(1)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_compareSchemas_existingAndExpectedAreNull() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + + mockBigqueryStream(); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn("name_space"); + when(airbyteStreamMock.getName()).thenReturn("stream_name"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + + // expected schema is null + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(null); + // existing schema is null + when(tableDefinitionMock.getSchema()).thenReturn(null); + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // should not use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(0)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_compareSchemas_expectedSchemaIsNull() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final Table tableMock = mock(Table.class); + final Schema schemaMock = mock(Schema.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + + mockBigqueryStream(); + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + when(airbyteStreamMock.getNamespace()).thenReturn("name_space"); + when(airbyteStreamMock.getName()).thenReturn("stream_name"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + // expected schema is null + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(null); + // existing schema is not null + when(tableDefinitionMock.getSchema()).thenReturn(schemaMock); + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // should use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(1)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_isDifferenceBetweenFields_equalType() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + final Schema existingSchemaMock = mock(Schema.class); + final Schema expectedSchemaMock = mock(Schema.class); + + mockBigqueryStream(); + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(tableDefinitionMock.getSchema()).thenReturn(existingSchemaMock); + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(expectedSchemaMock); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn("name_space"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + final FieldList existingFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.STRING).build()); + final FieldList expectedFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.STRING).build()); + when(existingSchemaMock.getFields()).thenReturn(existingFields); + when(expectedSchemaMock.getFields()).thenReturn(expectedFields); + + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // equal type should not use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(0)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_isDifferenceBetweenFields_notEqualType() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + final Schema existingSchemaMock = mock(Schema.class); + final Schema expectedSchemaMock = mock(Schema.class); + + mockBigqueryStream(); + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(tableDefinitionMock.getSchema()).thenReturn(existingSchemaMock); + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(expectedSchemaMock); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn("name_space"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + final FieldList existingFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.DATE).build()); + final FieldList expectedFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.STRING).build()); + when(existingSchemaMock.getFields()).thenReturn(existingFields); + when(expectedSchemaMock.getFields()).thenReturn(expectedFields); + + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + + // equal type should not use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(1)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_isDifferenceBetweenFields_existingFieldIsNull() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + final Schema existingSchemaMock = mock(Schema.class); + final Schema expectedSchemaMock = mock(Schema.class); + + mockBigqueryStream(); + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(tableDefinitionMock.getSchema()).thenReturn(existingSchemaMock); + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(expectedSchemaMock); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn("name_space"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + final FieldList expectedFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.STRING).build()); + final FieldList existingFields = mock(FieldList.class); + when(existingSchemaMock.getFields()).thenReturn(existingFields); + when(expectedSchemaMock.getFields()).thenReturn(expectedFields); + when(existingFields.get(anyString())).thenReturn(null); + + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + + // equal type should not use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(1)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_compareRepeatedMode_isEqual() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + final Schema existingSchemaMock = mock(Schema.class); + final Schema expectedSchemaMock = mock(Schema.class); + + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(tableDefinitionMock.getSchema()).thenReturn(existingSchemaMock); + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(expectedSchemaMock); + mockBigqueryStream(); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn("name_space"); + when(airbyteStreamMock.getName()).thenReturn("stream_name"); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + final FieldList existingFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.STRING).setMode(REPEATED).build()); + final FieldList expectedFields = FieldList.of(Field.newBuilder("name", StandardSQLTypeName.STRING).setMode(REPEATED).build()); + when(existingSchemaMock.getFields()).thenReturn(existingFields); + when(expectedSchemaMock.getFields()).thenReturn(expectedFields); + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // equal mode should not use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(0)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_compareSubFields_equalType() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final String streamName = "stream_name"; + final String nameSpace = "name_space"; + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + final Schema existingSchemaMock = mock(Schema.class); + final Schema expectedSchemaMock = mock(Schema.class); + + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(tableDefinitionMock.getSchema()).thenReturn(existingSchemaMock); + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(expectedSchemaMock); + mockBigqueryStream(); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn(nameSpace); + when(airbyteStreamMock.getName()).thenReturn(streamName); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + final FieldList expectedSubField = FieldList.of(Field.newBuilder("sub_field_name", StandardSQLTypeName.STRING).build()); + final FieldList existingSubField = FieldList.of(Field.newBuilder("sub_field_name", StandardSQLTypeName.STRING).build()); + final Field existingField = Field.newBuilder("field_name", LegacySQLTypeName.RECORD, existingSubField).build(); + final Field expectedField = Field.newBuilder("field_name", LegacySQLTypeName.RECORD, expectedSubField).build(); + when(existingSchemaMock.getFields()).thenReturn(FieldList.of(existingField)); + when(expectedSchemaMock.getFields()).thenReturn(FieldList.of(expectedField)); + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // equal subfield type should not use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(0)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_compareSubFields_notEqualType() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final String streamName = "stream_name"; + final String nameSpace = "name_space"; + final Table tableMock = mock(Table.class); + final TableDefinition tableDefinitionMock = mock(TableDefinition.class); + final Schema existingSchemaMock = mock(Schema.class); + final Schema expectedSchemaMock = mock(Schema.class); + + when(tableMock.getDefinition()).thenReturn(tableDefinitionMock); + when(tableDefinitionMock.getSchema()).thenReturn(existingSchemaMock); + when(bigQueryRecordFormatterMock.getBigQuerySchema()).thenReturn(expectedSchemaMock); + mockBigqueryStream(); + when(uploaderConfigMock.getTargetTableName()).thenReturn("target_table"); + when(airbyteStreamMock.getNamespace()).thenReturn(nameSpace); + when(airbyteStreamMock.getName()).thenReturn(streamName); + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(tableMock); + + final FieldList expectedSubField = FieldList.of(Field.newBuilder("sub_field_name", StandardSQLTypeName.DATE).build()); + final FieldList existingSubField = FieldList.of(Field.newBuilder("sub_field_name", StandardSQLTypeName.STRING).build()); + final Field existingField = Field.newBuilder("field_name", LegacySQLTypeName.RECORD, existingSubField).build(); + final Field expectedField = Field.newBuilder("field_name", LegacySQLTypeName.RECORD, expectedSubField).build(); + when(existingSchemaMock.getFields()).thenReturn(FieldList.of(existingField)); + when(expectedSchemaMock.getFields()).thenReturn(FieldList.of(expectedField)); + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + // not equal subfield type should use LegacyArrayFormatter + verify(bigQueryRecordFormatterMock, times(1)).setArrayFormatter(any(LegacyArrayFormatter.class)); + } + + @Test + void putStreamIntoUploaderMap_existingTableIsNull() throws IOException { + final Map> uploaderMap = new HashMap<>(); + final String streamName = "stream_name"; + final String nameSpace = "name_space"; + final String targetTableName = "target_table"; + final AirbyteStreamNameNamespacePair expectedResult = new AirbyteStreamNameNamespacePair(streamName, nameSpace); + + mockBigqueryStream(); + when(uploaderConfigMock.getTargetTableName()).thenReturn(targetTableName); + when(airbyteStreamMock.getNamespace()).thenReturn(nameSpace); + when(airbyteStreamMock.getName()).thenReturn(streamName); + // existing table is null + when(bigQueryMock.getTable(anyString(), anyString())).thenReturn(null); + + // run test + bqdd.putStreamIntoUploaderMap(airbyteStreamMock, uploaderConfigMock, uploaderMap); + + verify(bigQueryRecordFormatterMock, times(0)).setArrayFormatter(any(LegacyArrayFormatter.class)); + assertTrue(uploaderMap.containsKey(expectedResult)); + } + + private void mockBigqueryStream() { + when(uploaderConfigMock.getConfigStream()).thenReturn(configuredStreamMock); + when(uploaderConfigMock.getBigQuery()).thenReturn(bigQueryMock); + when(uploaderConfigMock.getFormatter()).thenReturn(bigQueryRecordFormatterMock); + when(configuredStreamMock.getStream()).thenReturn(airbyteStreamMock); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedUtilsTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedUtilsTest.java deleted file mode 100644 index 3de0e1d4e17b..000000000000 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/BigQueryDenormalizedUtilsTest.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.integrations.destination.bigquery; - -import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.params.provider.Arguments.arguments; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.cloud.bigquery.Field; -import com.google.cloud.bigquery.Field.Mode; -import com.google.cloud.bigquery.FieldList; -import com.google.cloud.bigquery.LegacySQLTypeName; -import com.google.cloud.bigquery.Schema; -import com.google.cloud.bigquery.StandardSQLTypeName; -import io.airbyte.integrations.base.JavaBaseConstants; -import io.airbyte.integrations.destination.bigquery.formatter.GcsBigQueryDenormalizedRecordFormatter; -import io.airbyte.integrations.destination.bigquery.util.TestBigQueryDenormalizedRecordFormatter; -import io.airbyte.integrations.destination.bigquery.util.TestGcsBigQueryDenormalizedRecordFormatter; -import io.airbyte.protocol.models.AirbyteRecordMessage; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; - -public class BigQueryDenormalizedUtilsTest { - - @ParameterizedTest - @MethodSource("actualAndExpectedSchemasProvider") - void testGcsSchema(final JsonNode schemaToProcess, final JsonNode expectedSchema) { - TestGcsBigQueryDenormalizedRecordFormatter rf = new TestGcsBigQueryDenormalizedRecordFormatter( - schemaToProcess, new BigQuerySQLNameTransformer()); - - assertEquals(expectedSchema, rf.formatJsonSchema(schemaToProcess)); - } - - @Test - void testSchema() { - final JsonNode jsonNodeSchema = getSchema(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(5, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field accepts_marketing_updated_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("accepts_marketing_updated_at").getType()); - // test field name - assertEquals(LegacySQLTypeName.STRING, fields.get("name").getType()); - - // test field permission_list - final Field permission_list = fields.get("permission_list"); - assertEquals(LegacySQLTypeName.RECORD, permission_list.getType()); - final Field bigQueryArray = permission_list.getSubFields().get("big_query_array"); - - assertEquals(LegacySQLTypeName.RECORD, bigQueryArray.getType()); - assertEquals(Mode.REPEATED, bigQueryArray.getMode()); - - final FieldList subFieldsBigQueryArray = bigQueryArray.getSubFields(); - assertEquals(LegacySQLTypeName.STRING, subFieldsBigQueryArray.get("domain").getType()); - - final Field grants = subFieldsBigQueryArray.get("grants"); - assertEquals(LegacySQLTypeName.RECORD, grants.getType()); - assertEquals(1, grants.getSubFields().size()); - assertEquals(LegacySQLTypeName.STRING, grants.getSubFields().get("big_query_array").getType()); - assertEquals(Mode.REPEATED, grants.getSubFields().get("big_query_array").getMode()); - } - - @Test - void testSchemaWithFormats() { - final JsonNode jsonNodeSchema = getSchemaWithFormats(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(5, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field updated_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("updated_at").getType()); - // test field name - assertEquals(LegacySQLTypeName.STRING, fields.get("name").getType()); - // test field date_of_birth - assertEquals(LegacySQLTypeName.DATE, fields.get("date_of_birth").getType()); - } - - @Test - void testSchemaWithBigInteger() { - final JsonNode jsonNodeSchema = getSchemaWithBigInteger(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(4, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field updated_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("updated_at").getType()); - // test field salary - assertEquals(StandardSQLTypeName.INT64, fields.get("salary").getType().getStandardType()); - } - - @Test - void testSchemaWithDateTime() { - final JsonNode jsonNodeSchema = getSchemaWithDateTime(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(4, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field updated_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("updated_at").getType()); - - // test field items - final Field items = fields.get("items"); - assertEquals(1, items.getSubFields().size()); - assertEquals(LegacySQLTypeName.RECORD, items.getType()); - assertEquals(LegacySQLTypeName.TIMESTAMP, - items.getSubFields().get("nested_datetime").getType()); - } - - @Test - void testSchemaWithInvalidArrayType() { - final JsonNode jsonNodeSchema = getSchemaWithInvalidArrayType(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(4, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field name - assertEquals(LegacySQLTypeName.STRING, fields.get("name").getType()); - - // test field permission_list - final Field permissionList = fields.get("permission_list"); - assertEquals(2, permissionList.getSubFields().size()); - assertEquals(LegacySQLTypeName.RECORD, permissionList.getType()); - assertEquals(Mode.REPEATED, permissionList.getMode()); - assertEquals(LegacySQLTypeName.STRING, permissionList.getSubFields().get("domain").getType()); - - assertEquals(LegacySQLTypeName.STRING, permissionList.getSubFields().get("grants").getType()); - assertEquals(Mode.REPEATED, permissionList.getSubFields().get("grants").getMode()); - } - - @Test - void testSchemaWithReferenceDefinition() { - final JsonNode jsonNodeSchema = getSchemaWithReferenceDefinition(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(3, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field users - assertEquals(LegacySQLTypeName.STRING, fields.get("users").getType()); - - } - - @Test - void testSchemaWithNestedDatetimeInsideNullObject() { - final JsonNode jsonNodeSchema = getSchemaWithNestedDatetimeInsideNullObject(); - GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( - jsonNodeSchema, new BigQuerySQLNameTransformer()); - - final Schema bigQuerySchema = rf.getBigQuerySchema(jsonNodeSchema); - - final FieldList fields = bigQuerySchema.getFields(); - - assertEquals(4, fields.size()); - // test field _airbyte_ab_id - assertEquals(LegacySQLTypeName.STRING, fields.get("_airbyte_ab_id").getType()); - // test field _airbyte_emitted_at - assertEquals(LegacySQLTypeName.TIMESTAMP, fields.get("_airbyte_emitted_at").getType()); - - // test field name - assertEquals(LegacySQLTypeName.STRING, fields.get("name").getType()); - - // test field appointment - final Field appointment = fields.get("appointment"); - assertEquals(2, appointment.getSubFields().size()); - assertEquals(LegacySQLTypeName.RECORD, appointment.getType()); - assertEquals(Mode.NULLABLE, appointment.getMode()); - - assertEquals(LegacySQLTypeName.STRING, appointment.getSubFields().get("street").getType()); - assertEquals(Mode.NULLABLE, appointment.getSubFields().get("street").getMode()); - - assertEquals(LegacySQLTypeName.TIMESTAMP, appointment.getSubFields().get("expTime").getType()); - assertEquals(Mode.NULLABLE, appointment.getSubFields().get("expTime").getMode()); - - } - - @Test - public void testEmittedAtTimeConversion() { - final TestBigQueryDenormalizedRecordFormatter mockedFormatter = Mockito.mock( - TestBigQueryDenormalizedRecordFormatter.class, Mockito.CALLS_REAL_METHODS); - - final ObjectMapper mapper = new ObjectMapper(); - final ObjectNode objectNode = mapper.createObjectNode(); - - final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); - airbyteRecordMessage.setEmittedAt(1602637589000L); - mockedFormatter.addAirbyteColumns(objectNode, airbyteRecordMessage); - - assertEquals("2020-10-14 01:06:29.000000+00:00", - objectNode.get(JavaBaseConstants.COLUMN_NAME_EMITTED_AT).textValue()); - } - - private static Stream actualAndExpectedSchemasProvider() { - return Stream.of( - arguments(getSchema(), getExpectedSchema()), - arguments(getSchemaWithFormats(), getExpectedSchemaWithFormats()), - arguments(getSchemaWithDateTime(), getExpectedSchemaWithDateTime()), - arguments(getSchemaWithInvalidArrayType(), getExpectedSchemaWithInvalidArrayType()), - arguments(getSchemaWithReferenceDefinition(), getExpectedSchemaWithReferenceDefinition()), - arguments(getSchemaWithNestedDatetimeInsideNullObject(), - getExpectedSchemaWithNestedDatetimeInsideNullObject()), - arguments(getSchemaArrays(), getExpectedSchemaArrays())); - } - -} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormatTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormatTest.java new file mode 100644 index 000000000000..5df21a6345f4 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaFormatTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class JsonSchemaFormatTest { + + @Test + void fromJsonSchemaFormat_matchByFormatAndType() { + JsonSchemaFormat result = JsonSchemaFormat.fromJsonSchemaFormat("date-time", "timestamp_with_timezone"); + assertEquals(JsonSchemaFormat.DATETIME_WITH_TZ, result); + } + + @Test + void fromJsonSchemaFormat_matchByFormat() { + JsonSchemaFormat result = JsonSchemaFormat.fromJsonSchemaFormat("date", null); + assertEquals(JsonSchemaFormat.DATE, result); + } + + @Test + void fromJsonSchemaFormat_notExistingFormat() { + JsonSchemaFormat result = JsonSchemaFormat.fromJsonSchemaFormat("not_existing_format", null); + assertNull(result); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaTypeTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaTypeTest.java new file mode 100644 index 000000000000..c5176fb09cf2 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/JsonSchemaTypeTest.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class JsonSchemaTypeTest { + + @Test + void fromJsonSchemaType_notPresent() { + assertThrows(IllegalArgumentException.class, () -> JsonSchemaType.fromJsonSchemaType("not_existing_value")); + } + + @Test + void fromJsonSchemaType_getType() { + JsonSchemaType result = JsonSchemaType.fromJsonSchemaType("string"); + assertEquals(JsonSchemaType.STRING, result); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java new file mode 100644 index 000000000000..212add9f3e3c --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery.formatter; + +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchema; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaArrays; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaWithDateTime; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaWithFormats; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaWithInvalidArrayType; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaWithNestedDatetimeInsideNullObject; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchema; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaArrays; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithBigInteger; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithDateTime; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithFormats; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithInvalidArrayType; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithNestedDatetimeInsideNullObject; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithReferenceDefinition; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.Field.Mode; +import com.google.cloud.bigquery.LegacySQLTypeName; +import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardSQLTypeName; +import io.airbyte.integrations.base.JavaBaseConstants; +import io.airbyte.integrations.destination.bigquery.BigQuerySQLNameTransformer; +import io.airbyte.protocol.models.AirbyteRecordMessage; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +class DefaultBigQueryDenormalizedRecordFormatterTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + private static Stream actualAndExpectedSchemasProvider() { + return Stream.of( + arguments(getSchema(), getExpectedSchema()), + arguments(getSchemaWithFormats(), getExpectedSchemaWithFormats()), + arguments(getSchemaWithDateTime(), getExpectedSchemaWithDateTime()), + arguments(getSchemaWithInvalidArrayType(), getExpectedSchemaWithInvalidArrayType()), + arguments(getSchemaWithNestedDatetimeInsideNullObject(), + getExpectedSchemaWithNestedDatetimeInsideNullObject()), + arguments(getSchemaArrays(), getExpectedSchemaArrays())); + } + + @ParameterizedTest + @MethodSource("actualAndExpectedSchemasProvider") + void testDefaultSchema(final JsonNode schemaToProcess, final JsonNode expectedSchema) { + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + schemaToProcess, new BigQuerySQLNameTransformer()); + + assertEquals(expectedSchema, rf.formatJsonSchema(schemaToProcess)); + } + + @Test + void testSchema() { + final JsonNode jsonNodeSchema = getSchema(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Field subFields = Field.newBuilder("big_query_array", LegacySQLTypeName.RECORD, + Field.of("domain", LegacySQLTypeName.STRING), + Field.of("grants", LegacySQLTypeName.RECORD, + Field.newBuilder("big_query_array", StandardSQLTypeName.STRING).setMode(Mode.REPEATED).build())) + .setMode(Mode.REPEATED).build(); + final Schema expectedResult = Schema.of( + Field.newBuilder("accepts_marketing_updated_at", LegacySQLTypeName.DATETIME).setMode(Mode.NULLABLE).build(), + Field.of("name", LegacySQLTypeName.STRING), + Field.of("permission_list", LegacySQLTypeName.RECORD, subFields), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void testSchemaWithFormats() { + final JsonNode jsonNodeSchema = getSchemaWithFormats(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.of("name", LegacySQLTypeName.STRING), + Field.of("date_of_birth", LegacySQLTypeName.DATE), + Field.of("updated_at", LegacySQLTypeName.DATETIME), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void testSchemaWithBigInteger() { + final JsonNode jsonNodeSchema = getSchemaWithBigInteger(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.of("salary", LegacySQLTypeName.INTEGER), + Field.of("updated_at", LegacySQLTypeName.DATETIME), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void testSchemaWithDateTime() { + final JsonNode jsonNodeSchema = getSchemaWithDateTime(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.of("updated_at", LegacySQLTypeName.DATETIME), + Field.of("items", LegacySQLTypeName.RECORD, Field.of("nested_datetime", LegacySQLTypeName.DATETIME)), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void testSchemaWithInvalidArrayType() { + final JsonNode jsonNodeSchema = getSchemaWithInvalidArrayType(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.of("name", LegacySQLTypeName.STRING), + Field.newBuilder("permission_list", LegacySQLTypeName.RECORD, + Field.of("domain", LegacySQLTypeName.STRING), + Field.newBuilder("grants", LegacySQLTypeName.STRING).setMode(Mode.REPEATED).build()) + .setMode(Mode.REPEATED).build(), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void testSchemaWithReferenceDefinition() { + final JsonNode jsonNodeSchema = getSchemaWithReferenceDefinition(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.of("users", LegacySQLTypeName.STRING), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void testSchemaWithNestedDatetimeInsideNullObject() { + final JsonNode jsonNodeSchema = getSchemaWithNestedDatetimeInsideNullObject(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.newBuilder("name", LegacySQLTypeName.STRING).setMode(Mode.NULLABLE).build(), + Field.newBuilder("appointment", LegacySQLTypeName.RECORD, + Field.newBuilder("street", LegacySQLTypeName.STRING).setMode(Mode.NULLABLE).build(), + Field.newBuilder("expTime", LegacySQLTypeName.DATETIME).setMode(Mode.NULLABLE).build()) + .setMode(Mode.NULLABLE).build(), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + public void testEmittedAtTimeConversion() { + final DefaultBigQueryDenormalizedRecordFormatter mockedFormatter = Mockito.mock( + DefaultBigQueryDenormalizedRecordFormatter.class, Mockito.CALLS_REAL_METHODS); + + final ObjectNode objectNode = mapper.createObjectNode(); + + final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); + airbyteRecordMessage.setEmittedAt(1602637589000L); + mockedFormatter.addAirbyteColumns(objectNode, airbyteRecordMessage); + + assertEquals("2020-10-14 01:06:29.000000+00:00", + objectNode.get(JavaBaseConstants.COLUMN_NAME_EMITTED_AT).textValue()); + } + + @Test + void formatRecord_objectType() throws JsonProcessingException { + final JsonNode jsonNodeSchema = getSchema(); + final DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final JsonNode objectNode = mapper.readTree(""" + {"name":"data"} + """); + final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); + airbyteRecordMessage.setEmittedAt(1602637589000L); + airbyteRecordMessage.setData(objectNode); + + final JsonNode result = rf.formatRecord(airbyteRecordMessage); + + assertNotNull(result); + assertTrue(result.has("name")); + assertEquals("data", result.get("name").textValue()); + assertEquals(JsonNodeType.STRING, result.get("name").getNodeType()); + } + + @Test + void formatRecord_containsRefDefinition() throws JsonProcessingException { + final JsonNode jsonNodeSchema = getSchema(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + rf.fieldsContainRefDefinitionValue.add("name"); + final JsonNode objectNode = mapper.readTree(""" + {"name":"data"} + """); + final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); + airbyteRecordMessage.setEmittedAt(1602637589000L); + airbyteRecordMessage.setData(objectNode); + + final JsonNode result = rf.formatRecord(airbyteRecordMessage); + + assertNotNull(result); + assertTrue(result.has("name")); + assertEquals("\"data\"", result.get("name").textValue()); + assertEquals(JsonNodeType.STRING, result.get("name").getNodeType()); + } + + @Test + void formatRecord_objectWithArray() throws JsonProcessingException { + final JsonNode jsonNodeSchema = getSchemaArrays(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final JsonNode objectNode = mapper.readTree(""" + {"object_with_arrays":["array_3"]} + """); + final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); + airbyteRecordMessage.setEmittedAt(1602637589000L); + airbyteRecordMessage.setData(objectNode); + + final JsonNode result = rf.formatRecord(airbyteRecordMessage); + + assertNotNull(result); + assertTrue(result.has("object_with_arrays")); + result.has("object_with_arrays"); + assertEquals(JsonNodeType.ARRAY, result.get("object_with_arrays").getNodeType()); + assertNotNull(result.get("object_with_arrays").get(0)); + assertEquals(JsonNodeType.STRING, result.get("object_with_arrays").get(0).getNodeType()); + } + + @Test + void formatRecordNotObject_thenThrowsError() throws JsonProcessingException { + final JsonNode jsonNodeSchema = getSchema(); + DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final JsonNode arrayNode = mapper.readTree(""" + ["one"]"""); + + final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); + airbyteRecordMessage.setEmittedAt(1602637589000L); + airbyteRecordMessage.setData(arrayNode); + + assertThrows(IllegalArgumentException.class, () -> rf.formatRecord(airbyteRecordMessage)); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/GcsBigQueryDenormalizedRecordFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/GcsBigQueryDenormalizedRecordFormatterTest.java new file mode 100644 index 000000000000..0f136f48c675 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/GcsBigQueryDenormalizedRecordFormatterTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery.formatter; + +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaWithReferenceDefinition; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithDateTime; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaWithReferenceDefinition; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.LegacySQLTypeName; +import com.google.cloud.bigquery.Schema; +import io.airbyte.integrations.base.JavaBaseConstants; +import io.airbyte.integrations.destination.bigquery.BigQuerySQLNameTransformer; +import io.airbyte.protocol.models.AirbyteRecordMessage; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class GcsBigQueryDenormalizedRecordFormatterTest { + + @Test + void refReplacement() { + final JsonNode jsonNodeSchema = getSchemaWithReferenceDefinition(); + final JsonNode expectedResult = getExpectedSchemaWithReferenceDefinition(); + final GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + + final JsonNode result = rf.formatJsonSchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + void dataTimeReplacement() { + final JsonNode jsonNodeSchema = getSchemaWithDateTime(); + final GcsBigQueryDenormalizedRecordFormatter rf = new GcsBigQueryDenormalizedRecordFormatter( + jsonNodeSchema, new BigQuerySQLNameTransformer()); + final Schema expectedResult = Schema.of( + Field.of("updated_at", LegacySQLTypeName.TIMESTAMP), + Field.of("items", LegacySQLTypeName.RECORD, Field.of("nested_datetime", LegacySQLTypeName.TIMESTAMP)), + Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), + Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); + + final Schema result = rf.getBigQuerySchema(jsonNodeSchema); + + assertEquals(expectedResult, result); + } + + @Test + public void testEmittedAtTimeConversion() { + final GcsBigQueryDenormalizedRecordFormatter mockedFormatter = Mockito.mock( + GcsBigQueryDenormalizedRecordFormatter.class, Mockito.CALLS_REAL_METHODS); + + final ObjectMapper mapper = new ObjectMapper(); + final ObjectNode objectNode = mapper.createObjectNode(); + + final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); + airbyteRecordMessage.setEmittedAt(1602637589000L); + mockedFormatter.addAirbyteColumns(objectNode, airbyteRecordMessage); + + assertEquals("1602637589000", + objectNode.get(JavaBaseConstants.COLUMN_NAME_EMITTED_AT).asText()); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java new file mode 100644 index 000000000000..1d76cb1ba964 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery.formatter.arrayformater; + +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaArrays; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaArrays; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.List; +import org.junit.jupiter.api.Test; + +class DefaultArrayFormatterTest { + + private final DefaultArrayFormatter formatter = new DefaultArrayFormatter(); + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + void surroundArraysByObjects() { + final JsonNode schemaArrays = getSchemaArrays(); + final JsonNode expectedSchemaArrays = getExpectedSchemaArrays(); + formatter.surroundArraysByObjects(schemaArrays); + assertEquals(expectedSchemaArrays, schemaArrays); + } + + @Test + void formatArrayItems() throws JsonProcessingException { + final JsonNode expectedArrayNode = mapper.readTree( + """ + [ + {"big_query_array": ["one", "two"]}, + {"big_query_array": ["one", "two"]} + ] + """); + final List arrayNodes = List.of( + mapper.readTree(""" + ["one", "two"]"""), + mapper.readTree(""" + ["one", "two"]""")); + + final JsonNode result = formatter.formatArrayItems(arrayNodes); + + assertEquals(expectedArrayNode, result); + } + + @Test + void formatArrayItems_notArray() throws JsonProcessingException { + final JsonNode objectNodeInput = mapper.readTree(""" + {"type":"object","items":{"type":"integer"}}"""); + final JsonNode expectedResult = mapper.readTree(""" + [{"type":"object","items":{"type":"integer"}}]"""); + + final JsonNode result = formatter.formatArrayItems(List.of(objectNodeInput)); + + assertEquals(expectedResult, result); + } + + @Test + void findArrays() throws JsonProcessingException { + final JsonNode schemaArrays = getSchemaArrays(); + final List expectedResult = List.of( + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}""")); + + final List result = formatter.findArrays(schemaArrays); + assertEquals(expectedResult, result); + } + + @Test + void findArrays_null() { + final List result = formatter.findArrays(null); + assertTrue(result.isEmpty()); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java new file mode 100644 index 000000000000..16b3d5fa74e1 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery.formatter.arrayformater; + +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.NESTED_ARRAY_FIELD; +import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.TYPE_FIELD; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaArraysLegacy; +import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaArrays; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeType; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class LegacyArrayFormatterTest { + + private final LegacyArrayFormatter formatter = new LegacyArrayFormatter(); + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + void surroundArraysByObjects() { + final JsonNode schemaArrays = getSchemaArrays(); + final JsonNode expectedSchemaArrays = getExpectedSchemaArraysLegacy(); + + formatter.surroundArraysByObjects(schemaArrays); + assertEquals(expectedSchemaArrays, schemaArrays); + } + + @Test + void findArrays() throws JsonProcessingException { + final JsonNode schemaArrays = getSchemaArrays(); + final List expectedResult = List.of( + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}""")); + + final List result = formatter.findArrays(schemaArrays); + + assertEquals(expectedResult, result); + } + + @Test + void findArrays_null() { + final List result = formatter.findArrays(null); + assertTrue(result.isEmpty()); + } + + @Test + void formatArrayItems() throws JsonProcessingException { + final JsonNode expectedArrayNode = mapper.readTree( + """ + {"big_query_array": [["one", "two"], ["one", "two"]]} + """); + final List arrayNodes = List.of( + mapper.readTree(""" + ["one", "two"]"""), + mapper.readTree(""" + ["one", "two"]""")); + + final JsonNode result = formatter.formatArrayItems(arrayNodes); + + assertEquals(expectedArrayNode, result); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java new file mode 100644 index 000000000000..bb8e0bef3c31 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.destination.bigquery.formatter.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +class FormatterUtilTest { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + void isAirbyteArray_typeIsNull() throws JsonProcessingException { + final JsonNode arrayNode = mapper.readTree( + """ + ["one", "two"]"""); + + final boolean result = FormatterUtil.isAirbyteArray(arrayNode); + assertFalse(result); + } + + @Test + void isAirbyteArray_typeFieldIsArray() throws JsonProcessingException { + final JsonNode arrayNode = mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""); + + boolean result = FormatterUtil.isAirbyteArray(arrayNode); + assertTrue(result); + } + + @Test + void isAirbyteArray_typeFieldIsNotArray() throws JsonProcessingException { + final JsonNode objectNode = mapper.readTree(""" + {"type":"object"}"""); + final boolean result = FormatterUtil.isAirbyteArray(objectNode); + assertFalse(result); + } + + @Test + void isAirbyteArray_textIsNotArray() throws JsonProcessingException { + final JsonNode arrayNode = mapper.readTree(""" + {"type":["notArrayText"]}"""); + final boolean result = FormatterUtil.isAirbyteArray(arrayNode); + assertFalse(result); + } + +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestSchemaUtils.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestSchemaUtils.java index 4e6498ecffe9..e2d3ea43d65e 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestSchemaUtils.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestSchemaUtils.java @@ -74,6 +74,14 @@ public static JsonNode getExpectedSchemaArrays() { return getTestDataFromResourceJson("expectedSchemaArrays.json"); } + public static JsonNode getExpectedSchemaArraysLegacy() { + return getTestDataFromResourceJson("expectedSchemaArraysLegacy.json"); + } + + public static JsonNode getSchemaWithAllOf() { + return getTestDataFromResourceJson("schemaAllOf.json"); + } + private static JsonNode getTestDataFromResourceJson(final String fileName) { final String fileContent; try { diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestBigQueryDenormalizedRecordFormatter.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestBigQueryDenormalizedRecordFormatter.java deleted file mode 100644 index 4a67fd9d467a..000000000000 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestBigQueryDenormalizedRecordFormatter.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.integrations.destination.bigquery.util; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import io.airbyte.integrations.destination.StandardNameTransformer; -import io.airbyte.integrations.destination.bigquery.formatter.DefaultBigQueryDenormalizedRecordFormatter; -import io.airbyte.protocol.models.AirbyteRecordMessage; - -public class TestBigQueryDenormalizedRecordFormatter extends - DefaultBigQueryDenormalizedRecordFormatter { - - public TestBigQueryDenormalizedRecordFormatter( - JsonNode jsonSchema, - StandardNameTransformer namingResolver) { - super(jsonSchema, namingResolver); - } - - @Override - public void addAirbyteColumns(ObjectNode data, - AirbyteRecordMessage recordMessage) { - // this method just exposes a protected method for testing making it public - super.addAirbyteColumns(data, recordMessage); - } - -} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestGcsBigQueryDenormalizedRecordFormatter.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestGcsBigQueryDenormalizedRecordFormatter.java deleted file mode 100644 index 469980dc8f46..000000000000 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/util/TestGcsBigQueryDenormalizedRecordFormatter.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.integrations.destination.bigquery.util; - -import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.integrations.destination.StandardNameTransformer; -import io.airbyte.integrations.destination.bigquery.formatter.GcsBigQueryDenormalizedRecordFormatter; - -public class TestGcsBigQueryDenormalizedRecordFormatter extends - GcsBigQueryDenormalizedRecordFormatter { - - public TestGcsBigQueryDenormalizedRecordFormatter( - JsonNode jsonSchema, - StandardNameTransformer namingResolver) { - super(jsonSchema, namingResolver); - } - - @Override - public JsonNode formatJsonSchema(JsonNode jsonSchema) { - return super.formatJsonSchema(jsonSchema); - } - -} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/expectedSchemaArraysLegacy.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/expectedSchemaArraysLegacy.json new file mode 100644 index 000000000000..15f76827e99c --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/expectedSchemaArraysLegacy.json @@ -0,0 +1,105 @@ +{ + "type": ["object"], + "properties": { + "object_with_arrays": { + "type": ["object"], + "properties": { + "array_3": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": "integer" + } + } + } + } + } + }, + "simple_string": { + "type": ["string"] + }, + "array_1": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": "integer" + } + } + } + } + } + } + }, + "array_4": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": "integer" + } + } + } + } + } + } + } + } + } + }, + "array_5": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": ["object"], + "properties": { + "big_query_array": { + "type": ["array"], + "items": { + "type": "integer" + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/schemaAllOf.json b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/schemaAllOf.json new file mode 100644 index 000000000000..84fe8c6393c3 --- /dev/null +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/resources/schemas/schemaAllOf.json @@ -0,0 +1,16 @@ +{ + "definitions": { + "schemaArray": { + "type": "array", + "items": { "$ref": "#" } + } + }, + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + + "allOf": { "$ref": "#/definitions/schemaArray" } + } +} From b4c16bfdbb9147467d375c4f995723843aa6d347 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 18 Oct 2022 13:14:29 -0400 Subject: [PATCH 165/498] Custom APM Tracing (#17947) * Add APM tracing to airbyte-workers * Add custom traces to airbyte-cron * Formatting * Refactor tag constants * Handle potential null object * Handle null tag values --- airbyte-cron/build.gradle | 1 + .../io/airbyte/cron/MicronautCronRunner.java | 2 + .../{ => selfhealing}/DefinitionsUpdater.java | 6 +- .../io/airbyte/cron/selfhealing/Temporal.java | 4 + .../cron/selfhealing/WorkspaceCleaner.java | 10 ++- airbyte-metrics/metrics-lib/build.gradle | 4 +- .../io/airbyte/metrics/lib/ApmTraceUtils.java | 62 +++++++++++++++ .../metrics/lib/ApmTraceUtilsTest.java | 78 +++++++++++++++++++ airbyte-workers/build.gradle | 1 + .../CheckConnectionActivityImpl.java | 13 ++++ .../CheckConnectionWorkflowImpl.java | 10 ++- .../catalog/DiscoverCatalogActivityImpl.java | 9 +++ .../catalog/DiscoverCatalogWorkflowImpl.java | 9 +++ .../ConnectionManagerWorkflowImpl.java | 55 ++++++++++--- .../AutoDisableConnectionActivityImpl.java | 7 ++ .../activities/ConfigFetchActivityImpl.java | 9 +++ .../ConnectionDeletionActivityImpl.java | 8 ++ .../activities/GenerateInputActivityImpl.java | 10 +++ ...obCreationAndStatusUpdateActivityImpl.java | 39 ++++++++++ .../activities/RecordMetricActivityImpl.java | 31 ++++++++ .../RouteToSyncTaskQueueActivityImpl.java | 9 +++ .../activities/StreamResetActivityImpl.java | 9 +++ .../WorkflowConfigActivityImpl.java | 4 + .../temporal/spec/SpecActivityImpl.java | 10 +++ .../temporal/spec/SpecWorkflowImpl.java | 9 +++ .../sync/DbtTransformationActivityImpl.java | 10 +++ .../sync/NormalizationActivityImpl.java | 11 +++ ...NormalizationSummaryCheckActivityImpl.java | 9 +++ .../sync/PersistStateActivityImpl.java | 7 ++ .../sync/ReplicationActivityImpl.java | 11 +++ .../temporal/sync/SyncWorkflowImpl.java | 18 ++++- .../trace/TemporalTraceConstants.java | 59 ++++++++++++++ deps.toml | 3 +- 33 files changed, 519 insertions(+), 18 deletions(-) rename airbyte-cron/src/main/java/io/airbyte/cron/{ => selfhealing}/DefinitionsUpdater.java (92%) create mode 100644 airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java create mode 100644 airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/ApmTraceUtilsTest.java create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java diff --git a/airbyte-cron/build.gradle b/airbyte-cron/build.gradle index 548c5d13efb4..6ff678e4b087 100644 --- a/airbyte-cron/build.gradle +++ b/airbyte-cron/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation 'io.sentry:sentry:6.3.1' implementation 'io.temporal:temporal-sdk:1.8.1' implementation 'io.temporal:temporal-serviceclient:1.8.1' + implementation libs.bundles.datadog implementation project(':airbyte-api') implementation project(':airbyte-analytics') diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/MicronautCronRunner.java b/airbyte-cron/src/main/java/io/airbyte/cron/MicronautCronRunner.java index 093957977810..868b6c788d64 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/MicronautCronRunner.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/MicronautCronRunner.java @@ -14,6 +14,8 @@ */ public class MicronautCronRunner { + public static final String SCHEDULED_TRACE_OPERATION_NAME = "scheduled"; + public static void main(final String[] args) { Micronaut.build(args) .mainClass(MicronautCronRunner.class) diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/DefinitionsUpdater.java b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/DefinitionsUpdater.java similarity index 92% rename from airbyte-cron/src/main/java/io/airbyte/cron/DefinitionsUpdater.java rename to airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/DefinitionsUpdater.java index 1000f95e9133..53214f4d24a1 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/DefinitionsUpdater.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/DefinitionsUpdater.java @@ -2,8 +2,11 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.cron; +package io.airbyte.cron.selfhealing; +import static io.airbyte.cron.MicronautCronRunner.SCHEDULED_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.config.Configs.DeploymentMode; import io.airbyte.config.EnvConfigs; import io.airbyte.config.init.ApplyDefinitionsHelper; @@ -43,6 +46,7 @@ public DefinitionsUpdater() { deploymentMode = envConfigs.getDeploymentMode(); } + @Trace(operationName = SCHEDULED_TRACE_OPERATION_NAME) @Scheduled(fixedRate = "30s", initialDelay = "1m") void updateDefinitions() { diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java index 39a0bc87f2d8..c2dd559ef0dc 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/Temporal.java @@ -4,6 +4,9 @@ package io.airbyte.cron.selfhealing; +import static io.airbyte.cron.MicronautCronRunner.SCHEDULED_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.TemporalClient; import io.micronaut.scheduling.annotation.Scheduled; import io.temporal.api.enums.v1.WorkflowExecutionStatus; @@ -21,6 +24,7 @@ public Temporal(final TemporalClient temporalClient) { this.temporalClient = temporalClient; } + @Trace(operationName = SCHEDULED_TRACE_OPERATION_NAME) @Scheduled(fixedRate = "10s") void cleanTemporal() { temporalClient.restartClosedWorkflowByStatus(WorkflowExecutionStatus.WORKFLOW_EXECUTION_STATUS_FAILED); diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/WorkspaceCleaner.java b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/WorkspaceCleaner.java index 6af6af927b2d..dfae48e00e25 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/WorkspaceCleaner.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/selfhealing/WorkspaceCleaner.java @@ -4,11 +4,14 @@ package io.airbyte.cron.selfhealing; +import static io.airbyte.cron.MicronautCronRunner.SCHEDULED_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.config.Configs; import io.airbyte.config.EnvConfigs; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.micronaut.context.annotation.Requires; import io.micronaut.context.env.Environment; -import io.micronaut.scheduling.annotation.Scheduled; import jakarta.inject.Singleton; import java.io.File; import java.io.IOException; @@ -17,6 +20,7 @@ import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.util.Date; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; @@ -47,11 +51,13 @@ public class WorkspaceCleaner { * * NOTE: this is currently only intended to work for docker */ - @Scheduled(fixedRate = "1d") + @Trace(operationName = SCHEDULED_TRACE_OPERATION_NAME) public void deleteOldFiles() throws IOException { final Date oldestAllowed = getDateFromDaysAgo(maxAgeFilesInDays); log.info("Deleting files older than {} days ({})", maxAgeFilesInDays, oldestAllowed); + ApmTraceUtils.addTagsToTrace(Map.of("oldest_date_allowed", oldestAllowed, "max_age", maxAgeFilesInDays)); + final AtomicInteger counter = new AtomicInteger(0); Files.walk(workspaceRoot) .map(Path::toFile) diff --git a/airbyte-metrics/metrics-lib/build.gradle b/airbyte-metrics/metrics-lib/build.gradle index de30a4c16e17..110dfe751b00 100644 --- a/airbyte-metrics/metrics-lib/build.gradle +++ b/airbyte-metrics/metrics-lib/build.gradle @@ -17,11 +17,13 @@ dependencies { implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-exporter-otlp") - implementation 'com.datadoghq:java-dogstatsd-client:4.0.0' + implementation libs.java.dogstatsd.client + implementation libs.bundles.datadog testImplementation project(':airbyte-config:config-persistence') testImplementation project(':airbyte-test-utils') testImplementation libs.platform.testcontainers.postgresql + testImplementation "io.opentracing:opentracing-util:0.33.0:tests" } Task publishArtifactsTask = getPublishArtifactsTask("$rootProject.ext.version", project) diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java new file mode 100644 index 000000000000..e7dd884a3a17 --- /dev/null +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.lib; + +import io.opentracing.Span; +import io.opentracing.util.GlobalTracer; +import java.util.Map; + +/** + * Collection of utility methods to help with performance tracing. + */ +public class ApmTraceUtils { + + /** + * String format for the name of tags added to spans. + */ + public static final String TAG_FORMAT = "airbyte.%s.%s"; + + /** + * Standard prefix for tags added to spans. + */ + public static final String TAG_PREFIX = "metadata"; + + /** + * Adds all the provided tags to the currently active span, if one exists.
    + * All tags added via this method will use the default {@link #TAG_PREFIX} namespace. + * + * @param tags A map of tags to be added to the currently active span. + */ + public static void addTagsToTrace(final Map tags) { + addTagsToTrace(tags, TAG_PREFIX); + } + + /** + * Adds all provided tags to the currently active span, if one exists, under the provided tag name + * namespace. + * + * @param tags A map of tags to be added to the currently active span. + * @param tagPrefix The prefix to be added to each custom tag name. + */ + public static void addTagsToTrace(final Map tags, final String tagPrefix) { + addTagsToTrace(GlobalTracer.get().activeSpan(), tags, tagPrefix); + } + + /** + * Adds all the provided tags to the currently active span, if one exists. + * + * @param span The {@link Span} that will be associated with the tags. + * @param tags A map of tags to be added to the currently active span. + * @param tagPrefix The prefix to be added to each custom tag name. + */ + public static void addTagsToTrace(final Span span, final Map tags, final String tagPrefix) { + if (span != null) { + tags.entrySet().forEach(entry -> { + span.setTag(String.format(TAG_FORMAT, tagPrefix, entry.getKey()), entry.getValue().toString()); + }); + } + } + +} diff --git a/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/ApmTraceUtilsTest.java b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/ApmTraceUtilsTest.java new file mode 100644 index 000000000000..70307ad3fd53 --- /dev/null +++ b/airbyte-metrics/metrics-lib/src/test/java/io/airbyte/metrics/lib/ApmTraceUtilsTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.metrics.lib; + +import static io.airbyte.metrics.lib.ApmTraceUtils.TAG_FORMAT; +import static io.airbyte.metrics.lib.ApmTraceUtils.TAG_PREFIX; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.util.GlobalTracerTestUtil; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * Test suite for the {@link ApmTraceUtils} class. + */ +class ApmTraceUtilsTest { + + private static final String TAG_1 = "tag1"; + private static final String TAG_2 = "tag2"; + private static final String VALUE_1 = "foo"; + private static final String VALUE_2 = "bar"; + private static final Map TAGS = Map.of(TAG_1, VALUE_1, TAG_2, VALUE_2); + + @Before + @After + public void clearGlobalTracer() { + GlobalTracerTestUtil.resetGlobalTracer(); + } + + @Test + void testAddingTags() { + final Span span = mock(Span.class); + final Tracer tracer = mock(Tracer.class); + when(tracer.activeSpan()).thenReturn(span); + GlobalTracerTestUtil.setGlobalTracerUnconditionally(tracer); + ApmTraceUtils.addTagsToTrace(TAGS); + verify(span, times(1)).setTag(String.format(TAG_FORMAT, TAG_PREFIX, TAG_1), VALUE_1); + verify(span, times(1)).setTag(String.format(TAG_FORMAT, TAG_PREFIX, TAG_2), VALUE_2); + } + + @Test + void testAddingTagsWithPrefix() { + final Span span = mock(Span.class); + final Tracer tracer = mock(Tracer.class); + when(tracer.activeSpan()).thenReturn(span); + GlobalTracerTestUtil.setGlobalTracerUnconditionally(tracer); + final String tagPrefix = "prefix"; + ApmTraceUtils.addTagsToTrace(TAGS, tagPrefix); + verify(span, times(1)).setTag(String.format(TAG_FORMAT, tagPrefix, TAG_1), VALUE_1); + verify(span, times(1)).setTag(String.format(TAG_FORMAT, tagPrefix, TAG_2), VALUE_2); + } + + @Test + void testAddingTagsToSpanWithPrefix() { + final String tagPrefix = "prefix"; + final Span span = mock(Span.class); + ApmTraceUtils.addTagsToTrace(span, TAGS, tagPrefix); + verify(span, times(1)).setTag(String.format(TAG_FORMAT, tagPrefix, TAG_1), VALUE_1); + verify(span, times(1)).setTag(String.format(TAG_FORMAT, tagPrefix, TAG_2), VALUE_2); + } + + @Test + void testAddingTagsToNullSpanWithPrefix() { + final String tagPrefix = "prefix"; + Assertions.assertDoesNotThrow(() -> ApmTraceUtils.addTagsToTrace(null, TAGS, tagPrefix)); + } + +} diff --git a/airbyte-workers/build.gradle b/airbyte-workers/build.gradle index 885400e6363f..e544ee4f2a69 100644 --- a/airbyte-workers/build.gradle +++ b/airbyte-workers/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation 'org.apache.commons:commons-text:1.9' implementation 'org.quartz-scheduler:quartz:2.3.2' implementation libs.micrometer.statsd + implementation libs.bundles.datadog implementation 'io.sentry:sentry:6.3.1' implementation 'net.bytebuddy:byte-buddy:1.12.14' implementation 'org.springframework:spring-core:5.3.22' diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java index 2ed068e633ab..937935134f19 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java @@ -4,7 +4,12 @@ package io.airbyte.workers.temporal.check.connection; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + import com.fasterxml.jackson.databind.JsonNode; +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; @@ -18,6 +23,7 @@ import io.airbyte.config.StandardCheckConnectionOutput.Status; import io.airbyte.config.helpers.LogConfigs; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.workers.Worker; import io.airbyte.workers.WorkerConfigs; @@ -36,6 +42,7 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.Map; @Singleton @Requires(env = WorkerMode.CONTROL_PLANE) @@ -74,8 +81,11 @@ public CheckConnectionActivityImpl(@Named("checkWorkerConfigs") final WorkerConf this.migratorFactory = migratorFactory; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public ConnectorJobOutput runWithJobOutput(final CheckConnectionInput args) { + ApmTraceUtils + .addTagsToTrace(Map.of(JOB_ID_KEY, args.getJobRunConfig().getJobId(), DOCKER_IMAGE_KEY, args.getLauncherConfig().getDockerImage())); final JsonNode fullConfig = secretsHydrator.hydrate(args.getConnectionConfiguration().getConnectionConfiguration()); final StandardCheckConnectionInput input = new StandardCheckConnectionInput() @@ -97,8 +107,11 @@ public ConnectorJobOutput runWithJobOutput(final CheckConnectionInput args) { return temporalAttemptExecution.get(); } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public StandardCheckConnectionOutput run(final CheckConnectionInput args) { + ApmTraceUtils + .addTagsToTrace(Map.of(JOB_ID_KEY, args.getJobRunConfig().getJobId(), DOCKER_IMAGE_KEY, args.getLauncherConfig().getDockerImage())); final ConnectorJobOutput output = runWithJobOutput(args); if (output.getFailureReason() != null) { return new StandardCheckConnectionOutput().withStatus(Status.FAILED).withMessage("Error checking connection"); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java index e8706c91a89b..a6f24a32c835 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java @@ -4,16 +4,23 @@ package io.airbyte.workers.temporal.check.connection; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.CheckConnectionWorkflow; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.ConnectorJobOutput.OutputType; import io.airbyte.config.StandardCheckConnectionInput; import io.airbyte.config.StandardCheckConnectionOutput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.temporal.annotations.TemporalActivityStub; import io.airbyte.workers.temporal.check.connection.CheckConnectionActivity.CheckConnectionInput; import io.temporal.workflow.Workflow; +import java.util.Map; public class CheckConnectionWorkflowImpl implements CheckConnectionWorkflow { @@ -23,11 +30,12 @@ public class CheckConnectionWorkflowImpl implements CheckConnectionWorkflow { @TemporalActivityStub(activityOptionsBeanName = "checkActivityOptions") private CheckConnectionActivity activity; + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public ConnectorJobOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig launcherConfig, final StandardCheckConnectionInput connectionConfiguration) { - + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DOCKER_IMAGE_KEY, launcherConfig.getDockerImage())); final CheckConnectionInput checkInput = new CheckConnectionInput(jobRunConfig, launcherConfig, connectionConfiguration); final int jobOutputVersion = diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java index f631de372f4b..7c384eabf65e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java @@ -4,7 +4,12 @@ package io.airbyte.workers.temporal.discover.catalog; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + import com.fasterxml.jackson.databind.JsonNode; +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; import io.airbyte.commons.temporal.CancellationHandler; @@ -15,6 +20,7 @@ import io.airbyte.config.helpers.LogConfigs; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.Worker; @@ -33,6 +39,7 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.Map; import lombok.extern.slf4j.Slf4j; @Singleton @@ -71,10 +78,12 @@ public DiscoverCatalogActivityImpl(@Named("discoverWorkerConfigs") final WorkerC this.airbyteVersion = airbyteVersion; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public ConnectorJobOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig launcherConfig, final StandardDiscoverCatalogInput config) { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DOCKER_IMAGE_KEY, launcherConfig.getDockerImage())); final JsonNode fullConfig = secretsHydrator.hydrate(config.getConnectionConfiguration()); final StandardDiscoverCatalogInput input = new StandardDiscoverCatalogInput() diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java index b216dbc8f026..fafb9ca380c5 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java @@ -4,22 +4,31 @@ package io.airbyte.workers.temporal.discover.catalog; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.DiscoverCatalogWorkflow; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.StandardDiscoverCatalogInput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.temporal.annotations.TemporalActivityStub; +import java.util.Map; public class DiscoverCatalogWorkflowImpl implements DiscoverCatalogWorkflow { @TemporalActivityStub(activityOptionsBeanName = "discoveryActivityOptions") private DiscoverCatalogActivity activity; + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public ConnectorJobOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig launcherConfig, final StandardDiscoverCatalogInput config) { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DOCKER_IMAGE_KEY, launcherConfig.getDockerImage())); return activity.run(jobRunConfig, launcherConfig, config); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java index 4b3a2ca80f1e..17ff160c5ee6 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java @@ -4,7 +4,12 @@ package io.airbyte.workers.temporal.scheduling; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; + import com.fasterxml.jackson.databind.JsonNode; +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.TemporalJobType; import io.airbyte.commons.temporal.TemporalWorkflowUtils; import io.airbyte.commons.temporal.exception.RetryableException; @@ -25,6 +30,7 @@ import io.airbyte.config.StandardSyncOutput; import io.airbyte.config.StandardSyncSummary; import io.airbyte.config.StandardSyncSummary.ReplicationStatus; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.metrics.lib.OssMetricsRegistry; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; @@ -78,6 +84,7 @@ import io.temporal.workflow.Workflow; import java.time.Duration; import java.time.Instant; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -145,9 +152,11 @@ public class ConnectionManagerWorkflowImpl implements ConnectionManagerWorkflow private Duration workflowDelay; + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void run(final ConnectionUpdaterInput connectionUpdaterInput) throws RetryableException { try { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionUpdaterInput.getConnectionId())); recordMetric(new RecordMetricInput(connectionUpdaterInput, Optional.empty(), OssMetricsRegistry.TEMPORAL_WORKFLOW_ATTEMPT, null)); workflowDelay = getWorkflowRestartDelaySeconds(); @@ -445,8 +454,10 @@ private void resetNewConnectionInput(final ConnectionUpdaterInput connectionUpda connectionUpdaterInput.setSkipScheduling(false); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void submitManualSync() { + traceConnectionId(); if (workflowState.isRunning()) { log.info("Can't schedule a manual workflow if a sync is running for connection {}", connectionId); return; @@ -455,8 +466,10 @@ public void submitManualSync() { workflowState.setSkipScheduling(true); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void cancelJob() { + traceConnectionId(); if (!workflowState.isRunning()) { log.info("Can't cancel a non-running sync for connection {}", connectionId); return; @@ -465,22 +478,28 @@ public void cancelJob() { cancellableSyncWorkflow.cancel(); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void deleteConnection() { + traceConnectionId(); workflowState.setDeleted(true); cancelJob(); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void connectionUpdated() { + traceConnectionId(); workflowState.setUpdated(true); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void resetConnection() { + traceConnectionId(); + // Assumes that the streams_reset has already been populated with streams to reset for this // connection - if (workflowState.isDoneWaiting()) { workflowState.setCancelledForReset(true); cancellableSyncWorkflow.cancel(); @@ -489,8 +508,11 @@ public void resetConnection() { } } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void resetConnectionAndSkipNextScheduling() { + traceConnectionId(); + if (workflowState.isDoneWaiting()) { workflowState.setCancelledForReset(true); workflowState.setSkipSchedulingNextWorkflow(true); @@ -501,32 +523,40 @@ public void resetConnectionAndSkipNextScheduling() { } } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public void retryFailedActivity() { + traceConnectionId(); workflowState.setRetryFailedActivity(true); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public WorkflowState getState() { + traceConnectionId(); return workflowState; } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public JobInformation getJobInformation() { - final Long jobId = workflowInternalState.getJobId(); + final Long jobId = workflowInternalState.getJobId() != null ? workflowInternalState.getJobId() : NON_RUNNING_JOB_ID; final Integer attemptNumber = workflowInternalState.getAttemptNumber(); + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionId, JOB_ID_KEY, jobId)); return new JobInformation( - jobId == null ? NON_RUNNING_JOB_ID : jobId, + jobId, attemptNumber == null ? NON_RUNNING_ATTEMPT_ID : attemptNumber); } + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public QuarantinedInformation getQuarantinedInformation() { - final Long jobId = workflowInternalState.getJobId(); + final Long jobId = workflowInternalState.getJobId() != null ? workflowInternalState.getJobId() : NON_RUNNING_JOB_ID; final Integer attemptNumber = workflowInternalState.getAttemptNumber(); + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionId, JOB_ID_KEY, jobId)); return new QuarantinedInformation( connectionId, - jobId == null ? NON_RUNNING_JOB_ID : jobId, + jobId, attemptNumber == null ? NON_RUNNING_ATTEMPT_ID : attemptNumber, workflowState.isQuarantined()); } @@ -568,17 +598,16 @@ private OUTPUT runMandatoryActivityWithOutput(final Function workflowState.isRetryFailedActivity()); @@ -898,4 +927,10 @@ private Duration getWorkflowRestartDelaySeconds() { return workflowConfigActivity.getWorkflowRestartDelaySeconds(); } + private void traceConnectionId() { + if (connectionId != null) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionId)); + } + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java index ca361d079705..b07f704e3e8d 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java @@ -7,14 +7,18 @@ import static io.airbyte.persistence.job.JobNotifier.CONNECTION_DISABLED_NOTIFICATION; import static io.airbyte.persistence.job.JobNotifier.CONNECTION_DISABLED_WARNING_NOTIFICATION; import static io.airbyte.persistence.job.models.Job.REPLICATION_TYPES; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; import static java.time.temporal.ChronoUnit.DAYS; +import datadog.trace.api.Trace; import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.StandardSync; import io.airbyte.config.StandardSync.Status; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.JobNotifier; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.Job; @@ -26,6 +30,7 @@ import jakarta.inject.Singleton; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -62,8 +67,10 @@ public AutoDisableConnectionActivityImpl(final ConfigRepository configRepository // failures, and that the connection's first job is at least that many days old // Notifications will be sent if a connection is disabled or warned if it has reached halfway to // disable limits + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public AutoDisableConnectionOutput autoDisableFailingConnection(final AutoDisableConnectionActivityInput input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); if (featureFlags.autoDisablesFailingConnections()) { try { // if connection is already inactive, no need to disable diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java index 88776de0bac0..11b1b9f7f75b 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java @@ -4,6 +4,10 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.Cron; @@ -13,6 +17,7 @@ import io.airbyte.config.helpers.ScheduleHelpers; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.Job; import io.airbyte.validation.json.JsonValidationException; @@ -25,6 +30,7 @@ import java.time.DateTimeException; import java.time.Duration; import java.util.Date; +import java.util.Map; import java.util.Optional; import java.util.TimeZone; import java.util.UUID; @@ -56,9 +62,11 @@ public ConfigFetchActivityImpl(final ConfigRepository configRepository, this.currentSecondsSupplier = currentSecondsSupplier; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public ScheduleRetrieverOutput getTimeToWait(final ScheduleRetrieverInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); final StandardSync standardSync = configRepository.getStandardSync(input.getConnectionId()); if (standardSync.getScheduleType() != null) { @@ -156,6 +164,7 @@ private ScheduleRetrieverOutput getTimeToWaitFromLegacy(final StandardSync stand } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public GetMaxAttemptOutput getMaxAttempt() { return new GetMaxAttemptOutput(syncJobMaxAttempts); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java index 9bde493d8a2c..3d59402bbdda 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java @@ -4,14 +4,20 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.config.persistence.ConfigNotFoundException; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.validation.json.JsonValidationException; import io.airbyte.workers.helper.ConnectionHelper; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.io.IOException; +import java.util.Map; @Singleton @Requires(env = WorkerMode.CONTROL_PLANE) @@ -23,9 +29,11 @@ public ConnectionDeletionActivityImpl(final ConnectionHelper connectionHelper) { this.connectionHelper = connectionHelper; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void deleteConnection(final ConnectionDeletionInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); connectionHelper.deleteConnection(input.getConnectionId()); } catch (final JsonValidationException | ConfigNotFoundException | IOException e) { throw new RetryableException(e); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java index aa3d782b209b..eae169f52d11 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java @@ -4,6 +4,10 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalWorkflowUtils; import io.airbyte.commons.temporal.config.WorkerMode; @@ -13,6 +17,7 @@ import io.airbyte.config.JobSyncConfig; import io.airbyte.config.ResetSourceConfiguration; import io.airbyte.config.StandardSyncInput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.Job; @@ -21,6 +26,7 @@ import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.util.List; +import java.util.Map; @Singleton @Requires(env = WorkerMode.CONTROL_PLANE) @@ -32,9 +38,11 @@ public GenerateInputActivityImpl(final JobPersistence jobPersistence) { this.jobPersistence = jobPersistence; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public GeneratedJobInput getSyncWorkflowInput(final SyncInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); final long jobId = input.getJobId(); final int attempt = input.getAttemptId(); final JobSyncConfig config; @@ -101,8 +109,10 @@ public GeneratedJobInput getSyncWorkflowInput(final SyncInput input) { } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public GeneratedJobInput getSyncWorkflowInputWithAttemptNumber(final SyncInputWithAttemptNumber input) { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); return getSyncWorkflowInput(new SyncInput( input.getAttemptNumber(), input.getJobId())); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java index 66533409192f..76fedf7870a4 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java @@ -4,7 +4,12 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + import com.google.common.collect.Lists; +import datadog.trace.api.Trace; import io.airbyte.commons.docker.DockerUtils; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.temporal.config.WorkerMode; @@ -26,6 +31,7 @@ import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.StreamResetPersistence; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.metrics.lib.MetricAttribute; import io.airbyte.metrics.lib.MetricClientFactory; import io.airbyte.metrics.lib.MetricTags; @@ -51,6 +57,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -96,9 +103,12 @@ public JobCreationAndStatusUpdateActivityImpl(final SyncJobFactory jobFactory, this.jobErrorReporter = jobErrorReporter; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public JobCreationOutput createNewJob(final JobCreationInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); + // Fail non-terminal jobs first to prevent this activity from repeatedly trying to create a new job // and failing, potentially resulting in the workflow ending up in a quarantined state. // Another non-terminal job is not expected to exist at this point in the normal case, but this @@ -157,9 +167,12 @@ private void emitSrcIdDstIdToReleaseStagesMetric(final UUID srcId, final UUID ds } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public AttemptCreationOutput createNewAttempt(final AttemptCreationInput input) throws RetryableException { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); + final long jobId = input.getJobId(); final Job createdJob = jobPersistence.getJob(jobId); @@ -175,9 +188,12 @@ public AttemptCreationOutput createNewAttempt(final AttemptCreationInput input) } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public AttemptNumberCreationOutput createNewAttemptNumber(final AttemptCreationInput input) throws RetryableException { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); + final long jobId = input.getJobId(); final Job createdJob = jobPersistence.getJob(jobId); @@ -193,9 +209,12 @@ public AttemptNumberCreationOutput createNewAttemptNumber(final AttemptCreationI } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void jobSuccess(final JobSuccessInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); + final long jobId = input.getJobId(); final int attemptId = input.getAttemptId(); @@ -220,8 +239,10 @@ public void jobSuccess(final JobSuccessInput input) { } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void jobSuccessWithAttemptNumber(final JobSuccessInputWithAttemptNumber input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId(), JOB_ID_KEY, input.getJobId())); jobSuccess(new JobSuccessInput( input.getJobId(), input.getAttemptNumber(), @@ -229,9 +250,12 @@ public void jobSuccessWithAttemptNumber(final JobSuccessInputWithAttemptNumber i input.getStandardSyncOutput())); } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void jobFailure(final JobFailureInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); + final long jobId = input.getJobId(); jobPersistence.failJob(jobId); final Job job = jobPersistence.getJob(jobId); @@ -252,9 +276,12 @@ public void jobFailure(final JobFailureInput input) { } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void attemptFailure(final AttemptFailureInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); + final int attemptId = input.getAttemptId(); final long jobId = input.getJobId(); final AttemptFailureSummary failureSummary = input.getAttemptFailureSummary(); @@ -280,8 +307,10 @@ public void attemptFailure(final AttemptFailureInput input) { } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void attemptFailureWithAttemptNumber(final AttemptNumberFailureInput input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId(), JOB_ID_KEY, input.getJobId())); attemptFailure(new AttemptFailureInput( input.getJobId(), input.getAttemptNumber(), @@ -290,9 +319,12 @@ public void attemptFailureWithAttemptNumber(final AttemptNumberFailureInput inpu input.getAttemptFailureSummary())); } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void jobCancelled(final JobCancelledInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); + final long jobId = input.getJobId(); final int attemptId = input.getAttemptId(); jobPersistence.failAttempt(jobId, attemptId); @@ -309,8 +341,11 @@ public void jobCancelled(final JobCancelledInput input) { } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void jobCancelledWithAttemptNumber(final JobCancelledInputWithAttemptNumber input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId(), JOB_ID_KEY, input.getJobId())); + jobCancelled(new JobCancelledInput( input.getJobId(), input.getAttemptNumber(), @@ -318,9 +353,11 @@ public void jobCancelledWithAttemptNumber(final JobCancelledInputWithAttemptNumb input.getAttemptFailureSummary())); } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void reportJobStart(final ReportJobStartInput input) { try { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); final Job job = jobPersistence.getJob(input.getJobId()); jobTracker.trackSync(job, JobState.STARTED); } catch (final IOException e) { @@ -328,8 +365,10 @@ public void reportJobStart(final ReportJobStartInput input) { } } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void ensureCleanJobState(final EnsureCleanJobStateInput input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); failNonTerminalJobs(input.getConnectionId()); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java index d65368b1877b..7c7708135f74 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java @@ -4,15 +4,23 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.commons.temporal.scheduling.ConnectionUpdaterInput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.metrics.lib.MetricAttribute; import io.airbyte.metrics.lib.MetricClient; import io.airbyte.metrics.lib.MetricTags; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; @@ -37,8 +45,10 @@ public RecordMetricActivityImpl(final MetricClient metricClient) { * * @param metricInput The information about the metric to record. */ + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void recordWorkflowCountMetric(final RecordMetricInput metricInput) { + ApmTraceUtils.addTagsToTrace(generateTags(metricInput.getConnectionUpdaterInput())); final List baseMetricAttributes = generateMetricAttributes(metricInput.getConnectionUpdaterInput()); if (metricInput.getMetricAttributes() != null) { baseMetricAttributes.addAll(Stream.of(metricInput.getMetricAttributes()).collect(Collectors.toList())); @@ -60,4 +70,25 @@ private List generateMetricAttributes(final ConnectionUpdaterIn return metricAttributes; } + /** + * Build the map of tags for instrumentation. + * + * @param connectionUpdaterInput The connection update input information. + * @return The map of tags for instrumentation. + */ + private Map generateTags(final ConnectionUpdaterInput connectionUpdaterInput) { + final Map tags = new HashMap(); + + if (connectionUpdaterInput != null) { + if (connectionUpdaterInput.getConnectionId() != null) { + tags.put(CONNECTION_ID_KEY, connectionUpdaterInput.getConnectionId()); + } + if (connectionUpdaterInput.getJobId() != null) { + tags.put(JOB_ID_KEY, connectionUpdaterInput.getJobId()); + } + } + + return tags; + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java index bae0ecbe978d..3bf7a4967afb 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java @@ -4,8 +4,14 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; + +import datadog.trace.api.Trace; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.workers.temporal.sync.RouterService; import jakarta.inject.Singleton; +import java.util.Map; @Singleton public class RouteToSyncTaskQueueActivityImpl implements RouteToSyncTaskQueueActivity { @@ -16,8 +22,11 @@ public RouteToSyncTaskQueueActivityImpl(final RouterService routerService) { this.routerService = routerService; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public RouteToSyncTaskQueueOutput route(final RouteToSyncTaskQueueInput input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); + final String taskQueueForConnectionId = routerService.getTaskQueue(input.getConnectionId()); return new RouteToSyncTaskQueueOutput(taskQueueForConnectionId); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java index 1499fa160467..a879ad7fe669 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java @@ -4,10 +4,17 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.StreamResetRecordsHelper; import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.micronaut.context.annotation.Requires; import jakarta.inject.Singleton; +import java.util.Map; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -21,8 +28,10 @@ public StreamResetActivityImpl(final StreamResetRecordsHelper streamResetRecords this.streamResetRecordsHelper = streamResetRecordsHelper; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public void deleteStreamResetRecordsForJob(final DeleteStreamResetRecordsForJobInput input) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId(), JOB_ID_KEY, input.getJobId())); streamResetRecordsHelper.deleteStreamResetRecordsForJob(input.getJobId(), input.getConnectionId()); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java index 19d8fa1135ee..e77d9f48562d 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java @@ -4,6 +4,9 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; import io.micronaut.context.annotation.Property; import io.micronaut.context.annotation.Requires; @@ -27,6 +30,7 @@ public WorkflowConfigActivityImpl(@Property(name = "airbyte.workflow.failure.res this.workflowRestartDelaySeconds = workflowRestartDelaySeconds; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public Duration getWorkflowRestartDelaySeconds() { return Duration.ofSeconds(workflowRestartDelaySeconds); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java index 3ad439a4c9bb..fe76c615d360 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java @@ -4,6 +4,11 @@ package io.airbyte.workers.temporal.spec; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; import io.airbyte.commons.temporal.CancellationHandler; @@ -12,6 +17,7 @@ import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.JobGetSpecConfig; import io.airbyte.config.helpers.LogConfigs; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.Worker; @@ -28,6 +34,7 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.Map; import java.util.function.Supplier; @Singleton @@ -58,8 +65,11 @@ public SpecActivityImpl(@Named("specWorkerConfigs") final WorkerConfigs workerCo this.airbyteVersion = airbyteVersion; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public ConnectorJobOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig launcherConfig) { + ApmTraceUtils.addTagsToTrace(Map.of(DOCKER_IMAGE_KEY, launcherConfig.getDockerImage(), JOB_ID_KEY, jobRunConfig.getJobId())); + final Supplier inputSupplier = () -> new JobGetSpecConfig().withDockerImage(launcherConfig.getDockerImage()); final ActivityExecutionContext context = Activity.getExecutionContext(); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java index 24ddfe96951c..76805d794f4f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java @@ -4,19 +4,28 @@ package io.airbyte.workers.temporal.spec; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.SpecWorkflow; import io.airbyte.config.ConnectorJobOutput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.temporal.annotations.TemporalActivityStub; +import java.util.Map; public class SpecWorkflowImpl implements SpecWorkflow { @TemporalActivityStub(activityOptionsBeanName = "specActivityOptions") private SpecActivity activity; + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public ConnectorJobOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig launcherConfig) { + ApmTraceUtils.addTagsToTrace(Map.of(DOCKER_IMAGE_KEY, launcherConfig.getDockerImage(), JOB_ID_KEY, jobRunConfig.getJobId())); return activity.run(jobRunConfig, launcherConfig); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java index eee02e8c70a1..cab8ad5ff8b7 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java @@ -4,6 +4,11 @@ package io.airbyte.workers.temporal.sync; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.api.client.invoker.generated.ApiException; import io.airbyte.api.client.model.generated.JobIdRequestBody; @@ -18,6 +23,7 @@ import io.airbyte.config.ResourceRequirements; import io.airbyte.config.helpers.LogConfigs; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.ContainerOrchestratorConfig; @@ -35,6 +41,7 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; @@ -81,11 +88,14 @@ public DbtTransformationActivityImpl(@Named("containerOrchestratorConfig") final this.airbyteApiClient = airbyteApiClient; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public Void run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig destinationLauncherConfig, final ResourceRequirements resourceRequirements, final OperatorDbtInput input) { + ApmTraceUtils.addTagsToTrace( + Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage())); final ActivityExecutionContext context = Activity.getExecutionContext(); return temporalUtils.withBackgroundHeartbeat( () -> { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java index 9b5fa6381223..4d954c8e2735 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java @@ -4,6 +4,11 @@ package io.airbyte.workers.temporal.sync; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.api.client.invoker.generated.ApiException; import io.airbyte.api.client.model.generated.JobIdRequestBody; @@ -21,6 +26,7 @@ import io.airbyte.config.StandardSyncOutput; import io.airbyte.config.helpers.LogConfigs; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.ContainerOrchestratorConfig; @@ -37,6 +43,7 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; @@ -86,10 +93,13 @@ public NormalizationActivityImpl(@Named("containerOrchestratorConfig") final Opt this.airbyteApiClient = airbyteApiClient; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public NormalizationSummary normalize(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig destinationLauncherConfig, final NormalizationInput input) { + ApmTraceUtils.addTagsToTrace( + Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage())); final ActivityExecutionContext context = Activity.getExecutionContext(); return temporalUtils.withBackgroundHeartbeat(() -> { final var fullDestinationConfig = secretsHydrator.hydrate(input.getDestinationConfiguration()); @@ -124,6 +134,7 @@ public NormalizationSummary normalize(final JobRunConfig jobRunConfig, () -> context); } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public NormalizationInput generateNormalizationInput(final StandardSyncInput syncInput, final StandardSyncOutput syncOutput) { return new NormalizationInput() diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java index 99a2bf2964a3..c46243c1e6ac 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java @@ -4,12 +4,18 @@ package io.airbyte.workers.temporal.sync; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.AttemptNormalizationStatus; import jakarta.inject.Singleton; import java.io.IOException; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -25,9 +31,12 @@ public NormalizationSummaryCheckActivityImpl(final Optional jobP this.jobPersistence = jobPersistence; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") public boolean shouldRunNormalization(final Long jobId, final Long attemptNumber, final Optional numCommittedRecords) throws IOException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId)); + // if job persistence is unavailable, default to running normalization if (jobPersistence.isEmpty()) { return true; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java index 63d2cd9b707f..e64339911be3 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java @@ -6,8 +6,11 @@ import static io.airbyte.config.helpers.StateMessageHelper.isMigration; import static io.airbyte.workers.helper.StateConverter.convertClientStateTypeToInternal; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; import com.google.common.annotations.VisibleForTesting; +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.api.client.invoker.generated.ApiException; import io.airbyte.api.client.model.generated.ConnectionIdRequestBody; @@ -19,12 +22,14 @@ import io.airbyte.config.StateType; import io.airbyte.config.StateWrapper; import io.airbyte.config.helpers.StateMessageHelper; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.protocol.models.CatalogHelpers; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.StreamDescriptor; import io.airbyte.workers.helper.StateConverter; import jakarta.inject.Singleton; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -39,8 +44,10 @@ public PersistStateActivityImpl(final AirbyteApiClient airbyteApiClient, final F this.featureFlags = featureFlags; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public boolean persist(final UUID connectionId, final StandardSyncOutput syncOutput, final ConfiguredAirbyteCatalog configuredCatalog) { + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionId.toString())); final State state = syncOutput.getState(); if (state != null) { // todo: these validation logic should happen on server side. diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java index aac2956e7a43..349aa5f1ae12 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java @@ -4,6 +4,12 @@ package io.airbyte.workers.temporal.sync; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; + +import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.api.client.invoker.generated.ApiException; import io.airbyte.api.client.model.generated.JobIdRequestBody; @@ -23,6 +29,7 @@ import io.airbyte.config.StandardSyncSummary; import io.airbyte.config.helpers.LogConfigs; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.metrics.lib.MetricClient; import io.airbyte.metrics.lib.MetricClientFactory; import io.airbyte.metrics.lib.MetricEmittingApps; @@ -52,6 +59,7 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.function.Supplier; @@ -103,11 +111,14 @@ public ReplicationActivityImpl(@Named("containerOrchestratorConfig") final Optio this.airbyteApiClient = airbyteApiClient; } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public StandardSyncOutput replicate(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig sourceLauncherConfig, final IntegrationLauncherConfig destinationLauncherConfig, final StandardSyncInput syncInput) { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, + destinationLauncherConfig.getDockerImage(), SOURCE_DOCKER_IMAGE_KEY, sourceLauncherConfig.getDockerImage())); final ActivityExecutionContext context = Activity.getExecutionContext(); return temporalUtils.withBackgroundHeartbeat( () -> { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java index fd604f1d878e..b53a201e407f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java @@ -4,6 +4,13 @@ package io.airbyte.workers.temporal.sync; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; +import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.SyncWorkflow; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; @@ -14,12 +21,14 @@ import io.airbyte.config.StandardSyncOperation.OperatorType; import io.airbyte.config.StandardSyncOutput; import io.airbyte.config.WebhookOperationSummary; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.workers.temporal.annotations.TemporalActivityStub; import io.temporal.workflow.Workflow; import java.io.IOException; +import java.util.Map; import java.util.Optional; import java.util.UUID; import org.slf4j.Logger; @@ -44,10 +53,10 @@ public class SyncWorkflowImpl implements SyncWorkflow { private PersistStateActivity persistActivity; @TemporalActivityStub(activityOptionsBeanName = "shortActivityOptions") private NormalizationSummaryCheckActivity normalizationSummaryCheckActivity; - @TemporalActivityStub(activityOptionsBeanName = "shortActivityOptions") private WebhookOperationActivity webhookOperationActivity; + @Trace(operationName = WORKFLOW_TRACE_OPERATION_NAME) @Override public StandardSyncOutput run(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig sourceLauncherConfig, @@ -55,6 +64,11 @@ public StandardSyncOutput run(final JobRunConfig jobRunConfig, final StandardSyncInput syncInput, final UUID connectionId) { + ApmTraceUtils + .addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionId.toString(), JOB_ID_KEY, jobRunConfig.getJobId(), SOURCE_DOCKER_IMAGE_KEY, + sourceLauncherConfig.getDockerImage(), + DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage())); + final int version = Workflow.getVersion(VERSION_LABEL, Workflow.DEFAULT_VERSION, CURRENT_VERSION); StandardSyncOutput syncOutput = replicationActivity.replicate(jobRunConfig, sourceLauncherConfig, destinationLauncherConfig, syncInput); @@ -101,7 +115,7 @@ public StandardSyncOutput run(final JobRunConfig jobRunConfig, } else if (standardSyncOperation.getOperatorType() == OperatorType.WEBHOOK) { LOGGER.info("running webhook operation"); LOGGER.debug("webhook operation input: {}", standardSyncOperation); - boolean success = webhookOperationActivity + final boolean success = webhookOperationActivity .invokeWebhook(new OperatorWebhookInput() .withExecutionUrl(standardSyncOperation.getOperatorWebhook().getExecutionUrl()) .withExecutionBody(standardSyncOperation.getOperatorWebhook().getExecutionBody()) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java new file mode 100644 index 000000000000..80f6aa6b8c19 --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.trace; + +/** + * Collection of constants for APM tracing of Temporal activities and workflows. + */ +public final class TemporalTraceConstants { + + /** + * Operation name for an APM trace of a Temporal activity. + */ + public static final String ACTIVITY_TRACE_OPERATION_NAME = "activity"; + + /** + * Operation name for an APM trace of a Temporal workflow. + */ + public static final String WORKFLOW_TRACE_OPERATION_NAME = "workflow"; + + private TemporalTraceConstants() {} + + /** + * Trace tag constants. + */ + public static final class Tags { + + /** + * Name of the APM trace tag that holds the connection ID value associated with the trace. + */ + public static final String CONNECTION_ID_KEY = "connection_id"; + + /** + * Name of the APM trace tag that holds the destination Docker image value associated with the + * trace. + */ + public static final String DESTINATION_DOCKER_IMAGE_KEY = "destination.docker_image"; + + /** + * Name of the APM trace tag that holds the Docker image value associated with the trace. + */ + public static final String DOCKER_IMAGE_KEY = "docker_image"; + + /** + * Name of the APM trace tag that holds the job ID value associated with the trace. + */ + public static final String JOB_ID_KEY = "job_id"; + + /** + * Name of the APM trace tag that holds the source Docker image value associated with the trace. + */ + public static final String SOURCE_DOCKER_IMAGE_KEY = "source.docker_image"; + + private Tags() {} + + } + +} diff --git a/deps.toml b/deps.toml index 9caaad7cae1a..5e3e650d3904 100644 --- a/deps.toml +++ b/deps.toml @@ -1,5 +1,5 @@ [versions] -datadog-version = "0.110.0" +datadog-version = "0.111.0" fasterxml_version = "2.13.3" flyway = "7.14.0" glassfish_version = "2.31" @@ -43,6 +43,7 @@ log4j-web = { module = "org.apache.logging.log4j:log4j-web", version.ref = "log4 jul-to-slf4j = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } jcl-over-slf4j = { module = "org.slf4j:jcl-over-slf4j", version.ref = "slf4j" } hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" } +java-dogstatsd-client = { module = "com.datadoghq:java-dogstatsd-client", version = "4.1.0"} javax-databind = { module = "javax.xml.bind:jaxb-api", version = "2.4.0-b180830.0359" } jooq = { module = "org.jooq:jooq", version.ref = "jooq" } jooq-codegen = { module = "org.jooq:jooq-codegen", version.ref = "jooq" } From 740bbcde16792ee0862df2214ab6725650bd3f5a Mon Sep 17 00:00:00 2001 From: RobertoBonnet Date: Tue, 18 Oct 2022 14:58:59 -0300 Subject: [PATCH 166/498] :bug: Source Zendesk Chat: engagements data fix infinity looping (#17745) * included engagements in chats schema * fix infinity looping in chats * added more meaninful to variable name --- .../source_zendesk_chat/schemas/chats.json | 82 +++++++++++++++++++ .../source_zendesk_chat/streams.py | 13 +++ 2 files changed, 95 insertions(+) diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json index aeafed68a73d..21e2542cbe5b 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json @@ -325,6 +325,88 @@ }, "zendesk_ticket_id": { "type": ["null", "integer"] + }, + "engagements": { + "items": { + "properties": { + "id": { + "type": ["null", "string"] + }, + "started_by": { + "type": ["null", "string"] + }, + "agent_id": { + "type": ["null", "string"] + }, + "agent_name": { + "type": ["null", "string"] + }, + "agent_full_name": { + "type": ["null", "string"] + }, + "department_id": { + "type": ["null", "integer"] + }, + "assigned": { + "type": ["null", "boolean"] + }, + "accepted": { + "type": ["null", "boolean"] + }, + "rating": { + "type": ["null", "string"] + }, + "comment": { + "type": ["null", "string"] + }, + "skills_requested": { + "type": ["null", "array"] + }, + "skills_fulfilled": { + "type": ["null", "boolean"] + }, + "timestamp": { + "format": "date-time", + "type": ["null", "string"] + }, + "duration": { + "type": ["null", "number"] + }, + "response_time": { + "properties": { + "first": { + "type": ["null", "integer"] + }, + "max": { + "type": ["null", "integer"] + }, + "avg": { + "type": ["null", "number"] + } + }, + "type": ["null", "object"], + "additionalProperties": true + }, + "count": { + "properties": { + "total": { + "type": ["null", "integer"] + }, + "agent": { + "type": ["null", "integer"] + }, + "visitor": { + "type": ["null", "integer"] + } + }, + "type": ["null", "object"], + "additionalProperties": true + } + }, + "type": ["null", "object"], + "additionalProperties": true + }, + "type": ["null", "array"] } } } diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py index 670a906dc14a..3a352bca2a98 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py @@ -232,6 +232,19 @@ class Chats(TimeIncrementalStream): data_field = "chats" limit = 1000 + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + response_data = response.json() + if response_data["count"] == self.limit: + next_page = { + "start_time": response_data["end_time"] + } + + start_id = response_data.get("end_id") + if start_id: + next_page.update({"start_id": start_id}) + + return next_page + class Shortcuts(Stream): """ From 70dd9a85cfd86b2d6c2fde646e37cca720306f02 Mon Sep 17 00:00:00 2001 From: Marcos Marx Date: Tue, 18 Oct 2022 14:14:02 -0400 Subject: [PATCH 167/498] :tada: Source Zendesk Chat: engagements data fix infinity looping + gradlew format (#18121) * fix infinity looping in chats * added more meaninful to variable name * bump docker version * auto-bump connector version * run format Co-authored-by: Roberto Bonnet Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- ...gQueryDenormalizedRecordFormatterTest.java | 28 ++-- .../DefaultArrayFormatterTest.java | 65 ++++---- .../LegacyArrayFormatterTest.java | 56 ++++--- .../formatter/util/FormatterUtilTest.java | 14 +- ...lasticsearchDestinationAcceptanceTest.java | 6 + ...lasticsearchDestinationAcceptanceTest.java | 5 + ...lasticsearchDestinationAcceptanceTest.java | 7 +- .../SshRedisDestinationAcceptanceTest.java | 7 +- .../source-file/source_file/client.py | 1 - .../source-file/unit_tests/test_client.py | 2 + .../source/jdbc/AbstractJdbcSource.java | 3 +- .../integration_tests/state.json | 80 +++++----- .../source_shopify/schemas/collections.json | 2 +- .../schemas/metafield_locations.json | 2 +- .../schemas/metafield_orders.json | 2 +- .../schemas/metafield_pages.json | 2 +- .../schemas/metafield_product_images.json | 2 +- .../schemas/metafield_product_variants.json | 2 +- .../schemas/metafield_products.json | 2 +- .../schemas/metafield_shops.json | 2 +- .../schemas/metafield_smart_collections.json | 2 +- .../schemas/product_images.json | 2 +- .../schemas/product_variants.json | 10 +- .../schemas/smart_collections.json | 18 +-- .../connectors/source-zendesk-chat/Dockerfile | 2 +- .../source_zendesk_chat/schemas/chats.json | 144 +++++++++--------- .../source_zendesk_chat/streams.py | 6 +- docs/integrations/sources/zendesk-chat.md | 1 + 30 files changed, 246 insertions(+), 233 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index febc17f8d376..a03309614d8b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1147,7 +1147,7 @@ - name: Zendesk Chat sourceDefinitionId: 40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4 dockerRepository: airbyte/source-zendesk-chat - dockerImageTag: 0.1.10 + dockerImageTag: 0.1.11 documentationUrl: https://docs.airbyte.com/integrations/sources/zendesk-chat icon: zendesk.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 28a3982d30a8..d9afc664060d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11736,7 +11736,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-zendesk-chat:0.1.10" +- dockerImage: "airbyte/source-zendesk-chat:0.1.11" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/zendesk-chat" connectionSpecification: diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java index 212add9f3e3c..7ceed170afb8 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/DefaultBigQueryDenormalizedRecordFormatterTest.java @@ -74,9 +74,9 @@ void testSchema() { DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( jsonNodeSchema, new BigQuerySQLNameTransformer()); final Field subFields = Field.newBuilder("big_query_array", LegacySQLTypeName.RECORD, - Field.of("domain", LegacySQLTypeName.STRING), - Field.of("grants", LegacySQLTypeName.RECORD, - Field.newBuilder("big_query_array", StandardSQLTypeName.STRING).setMode(Mode.REPEATED).build())) + Field.of("domain", LegacySQLTypeName.STRING), + Field.of("grants", LegacySQLTypeName.RECORD, + Field.newBuilder("big_query_array", StandardSQLTypeName.STRING).setMode(Mode.REPEATED).build())) .setMode(Mode.REPEATED).build(); final Schema expectedResult = Schema.of( Field.newBuilder("accepts_marketing_updated_at", LegacySQLTypeName.DATETIME).setMode(Mode.NULLABLE).build(), @@ -147,8 +147,8 @@ void testSchemaWithInvalidArrayType() { final Schema expectedResult = Schema.of( Field.of("name", LegacySQLTypeName.STRING), Field.newBuilder("permission_list", LegacySQLTypeName.RECORD, - Field.of("domain", LegacySQLTypeName.STRING), - Field.newBuilder("grants", LegacySQLTypeName.STRING).setMode(Mode.REPEATED).build()) + Field.of("domain", LegacySQLTypeName.STRING), + Field.newBuilder("grants", LegacySQLTypeName.STRING).setMode(Mode.REPEATED).build()) .setMode(Mode.REPEATED).build(), Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); @@ -181,8 +181,8 @@ void testSchemaWithNestedDatetimeInsideNullObject() { final Schema expectedResult = Schema.of( Field.newBuilder("name", LegacySQLTypeName.STRING).setMode(Mode.NULLABLE).build(), Field.newBuilder("appointment", LegacySQLTypeName.RECORD, - Field.newBuilder("street", LegacySQLTypeName.STRING).setMode(Mode.NULLABLE).build(), - Field.newBuilder("expTime", LegacySQLTypeName.DATETIME).setMode(Mode.NULLABLE).build()) + Field.newBuilder("street", LegacySQLTypeName.STRING).setMode(Mode.NULLABLE).build(), + Field.newBuilder("expTime", LegacySQLTypeName.DATETIME).setMode(Mode.NULLABLE).build()) .setMode(Mode.NULLABLE).build(), Field.of("_airbyte_ab_id", LegacySQLTypeName.STRING), Field.of("_airbyte_emitted_at", LegacySQLTypeName.TIMESTAMP)); @@ -213,8 +213,8 @@ void formatRecord_objectType() throws JsonProcessingException { final DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( jsonNodeSchema, new BigQuerySQLNameTransformer()); final JsonNode objectNode = mapper.readTree(""" - {"name":"data"} - """); + {"name":"data"} + """); final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); airbyteRecordMessage.setEmittedAt(1602637589000L); airbyteRecordMessage.setData(objectNode); @@ -234,8 +234,8 @@ void formatRecord_containsRefDefinition() throws JsonProcessingException { jsonNodeSchema, new BigQuerySQLNameTransformer()); rf.fieldsContainRefDefinitionValue.add("name"); final JsonNode objectNode = mapper.readTree(""" - {"name":"data"} - """); + {"name":"data"} + """); final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); airbyteRecordMessage.setEmittedAt(1602637589000L); airbyteRecordMessage.setData(objectNode); @@ -254,8 +254,8 @@ void formatRecord_objectWithArray() throws JsonProcessingException { DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( jsonNodeSchema, new BigQuerySQLNameTransformer()); final JsonNode objectNode = mapper.readTree(""" - {"object_with_arrays":["array_3"]} - """); + {"object_with_arrays":["array_3"]} + """); final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); airbyteRecordMessage.setEmittedAt(1602637589000L); airbyteRecordMessage.setData(objectNode); @@ -276,7 +276,7 @@ void formatRecordNotObject_thenThrowsError() throws JsonProcessingException { DefaultBigQueryDenormalizedRecordFormatter rf = new DefaultBigQueryDenormalizedRecordFormatter( jsonNodeSchema, new BigQuerySQLNameTransformer()); final JsonNode arrayNode = mapper.readTree(""" - ["one"]"""); + ["one"]"""); final AirbyteRecordMessage airbyteRecordMessage = new AirbyteRecordMessage(); airbyteRecordMessage.setEmittedAt(1602637589000L); diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java index 1d76cb1ba964..4b79c260ad49 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/DefaultArrayFormatterTest.java @@ -32,16 +32,16 @@ void surroundArraysByObjects() { void formatArrayItems() throws JsonProcessingException { final JsonNode expectedArrayNode = mapper.readTree( """ - [ - {"big_query_array": ["one", "two"]}, - {"big_query_array": ["one", "two"]} - ] - """); + [ + {"big_query_array": ["one", "two"]}, + {"big_query_array": ["one", "two"]} + ] + """); final List arrayNodes = List.of( - mapper.readTree(""" - ["one", "two"]"""), - mapper.readTree(""" - ["one", "two"]""")); + mapper.readTree(""" + ["one", "two"]"""), + mapper.readTree(""" + ["one", "two"]""")); final JsonNode result = formatter.formatArrayItems(arrayNodes); @@ -50,10 +50,10 @@ void formatArrayItems() throws JsonProcessingException { @Test void formatArrayItems_notArray() throws JsonProcessingException { - final JsonNode objectNodeInput = mapper.readTree(""" - {"type":"object","items":{"type":"integer"}}"""); - final JsonNode expectedResult = mapper.readTree(""" - [{"type":"object","items":{"type":"integer"}}]"""); + final JsonNode objectNodeInput = mapper.readTree(""" + {"type":"object","items":{"type":"integer"}}"""); + final JsonNode expectedResult = mapper.readTree(""" + [{"type":"object","items":{"type":"integer"}}]"""); final JsonNode result = formatter.formatArrayItems(List.of(objectNodeInput)); @@ -64,26 +64,27 @@ void formatArrayItems_notArray() throws JsonProcessingException { void findArrays() throws JsonProcessingException { final JsonNode schemaArrays = getSchemaArrays(); final List expectedResult = List.of( - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""), - mapper.readTree(""" + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree( + """ {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}""")); + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}""")); final List result = formatter.findArrays(schemaArrays); assertEquals(expectedResult, result); diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java index 16b3d5fa74e1..bb20569615ed 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/arrayformater/LegacyArrayFormatterTest.java @@ -4,19 +4,14 @@ package io.airbyte.integrations.destination.bigquery.formatter.arrayformater; -import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.NESTED_ARRAY_FIELD; -import static io.airbyte.integrations.destination.bigquery.formatter.util.FormatterUtil.TYPE_FIELD; import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getExpectedSchemaArraysLegacy; import static io.airbyte.integrations.destination.bigquery.util.BigQueryDenormalizedTestSchemaUtils.getSchemaArrays; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.JsonNodeType; import java.util.List; import org.junit.jupiter.api.Test; @@ -38,26 +33,27 @@ void surroundArraysByObjects() { void findArrays() throws JsonProcessingException { final JsonNode schemaArrays = getSchemaArrays(); final List expectedResult = List.of( - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""), - mapper.readTree(""" + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""), + mapper.readTree( + """ {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), - mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}""")); + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":["array"],"items":{"type":"integer"}}}"""), + mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}""")); final List result = formatter.findArrays(schemaArrays); @@ -74,13 +70,13 @@ void findArrays_null() { void formatArrayItems() throws JsonProcessingException { final JsonNode expectedArrayNode = mapper.readTree( """ - {"big_query_array": [["one", "two"], ["one", "two"]]} - """); + {"big_query_array": [["one", "two"], ["one", "two"]]} + """); final List arrayNodes = List.of( - mapper.readTree(""" - ["one", "two"]"""), - mapper.readTree(""" - ["one", "two"]""")); + mapper.readTree(""" + ["one", "two"]"""), + mapper.readTree(""" + ["one", "two"]""")); final JsonNode result = formatter.formatArrayItems(arrayNodes); diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java index bb8e0bef3c31..b236473ffc49 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test/java/io/airbyte/integrations/destination/bigquery/formatter/util/FormatterUtilTest.java @@ -20,7 +20,7 @@ class FormatterUtilTest { void isAirbyteArray_typeIsNull() throws JsonProcessingException { final JsonNode arrayNode = mapper.readTree( """ - ["one", "two"]"""); + ["one", "two"]"""); final boolean result = FormatterUtil.isAirbyteArray(arrayNode); assertFalse(result); @@ -28,8 +28,8 @@ void isAirbyteArray_typeIsNull() throws JsonProcessingException { @Test void isAirbyteArray_typeFieldIsArray() throws JsonProcessingException { - final JsonNode arrayNode = mapper.readTree(""" - {"type":["array"],"items":{"type":"integer"}}"""); + final JsonNode arrayNode = mapper.readTree(""" + {"type":["array"],"items":{"type":"integer"}}"""); boolean result = FormatterUtil.isAirbyteArray(arrayNode); assertTrue(result); @@ -37,16 +37,16 @@ void isAirbyteArray_typeFieldIsArray() throws JsonProcessingException { @Test void isAirbyteArray_typeFieldIsNotArray() throws JsonProcessingException { - final JsonNode objectNode = mapper.readTree(""" - {"type":"object"}"""); + final JsonNode objectNode = mapper.readTree(""" + {"type":"object"}"""); final boolean result = FormatterUtil.isAirbyteArray(objectNode); assertFalse(result); } @Test void isAirbyteArray_textIsNotArray() throws JsonProcessingException { - final JsonNode arrayNode = mapper.readTree(""" - {"type":["notArrayText"]}"""); + final JsonNode arrayNode = mapper.readTree(""" + {"type":["notArrayText"]}"""); final boolean result = FormatterUtil.isAirbyteArray(arrayNode); assertFalse(result); } diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java index b3d19e4b6a48..ca84b4d13006 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshElasticsearchDestinationAcceptanceTest.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.destination.elasticsearch; import com.fasterxml.jackson.databind.JsonNode; @@ -12,6 +16,7 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer; public abstract class SshElasticsearchDestinationAcceptanceTest extends ElasticsearchDestinationAcceptanceTest { + private static final Network network = Network.newNetwork(); private static final SshBastionContainer bastion = new SshBastionContainer(); private static ElasticsearchContainer container; @@ -61,4 +66,5 @@ public static void afterAll() { container.close(); bastion.getContainer().close(); } + } diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java index e9c94405ea32..f556c811a3b4 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshKeyElasticsearchDestinationAcceptanceTest.java @@ -1,3 +1,7 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.destination.elasticsearch; import io.airbyte.integrations.base.ssh.SshTunnel; @@ -7,4 +11,5 @@ public class SshKeyElasticsearchDestinationAcceptanceTest extends SshElasticsear public SshTunnel.TunnelMethod getTunnelMethod() { return SshTunnel.TunnelMethod.SSH_KEY_AUTH; } + } diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java index 62c83133b5be..55a4a89d5711 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/SshPasswordElasticsearchDestinationAcceptanceTest.java @@ -1,8 +1,13 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.destination.elasticsearch; import io.airbyte.integrations.base.ssh.SshTunnel; -public class SshPasswordElasticsearchDestinationAcceptanceTest extends SshElasticsearchDestinationAcceptanceTest { +public class SshPasswordElasticsearchDestinationAcceptanceTest extends SshElasticsearchDestinationAcceptanceTest { + @Override public SshTunnel.TunnelMethod getTunnelMethod() { return SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH; diff --git a/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java index c358f78d6ff7..6825a6a58669 100644 --- a/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-redis/src/test-integration/java/io/airbyte/integrations/destination/redis/SshRedisDestinationAcceptanceTest.java @@ -86,9 +86,9 @@ protected JsonNode getFailCheckConfig() { @Override protected List retrieveRecords(TestDestinationEnv testEnv, - String streamName, - String namespace, - JsonNode streamSchema) { + String streamName, + String namespace, + JsonNode streamSchema) { var key = redisNameTransformer.keyName(namespace, streamName); return redisCache.getAll(key).stream() .sorted(Comparator.comparing(RedisRecord::getTimestamp)) @@ -97,7 +97,6 @@ protected List retrieveRecords(TestDestinationEnv testEnv, .collect(Collectors.toList()); } - @Override protected boolean implementsNamespaces() { return true; diff --git a/airbyte-integrations/connectors/source-file/source_file/client.py b/airbyte-integrations/connectors/source-file/source_file/client.py index d07ba892f93b..9d6ee5ae2aa5 100644 --- a/airbyte-integrations/connectors/source-file/source_file/client.py +++ b/airbyte-integrations/connectors/source-file/source_file/client.py @@ -317,7 +317,6 @@ def load_dataframes(self, fp, skip_data=False) -> Iterable: logger.error(error_msg) raise ConfigurationError(error_msg) from err - reader_options = {**self._reader_options} try: if self._reader_format == "csv": diff --git a/airbyte-integrations/connectors/source-file/unit_tests/test_client.py b/airbyte-integrations/connectors/source-file/unit_tests/test_client.py index 72825ac664f5..25efd8c0ecf6 100644 --- a/airbyte-integrations/connectors/source-file/unit_tests/test_client.py +++ b/airbyte-integrations/connectors/source-file/unit_tests/test_client.py @@ -17,6 +17,7 @@ def wrong_format_client(): format="wrong", ) + @pytest.fixture def csv_format_client(): return Client( @@ -26,6 +27,7 @@ def csv_format_client(): format="csv", ) + @pytest.mark.parametrize( "storage, expected_scheme", [ diff --git a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java index 3c694621e23a..6cd6b925e982 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java +++ b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java @@ -387,7 +387,8 @@ protected String getWrappedColumnNames(final JdbcDatabase database, final Connection connection, final List columnNames, final String schemaName, - final String tableName) throws SQLException { + final String tableName) + throws SQLException { return sourceOperations.enquoteIdentifierList(connection, columnNames); } diff --git a/airbyte-integrations/connectors/source-shopify/integration_tests/state.json b/airbyte-integrations/connectors/source-shopify/integration_tests/state.json index c362f65b2358..09560a7a843c 100644 --- a/airbyte-integrations/connectors/source-shopify/integration_tests/state.json +++ b/airbyte-integrations/connectors/source-shopify/integration_tests/state.json @@ -1,80 +1,80 @@ { "customers": { - "updated_at": "2022-06-22T03:50:13-07:00" + "updated_at": "2022-06-22T03:50:13-07:00" }, "orders": { - "updated_at": "2022-10-10T06:21:53-07:00" + "updated_at": "2022-10-10T06:21:53-07:00" }, "draft_orders": { - "updated_at": "2022-10-08T05:07:29-07:00" + "updated_at": "2022-10-08T05:07:29-07:00" }, "products": { - "updated_at": "2022-10-10T06:21:56-07:00" + "updated_at": "2022-10-10T06:21:56-07:00" }, "abandoned_checkouts": {}, "metafields": { - "updated_at": "2022-05-30T23:42:02-07:00" + "updated_at": "2022-05-30T23:42:02-07:00" }, "collects": { - "id": 29427031703740 + "id": 29427031703740 }, "custom_collections": { - "updated_at": "2022-10-08T04:44:51-07:00" + "updated_at": "2022-10-08T04:44:51-07:00" }, "order_refunds": { - "created_at": "2022-10-10T06:21:53-07:00", - "orders": { - "updated_at": "2022-10-10T06:21:53-07:00" - } + "created_at": "2022-10-10T06:21:53-07:00", + "orders": { + "updated_at": "2022-10-10T06:21:53-07:00" + } }, "order_risks": { - "id": 6446736474301, - "orders": { - "updated_at": "2022-03-07T02:09:04-08:00" - } + "id": 6446736474301, + "orders": { + "updated_at": "2022-03-07T02:09:04-08:00" + } }, "transactions": { - "created_at": "2022-10-10T06:21:52-07:00", - "orders": { - "updated_at": "2022-10-10T06:21:53-07:00" - } + "created_at": "2022-10-10T06:21:52-07:00", + "orders": { + "updated_at": "2022-10-10T06:21:53-07:00" + } }, "tender_transactions": { - "processed_at": "2022-10-10T06:21:52-07:00" + "processed_at": "2022-10-10T06:21:52-07:00" }, "pages": { - "updated_at": "2022-10-08T08:07:00-07:00" + "updated_at": "2022-10-08T08:07:00-07:00" }, "price_rules": { - "updated_at": "2021-09-10T06:48:10-07:00" + "updated_at": "2021-09-10T06:48:10-07:00" }, "discount_codes": { - "price_rules": { - "updated_at": "2021-09-10T06:48:10-07:00" - }, + "price_rules": { "updated_at": "2021-09-10T06:48:10-07:00" + }, + "updated_at": "2021-09-10T06:48:10-07:00" }, "inventory_items": { - "products": { - "updated_at": "2022-03-17T03:10:35-07:00" - }, - "updated_at": "2022-03-06T14:12:20-08:00" + "products": { + "updated_at": "2022-03-17T03:10:35-07:00" + }, + "updated_at": "2022-03-06T14:12:20-08:00" }, "inventory_levels": { - "locations": {}, - "updated_at": "2022-10-10T06:21:56-07:00" + "locations": {}, + "updated_at": "2022-10-10T06:21:56-07:00" }, "fulfillment_orders": { - "id": 5567724486845, - "orders": { - "updated_at": "2022-10-10T06:05:29-07:00" - } + "id": 5567724486845, + "orders": { + "updated_at": "2022-10-10T06:05:29-07:00" + } }, "fulfillments": { - "updated_at": "2022-06-22T03:50:14-07:00", - "orders": { - "updated_at": "2022-10-10T06:05:29-07:00" - } + "updated_at": "2022-06-22T03:50:14-07:00", + "orders": { + "updated_at": "2022-10-10T06:05:29-07:00" + } }, "balance_transactions": {} -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json index d2503f842f83..6243a695693e 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json @@ -43,4 +43,4 @@ "type": ["null", "string"] } } -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json index bf74f22f5d1f..1e91c726368f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json @@ -43,4 +43,4 @@ } }, "type": ["null", "object"] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json index 1b9299c3976e..378cb2463ca8 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json @@ -24,7 +24,7 @@ "type": ["null", "string"] }, "width": { - "type": ["null", "integer"] + "type": ["null", "integer"] }, "height": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json index c5cac4d7e253..9b958269e78c 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json @@ -99,14 +99,14 @@ } } }, - "requires_shipping" : { - "type" : ["null", "boolean"] + "requires_shipping": { + "type": ["null", "boolean"] }, - "admin_graphql_api_id" : { - "type" : ["null", "string"] + "admin_graphql_api_id": { + "type": ["null", "string"] }, "shop_url": { "type": ["null", "string"] } } -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json index 01930de03b54..a931b2c72b76 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json @@ -30,20 +30,20 @@ "disjunctive": { "type": ["null", "boolean"] }, - "rules" : { - "type" : ["null", "array"], - "items" : { - "type" : ["null", "string"] + "rules": { + "type": ["null", "array"], + "items": { + "type": ["null", "string"] } }, - "published_scope" : { - "type" : ["null", "string"] + "published_scope": { + "type": ["null", "string"] }, - "admin_graphql_api_id" : { - "type" : ["null", "string"] + "admin_graphql_api_id": { + "type": ["null", "string"] }, "shop_url": { "type": ["null", "string"] } } -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile b/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile index ed53b3741602..409f1b8a8038 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile +++ b/airbyte-integrations/connectors/source-zendesk-chat/Dockerfile @@ -16,5 +16,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main_dev.py"] -LABEL io.airbyte.version=0.1.10 +LABEL io.airbyte.version=0.1.11 LABEL io.airbyte.name=airbyte/source-zendesk-chat diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json index 21e2542cbe5b..308eb7f93834 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/schemas/chats.json @@ -328,83 +328,83 @@ }, "engagements": { "items": { - "properties": { - "id": { - "type": ["null", "string"] - }, - "started_by": { - "type": ["null", "string"] - }, - "agent_id": { - "type": ["null", "string"] - }, - "agent_name": { - "type": ["null", "string"] - }, - "agent_full_name": { - "type": ["null", "string"] - }, - "department_id": { - "type": ["null", "integer"] - }, - "assigned": { - "type": ["null", "boolean"] - }, - "accepted": { - "type": ["null", "boolean"] - }, - "rating": { - "type": ["null", "string"] - }, - "comment": { - "type": ["null", "string"] - }, - "skills_requested": { - "type": ["null", "array"] - }, - "skills_fulfilled": { - "type": ["null", "boolean"] + "properties": { + "id": { + "type": ["null", "string"] + }, + "started_by": { + "type": ["null", "string"] + }, + "agent_id": { + "type": ["null", "string"] + }, + "agent_name": { + "type": ["null", "string"] + }, + "agent_full_name": { + "type": ["null", "string"] + }, + "department_id": { + "type": ["null", "integer"] + }, + "assigned": { + "type": ["null", "boolean"] + }, + "accepted": { + "type": ["null", "boolean"] + }, + "rating": { + "type": ["null", "string"] + }, + "comment": { + "type": ["null", "string"] + }, + "skills_requested": { + "type": ["null", "array"] + }, + "skills_fulfilled": { + "type": ["null", "boolean"] + }, + "timestamp": { + "format": "date-time", + "type": ["null", "string"] + }, + "duration": { + "type": ["null", "number"] + }, + "response_time": { + "properties": { + "first": { + "type": ["null", "integer"] }, - "timestamp": { - "format": "date-time", - "type": ["null", "string"] + "max": { + "type": ["null", "integer"] }, - "duration": { - "type": ["null", "number"] + "avg": { + "type": ["null", "number"] + } + }, + "type": ["null", "object"], + "additionalProperties": true + }, + "count": { + "properties": { + "total": { + "type": ["null", "integer"] }, - "response_time": { - "properties": { - "first": { - "type": ["null", "integer"] - }, - "max": { - "type": ["null", "integer"] - }, - "avg": { - "type": ["null", "number"] - } - }, - "type": ["null", "object"], - "additionalProperties": true + "agent": { + "type": ["null", "integer"] }, - "count": { - "properties": { - "total": { - "type": ["null", "integer"] - }, - "agent": { - "type": ["null", "integer"] - }, - "visitor": { - "type": ["null", "integer"] - } - }, - "type": ["null", "object"], - "additionalProperties": true + "visitor": { + "type": ["null", "integer"] } - }, - "type": ["null", "object"], - "additionalProperties": true + }, + "type": ["null", "object"], + "additionalProperties": true + } + }, + "type": ["null", "object"], + "additionalProperties": true }, "type": ["null", "array"] } diff --git a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py index 3a352bca2a98..5e26e8119972 100644 --- a/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py +++ b/airbyte-integrations/connectors/source-zendesk-chat/source_zendesk_chat/streams.py @@ -233,11 +233,9 @@ class Chats(TimeIncrementalStream): limit = 1000 def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: - response_data = response.json() + response_data = response.json() if response_data["count"] == self.limit: - next_page = { - "start_time": response_data["end_time"] - } + next_page = {"start_time": response_data["end_time"]} start_id = response_data.get("end_id") if start_id: diff --git a/docs/integrations/sources/zendesk-chat.md b/docs/integrations/sources/zendesk-chat.md index a277fb8df640..dc10aa231c1b 100644 --- a/docs/integrations/sources/zendesk-chat.md +++ b/docs/integrations/sources/zendesk-chat.md @@ -78,6 +78,7 @@ The connector is restricted by normal Zendesk [requests limitation](https://deve | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | +| 0.1.11 | 2022-10-18 | [17745](https://github.com/airbytehq/airbyte/pull/17745) | Add Engagements Stream and fix infity looping | | 0.1.10 | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states. | | 0.1.9 | 2022-08-23 | [15879](https://github.com/airbytehq/airbyte/pull/15879) | Corrected specification and stream schemas to support backward capability | | 0.1.8 | 2022-06-28 | [13387](https://github.com/airbytehq/airbyte/pull/13387) | Add state checkpoint to allow long runs | From d016ef11c94dbb78049f7645755414960acfc58f Mon Sep 17 00:00:00 2001 From: Yowan Ramchoreeter <26179814+YowanR@users.noreply.github.com> Date: Tue, 18 Oct 2022 14:26:07 -0400 Subject: [PATCH 168/498] doc_update_oath_issue_gsc (#17967) * doc_update_oath_issue_gsc We are currently blocked on some GCS OAuth workflows and we are adding a note to inform our customers. * Updated wording * Fixed wording for this connector Approved and good to go --- docs/integrations/sources/google-search-console.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/integrations/sources/google-search-console.md b/docs/integrations/sources/google-search-console.md index ce01a8b19d9a..c8d0074504a9 100644 --- a/docs/integrations/sources/google-search-console.md +++ b/docs/integrations/sources/google-search-console.md @@ -7,6 +7,12 @@ This page contains the setup guide and reference information for the google sear * Credentials to a Google Service Account \(or Google Service Account with delegated Domain Wide Authority\) or Google User Account +:::note + +Since Google has deprecated certain [OAuth workflows](https://developers.google.com/identity/protocols/oauth2/resources/oob-migration), OAuth isn't supported for this connector at this time. + +::: + ## Setup guide ### Step 1: Set up google search console From d7fd7b677759f4c4c92003ecb592c15a43399f6e Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Tue, 18 Oct 2022 11:36:59 -0700 Subject: [PATCH 169/498] `recordsRead` should be a long (#18123) --- .../io/airbyte/workers/general/DefaultReplicationWorker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java index d6c60ab1e47a..f2c67ccd9b3d 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java @@ -337,7 +337,7 @@ private static Runnable getReplicationRunnable(final AirbyteSource source, return () -> { MDC.setContextMap(mdc); LOGGER.info("Replication thread started."); - var recordsRead = 0; + Long recordsRead = 0L; final Map, Integer>> validationErrors = new HashMap<>(); try { while (!cancelled.get() && !source.isFinished()) { From adf2c5ff8ea6331dcd07f24a0e4ce8ec895d0195 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Tue, 18 Oct 2022 11:51:25 -0700 Subject: [PATCH 170/498] Bmoric/remove dep connector worker (#17977) * test [ci skip] * Autogenerated files * Add missing annotation * Remove unused json2Schema block from worker * Move tess * Missing deps and format * Fix test build * TMP * Add missing dependencies * PR comments * Tmp * [ci skip] Tmp * Fix acceptance test and add the seed dependency * Fix build * For diff * tmp * Build pass * make the worker to be on the platform only * fix setting.yaml * remove dependency * Fix pmd * Fix Cron * Add chart * Fix cron * Fix server build.gradle * Fix jar conflict * PR comments * Add cron micronaut environemnt * Update required build path * remove all dependencies on worker * Typo * Add gradle exclusions * Add exclusion to avoid conflict * more exclusion * Move more class * Exclude worker from the general build * Fix connector build --- .github/workflows/gradle.yml | 4 ++-- .../io/airbyte/workers/general/CheckConnectionWorker.java | 0 .../workers/general/DefaultCheckConnectionWorker.java | 0 .../workers/general/DefaultDiscoverCatalogWorker.java | 0 .../io/airbyte/workers/general/DefaultGetSpecWorker.java | 0 .../io/airbyte/workers/general/DiscoverCatalogWorker.java | 0 .../main/java/io/airbyte/workers/general/EchoWorker.java | 0 .../java/io/airbyte/workers/general/GetSpecWorker.java | 0 .../io/airbyte/workers/helper/EntrypointEnvChecker.java | 0 airbyte-config/config-persistence/build.gradle | 4 ++++ airbyte-db/db-lib/build.gradle | 4 ++++ airbyte-integrations/bases/base-normalization/build.gradle | 1 - .../bases/standard-destination-test/build.gradle | 2 +- .../bases/standard-source-test/build.gradle | 2 +- .../connectors/destination-clickhouse/build.gradle | 2 +- .../connectors/destination-gcs/build.gradle | 1 - .../destination-postgres-strict-encrypt/build.gradle | 2 +- airbyte-test-utils/build.gradle | 7 ++++++- airbyte-tests/build.gradle | 2 +- settings.gradle | 2 +- 20 files changed, 22 insertions(+), 11 deletions(-) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/CheckConnectionWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/DiscoverCatalogWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/EchoWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/general/GetSpecWorker.java (100%) rename {airbyte-workers => airbyte-commons-worker}/src/main/java/io/airbyte/workers/helper/EntrypointEnvChecker.java (100%) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 68bf71198a9f..012d7541d24d 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -61,7 +61,7 @@ jobs: - 'airbyte-cdk/**' - 'airbyte-protocol/**' - 'airbyte-integrations/**' - - 'airbyte-workers/**' + - 'airbyte-commons-worker/**' db: - 'airbyte-db/**' frontend: @@ -232,7 +232,7 @@ jobs: run: SUB_BUILD=CONNECTORS_BASE ./gradlew format --scan --info --stacktrace - name: Build - run: SUB_BUILD=CONNECTORS_BASE ./gradlew build -x :airbyte-workers:test --scan + run: SUB_BUILD=CONNECTORS_BASE ./gradlew build --scan - name: Ensure no file change run: git --no-pager diff && test -z "$(git --no-pager diff)" diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/CheckConnectionWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/CheckConnectionWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/CheckConnectionWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/CheckConnectionWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/DiscoverCatalogWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DiscoverCatalogWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/DiscoverCatalogWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DiscoverCatalogWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/EchoWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/EchoWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/EchoWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/EchoWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/general/GetSpecWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/GetSpecWorker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/general/GetSpecWorker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/general/GetSpecWorker.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/helper/EntrypointEnvChecker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/EntrypointEnvChecker.java similarity index 100% rename from airbyte-workers/src/main/java/io/airbyte/workers/helper/EntrypointEnvChecker.java rename to airbyte-commons-worker/src/main/java/io/airbyte/workers/helper/EntrypointEnvChecker.java diff --git a/airbyte-config/config-persistence/build.gradle b/airbyte-config/config-persistence/build.gradle index bf412227e240..824882d95d60 100644 --- a/airbyte-config/config-persistence/build.gradle +++ b/airbyte-config/config-persistence/build.gradle @@ -3,6 +3,10 @@ plugins { id 'airbyte-integration-test-java' } +configurations.all { + exclude group: 'io.micronaut.flyway' +} + dependencies { implementation project(':airbyte-commons') implementation project(':airbyte-commons-docker') diff --git a/airbyte-db/db-lib/build.gradle b/airbyte-db/db-lib/build.gradle index ce0ac2ee9575..89e2bbd6d4ea 100644 --- a/airbyte-db/db-lib/build.gradle +++ b/airbyte-db/db-lib/build.gradle @@ -9,6 +9,10 @@ configurations { migrations.extendsFrom implementation } +configurations.all { + exclude group: 'io.micronaut.flyway' +} + dependencies { api libs.hikaricp api libs.jooq.meta diff --git a/airbyte-integrations/bases/base-normalization/build.gradle b/airbyte-integrations/bases/base-normalization/build.gradle index 5236723887e1..5030b7264723 100644 --- a/airbyte-integrations/bases/base-normalization/build.gradle +++ b/airbyte-integrations/bases/base-normalization/build.gradle @@ -10,7 +10,6 @@ airbytePython { } dependencies { - implementation project(':airbyte-workers') implementation project(':airbyte-commons-worker') } diff --git a/airbyte-integrations/bases/standard-destination-test/build.gradle b/airbyte-integrations/bases/standard-destination-test/build.gradle index edc9bdb84971..8086385c174e 100644 --- a/airbyte-integrations/bases/standard-destination-test/build.gradle +++ b/airbyte-integrations/bases/standard-destination-test/build.gradle @@ -7,7 +7,7 @@ dependencies { implementation project(':airbyte-config:config-models') implementation project(':airbyte-integrations:bases:base-java') implementation project(':airbyte-protocol:protocol-models') - implementation project(':airbyte-workers') + implementation project(':airbyte-commons-worker') implementation(enforcedPlatform('org.junit:junit-bom:5.8.2')) implementation 'org.junit.jupiter:junit-jupiter-api' diff --git a/airbyte-integrations/bases/standard-source-test/build.gradle b/airbyte-integrations/bases/standard-source-test/build.gradle index b27667a27491..a006bbc02d76 100644 --- a/airbyte-integrations/bases/standard-source-test/build.gradle +++ b/airbyte-integrations/bases/standard-source-test/build.gradle @@ -17,7 +17,7 @@ dependencies { implementation project(':airbyte-config:config-models') implementation project(':airbyte-config:config-persistence') implementation project(':airbyte-protocol:protocol-models') - implementation project(':airbyte-workers') + implementation project(':airbyte-commons-worker') implementation 'org.mockito:mockito-core:4.6.1' implementation 'net.sourceforge.argparse4j:argparse4j:0.8.1' diff --git a/airbyte-integrations/connectors/destination-clickhouse/build.gradle b/airbyte-integrations/connectors/destination-clickhouse/build.gradle index c6d80b88640d..e1e41edb6496 100644 --- a/airbyte-integrations/connectors/destination-clickhouse/build.gradle +++ b/airbyte-integrations/connectors/destination-clickhouse/build.gradle @@ -25,7 +25,7 @@ dependencies { integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-clickhouse') - integrationTestJavaImplementation project(':airbyte-workers') + integrationTestJavaImplementation project(':airbyte-commons-worker') // https://mvnrepository.com/artifact/org.testcontainers/clickhouse integrationTestJavaImplementation libs.connectors.destination.testcontainers.clickhouse integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-normalization').airbyteDocker.outputs) diff --git a/airbyte-integrations/connectors/destination-gcs/build.gradle b/airbyte-integrations/connectors/destination-gcs/build.gradle index 590e3d849500..80dae0ede86e 100644 --- a/airbyte-integrations/connectors/destination-gcs/build.gradle +++ b/airbyte-integrations/connectors/destination-gcs/build.gradle @@ -43,6 +43,5 @@ dependencies { integrationTestJavaImplementation project(':airbyte-integrations:bases:standard-destination-test') integrationTestJavaImplementation project(':airbyte-integrations:connectors:destination-gcs') - integrationTestJavaImplementation project(':airbyte-workers') integrationTestJavaImplementation project(':airbyte-commons-worker') } diff --git a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/build.gradle b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/build.gradle index dacef77f83a3..5e7fa9bf897e 100644 --- a/airbyte-integrations/connectors/destination-postgres-strict-encrypt/build.gradle +++ b/airbyte-integrations/connectors/destination-postgres-strict-encrypt/build.gradle @@ -23,6 +23,6 @@ dependencies { implementation files(project(':airbyte-integrations:bases:base-java').airbyteDocker.outputs) integrationTestJavaImplementation files(project(':airbyte-integrations:bases:base-normalization').airbyteDocker.outputs) integrationTestJavaImplementation project(':airbyte-config:config-models') - integrationTestJavaImplementation project(':airbyte-workers') + integrationTestJavaImplementation project(':airbyte-commons-worker') } diff --git a/airbyte-test-utils/build.gradle b/airbyte-test-utils/build.gradle index f6c3dfa412d0..39a3aa7eb46a 100644 --- a/airbyte-test-utils/build.gradle +++ b/airbyte-test-utils/build.gradle @@ -2,11 +2,16 @@ plugins { id 'java-library' } +configurations.all { + exclude group: 'io.micronaut.jaxrs' + exclude group: 'io.micronaut.sql' +} + dependencies { api project(':airbyte-db:db-lib') implementation project(':airbyte-api') implementation project(':airbyte-commons-temporal') - implementation project(':airbyte-workers') + implementation project(':airbyte-commons-worker') implementation 'io.fabric8:kubernetes-client:5.12.2' implementation 'io.temporal:temporal-sdk:1.8.1' diff --git a/airbyte-tests/build.gradle b/airbyte-tests/build.gradle index 8600ff04a42b..da79354c3a49 100644 --- a/airbyte-tests/build.gradle +++ b/airbyte-tests/build.gradle @@ -49,7 +49,7 @@ dependencies { acceptanceTestsImplementation project(':airbyte-db:db-lib') acceptanceTestsImplementation project(':airbyte-tests') acceptanceTestsImplementation project(':airbyte-test-utils') - acceptanceTestsImplementation project(':airbyte-workers') + acceptanceTestsImplementation project(':airbyte-commons-worker') acceptanceTestsImplementation 'com.fasterxml.jackson.core:jackson-databind' acceptanceTestsImplementation 'io.github.cdimascio:java-dotenv:3.0.0' diff --git a/settings.gradle b/settings.gradle index 4ba0c20041f8..a9d7fcea98fc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -76,7 +76,6 @@ include ':airbyte-queue' include ':airbyte-test-utils' // airbyte-workers has a lot of dependencies. -include ':airbyte-workers' // reused by acceptance tests in connector base. include ':airbyte-analytics' // transitively used by airbyte-workers. include ':airbyte-commons-temporal' include ':airbyte-commons-worker' @@ -98,6 +97,7 @@ if (!System.getenv().containsKey("SUB_BUILD") || System.getenv().get("SUB_BUILD" include ':airbyte-tests' include ':airbyte-webapp' include ':airbyte-webapp-e2e-tests' + include ':airbyte-workers' } // connectors base From 370feccb51bf0a5cbba311c27c31c3a837300469 Mon Sep 17 00:00:00 2001 From: midavadim Date: Tue, 18 Oct 2022 22:45:04 +0300 Subject: [PATCH 171/498] increased timeout for sat tests (#18128) Source Harvest - increase timeout for sat tests --- .../connectors/source-harvest/acceptance-test-config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-integrations/connectors/source-harvest/acceptance-test-config.yml b/airbyte-integrations/connectors/source-harvest/acceptance-test-config.yml index b23a0698ff48..b7aeaf3388ee 100644 --- a/airbyte-integrations/connectors/source-harvest/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-harvest/acceptance-test-config.yml @@ -23,6 +23,7 @@ tests: cursor_paths: contacts: ["updated_at"] expenses_clients: ["to"] + timeout_seconds: 2400 full_refresh: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" From 11699d4fe1510c8ea3f7fea9017f53e7a96932bc Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Tue, 18 Oct 2022 22:59:38 +0200 Subject: [PATCH 172/498] Alex/mvp UI for dbt cloud integration (#18095) * Add lightly-styled ui for dbt cloud settings * Add CollapsablePanel component * Add CollapsablePanel around url input, MVP styling To get the styling to work, I needed to edit `LabeledInput` to accept a `className` prop, so I could give it contextually-specific styling. * Add new feature flag for dbt cloud integration This feature isn't added to either OSS or cloud builds; it will be dynamically toggled for specific targeted accounts via LaunchDarkly's `featureService.overwrites` key. * Put settings page dbt cloud ui behind feature flag * Add feature-flagged CloudTransformationsCard * Extract (and rename) DbtCloudTransformationsCard * Extract EmptyTransformationList component * List transformations if any, "no integration" UI This still uses some hardcoded conditions instead of anything resembling actual data * Initial UI for cloud transform jobs * Use formik-backed inputs for job list data fields * Improve job list management with FieldArray et al * WIP: build payload to save job data as operations There's some key data missing and it's not currently wired up * Start pulling dbt cloud business logic to its own module * Renaming pass (s/transformation/job/g) * Move more logic into dbt service module * Renaming pass (s/project/account/) * Improve useDbtIntegration hook * Add skeleton of updateWorkspace fn * Connect pages to actual backend (no new jobs tho) * Add hacky initial add new job implementation * Put the whole dbt cloud card inside FieldArray This dramatically simplifies adding to the list of jobs. * Fix button placement, loss of focus on input Never use the input prop in your component key, kids. * re-extract DbtJobsList component * Add input labels for dbt cloud job list * Validate dbt cloud jobs so bad data doesn't crash the party * Fix typo * Improve dirty form tracking for dbt jobs list * Remove unused input, add loading state to dbt cloud settings view * Handle no integration, dirty states in dbt jobs list Co-authored-by: Alex Birdsall --- .../public/images/external/dbt-bit_tm.png | Bin 0 -> 4022 bytes .../public/images/octavia/worker.png | Bin 0 -> 14408 bytes .../components/LabeledInput/LabeledInput.tsx | 5 +- .../src/hooks/services/Feature/types.tsx | 1 + .../src/packages/cloud/locales/en.json | 7 + .../src/packages/cloud/services/dbtCloud.ts | 113 +++++++++++ .../views/settings/CloudSettingsPage.tsx | 20 +- .../DbtCloudSettingsView.module.scss | 14 ++ .../integrations/DbtCloudSettingsView.tsx | 44 +++++ .../cloud/views/settings/routePaths.ts | 1 + .../ConnectionTransformationTab.tsx | 6 +- .../DbtCloudTransformationsCard.module.scss | 91 +++++++++ .../DbtCloudTransformationsCard.tsx | 181 ++++++++++++++++++ 13 files changed, 480 insertions(+), 3 deletions(-) create mode 100644 airbyte-webapp/public/images/external/dbt-bit_tm.png create mode 100644 airbyte-webapp/public/images/octavia/worker.png create mode 100644 airbyte-webapp/src/packages/cloud/services/dbtCloud.ts create mode 100644 airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss create mode 100644 airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx create mode 100644 airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss create mode 100644 airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx diff --git a/airbyte-webapp/public/images/external/dbt-bit_tm.png b/airbyte-webapp/public/images/external/dbt-bit_tm.png new file mode 100644 index 0000000000000000000000000000000000000000..b20774dced5ac5e7b3e7d5e502afab2472702648 GIT binary patch literal 4022 zcmV;n4@vNeP)7B1WM&Bo&}S(#D>Ey8@0kY@mXPYNsDDI8Mj=dzc=C@rq__wO-$DY5 z09d1&&p?QG^!+hh{&j%A{^uPq!J>{a-~;;h9v$bJKE`x>Q)q*LV@SjSzWP@iJOCjF zVxv9}pXQzcCL8qQ{22d(UbuKpFS-d#Fp!W}0dXq}QlozR9UbEx`leN+xF-^{67uIe z@&#@{E?C$hpdvEj-grX3^}ppYZqhM+4h1l_pvB|m4XDUZ>o^g4)Pso@eOdG6G{tB# zYq3RQuEW$66TC(WTHA{|`(|F`%?F1jK>D18T`VNz8cAW#&M{6KE~E8OL8}*v_%`3E zX966*Xht)=g&g~tS}^_IFe+c_)lZZzvi%=0StoY=~{*o5%9xZ_-l3TYR zHVPNB1c7Rx$*DRuGP#jHS*$O=fGV66FJ=kW)!$?5(R+W$N^{npEI;h8^I){pY8sJB zTiF7`TnXA6t1%R{8ugHE$Z!hv$zwx7YWCxO(|)>ZIef7iLyDnpNi+oSS zljXPc9(f*^Bv^w_ca4_|RfN{jIXQ)(pilT^x`V&`u?;0>^4=P6ID^^q1(Q)ai_pm` z1P9ijQh7lM#;IzeDJkl;f4+N4Vs1cKVwTLq`JGiTh!!-8pCMRKo?Z6JW<#V;&g=zw z<=}+er2;>JK~OxU`lO(ePAt$eI?R3Aak0!=1n!or9U{^NQ;B{87e3S`4I)pypYNQ+ zXhT#@UfPS8#4JhG<^dyAArW1n+?%NYL;NS&m3SDQsEh zYlgUtZndq`ClKmjh=jzv2(hD<(ebT#{CyCoYPDRgyWu=puC`7M3?Zu=AD|Rqx6JqW zJ7b>BG|FC34iIP=E-%Is_$s}HGRgP`N&}LxtNXs$kkZ(T{=5f6kmbN*C?mR?@5G00 zzLMFC+o+KjWv%cT{^d<{N|NQW$M6>-Ix)l)iy|8zP>rH0Z2Q<{de0ip~pQkZwOT zN9h}&^`<9$dDx(kQr`DnBx_)p>szomN)N_j(^DxUHRUV#>d?5LuWynS!uS(JgT?JmbrM-=5 z`mhA=ecM)amfR9FZXcXwbAFjobef(F*^n&aN1h~#9h-Ij{-eM)9?Zk%FLD=44VW=8 zH^MC9CDVJtkfQlM}2#^{bOSHbmRL^bN#- zD$}{?J5NUb#_a3{m+__~Z|}7bO$eX1%`)9hMhcqCc*dBQtBsRPddkj_EeIOBWxAV; zj?j|18QJ332F3t5S<_S4JBp}GcjR>T;8=p@GM)v-?Y&Om076B$UnH9B!kgTUXNlpx z*JAyLKj~{&JHRdWYYbyN%h7?$c$PqIL;ktE-`9o+5uoiz3(?9%f>udN>Dn9}lHdbn zL)MpHToZPREeHwWHWBP)vIs5VZafFz#O1e`CT9sx*iRVf%8Z53%ca!NNv_$oJa2c<}wAB~} zPncTJy3d27bZPPI44u1dcN6aOC?N&)YYbz0%h7@RJW7ZkiC{0r1kKyEN(O?LR~0iC zp(VUst1Pr(=De_ejbRuSG;h}`36u!>dg*Di5n96Awc-ZhU6IX4qcARL9i_R?BU;oz zNq;dPp@I86qDA<#A~*;uf>x+L4^9vxq78E&8{j$C1g-l#I6;gCj}yUO>_un^Cx~&N zR|K0709FOf31Texw<0(QyAc{VK@1*;gBDF-UC;{d^9Y!5qO-(_pyQ6x>JrBx&z~Di zV9{CPCPGUtlQ?D_cWJ`~+{fMD7uChrMY;gdCBR8=&1UWdefO(oM0IH@Nv2D^n?iM& zGF^grMBT<$zqKGRoC!LcNMHD_U;}6|_c;|uC29dJ2oQG>S`xnP7%1ir(TF+~K^`l& zg02Rg7>*QkhbTmyh#=3V#`YABOOUgUClGTlYJ#YqNmqlgIHN@OYX}gx5n6(x4PlW> z%#wg3^?L{u_kvDENN-kdF?R@3)B;W+P}~bToYLLYV(t*2s6RlU6d-5_L(qoA+#wS+ zH)>g?2tkLyv5h6)?2;ZKR1ew^C@kpy{0(f83f{$I5f*fR7IR;8eh9jZ7sR-<8UzXp zdJxs>{-hV2A4VqTFs_pl-a5e*1qfQyAPBIQU5Tm&Q_>YPJ!TM~J!+L`~n<;}LVl`JGh=j4HT7;HvxW1U5k_>Qnllwy%DU4Q)HToYX%EClbqc zZUoz7TWWOIVG9DmjpaaW023yNTM!V|BQ!_n2nZ-jJBZM@2Ta%??uAnEuqtSZ&a2_q zo*9+!e!9B>0btE?#8s1J6e3+H?=@CbB8W9$;sO1)k5(cyCxS6^@ep_aeE^1i6CGNm`Fjd6GWqygA>8hATI`W-^T>eY=lO9#$ql7^S&mC#v(K) zf+fVm6-09p8j}~mlnW165RC|0CxY+*n393sC5UDsbYLA>j6o2d`4pw67BofYElvbW z%{)bEZ|vq&Nnw@&AT+Gvkfz%pIY{qm@Od!U-@;2a&E(lx{+S z3zcq*P7remI;egP0rD~sZD3q5UvwF7VnJv98UdB*(iZL>E|9r;9?`YN z{Y94X49?Y_lspmo=dj9jNnL)$mxm1ym-p!-`5G9T6x!!8k)X3OT?2wbTusrG-C?+U zv_y(K)847rdPjd8v?iqENnX@Sjb|_*n-Yx)BOy=F?i@8 z?*6{5FNn!D#5e*M#pCatCy0|o=y1E~h729-+K`kYHb-ltR4QAp%Xni6I{0l>W6V0% z!X_KSWj>S4!{<9rD`F#83ZvOpHGBRwZnwrN+hwo05vIlR5QpvEMi>;|-0_k!`CwqbR*FZj|mtJXu+#cS@c*Yry2gQ8(W32p!iL+}z8u`0B6; zvorVgY9o^iM^SoUgwD!zu{9v&j?x|2Ur)j1jGJDk&bqt)nyVobGpvl4$Ud9ogZd{Y)+RWA^|QJk7S-)| zJ&@=C1A5ABWp=)Agw|tllX&Mk8}xJH=}Lpd&3h`xMZj~iaE+nAvc4j2{+@WU+`JNV zPt}B+MDyxwDUzjI5jwnXx*QnXey^LJY+xs9DyHUyG~z&2zw()8$0Ks@}0~ z4&43ifSaDiVTx|NyI(cwYc0Djq|9ZSkcg`!|9XTj+tsS*!)sd9>S4L*X*4-)Ep8v2 zY1@HRg-d>dRJ`fE|4Pu>*OagnWlPlD^fUsB((B*lo3Y=&mX$X~kdCc6*f!a|h^B7$6VMr@dFAwj# z7PETxPMNRtBNot@2FXK(pNC0AF+Tqa!1kF4<*)r_>&ge|_H5i~;9x+RMA$EL-$itw&PbQ=bys01`vTXMOVn;5I&i-$~ zcQN;JRpvw3f%th3hDhjO$Gm6mShf_BIM$|V>MshlDD(1gBl>PW0mioZQI(_HdKV9} zVjwojm(_js%9BunZoXqML;_XW=n)Y@>?nJ&d$bLaaaHDvZW;Z?s^Ao4D}_JMN}&jS zrI74Jy6WTQdTpRq8 z-gqz%pZ_`ytAwY^a4>Ve9b&>~8Xd7K`+=Qlbtj@ht~SY1-LsL^J^e?1s^`u(S;)5u zs(wwmfGSTKq$_9@rN77~r5gB)EW#&b2-G*m2H3C0QSiy@Ne3aTqCekR_#A%W_?+X&#gm|sO&d=UZ%>0 zZM;Yvb-Qi@EYV-k#rkRX$(AY1Ut1Qleu@EP+k~W_u7MlaCp*TeupNk5Kg|%bq+;Lh zena-jMdD<|(UF+-Q;k5`k1t;uRuV<7CwqgX#L@X?ijjiuVEFXJKG{=@30pRa&3$aR z4pw6eT4LbEvXBEoJo%B>NkTtsHUbZFw1FZ(N04fJ8gx%|347}{{J9mT%_IV7aj`U5f@>7aJv!i(6A&Lw(;0L!j|pt%~w^W z%`}-x=bLtZI?6OM!rsJW(i(B!@7SKk76^Y+qQUjaxL!9|C=N5FTs~ z-fnEj%xM9CPcFDPGC|s|f%L3aICQEBemUG=Y87iw;<+2K9fZFTXZ|k%7{uo@Kb=1# zXwI{bkA(OrXOPJ?P*Ntr&rZu3V2#*24gw3eKW(2KP2frhbKOL+y9XQ^6 z)?hMyjQ#zF%>=`o{#O8;858O9-L@~LdB#P%bli3mT*+;Qm`DLI=q5OwUJH}P3N66v z1dzPzsck0weCnbIo_H(YR8S`0ik-ZUtsaIk{Vy0XBk!3XdT8gS87@HqjzEn`8jX6Z zZ%}}jryVF&2B6a!AaLU1cDhBQb-z<*(jdT_3oGV&(zF^w+{GN}EZqDnHaQGa`nv<@ zkvh@m=#Fn^3LPD}Ry!J-RA4Zgz}49Xt`)R_pN~DT*^J)6S+)-1vgt5A*#&4Olk4fr z;)%Fv8w^vu_;&`786D~J{V!W)k8?qgdQwHT6e24wB1Ex@DzCGTdS*=!oD|7S2c;Vl@3M&I8Oz%g;cx1WA2j(?+L7@gdQdXT~BdEYxh z>=dDl4ltQk94KkmgIHvOMN?hD)0Gp4Cyat&Mt?^DBVTzg;XW>>fAwud&{!-pXkw`b z1dg1(53~ZYL#%dz$pAfi7i=~M(slToL)txzhu!&@-7#(A=to z(sBvq`yDw^30y736*sR3Q%>Fy<3&f6Q>X5=(Ed!>aRf;qnh8Z#8??qlK7Kh(U z!<(*D=^*QBGh|lTsuMPi5u5kO(77(Co~FgC%1YJ}uU9bC(Ag|rKG)G;FwOfwPiOwh%N zVD=X~H7A`IG{}u!ZX8-|lg1x6mBBEfzmp&6=4{VJOVH%IWPT{jn(7Zh{tm$5bdN!U z!2}IWa!?>p#P74SWm|Q?W--7ABQwzvE|i;b>Rc^~M*|4mI_x2usVfXeJM&?f(BDCXPWb4p3HzRT zG|rAj`)fDMmd}D)A#X2RsudQCp%^n4zMeebc64FG=YUG8rfw33)lh^|;W{o1BO3Mq zq6Nv!FOb=Oxg{5v3_9phOd3(V>Gj4gE0I{CEEJE#wIGd;Txe&@0w;`*5*LDcce?R? z4-at!6>?i)Nh=1l`QX5(0gKH zXJatjoevU`3=Jg~xCtoHt%G4i!y3RK&ru&gzx0Px8;%`{MmrD+0>ItTA3ommAk?)M z-%?+MyUHr1RI?-wiwCX_-k{d0z(^B=wUXlJ5Ftlm6RxFA@uL6gD9 zEdt_$r+~jl46qnxqsWK}T~T`jG)t#`tvp;F!sT+mtdIC zPyk45xCm@f*oX&R3+7y!>L$;38yPa?!dt5j1%@WEK^ShXcp%)TlEQ$=bffJi>-{E+ z?K$Nq;6m}*hC^q|VsT3g3=}l#8BZy*1WGbk}fS9f=7Lp>DQ1vix1vYMnp5&Y&Pc0TENlC8G-`*Q0K64bZbGTP=ZRM!6=8m>pW3wGc?rInl^v- z-dCl?1+U^JNzcC74DOE4pUS>Bz$*eqEkf~twnaq-Kx2{harJG*;2wPjQm45B$JQ55 z^o1k8{RqcTroos|VdyfjA@9UEG*V_z$16255QLqDxFjXmI|;F4SGbUV0%~e%&^_Sc zxndaW^9tDW&9PBS(P^^yPW0`A|L3_`s%Uu_h6&xOT}&UJI_DE7HxFi0a|;w)&qMG8 zz_qb~y2fTGudajorWTON6;|L85;(hpt(~3vUmtw=xQ}1ZdEDgMqi^@5=tXn=&^qMV zo55=?vj&fhwsu7;u>5wyth+ri4$7jy1EvdX+m!(yZQKXZR1;KhN769ylAaRQHh59Td@3&u_2z`5$zC~LM)E(9|t(lAdLA3BQ^IsIk0 z2ct&8J^{hM`2~d@Vz5{b;wC-}0o^Kq$#IF}oyZ3g*5cXPLuJLy1?I`|9-clnP6B6m z{$KxzKsVw29)~PS8F?>{d{829mYe}7ztE#-3|o!r0IXOZ-*r+(+HRONYZmO-Mo_}>Sys&N@oX*`wZR-TA zlT2NlNCQ7%3@LE(?dv~AMn+-+m>pxjQkdZ5A2cU%Lh7u!OIL07@ed|q_txTkH$k@w z;238#Ogf3dWP-K_r`?EjIt|p&Y;YFXckMfL=nvSmX_M9Q zbqyj6wb_AZV4GEeT9+ubAaA!w5|fMTu^FPHqiK3zzBzW%^y6T@uT@R;wZP_Z!Byx5 zi4&(xV=|d9z#!AD0_Z^sWG#s#O4}vkn>x9)FyAPXwHvo@+Gs+3;$X4M$J-?|!O6}% zIJ)wyW&|nl=yVelRG)*yL_e!OCnZN?0@u1^!q3MIZyXDC8fMWsjaK`Jj_j%Uqaysk z)|LxyURvZfL)Y(8S75RhqYk!IX~js(^Zot(p|G%UQh0Ry>y82!lDa3gGi9yK4QhkY z2nZZFySmeYBch*#L5_aiDu4>LN@=yh-cf+YNzF~2oRNOI{rR;k5_bLYZE)s=G$M*b z9lKJZ0w#2i3|a^0k>og?6eVG>nrQ;5x&pK6c2<1~-JRgaZ=Qvi5dj#qwuA9wBVpf9 zufiXPN+8U8G{|Mv$@950$G{t}+y{=R4;dI7PaG8r-+%oKwT}!&Mpw~xH}6?mqa3sA zF~YJ>9?z8)7#8)ClZzY833Yv7a5%A^niO(bjY=*v_l>&P!`q+e{cspWy4BH;TVGuP zIFhWlc8D~MS_K@gb&;)3tAR!oC8v-70cVdL{t-7_>7(C7AGgTPoOoB41Vl2gfypp8 zKFbahwr!Zpv$h8o%^eT(XO9DgLXB3k4V*li3vAh4RMe%W!~rxKo!Q_4px)nj;a>R1 zL$fHi!^^{&s-Gc0-c-W^9}GR{Q3RVz-C24|W5%4Zr+HZl*V@_=Mrc%Qk^yBu9p$@+ zf8gTs(!#g&I$fqz+!pNY?m;u?b(FD(~y5I`%LWy7-tDT+~fdpmgg z_(F4IJ>+Ixf=lVAp{%$7T3ecACi9G+5Bljh*xB9_x)JOYon{VXGK>^}qr)e_nbTij zBG#u@2ZWo5m3b+%2)1t$!O|qNx=D?B1lIlQb@=SPwN}TwINMXLlMWw8|42`9xIm__mn&XGeK@`rm8s?Bvfx*UE%>F9w6z zscLRLm2v~RRRFcQS?9mbNIU*i;)E$(8|@vP;D>KM1qXB~N(-+;L0%5na@mFtpZfagp-|Dg;xbtBj-v-rGl+Fo_j77^*3%Qx;(wxkU=Wur zhj2zxeC*T{7cch*Aklpe8oj1VD4Cg%MuoeKdIRWCG-3J{ld48YiChRpH6d{SJ>LPx zsTFS!4P;UTMePfdV_$?xlX@A5Fn8P&aPa(RFmLH+Fm-AWga&&em$0C?q!!MlRf8XQ z8Y~&J9CS(@iccDHAJbfB4;F&514xyooqTJz+*MkdYeK*`hBw7;;GosXx3!}hA&puM z;?@=@DacXO)l|F$gFNT(c7xgEywTXkVbfqE;Pi|UhJOF{E7uZ6gt34A<7Cr2o4yy` zps&(rmV6&LC4Oa>?jwvs^dAdBqoMvFh76gENqjLH4ho$Ck1{dLHyp1YQtub|Q zbz@s8ri&`@NAwWt6$+jX9-v2Gkj`YFzBYmZopqg&iH!z&I;#Uv6rCLa9=Z5A{BY<= zQWrDdvVRtfh3b#-etgozXG6lHX*^q799R+%H8(-QwQNy!dGP~ao-{hBbh{=k`7f8? z#%Yy`oRadgxbWbJF)4ulCcIthS}7D||)i9+Qc>aYnp>7|B31&Ek|0shFlQ z@WvV`gMv~*l?~T0;c5r&PX1k;T7Jl{N{3J%40JmOgO_VCH6Yk5JP`*7sca+jJPkNt zbUHeisG?rFc`hiJ^`ZIQ1SQoZhy-~(|AbDbGkSQqtSidR7KBE`JfPR=pfK;MCi}vf z*T6h=TMa`%Lz!aM8ow2qrg15cL_3^tzvnNn=#HVZGjy14*3RtHQvn>v=_C?lBwL&HowooX5* zavq9K!hz&B$$n}8nYsr93AN>w9V1`Q_w^_2g+g%i^d)ID&}h{l5w+Ce$1`A7D?>s< zS|f0!u;^rMQ!|yBn3Nbl??#7~)Miq(E}d0<%llla(y10bpC=f(vXQaW&y+^^BGcqv9C;1Yc&LZ_6i(m+7C^Pb; z1pZkUN8iy-4lLkvX{IZMCB#bWD|kwbpX^ueKfpu?6k5G8vG)Nu$9Em=> z3xiBU0ieHwoySwptvU`Nz7ud%GQr8t2UgAgob0>NEY~*+s?+rs^Y?e_LFl;AMvEKX zsh3R9B@+8;oz(T+0%*&=4VHt%Wbu0z{WOS5;P-P>41#da@BLgqm4Wd0O6=257!gEG zY{E1(*ux;wPyo1WTsko82#EHeWj1hi5vJOgXE)rEp){M#R@4}6G{fLmr7>`<6-xVW9%;q!bl-H(( zL8PGokYVf?ca}GVq*bOT$1)-VeHYx|T$0w9aJ}+u!x#G=ru?p+dagf_*%L6!W1*j8 z`uyH`y_i<|>erQUCVLCPmZU2_@8_CH4Db;ejlMzP;s$;(&X8987N|8cD+$peD#m-D zBc)kYFo-l1mD$-+dBcpZizhb)C5K2-1qmZUFaxfd+#oW%Dwfuf;C5f7hGZ4NX~#1+ zA9Ho|6D~=4y}KXiN_=!jl5ap=;F)@Shd#K|LY(R_toP~8MR2v~5ZOe+Up>Jixy%)O zp7Y(KN8B@QMDQ&7m7?94^K+u6WLGJ2pha8;-ooy*VR_v}^7b2etcQ%r#rrOaFjv`&Jqi zDiNt<8}x$Up_<(C^6#hC3sDr!ou0Igic#hiRl|bR01OywA;3okQvB1NZc(ss^4m7( zq)!pI*G#Ey$c4I=>x2uG3YEBCrE7078uW?-X&>a2K zM7vZB8o3TC>n_3R%U`H7^L{>xN4*JsC$a}jLm7!7X`-imJx0BJ&x}u8Y;Byu!R{NF zk~j+FDm8q#wH0Phcm+C&%{rnk-JiSWr*~W>5kpS4UKlcC-t>i=`gpfAh*TOmWL)0^ z=dbMmd!DN?%zu(G*e8kUDhR@0H>bBd(pR6@;lWd{fqk$9oZUR21(VtPcWhDUv>H+- z_aU|d7(yBf4kTGt63``?=Z~u!vi5)ced_B8BbKfT4^+@*j88;8$OoZowV69C*vHrb z9L>}Xq_N|>z}^o?XvXpe-n0lcS>JeK-IBasEe5$m{gNRm_CB((59T6(-V@}`lllTr zgYnj?`peMReiZ~v^E?2JMn%d<2ypA+zd(ZxpfEYWeaDm_;Y7JcFaJEf;8l&$xDn0g z2Qsexw70UxVZ-Z>E)O7uQEpCB3=Fq|9nZyD23n%h<2%uzMho3%vam=kPwD*7mmV~W zR2qtP2MjKZ?bjOZQ{3WKJfWZ zZ^D@972qcv1uSMq&1(USHBtw37lZshG-Z|JRlw8B72J7DxLnb+4&mN4h)e@|q>vc3dg<8)*_uduG)#2(hsNvEutZ?{4|(fR9VqzsH6p2QY0T zAiy&b{5;}Fp|5q3t;qsBTHl~qk*OaY2z`Lr<2%VbT+I0iD(f?$xay3)MO=Cwbsd@1 zN$OcLyuG~}nwpwYQ)VpK#CNcdcN2P1MN2AlAv}EiXl14OPs+sNC*Z%ZAkbhMG5217 zX5J12t82sgs%H2lw^Ftty&wR;ic$mIFZc><$EqCML9JFpaY@<#iPW01yTd%c8D^EjO_GY-8 zw@Z&=pKutN*SHsYOvJ&oQOcCN7oYI(_NUp}b;qVjS)oR)gx01;)8V~8t-7o zg!5XP8>pICAOB!*a(1D4`vpB;RbIRUyHLUq(x5Gw5$Wc*?8)&FR?9K!F#f5=^!AS2 z%1aEB$;QW#AAuI&UA_)H;82B0MmqtY6Ku!9gfE*(EfI;%JScQV%2?T(SIKmt85R_^ zseW$|SL`aNIJG@1f8U=12j2uw*9c)J@AVRqJ-%BV(?hhL#XR3vyBA+*X)7a9mp8DH ziQJ^)>5+kfYo5G+$(Ku}rzV}cbmhl}=2j_>$Boi(91|RzoDncCsF35XUOvC|;^{wk zGMTKYF786GwRZ%us9DfZTeTa^L5U%wLF2$6XIm;wqR?o8%OYUV!GX&L76KLH?(k`} zyMqlMhl@n1hEkCn*i1SQAmv&;pv4LjwH5=&MyQskph97U(N0Xb*Mk9SH#hpxN|8XR zFsl<$du_(XT}zTz&-v=tNu$?J?+IcjSs5{Xa3Uc&TERPyKKG-+pnCwn8p6WDKqZ$x zxO(2~jWMA?EF4TZO10+Nhnu(l=hWrA+~-y=f5y??HnuRQvMZRtpfSM7Nx*k=^Ez5l zlK*1l$OM5M0?1^s$q~*l#57m{vZCCbsA3+%K_v4)0OT?m;ErN6)|GD)V@HQZj4pDW z>_Mc|f+LMZ%_3@3X(6Ld3=u*Xm>K2?vGxpLsM`u+4a%eo39oW z=PiEvmAh6?n$*^8(2SpxlJeNvd+wQ|V%j+JeZyhT`L`(to)DMEBT8}UN1tz8@ZLM? zOG@&u30N!^v^6)AK>e+)g>oA-;B-NwA zO>B@DXw+0@nMMZ<3Jny?^f1cL6Sm$R59>!dLyQR%xzd&{@CY$cta9jmFMPK9&zCk4 zfT^4wSm*TWTMC=s{5B!vn_pL4pwdzR-AUXIG0HYqgH!dYrKP1{&>L#67L-&?7!@0} zWcuV?0ns6Q*DRfPoa^LzpUE%EF>&s4h#NB&X3d@r1v#0N%RtKJ{e!}xT_Ry^{B~<( zO=T%Hh0e*rR*{-I!xM&RVrM4}R0r}F$N6WxIyq+kI6w284Z?wz^Y@5#$!0QWd#_g` zC(%G6S3pXLH#{642vc2I;ANBnU8BH+Xy0qSXbmRVQPO&>O|8%9xlgHFU(P~F8Zp!@S;1@`0NzJ!wWc(;-sI7)e7tT`)qm$BkolXtYrq6`3l0qP>N`3L| zR(?Wa;{A!^l2^1V+H2ayVij86gRstqH4PZRS0~55GBez}Yf6$7bsU%7JrR|F8RB9O z?WpHwxzb=kfD?rAX~0m+z@XFoW-bceC3-{8JSzs)6b<{ zJfE(tudj7wFq!slZtj%NH#R95JcRC$b@3dO6kG%HbeKsh0!GQwEm>KT%#7GZ8*Alpv;8~Jfs{?zT z5;>AE!j0YVo)NWQD@5m-mAbQi9c$3*MkmKV9M$uDiL4&ZWq(I1!7rdsm(AtcKN0LV z(fZ;{q)=GR!zfED^sX`|61iQg)fU#)R2{sKp0;uK&YzB_r=O8Y#A3QaF88#tv7wEK zj)9SJaS$063T-tNkbfx+$}`V_uC5fCOY%U&;DU#@FNB6iI82^0J!;;9#rFjS1TGj6 z6Ei0FO12b%CNbmNUVm+%I#8q58E$F;A150Qh}7K$%ElfTs*PqtLuzE8kH`dt$$V)H zVQTUU#Wbjp44IE(D3>AbECt6=mf7w3~1xbn3tkJ(xV|5gL!{^frEUfg6)9 zDJkhJAGFs@4J9MBg&8z{T^@uOTOh@q1rvoF7{NEdUBMoZIx-w0QMZo@^M|(T601(! z(al(~>cNQTUU+q7+NGS6Pd)$A9*gU|Wf~}e2Bpr>XU)Gzrl73MPO?FZRH5&VESY+& z{mG>pq20isYS4L@e&AwjRq5f=tM#VqZR#5=uxhN9Auv`kO>6{IB>&E%8byWMFJArL z(3AIVFsOZZ%3GvF;ig15hP$7l&qjD~Y_aV;CsWSt3@$&qd9W{gsk!s5{@mO|jm5n+-F(U14WY z^*eIwNs=uRc>_2*b*#?R(XC!bjVsF<%+nWgf6SPW@K_R;V;wP?IO-9|%-gZ7u`Rc0 z#hND)CQO=~JTW;LYU`Uo;O70{CvU&{jBkK{!0bgU-fkB)%Xmhusi9RuYZZ&3qP7mI z8(Xk7V``}pTH3_WDv=T(O*3cDRn8QK@Ex4pz)>K8z9{#~l`0rOrhdQx>WsZbD4Ea4 zLXOiQ$B{)^nP{o0bcQbLluI-AVtw-cRBbz2oGk^%tEHs+In#^BoBv3Ze_^P-t@C_e zAtmbi0>FG**&{^=%!k^)r z{j;Brj*SN=7a`=AR8WRUeB!vc&3g`?&bykaJbYl!#*1kuf}r<2cVb-@Vk_xQY=4JBRz1bS^h_ugKgy7agl4BxvVcH zVA@H9ksUjX?0i{R3Ay5SK=67N#=5aNy^EctGBPp9+R{TG^vJRF53*vz=8xj@tW}<) zgD)(bwiP~odksAC@{150*1e7a5sUGO<9Qh4e8Jn#A5I=Q2yLw`#KI-9&QsVbpnuA# zC@mq4gwJg502(lWO1+-)ZCaHYI4{$HU&?5h814)9 z7=dYMY=dh>RX}fUgG{j!LM+&%Osa+*#jOp8QA{={wPfLz%KPj&zsERmW{ts^Td0=@ zTx)BGLv@X?x3V5;m1-gri7&Xj$0ba6aSZ5nT{R8a98 zI>*-|0T$e~5kCF*T6pY*buc0}-fDk9a2VBQ&Rqh#f7xUN{IreyU($$GJfIbT2Mpln=|u;wH%SxMK0L=`=7T9T68mfxNq~dFX|i|@ zCpHK;tZ?AOg~PSX0{HVp8WhxuYWJ77eNrXU{D57wdV7a@-Mw`2We+dk*rvej7xR`@z}Kzt{f>^d3)f;#VI$43DmR7F^xkp}C>KBo?(uFyqirUsrvl^m^X0 zva;fnmi_l6L~c^5i{1KJ99nXMNyBhMHKN6<{8(z3y-lX;T{-brwvL$P;m=`5Cj}@_00hh0q)K%BE{F$9wc4qU=(;4V%wLJaG zzwc%+SPNAO`B;Ta=8&CvfmNKJ`?*G=Ax{l!?dRt|NE-Rj^Y_jD&T1n@XyB!fJy3_K z$!w^@{(+${u72$4XGe`qw#D=ioIQCIPW*BOR?q*T-~ZLnave7Py3mB8_&JqILBu6_ z6f0R~!kNuxyWutWxmW?ZJ)B4u?fEAhm~SqelE`Q;tAQ((4UnnDD38j}&}1;FtMsNf z&>r6pgFpiYaNR%WPkZm>`;XqtBLO-|+Aaqp0&9<{!fS<9rqc3;#>)DZ@;{DcA3c?R zZL@=eV0p}_(f^sd^nNCWp#J^V%l};8M>k$fIy?8pM=lGz++wVD6qTQV4cn&>ksB5n z9sj}FXI@(BCiDa)=Iz>AnxVC&QS;sB9~mCJdmk^rdu+egSz3JtHvTl{yxyRgHh%o* z3A0ltE*m#`ivGDV+f7o$4{qlM|ti1DhCXHq{#fJX*ItS)T4f;tx%5{*YHbSkQ zk`ER09k|+8Y$5F1cKyNcWcqn92sB^-fBfvp!}l$jG~-s!)1!q-RJYdPK!8kLau%TJq@R2CN(TAWG(pJaiBC5f>s=dPOfeXqX1*){|A8&hp)2iG~UX1p)a!9gXppbS|Ifz*TYCUO}s{hSm=Z$OS*P^Wme zR&RV627v||N^_etHQ~Lu=)hhNWgr0cT$9C;$iALj%O=lhFqvp0!hL{ckQ=|-vS~q8 zc`XTtC4sq2kI1~|u`R?Fws_jdK=hv_Rn}#|zT?y)KcqC8Olu|+6v)!gWN{wi0Fs&y z68aPAbgIINLCIafQj_V=W zikn@Ix)%-9h_R20^0${rW#E9~@apw)^p?*;d36K$dwapNYgSUvu)P^-6U#uMR6%7` z9rg6Fha)?6M|dn$U%dJu=+$rFE=-=m z>{WKmpI&ifIaH$>FN49^1w3DU5mv2T1$WPwJn>3y{*sZhwz=;(v0q=p zXL5g5=;85JEwmY_s)n(a!AbD=`(tB;Ya4@tn7O@?HxFk@frAd#G{K5fYxjaxOud}hIe11jxo_#@ShLAf|I8&fRCpK z#Xc<_fshm$4vAVV9DnX*sL08KwHj4~U7%-#&esnX<(0!VaXs;0w}aXDBcVqUAtEB4 z+Sk8yyD85ywk*S(NBZ4!)n{bdmN`+~Q(2eq76 zvPuo<8QCM#(laId5B=HR*ep5$X28f&n`GHw@`rEx8TA7Nka){q|G4M$qXnfk@X)d; zz_(L@T5F)VQKi;U)62+$Z+(#l!(`S0@80^-rk`_fhq&0+npI0-<+26f=i>=#wGyVJ zBvE3UEM!aGH5vk9cL+7Vs0^-`HGsmv0R|WKVS79Ezx=$tsijZEVkxycuqa^+OnLLw zUWOYF_f4IY#KAUw-BS-u&%AP7S6EV3R8UlUIy1Y#p|qlcgp3HFw-pZ^D1c<-JcjLJ z=GC&TSvlpjv%f6@Hy0izO0{@{G-#M8LFi_lE5~G7t6Z}5zYUj0ndJ0-j2#w>Q4Qul zNE9kD6>odpX$^`wkywI2cCMCbq6Q6F%$yL> zKNO1d^Dt#(K9@wEh($VTOk5XuWJyZ%gP2(q=W4Yo9K<||^HU~_VPPW)=qNP)p7>?| zVfnise*J)4rP>3xL4#G5Mx3!ayx4bJM5KKVSZ)#xRKNCZ-?Mo%IZPk}i#L>e)>iSZ%m_)JDFF$u}ZM4yl- zbON^6p8t;+r$6uh9WGwZfh`-~fynT_jVvu@AvSJ12=No=phUszlOxwPmx4hfgRqEj z46QjqQ$-_5 z8qB#7B-}Jwt2H*`Ep=%YiQ(0Ee+E})dwB2ll`twg5Zqh^WKlTeP6OETZICks=)5bD zBUluOHe3$1kA%C3QPJ2cQ^}G&281+m(*tGNTeiQ zN=o576U?Maj(|y)HW>qE4km!h$}4MH=HC10=zlzNKmDmkS9T2?a&3fb(#~f>(8y%4 z;oCydm9sECDFW2Cu8>>j00Eu>;2t6eTdp2vO`QPF&H|J;-3FUV(GCn2yUX1mM5df^ zINd85H?>IMoe#hM^JLnkZ zaw+)hAC7Xr-*p=Gqn^??1hk+4Psl+ljf*KK0w{@_YIQnFXEwI9>p%SJ(9C1!3oiHg z8NW}*L?52y;o^u{ejA|;SGdsMGj6oAF=0mkgRgBtTBsYba?BrRuBlK<&Y!!G6`PTn<9=8282o)+SJm02tUPe`i$bG{2ld6-81l1+daV<6c7vo_y`CjI7)>7*8E=+5KQn zZgGD!F|<^`GTF`#a-~EEDcU_(E6&)RU%#bLtRxpZ6&ne=;h?pLQL%xrYRP0E%UK8c zIa5#Nl*kmOvlojl?>Tf~eSUG{;hsOjWiVEYm(AQt$}?ItX3JY^G=dC+=p2$b#w2N4 zlL-E(Z7#jgEZSu>r=N15|NlSN)6@Og58ixX!qk)`np&lR?A&75ceV)9P98PQS-8wN zZsKGH(SM|baR06!AR*WpjiDvx-$vh+tW42K#1OIs)RKPKc|h^zI~(qosZ{%5kZ3Rg zjQ4c3&H7}?B%qtxb^W5+_{ElMpMDQ_v-U()@E8Z~6jpCJ~Tc&qzRn5}KN5GoO26&6Y=2-yK$7!i}eXsoY+ z+NyHMNIL;57fpjz%jQFW!FvV%cRt^NRLi00R05B04$*DhzNhuY^&b%@J_81k1`|NC z)^YX-PZ!8}Z8hXp*F#f%J+O32a6lj>8XZW~I{33fRQf|f^-PWC_8;Z#&g0y@D$wVd zWOwHYZZ=$|8o4gSjt^xrC1&p#z(G-1jk?u`X9Esw76>pPaGz?H5;q zSuYKg{A`)bRWrQYR!{SC3kY+tr2te@IY^9HV}K#j(GE&$>%dmg22K;lz`wTqR{3$O zl31wieRb%L30#p#w0PyR`R_iu`ko+vKWfH6pQ4e}wCK^jAO?y`t%myQQm}OpVjt#m zjdqzF*1x+kd;6{fB$itXLqLOR#1LoiALPRre-B9T6H=qY6>n<0xD7n)xRkEYqtCQC zx8@?=;Pjj9mkf1s=lYuMDnGH%fViG#I@wha51Q3lJsBdTl zdR;9j@=HKAake9Wbh4*HsuD>gE!90cR47O}q%r^MwSw(OPn?s;#tCUssTA6(u0os-6J(+qXprd9{jfvM+m^BE`(MaP#dR=5G?)OAVMhT- zoZEX7y6Yi}YwCcd*HDfy0rl6yrYx1-aN@5zyg%6M{+FXd58mzL@pP~~e*ud|k7`tF zn5gqe^xiTBL*jPjTBGS;rCj;-sm9j*KbF-r?JBQh9jkA#uQeFBjw*$zL~Ee!HL&#z z7x(DcgfS0|o0z)L-9uiBX%z>$ zqR-&&=G@_Sn5g$AboeXs&ccLfAvCwiFrC$GIDh_P)3#qv|F&iOp4HHM6{C53;aUjjEqcWiXofh%8?ij|N5Rfk_BM*p}p!27;7G*%QS zuK1|is^lstlFHNt5_#s)x~3y#awRFtBf6!(`RK&=!^kGY8q7cwQBP7iQ5-JUdqivk zGb%PtfA`}1m|CscSXEwfKKt_dFZTSng;j3Qp(vzPj?VQmHsx zURvy?H|WJ@j{ZhU8rpB>M+k>ufMG&xLgIX1|DbUgJ!$%5-!FH=FsGpakPuN41VB<2 zZ~I`Z4?Gsv{mjH?ju-4ZSKGD_ZjZ379DJC2Tbc8CwMNwZBy1IJ!AEu$=KuZdY zagx%cjylFOaBj`&JvUAqt9;u O0000 & InputProps; +type LabeledInputProps = Pick & + InputProps & { className?: string }; const LabeledInput: React.FC = ({ error, @@ -11,6 +12,7 @@ const LabeledInput: React.FC = ({ message, label, labelAdditionLength, + className, ...inputProps }) => ( = ({ success={success} message={message} label={label} + className={className} labelAdditionLength={labelAdditionLength} > diff --git a/airbyte-webapp/src/hooks/services/Feature/types.tsx b/airbyte-webapp/src/hooks/services/Feature/types.tsx index f847aa37d2e2..2a7c9d8274a6 100644 --- a/airbyte-webapp/src/hooks/services/Feature/types.tsx +++ b/airbyte-webapp/src/hooks/services/Feature/types.tsx @@ -1,6 +1,7 @@ export enum FeatureItem { AllowUploadCustomImage = "ALLOW_UPLOAD_CUSTOM_IMAGE", AllowCustomDBT = "ALLOW_CUSTOM_DBT", + AllowDBTCloudIntegration = "ALLOW_DBT_CLOUD_INTEGRATION", AllowUpdateConnectors = "ALLOW_UPDATE_CONNECTORS", AllowOAuthConnector = "ALLOW_OAUTH_CONNECTOR", AllowCreateConnection = "ALLOW_CREATE_CONNECTION", diff --git a/airbyte-webapp/src/packages/cloud/locales/en.json b/airbyte-webapp/src/packages/cloud/locales/en.json index 2e762fb5820e..a1770af77576 100644 --- a/airbyte-webapp/src/packages/cloud/locales/en.json +++ b/airbyte-webapp/src/packages/cloud/locales/en.json @@ -86,6 +86,13 @@ "settings.accountSettings.updateNameSuccess": "Your name has been updated!", "settings.userSettings": "User settings", "settings.workspaceSettings": "Workspace settings", + "settings.integrationSettings": "Integration settings", + "settings.integrationSettings.dbtCloudSettings": "dbt Cloud Integration", + "settings.integrationSettings.dbtCloudSettings.form.serviceToken": "Service Token", + "settings.integrationSettings.dbtCloudSettings.form.advancedOptions": "Advanced options", + "settings.integrationSettings.dbtCloudSettings.form.singleTenantUrl": "Single-tenant URL", + "settings.integrationSettings.dbtCloudSettings.form.testConnection": "Test connection", + "settings.integrationSettings.dbtCloudSettings.form.submit": "Save changes", "settings.generalSettings": "General Settings", "settings.generalSettings.changeWorkspace": "Change Workspace", "settings.generalSettings.form.name.label": "Workspace name", diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts new file mode 100644 index 000000000000..9220cdb418f5 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -0,0 +1,113 @@ +// This module is for the business logic of working with dbt Cloud webhooks. +// Static config data, urls, functions which wrangle the APIs to manipulate +// records in ways suited to the UI user workflows--all the implementation +// details of working with dbtCloud jobs as webhook operations, all goes here. +// The presentation logic and orchestration in the UI all goes elsewhere. +// +// About that business logic: +// - for now, the code treats "webhook operations" and "dbt Cloud job" as synonymous. +// - custom domains aren't yet supported + +import isEmpty from "lodash/isEmpty"; +import { useMutation } from "react-query"; + +import { OperatorType, WebBackendConnectionRead, OperationRead } from "core/request/AirbyteClient"; +import { useWebConnectionService } from "hooks/services/useConnectionHook"; +import { useCurrentWorkspace } from "hooks/services/useWorkspace"; +import { useUpdateWorkspace } from "services/workspaces/WorkspacesService"; + +export interface DbtCloudJob { + account: string; + job: string; + operationId?: string; +} +const dbtCloudDomain = "https://cloud.getdbt.com"; +const webhookConfigName = "dbt cloud"; +const executionBody = `{"cause": "airbyte"}`; +const jobName = (t: DbtCloudJob) => `${t.account}/${t.job}`; + +const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { + const { operationId } = operation; + const { executionUrl } = operation.operatorConfiguration.webhook || {}; + + const matches = (executionUrl || "").match(/\/accounts\/([^/]+)\/jobs\/([^]+)\//); + + if (!matches) { + throw new Error(`Cannot extract dbt cloud job params from executionUrl ${executionUrl}`); + } else { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_fullUrl, account, job] = matches; + + return { + account, + job, + operationId, + }; + } +}; +const isDbtCloudJob = (operation: OperationRead): boolean => + operation.operatorConfiguration.operatorType === OperatorType.webhook; + +export const useSubmitDbtCloudIntegrationConfig = () => { + const { workspaceId } = useCurrentWorkspace(); + const { mutateAsync: updateWorkspace } = useUpdateWorkspace(); + + return useMutation(async (authToken: string) => { + await updateWorkspace({ + workspaceId, + webhookConfigs: [ + { + name: webhookConfigName, + authToken, + }, + ], + }); + }); +}; + +export const useDbtIntegration = (connection: WebBackendConnectionRead) => { + const workspace = useCurrentWorkspace(); + const { workspaceId } = workspace; + const connectionService = useWebConnectionService(); + + // TODO extract shared isDbtWebhookConfig predicate + const hasDbtIntegration = !isEmpty(workspace.webhookConfigs?.filter((config) => /dbt/.test(config.name || ""))); + const webhookConfigId = workspace.webhookConfigs?.find((config) => /dbt/.test(config.name || ""))?.id; + + const dbtCloudJobs = [...(connection.operations?.filter((operation) => isDbtCloudJob(operation)) || [])].map( + toDbtCloudJob + ); + const otherOperations = [...(connection.operations?.filter((operation) => !isDbtCloudJob(operation)) || [])]; + + const saveJobs = (jobs: DbtCloudJob[]) => { + // TODO dynamically use the workspace's configured dbt cloud domain when it gets returned by backend + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}/run`; + + return connectionService.update({ + connectionId: connection.connectionId, + operations: [ + ...otherOperations, + ...jobs.map((job) => ({ + workspaceId, + ...(job.operationId ? { operationId: job.operationId } : {}), + name: jobName(job), + operatorConfiguration: { + operatorType: OperatorType.webhook, + webhook: { + executionUrl: urlForJob(job), + // if `hasDbtIntegration` is true, webhookConfigId is guaranteed to exist + ...(webhookConfigId ? { webhookConfigId } : {}), + executionBody, + }, + }, + })), + ], + }); + }; + + return { + hasDbtIntegration, + dbtCloudJobs, + saveJobs, + }; +}; diff --git a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx index 86b89add3811..8dd379a615c7 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx @@ -1,7 +1,9 @@ import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; +import { FeatureItem, useFeature } from "hooks/services/Feature"; // import useConnector from "hooks/services/useConnector"; +import { DbtCloudSettingsView } from "packages/cloud/views/settings/integrations/DbtCloudSettingsView"; import { AccountSettingsView } from "packages/cloud/views/users/AccountSettingsView"; import { UsersSettingsView } from "packages/cloud/views/users/UsersSettingsView"; import { WorkspaceSettingsView } from "packages/cloud/views/workspaces/WorkspaceSettingsView"; @@ -20,6 +22,7 @@ import { CloudSettingsRoutes } from "./routePaths"; export const CloudSettingsPage: React.FC = () => { // TODO: uncomment when supported in cloud // const { countNewSourceVersion, countNewDestinationVersion } = useConnector(); + const supportsCloudDbtIntegration = useFeature(FeatureItem.AllowDBTCloudIntegration); const pageConfig = useMemo( () => ({ @@ -82,9 +85,24 @@ export const CloudSettingsPage: React.FC = () => { }, ], }, + ...(supportsCloudDbtIntegration + ? [ + { + category: , + routes: [ + { + path: CloudSettingsRoutes.DbtCloud, + name: , + component: DbtCloudSettingsView, + id: "integrationSettings.dbtCloudSettings", + }, + ], + }, + ] + : []), ], }), - [] + [supportsCloudDbtIntegration] ); return ; diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss new file mode 100644 index 000000000000..fa65ebca2f51 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss @@ -0,0 +1,14 @@ +@use "scss/colors"; +@use "scss/variables" as vars; + +$item-spacing: 25px; + +.controlGroup { + display: flex; + justify-content: flex-end; + margin-top: $item-spacing; + + .button { + margin-left: 1em; + } +} diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx new file mode 100644 index 000000000000..dd2ddd269a35 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx @@ -0,0 +1,44 @@ +import { Field, FieldProps, Form, Formik } from "formik"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { LabeledInput } from "components/LabeledInput"; +import { Button } from "components/ui/Button"; + +import { useSubmitDbtCloudIntegrationConfig } from "packages/cloud/services/dbtCloud"; +import { Content, SettingsCard } from "pages/SettingsPage/pages/SettingsComponents"; + +import styles from "./DbtCloudSettingsView.module.scss"; + +export const DbtCloudSettingsView: React.FC = () => { + const { mutate: submitDbtCloudIntegrationConfig, isLoading } = useSubmitDbtCloudIntegrationConfig(); + return ( + }> + + submitDbtCloudIntegrationConfig(serviceToken)} + > +
    + + {({ field }: FieldProps) => ( + } + type="text" + /> + )} + +
    + +
    + +
    +
    +
    + ); +}; diff --git a/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts b/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts index c82011c13398..5926f6aeaf7e 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts +++ b/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts @@ -9,4 +9,5 @@ export const CloudSettingsRoutes = { Workspace: "workspaces", AccessManagement: "access-management", + DbtCloud: "dbt-cloud", } as const; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx index 41752ba8ffe9..bbb6b9983d81 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx @@ -25,6 +25,7 @@ import { import { FormCard } from "views/Connection/FormCard"; import styles from "./ConnectionTransformationTab.module.scss"; +import { DbtCloudTransformationsCard } from "./ConnectionTransformationTab/DbtCloudTransformationsCard"; const CustomTransformationsCard: React.FC<{ operations?: OperationCreate[]; @@ -100,6 +101,8 @@ export const ConnectionTransformationTab: React.FC = () => { useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_TRANSFORMATION); const { supportsNormalization } = definition; const supportsDbt = useFeature(FeatureItem.AllowCustomDBT) && definition.supportsDbt; + const supportsCloudDbtIntegration = useFeature(FeatureItem.AllowDBTCloudIntegration) && definition.supportsDbt; + const noSupportedTransformations = !supportsNormalization && !supportsDbt && !supportsCloudDbtIntegration; const onSubmit: FormikOnSubmit<{ transformations?: OperationRead[]; normalization?: NormalizationType }> = async ( values, @@ -134,7 +137,8 @@ export const ConnectionTransformationTab: React.FC = () => { > {supportsNormalization && } {supportsDbt && } - {!supportsNormalization && !supportsDbt && ( + {supportsCloudDbtIntegration && } + {noSupportedTransformations && ( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss new file mode 100644 index 000000000000..95de6155ca5a --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -0,0 +1,91 @@ +@use "scss/colors"; + +.jobListContainer { + padding: 25px 25px 22px; + background-color: colors.$grey-50; +} + +.jobListTitle { + display: flex; + justify-content: space-between; +} + +.jobListForm { + width: 100%; +} + +.emptyListContent { + display: flex; + flex-direction: column; + align-items: center; + + > img { + width: 111px; + height: 111px; + margin: 20px 0; + } +} + +.contextExplanation { + color: colors.$grey-300; + width: 100%; + + & a { + color: colors.$grey-300; + } +} + +.jobListButtonGroup { + display: flex; + justify-content: flex-end; + margin-top: 20px; + width: 100%; +} + +.jobListButton { + margin-left: 10px; +} + +.jobListItem { + margin-top: 10px; + padding: 18px; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + + & img { + height: 32px; + width: 32px; + } +} + +.jobListItemIntegrationName { + display: flex; + align-items: center; +} + +.jobListItemInputGroup { + display: flex; + justify-content: space-between; + align-items: center; +} + +.jobListItemInput { + height: fit-content; + margin-left: 1em; +} + +.jobListItemInputLabel { + font-size: 11px; + font-weight: 500; +} + +.jobListItemDelete { + color: colors.$grey-200; + font-size: large; + margin: 0 1em; + cursor: pointer; + border: none; + background-color: inherit; +} diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx new file mode 100644 index 000000000000..18b04c2ba153 --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -0,0 +1,181 @@ +import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classNames from "classnames"; +import { Field, Form, Formik, FieldArray, FieldProps, FormikHelpers } from "formik"; +import { Link } from "react-router-dom"; +import { array, object, number } from "yup"; + +import { FormChangeTracker } from "components/FormChangeTracker"; +import { Button } from "components/ui/Button"; +import { Card } from "components/ui/Card"; +import { Input } from "components/ui/Input"; + +import { WebBackendConnectionRead } from "core/request/AirbyteClient"; +import { useCurrentWorkspace } from "hooks/services/useWorkspace"; +import { DbtCloudJob, useDbtIntegration } from "packages/cloud/services/dbtCloud"; +import { RoutePaths } from "pages/routePaths"; + +import styles from "./DbtCloudTransformationsCard.module.scss"; + +interface DbtJobListValues { + jobs: DbtCloudJob[]; +} + +const dbtCloudJobListSchema = object({ + jobs: array().of( + object({ + account: number().required().positive().integer(), + job: number().required().positive().integer(), + }) + ), +}); + +export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBackendConnectionRead }) => { + // Possible render paths: + // 1) IF the workspace has no dbt cloud account linked + // THEN show "go to your settings to connect your dbt Cloud Account" text + // and the "Don't have a dbt account?" hero/media element + // 2) IF the workspace has a dbt cloud account linked... + // 2.1) AND the connection has no saved dbt jobs (cf: operations) + // THEN show empty jobs list and the "+ Add transformation" button + // 2.2) AND the connection has saved dbt jobs + // THEN show the jobs list and the "+ Add transformation" button + + const { hasDbtIntegration, saveJobs, dbtCloudJobs } = useDbtIntegration(connection); + const onSubmit = (values: DbtJobListValues, { resetForm }: FormikHelpers) => { + saveJobs(values.jobs).then(() => resetForm({ values })); + }; + + return ( + { + return ( +
    + + { + return ( + + Transformations + {hasDbtIntegration && ( + + )} + + } + > + {hasDbtIntegration ? ( + + ) : ( + + )} + + ); + }} + /> + + ); + }} + /> + ); +}; + +const DbtJobsList = ({ + jobs, + remove, + isValid, + dirty, +}: { + jobs: DbtCloudJob[]; + remove: (i: number) => void; + isValid: boolean; + dirty: boolean; +}) => ( +
    +

    After an Airbyte sync job has completed, the following jobs will run

    + {jobs.length ? ( + jobs.map((_j, i) => remove(i)} />) + ) : ( + <> + An octopus wearing a hard hat, tools at the ready + No transformations + + )} +
    + + +
    +
    +); + +// TODO give feedback on validation errors (red outline and validation message) +const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: () => void }) => { + return ( + +
    + dbt logo + dbt Cloud transform +
    +
    +
    + + {({ field }: FieldProps) => ( + <> + + + + )} + +
    +
    + + {({ field }: FieldProps) => ( + <> + + + + )} + +
    + +
    +
    + ); +}; + +const NoDbtIntegration = ({ className }: { className: string }) => { + const { workspaceId } = useCurrentWorkspace(); + const dbtSettingsPath = `/${RoutePaths.Workspaces}/${workspaceId}/${RoutePaths.Settings}/dbt-cloud`; + return ( +
    +

    After an Airbyte sync job has completed, the following jobs will run

    +

    + Go to your settings to connect your dbt Cloud account +

    + +
    + ); +}; + +const DbtCloudSignupBanner = () =>
    ; From 19a296cfc8e72bdd728c3c38e41ced26bb076cce Mon Sep 17 00:00:00 2001 From: Lake Mossman Date: Tue, 18 Oct 2022 15:42:04 -0700 Subject: [PATCH 173/498] Add openAPI spec for Connector Builder Server (#17535) * add openapi spec * add 'a' * rename stream test to stream read and add logs * move logs * group results by slice/page and add more request params * address PR/zoom feedback * move request and response into their own definitions * add sliceDescriptor * fix type of state prop and remove numPages * change order --- .../src/main/openapi/openapi.yaml | 243 ++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 connector-builder-server/src/main/openapi/openapi.yaml diff --git a/connector-builder-server/src/main/openapi/openapi.yaml b/connector-builder-server/src/main/openapi/openapi.yaml new file mode 100644 index 000000000000..5479827c5e15 --- /dev/null +++ b/connector-builder-server/src/main/openapi/openapi.yaml @@ -0,0 +1,243 @@ +openapi: 3.0.0 +info: + description: | + Connector Builder Server API + + version: "1.0.0" + title: Connector Builder Server API + contact: + email: contact@airbyte.io + license: + name: MIT + url: "https://opensource.org/licenses/MIT" +externalDocs: + description: Find out more about Connector Builder + url: "https://docs.airbyte.com/connector-development/config-based/overview/" + +paths: + /v1/stream/read: + post: + summary: Reads a specific stream in the source. TODO in a later phase - only read a single slice of data. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/StreamReadRequestBody" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/StreamRead" + "400": + $ref: "#/components/responses/ExceptionResponse" + "422": + $ref: "#/components/responses/InvalidInputResponse" + /v1/streams/list: + post: + summary: List all streams present in the connector definition, along with their specific request URLs + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/StreamsListRequestBody" + required: true + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/StreamsListRead" + "400": + $ref: "#/components/responses/ExceptionResponse" + "422": + $ref: "#/components/responses/InvalidInputResponse" + +components: + schemas: + StreamRead: + type: object + required: + - slices + properties: + slices: + type: array + description: The stream slices returned from the read command + items: + type: object + required: + - sliceDescriptor + - pages + properties: + sliceDescriptor: + type: object + description: 'An object describing the current slice, e.g. {start_time: "2021-01-01", end_time: "2021-01-31"}' + pages: + type: array + description: The pages returned from the read command + items: + type: object + required: + - airbyteMessages + - request + - response + properties: + airbyteMessages: + type: array + description: The RECORD/STATE/LOG AirbyteMessages coming from the read operation for this page + items: + $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteMessage" + request: + $ref: "#/components/schemas/HttpRequest" + response: + $ref: "#/components/schemas/HttpResponse" + StreamReadRequestBody: + type: object + required: + - definition + - stream + properties: + definition: + $ref: "#/components/schemas/ConnectorDefinitionBody" + description: The config-based connector definition contents + stream: + type: string + description: Name of the stream to read + state: + $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage" + # --- Potential addition for a later phase --- + # numPages: + # type: integer + # description: Number of pages to read from the source for each slice + # default: 1 + HttpRequest: + type: object + required: + - url + properties: + url: + type: string + description: URL that the request was sent to + parameters: + type: object + description: The request parameters that were set on the HTTP request, if any + body: + type: object + description: The body of the HTTP request, if present + headers: + type: object + description: The headers of the HTTP request, if any + HttpResponse: + type: object + required: + - status + properties: + status: + type: integer + description: The status of the response + body: + type: object + description: The body of the HTTP response, if present + headers: + type: object + description: The headers of the HTTP response, if any + ConnectorDefinitionBody: + $ref: ../../../../airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json + AirbyteProtocol: + $ref: ../../../../airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml + StreamsListRequestBody: + type: object + required: + - definition + properties: + definition: + $ref: "#/components/schemas/ConnectorDefinitionBody" + description: The config-based connector definition contents + StreamsListRead: + type: object + required: + - streams + properties: + streams: + type: array + items: + type: object + description: The stream names present in the connector definition + required: + - name + - url + properties: + name: + type: string + description: The name of the stream + url: + type: string + format: uri + description: The URL to which read requests will be made for this stream + # --- Potential addition for a later phase --- + # slices: + # type: array + # description: list of slices that will be retrieved for this stream + # items: + # type: object + + # The following exception structs were copied from airbyte-api/src/main/openapi/config.yaml + InvalidInputProperty: + type: object + required: + - propertyPath + properties: + propertyPath: + type: string + invalidValue: + type: string + message: + type: string + KnownExceptionInfo: + type: object + required: + - message + properties: + message: + type: string + exceptionClassName: + type: string + exceptionStack: + type: array + items: + type: string + InvalidInputExceptionInfo: + type: object + required: + - message + - validationErrors + properties: + message: + type: string + exceptionClassName: + type: string + exceptionStack: + type: array + items: + type: string + validationErrors: + type: array + items: + $ref: "#/components/schemas/InvalidInputProperty" + + responses: + InvalidInputResponse: + description: Input failed validation + content: + application/json: + schema: + $ref: "#/components/schemas/InvalidInputExceptionInfo" + ExceptionResponse: + description: Exception occurred; see message for details. + content: + application/json: + schema: + $ref: "#/components/schemas/KnownExceptionInfo" From d9a48c9449537c636c3393cfeca1ae18ed1add1f Mon Sep 17 00:00:00 2001 From: Zachary Heller Date: Tue, 18 Oct 2022 19:21:37 -0400 Subject: [PATCH 174/498] :bug: Destination GCS: Fix error logs to log 'Gcs' rather than 'AWS' (#17901) * Fix error logs to log 'Gcs' rather than 'AWS' * update connector version * auto-bump connector version Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/destination_definitions.yaml | 2 +- .../init/src/main/resources/seed/destination_specs.yaml | 2 +- airbyte-integrations/connectors/destination-gcs/Dockerfile | 2 +- .../airbyte/integrations/destination/gcs/GcsDestination.java | 4 ++-- docs/integrations/destinations/gcs.md | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index ae33fd671b27..9e0ddb46b9c4 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -113,7 +113,7 @@ - name: Google Cloud Storage (GCS) destinationDefinitionId: ca8f6566-e555-4b40-943a-545bf123117a dockerRepository: airbyte/destination-gcs - dockerImageTag: 0.2.11 + dockerImageTag: 0.2.12 documentationUrl: https://docs.airbyte.com/integrations/destinations/gcs icon: googlecloudstorage.svg resourceRequirements: diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 166b39563111..290f7af1b285 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -1983,7 +1983,7 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-gcs:0.2.11" +- dockerImage: "airbyte/destination-gcs:0.2.12" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/gcs" connectionSpecification: diff --git a/airbyte-integrations/connectors/destination-gcs/Dockerfile b/airbyte-integrations/connectors/destination-gcs/Dockerfile index c624922ba425..c5534e0a9128 100644 --- a/airbyte-integrations/connectors/destination-gcs/Dockerfile +++ b/airbyte-integrations/connectors/destination-gcs/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-gcs COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.2.11 +LABEL io.airbyte.version=0.2.12 LABEL io.airbyte.name=airbyte/destination-gcs diff --git a/airbyte-integrations/connectors/destination-gcs/src/main/java/io/airbyte/integrations/destination/gcs/GcsDestination.java b/airbyte-integrations/connectors/destination-gcs/src/main/java/io/airbyte/integrations/destination/gcs/GcsDestination.java index 5a7624501d8b..8de907332849 100644 --- a/airbyte-integrations/connectors/destination-gcs/src/main/java/io/airbyte/integrations/destination/gcs/GcsDestination.java +++ b/airbyte-integrations/connectors/destination-gcs/src/main/java/io/airbyte/integrations/destination/gcs/GcsDestination.java @@ -57,14 +57,14 @@ public AirbyteConnectionStatus check(final JsonNode config) { return new AirbyteConnectionStatus().withStatus(Status.SUCCEEDED); } catch (final AmazonS3Exception e) { - LOGGER.error("Exception attempting to access the AWS bucket: {}", e.getMessage()); + LOGGER.error("Exception attempting to access the Gcs bucket: {}", e.getMessage()); final String message = getErrorMessage(e.getErrorCode(), 0, e.getMessage(), e); AirbyteTraceMessageUtility.emitConfigErrorTrace(e, message); return new AirbyteConnectionStatus() .withStatus(Status.FAILED) .withMessage(message); } catch (final Exception e) { - LOGGER.error("Exception attempting to access the AWS bucket: {}. Please make sure you account has all of these roles: {}", e.getMessage(), + LOGGER.error("Exception attempting to access the Gcs bucket: {}. Please make sure you account has all of these roles: {}", e.getMessage(), EXPECTED_ROLES); AirbyteTraceMessageUtility.emitConfigErrorTrace(e, e.getMessage()); return new AirbyteConnectionStatus() diff --git a/docs/integrations/destinations/gcs.md b/docs/integrations/destinations/gcs.md index d01a771aec52..2e1661fa0e5e 100644 --- a/docs/integrations/destinations/gcs.md +++ b/docs/integrations/destinations/gcs.md @@ -235,6 +235,7 @@ Under the hood, an Airbyte data stream in Json schema is first converted to an A | Version | Date | Pull Request | Subject | |:--------| :--- | :--- | :--- | +| 0.2.12 | 2022-10-18 | [\#17901](https://github.com/airbytehq/airbyte/pull/17901) | Fix logging to GCS | | 0.2.11 | 2022-09-01 | [\#16243](https://github.com/airbytehq/airbyte/pull/16243) | Fix Json to Avro conversion when there is field name clash from combined restrictions (`anyOf`, `oneOf`, `allOf` fields) | | 0.2.10 | 2022-08-05 | [\#14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | | 0.2.9 | 2022-06-24 | [\#14114](https://github.com/airbytehq/airbyte/pull/14114) | Remove "additionalProperties": false from specs for connectors with staging | From 3b12a585ec8b202ccf9c1f54db8016b13b78f24d Mon Sep 17 00:00:00 2001 From: Delena Malan Date: Wed, 19 Oct 2022 01:28:38 +0200 Subject: [PATCH 175/498] Docs: Fix backoff stategy docs (#18143) * Fix backoff strategy docs * Update config_component_schema.json --- .../declarative/config_component_schema.json | 20 +++++++++---------- .../error_handlers/composite_error_handler.py | 4 ++-- .../error_handlers/default_error_handler.py | 2 +- .../error-handling.md | 20 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json b/airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json index f8ff83b6e52d..5ae05dbf5fcf 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json @@ -648,7 +648,7 @@ } } ], - "description": "\n Error handler that sequentially iterates over a list of `ErrorHandler`s\n\n Sample config chaining 2 different retriers:\n error_handler:\n type: \"CompositeErrorHandler\"\n error_handlers:\n - response_filters:\n - predicate: \"{{ 'codase' in response }}\"\n action: RETRY\n backoff_strategies:\n - type: \"ConstantBackoffStrategy\"\n backoff_time_in_seconds: 5\n - response_filters:\n - http_codes: [ 403 ]\n action: RETRY\n backoff_strategies:\n - type: \"ConstantBackoffStrategy\"\n backoff_time_in_seconds: 10\n Attributes:\n error_handlers (List[ErrorHandler]): list of error handlers\n " + "description": "\n Error handler that sequentially iterates over a list of `ErrorHandler`s\n\n Sample config chaining 2 different retriers:\n error_handler:\n type: \"CompositeErrorHandler\"\n error_handlers:\n - response_filters:\n - predicate: \"{{ 'codase' in response }}\"\n action: RETRY\n backoff_strategies:\n - type: \"ConstantBackoff\"\n backoff_time_in_seconds: 5\n - response_filters:\n - http_codes: [ 403 ]\n action: RETRY\n backoff_strategies:\n - type: \"ConstantBackoff\"\n backoff_time_in_seconds: 10\n Attributes:\n error_handlers (List[ErrorHandler]): list of error handlers\n " }, "DefaultErrorHandler": { "allOf": [ @@ -677,16 +677,16 @@ "items": { "anyOf": [ { - "$ref": "#/definitions/ConstantBackoffStrategy" + "$ref": "#/definitions/ConstantBackoff" }, { - "$ref": "#/definitions/ExponentialBackoffStrategy" + "$ref": "#/definitions/ExponentialBackoff" }, { - "$ref": "#/definitions/WaitTimeFromHeaderBackoffStrategy" + "$ref": "#/definitions/WaitTimeFromHeader" }, { - "$ref": "#/definitions/WaitUntilTimeFromHeaderBackoffStrategy" + "$ref": "#/definitions/WaitUntilTimeFromHeader" } ] } @@ -694,7 +694,7 @@ } } ], - "description": "\n Default error handler.\n\n By default, the handler will only retry server errors (HTTP 5XX) and too many requests (HTTP 429) with exponential backoff.\n\n If the response is successful, then return SUCCESS\n Otherwise, iterate over the response_filters.\n If any of the filter match the response, then return the appropriate status.\n If the match is RETRY, then iterate sequentially over the backoff_strategies and return the first non-None backoff time.\n\n Sample configs:\n\n 1. retry 10 times\n `\n error_handler:\n max_retries: 10\n `\n 2. backoff for 5 seconds\n `\n error_handler:\n backoff_strategies:\n - type: \"ConstantBackoffStrategy\"\n backoff_time_in_seconds: 5\n `\n 3. retry on HTTP 404\n `\n error_handler:\n response_filters:\n - http_codes: [ 404 ]\n action: RETRY\n `\n 4. ignore HTTP 404\n `\n error_handler:\n - http_codes: [ 404 ]\n action: IGNORE\n `\n 5. retry if error message contains `retrythisrequest!` substring\n `\n error_handler:\n response_filters:\n - error_message_contain: \"retrythisrequest!\"\n action: IGNORE\n `\n 6. retry if 'code' is a field present in the response body\n `\n error_handler:\n response_filters:\n - predicate: \"{{ 'code' in response }}\"\n action: IGNORE\n `\n\n 7. ignore 429 and retry on 404\n `\n error_handler:\n - http_codes: [ 429 ]\n action: IGNORE\n - http_codes: [ 404 ]\n action: RETRY\n `\n\n Attributes:\n response_filters (Optional[List[HttpResponseFilter]]): response filters to iterate on\n max_retries (Optional[int]): maximum retry attempts\n backoff_strategies (Optional[List[BackoffStrategy]]): list of backoff strategies to use to determine how long\n to wait before retrying\n " + "description": "\n Default error handler.\n\n By default, the handler will only retry server errors (HTTP 5XX) and too many requests (HTTP 429) with exponential backoff.\n\n If the response is successful, then return SUCCESS\n Otherwise, iterate over the response_filters.\n If any of the filter match the response, then return the appropriate status.\n If the match is RETRY, then iterate sequentially over the backoff_strategies and return the first non-None backoff time.\n\n Sample configs:\n\n 1. retry 10 times\n `\n error_handler:\n max_retries: 10\n `\n 2. backoff for 5 seconds\n `\n error_handler:\n backoff_strategies:\n - type: \"ConstantBackoff\"\n backoff_time_in_seconds: 5\n `\n 3. retry on HTTP 404\n `\n error_handler:\n response_filters:\n - http_codes: [ 404 ]\n action: RETRY\n `\n 4. ignore HTTP 404\n `\n error_handler:\n - http_codes: [ 404 ]\n action: IGNORE\n `\n 5. retry if error message contains `retrythisrequest!` substring\n `\n error_handler:\n response_filters:\n - error_message_contain: \"retrythisrequest!\"\n action: IGNORE\n `\n 6. retry if 'code' is a field present in the response body\n `\n error_handler:\n response_filters:\n - predicate: \"{{ 'code' in response }}\"\n action: IGNORE\n `\n\n 7. ignore 429 and retry on 404\n `\n error_handler:\n - http_codes: [ 429 ]\n action: IGNORE\n - http_codes: [ 404 ]\n action: RETRY\n `\n\n Attributes:\n response_filters (Optional[List[HttpResponseFilter]]): response filters to iterate on\n max_retries (Optional[int]): maximum retry attempts\n backoff_strategies (Optional[List[BackoffStrategy]]): list of backoff strategies to use to determine how long\n to wait before retrying\n " }, "HttpResponseFilter": { "type": "object", @@ -745,7 +745,7 @@ }, "description": "InterpolatedBoolean(condition: str, options: dataclasses.InitVar[typing.Mapping[str, typing.Any]])" }, - "ConstantBackoffStrategy": { + "ConstantBackoff": { "allOf": [ { "$ref": "#/definitions/BackoffStrategy" @@ -767,7 +767,7 @@ "properties": {}, "description": "\n Backoff strategy defining how long to wait before retrying a request that resulted in an error.\n " }, - "ExponentialBackoffStrategy": { + "ExponentialBackoff": { "allOf": [ { "$ref": "#/definitions/BackoffStrategy" @@ -784,7 +784,7 @@ ], "description": "\n Backoff strategy with an exponential backoff interval\n\n Attributes:\n factor (float): multiplicative factor\n " }, - "WaitTimeFromHeaderBackoffStrategy": { + "WaitTimeFromHeader": { "allOf": [ { "$ref": "#/definitions/BackoffStrategy" @@ -804,7 +804,7 @@ ], "description": "\n Extract wait time from http header\n\n Attributes:\n header (str): header to read wait time from\n regex (Optional[str]): optional regex to apply on the header to extract its value\n " }, - "WaitUntilTimeFromHeaderBackoffStrategy": { + "WaitUntilTimeFromHeader": { "allOf": [ { "$ref": "#/definitions/BackoffStrategy" diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py index 0c2cfe5da878..ffaed5bcb606 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/composite_error_handler.py @@ -26,13 +26,13 @@ class CompositeErrorHandler(ErrorHandler, JsonSchemaMixin): - predicate: "{{ 'codase' in response }}" action: RETRY backoff_strategies: - - type: "ConstantBackoffStrategy" + - type: "ConstantBackoff" backoff_time_in_seconds: 5 - response_filters: - http_codes: [ 403 ] action: RETRY backoff_strategies: - - type: "ConstantBackoffStrategy" + - type: "ConstantBackoff" backoff_time_in_seconds: 10 Attributes: error_handlers (List[ErrorHandler]): list of error handlers diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py index 4275d0ce6ba6..70168560cac2 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/error_handlers/default_error_handler.py @@ -41,7 +41,7 @@ class DefaultErrorHandler(ErrorHandler, JsonSchemaMixin): ` error_handler: backoff_strategies: - - type: "ConstantBackoffStrategy" + - type: "ConstantBackoff" backoff_time_in_seconds: 5 ` 3. retry on HTTP 404 diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md b/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md index 2346cbd76db9..fdb3a6f07d61 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/error-handling.md @@ -183,7 +183,7 @@ Schema: ### Constant Backoff -When using the `ConstantBackoffStrategy`, the requester will backoff with a constant interval. +When using the `ConstantBackoff` strategy, the requester will backoff with a constant interval. Schema: @@ -202,7 +202,7 @@ Schema: ### Wait time defined in header -When using the `WaitTimeFromHeaderBackoffStrategy`, the requester will backoff by an interval specified in the response header. +When using the `WaitTimeFromHeader`, the requester will backoff by an interval specified in the response header. In this example, the requester will backoff by the response's "wait_time" header value: Schema: @@ -230,7 +230,7 @@ requester: error_handler: <...> backoff_strategies: - - type: "WaitTimeFromHeaderBackoffStrategy" + - type: "WaitTimeFromHeader" header: "wait_time" ``` @@ -244,14 +244,14 @@ requester: error_handler: <...> backoff_strategies: - - type: "WaitTimeFromHeaderBackoffStrategy" + - type: "WaitTimeFromHeader" header: "wait_time" regex: "[-+]?\d+" ``` ### Wait until time defined in header -When using the `WaitUntilTimeFromHeaderBackoffStrategy`, the requester will backoff until the time specified in the response header. +When using the `WaitUntilTimeFromHeader` backoff strategy, the requester will backoff until the time specified in the response header. In this example, the requester will wait until the time specified in the "wait_until" header value: Schema: @@ -281,7 +281,7 @@ requester: error_handler: <...> backoff_strategies: - - type: "WaitUntilTimeFromHeaderBackoffStrategy" + - type: "WaitUntilTimeFromHeader" header: "wait_until" regex: "[-+]?\d+" min_wait: 5 @@ -302,9 +302,9 @@ requester: error_handler: <...> backoff_strategies: - - type: "WaitTimeFromHeaderBackoffStrategy" + - type: "WaitTimeFromHeader" header: "wait_time" - - type: "ConstantBackoffStrategy" + - type: "ConstantBackoff" backoff_time_in_seconds: 5 ``` @@ -340,13 +340,13 @@ requester: - predicate: "{{ 'code' in response }}" action: RETRY backoff_strategies: - - type: "ConstantBackoffStrategy" + - type: "ConstantBackoff" backoff_time_in_seconds: 5 - response_filters: - http_codes: [ 403 ] action: RETRY backoff_strategies: - - type: "ExponentialBackoffStrategy" + - type: "ExponentialBackoff" ``` ## More readings From 66e0055822eb7fef811cd8a222e9d8adb4c67b29 Mon Sep 17 00:00:00 2001 From: Akash Kulkarni <113392464+akashkulk@users.noreply.github.com> Date: Tue, 18 Oct 2022 16:54:40 -0700 Subject: [PATCH 176/498] Sentry Integration : Stop reporting all non system-error error types. (#18133) * Sentry Integration : Stop reporting all non system-error error types. --- .../job/errorreporter/JobErrorReporter.java | 10 ++++++ .../errorreporter/JobErrorReporterTest.java | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java index e5e551e1fdbe..64d9996bf3d3 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/errorreporter/JobErrorReporter.java @@ -4,6 +4,7 @@ package io.airbyte.persistence.job.errorreporter; +import com.google.common.collect.ImmutableSet; import edu.umd.cs.findbugs.annotations.Nullable; import io.airbyte.commons.lang.Exceptions; import io.airbyte.commons.map.MoreMaps; @@ -11,6 +12,7 @@ import io.airbyte.config.Configs.DeploymentMode; import io.airbyte.config.FailureReason; import io.airbyte.config.FailureReason.FailureOrigin; +import io.airbyte.config.FailureReason.FailureType; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardWorkspace; @@ -47,6 +49,9 @@ public class JobErrorReporter { private static final String NORMALIZATION_REPOSITORY_META_KEY = "normalization_repository"; private static final String JOB_ID_KEY = "job_id"; + private static final ImmutableSet UNSUPPORTED_FAILURETYPES = + ImmutableSet.of(FailureType.CONFIG_ERROR, FailureType.MANUAL_CANCELLATION); + private final ConfigRepository configRepository; private final DeploymentMode deploymentMode; private final String airbyteVersion; @@ -268,6 +273,11 @@ private void reportJobFailureReason(@Nullable final StandardWorkspace workspace, final FailureReason failureReason, final String dockerImage, final Map metadata) { + // Failure types associated with a config-error or a manual-cancellation should NOT be reported. + if (UNSUPPORTED_FAILURETYPES.contains(failureReason.getFailureType())) { + return; + } + final Map commonMetadata = new HashMap<>(Map.ofEntries( Map.entry(AIRBYTE_VERSION_META_KEY, airbyteVersion), Map.entry(DEPLOYMENT_MODE_META_KEY, deploymentMode.name()))); diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java index 332c2b4bde75..ae97e8fbeedb 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/errorreporter/JobErrorReporterTest.java @@ -499,4 +499,36 @@ void testReportSpecJobFailure() { Mockito.verifyNoMoreInteractions(jobErrorReportingClient); } + @Test + void testReportUnsupportedFailureType() { + final FailureReason readFailureReason = new FailureReason() + .withMetadata(new Metadata() + .withAdditionalProperty(FROM_TRACE_MESSAGE, true) + .withAdditionalProperty(CONNECTOR_COMMAND_KEY, READ_COMMAND)) + .withFailureOrigin(FailureOrigin.SOURCE) + .withFailureType(FailureType.CONFIG_ERROR); + + final FailureReason discoverFailureReason = new FailureReason() + .withMetadata(new Metadata() + .withAdditionalProperty(FROM_TRACE_MESSAGE, true) + .withAdditionalProperty(CONNECTOR_COMMAND_KEY, DISCOVER_COMMAND)) + .withFailureOrigin(FailureOrigin.SOURCE) + .withFailureType(FailureType.MANUAL_CANCELLATION); + + final FailureReason checkFailureReason = new FailureReason() + .withMetadata(new Metadata() + .withAdditionalProperty(FROM_TRACE_MESSAGE, true) + .withAdditionalProperty(CONNECTOR_COMMAND_KEY, CHECK_COMMAND)) + .withFailureOrigin(FailureOrigin.SOURCE) + .withFailureType(FailureType.CONFIG_ERROR); + + final ConnectorJobReportingContext jobContext = new ConnectorJobReportingContext(JOB_ID, SOURCE_DOCKER_IMAGE); + + jobErrorReporter.reportSpecJobFailure(readFailureReason, jobContext); + jobErrorReporter.reportSpecJobFailure(discoverFailureReason, jobContext); + jobErrorReporter.reportSpecJobFailure(checkFailureReason, jobContext); + + Mockito.verifyNoInteractions(jobErrorReportingClient); + } + } From b18f95021e509dca7faf17131433280319beae3d Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Wed, 19 Oct 2022 04:52:47 +0200 Subject: [PATCH 177/498] Fix minor DBT Cloud Errors. (#18147) Fixes URL formatting for dbt cloud integration. Note, without a trailing / the dbt Cloud API returns a 308. Some other small fixes: - Retries around the dbt Cloud invocation - Updates the regex to handle the new URL format. --- .../src/packages/cloud/services/dbtCloud.ts | 4 +-- .../sync/WebhookOperationActivityImpl.java | 33 +++++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 9220cdb418f5..e49429328343 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -30,7 +30,7 @@ const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { const { operationId } = operation; const { executionUrl } = operation.operatorConfiguration.webhook || {}; - const matches = (executionUrl || "").match(/\/accounts\/([^/]+)\/jobs\/([^]+)\//); + const matches = (executionUrl || "").match(/\/accounts\/([^/]+)\/jobs\/([^]+)\/run\//); if (!matches) { throw new Error(`Cannot extract dbt cloud job params from executionUrl ${executionUrl}`); @@ -81,7 +81,7 @@ export const useDbtIntegration = (connection: WebBackendConnectionRead) => { const saveJobs = (jobs: DbtCloudJob[]) => { // TODO dynamically use the workspace's configured dbt cloud domain when it gets returned by backend - const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}/run`; + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}/run/`; return connectionService.update({ connectionId: connection.connectionId, diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java index e5edb1d8bce1..8e55be47e09c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java @@ -11,7 +11,6 @@ import io.airbyte.config.WebhookOperationConfigs; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import jakarta.inject.Singleton; -import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -24,6 +23,7 @@ public class WebhookOperationActivityImpl implements WebhookOperationActivity { private static final Logger LOGGER = LoggerFactory.getLogger(WebhookOperationActivityImpl.class); + private static final int MAX_RETRIES = 3; private final HttpClient httpClient; @@ -38,6 +38,7 @@ public WebhookOperationActivityImpl(final HttpClient httpClient, final SecretsHy @Override public boolean invokeWebhook(OperatorWebhookInput input) { LOGGER.debug("Webhook operation input: {}", input); + LOGGER.debug("Found webhook config: {}", input.getWorkspaceWebhookConfigs()); final JsonNode fullWebhookConfigJson = secretsHydrator.hydrate(input.getWorkspaceWebhookConfigs()); final WebhookOperationConfigs webhookConfigs = Jsons.object(fullWebhookConfigJson, WebhookOperationConfigs.class); final Optional webhookConfig = @@ -48,7 +49,6 @@ public boolean invokeWebhook(OperatorWebhookInput input) { LOGGER.info("Invoking webhook operation {}", webhookConfig.get().getName()); - LOGGER.debug("Found webhook config: {}", webhookConfig.get()); final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() .uri(URI.create(input.getExecutionUrl())); if (input.getExecutionBody() != null) { @@ -59,18 +59,25 @@ public boolean invokeWebhook(OperatorWebhookInput input) { .header("Content-Type", "application/json") .header("Authorization", "Bearer " + webhookConfig.get().getAuthToken()).build(); } - try { - HttpResponse response = this.httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); - LOGGER.debug("Webhook response: {}", response == null ? null : response.body()); - LOGGER.info("Webhook response status: {}", response == null ? "empty response" : response.statusCode()); - // Return true if the request was successful. - boolean isSuccessful = response != null && response.statusCode() >= 200 && response.statusCode() <= 300; - LOGGER.info("Webhook {} execution status {}", webhookConfig.get().getName(), isSuccessful ? "successful" : "failed"); - return isSuccessful; - } catch (IOException | InterruptedException e) { - LOGGER.error(e.getMessage()); - throw new RuntimeException(e); + + Exception finalException = null; + // TODO(mfsiega-airbyte): replace this loop with retries configured on the HttpClient impl. + for (int i = 0; i < MAX_RETRIES; i++) { + try { + HttpResponse response = this.httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + LOGGER.debug("Webhook response: {}", response == null ? null : response.body()); + LOGGER.info("Webhook response status: {}", response == null ? "empty response" : response.statusCode()); + // Return true if the request was successful. + boolean isSuccessful = response != null && response.statusCode() >= 200 && response.statusCode() <= 300; + LOGGER.info("Webhook {} execution status {}", webhookConfig.get().getName(), isSuccessful ? "successful" : "failed"); + return isSuccessful; + } catch (Exception e) { + LOGGER.warn(e.getMessage()); + finalException = e; + } } + // If we ever get here, it means we exceeded MAX_RETRIES without returning in the happy path. + throw new RuntimeException(finalException); } } From 8c780bd3aaf905fa0fe2fa7cdac2299b17bac7c9 Mon Sep 17 00:00:00 2001 From: Ryan Fu Date: Tue, 18 Oct 2022 21:53:30 -0700 Subject: [PATCH 178/498] Publishes Postgres, MySQL, MSSQL source with changes from #18041 (#18086) * Publishes Postgres, MySQL, MSSQL source with changes from #18041 * auto-bump connector version * auto-bump connector version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 6 +- .../src/main/resources/seed/source_specs.yaml | 6 +- .../source-mssql-strict-encrypt/Dockerfile | 2 +- .../connectors/source-mssql/Dockerfile | 2 +- .../source-mysql-strict-encrypt/Dockerfile | 3 +- .../connectors/source-mysql/Dockerfile | 2 +- .../source-postgres-strict-encrypt/Dockerfile | 2 +- .../connectors/source-postgres/Dockerfile | 2 +- docs/integrations/sources/mssql.md | 109 +++++++------- docs/integrations/sources/mysql.md | 137 +++++++++--------- docs/integrations/sources/postgres.md | 5 +- 11 files changed, 139 insertions(+), 137 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index a03309614d8b..1d61824fdfd2 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -630,7 +630,7 @@ - name: Microsoft SQL Server (MSSQL) sourceDefinitionId: b5ea17b1-f170-46dc-bc31-cc744ca984c1 dockerRepository: airbyte/source-mssql - dockerImageTag: 0.4.20 + dockerImageTag: 0.4.21 documentationUrl: https://docs.airbyte.com/integrations/sources/mssql icon: mssql.svg sourceType: database @@ -678,7 +678,7 @@ - name: MySQL sourceDefinitionId: 435bb9a5-7887-4809-aa58-28c27df0d7ad dockerRepository: airbyte/source-mysql - dockerImageTag: 1.0.4 + dockerImageTag: 1.0.5 documentationUrl: https://docs.airbyte.com/integrations/sources/mysql icon: mysql.svg sourceType: database @@ -836,7 +836,7 @@ - name: Postgres sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres - dockerImageTag: 1.0.16 + dockerImageTag: 1.0.17 documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index d9afc664060d..5650e0811966 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -6134,7 +6134,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mssql:0.4.20" +- dockerImage: "airbyte/source-mssql:0.4.21" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/mssql" connectionSpecification: @@ -6964,7 +6964,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mysql:1.0.4" +- dockerImage: "airbyte/source-mysql:1.0.5" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/mysql" connectionSpecification: @@ -8596,7 +8596,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-postgres:1.0.16" +- dockerImage: "airbyte/source-postgres:1.0.17" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile index b225619fc539..c8f5d91abaa6 100644 --- a/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-mssql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.4.20 +LABEL io.airbyte.version=0.4.21 LABEL io.airbyte.name=airbyte/source-mssql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mssql/Dockerfile b/airbyte-integrations/connectors/source-mssql/Dockerfile index 286f221a5d76..30292799c5e3 100644 --- a/airbyte-integrations/connectors/source-mssql/Dockerfile +++ b/airbyte-integrations/connectors/source-mssql/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-mssql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.4.20 +LABEL io.airbyte.version=0.4.21 LABEL io.airbyte.name=airbyte/source-mssql diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile index 70596dc34839..f53b417be56f 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile @@ -16,7 +16,6 @@ ENV APPLICATION source-mysql-strict-encrypt COPY --from=build /airbyte /airbyte - -LABEL io.airbyte.version=1.0.4 +LABEL io.airbyte.version=1.0.5 LABEL io.airbyte.name=airbyte/source-mysql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mysql/Dockerfile b/airbyte-integrations/connectors/source-mysql/Dockerfile index 0d6a6b405691..27c9dfb99687 100644 --- a/airbyte-integrations/connectors/source-mysql/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.4 +LABEL io.airbyte.version=1.0.5 LABEL io.airbyte.name=airbyte/source-mysql diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile index ff44c65d602e..5d01692517ec 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.16 +LABEL io.airbyte.version=1.0.17 LABEL io.airbyte.name=airbyte/source-postgres-strict-encrypt diff --git a/airbyte-integrations/connectors/source-postgres/Dockerfile b/airbyte-integrations/connectors/source-postgres/Dockerfile index 7984704fde70..419eda26aa18 100644 --- a/airbyte-integrations/connectors/source-postgres/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.16 +LABEL io.airbyte.version=1.0.17 LABEL io.airbyte.name=airbyte/source-postgres diff --git a/docs/integrations/sources/mssql.md b/docs/integrations/sources/mssql.md index 2d24d7e9c952..cf56318246f0 100644 --- a/docs/integrations/sources/mssql.md +++ b/docs/integrations/sources/mssql.md @@ -339,58 +339,59 @@ WHERE actor_definition_id ='b5ea17b1-f170-46dc-bc31-cc744ca984c1' AND (configura ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------| :----------------------------------------------------- |:-------------------------------------------------------------------------------------------------------| -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | -| 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | -| 0.4.17 | 2022-09-01 | [16261](https://github.com/airbytehq/airbyte/pull/16261) | Emit state messages more frequently | -| 0.4.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.4.15 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.4.14 | 2022-08-10 | [15430](https://github.com/airbytehq/airbyte/pull/15430) | fixed a bug on handling special character on database name -| 0.4.13 | 2022-08-04 | [15268](https://github.com/airbytehq/airbyte/pull/15268) | Added [] enclosing to escape special character in the database name | -| 0.4.12 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.4.11 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.4.10 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.4.9 | 2022-07-05 | [14379](https://github.com/airbytehq/airbyte/pull/14379) | Aligned Normal and CDC migration + added some fixes for datatypes processing | -| 0.4.8 | 2022-06-24 | [14121](https://github.com/airbytehq/airbyte/pull/14121) | Omit using 'USE' keyword on Azure SQL with CDC | -| 0.4.5 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.4.3 | 2022-06-17 | [13887](https://github.com/airbytehq/airbyte/pull/13887) | Increase version to include changes from [13854](https://github.com/airbytehq/airbyte/pull/13854) | -| 0.4.2 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | -| 0.4.1 | 2022-05-25 | [13419](https://github.com/airbytehq/airbyte/pull/13419) | Correct enum for Standard method. | -| 0.4.0 | 2022-05-25 | [12759](https://github.com/airbytehq/airbyte/pull/12759) [13168](https://github.com/airbytehq/airbyte/pull/13168) | For CDC, Add option to ignore existing data and only sync new changes from the database. | -| 0.3.22 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.3.21 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.3.19 | 2022-03-31 | [11495](https://github.com/airbytehq/airbyte/pull/11495) | Adds Support to Chinese MSSQL Server Agent | -| 0.3.18 | 2022-03-29 | [11010](https://github.com/airbytehq/airbyte/pull/11010) | Adds JDBC Params | -| 0.3.17 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.3.16 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.3.15 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | -| 0.3.14 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | -| 0.3.13 | 2022-01-07 | [9094](https://github.com/airbytehq/airbyte/pull/9094) | Added support for missed data types | -| 0.3.12 | 2021-12-30 | [9206](https://github.com/airbytehq/airbyte/pull/9206) | Update connector fields title/description | -| 0.3.11 | 2021-12-24 | [8958](https://github.com/airbytehq/airbyte/pull/8958) | Add support for JdbcType.ARRAY | -| 0.3.10 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | | -| 0.3.9 | 2021-11-09 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Improve support for binary and varbinary data types | | -| 0.3.8 | 2021-10-26 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Fixed data type (smalldatetime, smallmoney) conversion from mssql source | | -| 0.3.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | | -| 0.3.6 | 2021-09-17 | [6318](https://github.com/airbytehq/airbyte/pull/6318) | Added option to connect to DB via SSH | | -| 0.3.4 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | | -| 0.3.3 | 2021-07-05 | [4689](https://github.com/airbytehq/airbyte/pull/4689) | Add CDC support | | -| 0.3.2 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | | -| 0.3.1 | 2021-06-08 | [3893](https://github.com/airbytehq/airbyte/pull/3893) | Enable SSL connection | | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | | -| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | | -| 0.1.11 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | \] | -| 0.1.10 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | | -| 0.1.9 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | | -| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | | -| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | | -| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | | -| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------| +| 0.4.21 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | +| 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | +| 0.4.17 | 2022-09-01 | [16261](https://github.com/airbytehq/airbyte/pull/16261) | Emit state messages more frequently | +| 0.4.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.4.15 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.4.14 | 2022-08-10 | [15430](https://github.com/airbytehq/airbyte/pull/15430) | fixed a bug on handling special character on database name | +| 0.4.13 | 2022-08-04 | [15268](https://github.com/airbytehq/airbyte/pull/15268) | Added [] enclosing to escape special character in the database name | +| 0.4.12 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.4.11 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.4.10 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.4.9 | 2022-07-05 | [14379](https://github.com/airbytehq/airbyte/pull/14379) | Aligned Normal and CDC migration + added some fixes for datatypes processing | +| 0.4.8 | 2022-06-24 | [14121](https://github.com/airbytehq/airbyte/pull/14121) | Omit using 'USE' keyword on Azure SQL with CDC | +| 0.4.5 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.4.3 | 2022-06-17 | [13887](https://github.com/airbytehq/airbyte/pull/13887) | Increase version to include changes from [13854](https://github.com/airbytehq/airbyte/pull/13854) | +| 0.4.2 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | +| 0.4.1 | 2022-05-25 | [13419](https://github.com/airbytehq/airbyte/pull/13419) | Correct enum for Standard method. | +| 0.4.0 | 2022-05-25 | [12759](https://github.com/airbytehq/airbyte/pull/12759) [13168](https://github.com/airbytehq/airbyte/pull/13168) | For CDC, Add option to ignore existing data and only sync new changes from the database. | +| 0.3.22 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.3.21 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.3.19 | 2022-03-31 | [11495](https://github.com/airbytehq/airbyte/pull/11495) | Adds Support to Chinese MSSQL Server Agent | +| 0.3.18 | 2022-03-29 | [11010](https://github.com/airbytehq/airbyte/pull/11010) | Adds JDBC Params | +| 0.3.17 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.3.16 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.3.15 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | +| 0.3.14 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | +| 0.3.13 | 2022-01-07 | [9094](https://github.com/airbytehq/airbyte/pull/9094) | Added support for missed data types | +| 0.3.12 | 2021-12-30 | [9206](https://github.com/airbytehq/airbyte/pull/9206) | Update connector fields title/description | +| 0.3.11 | 2021-12-24 | [8958](https://github.com/airbytehq/airbyte/pull/8958) | Add support for JdbcType.ARRAY | +| 0.3.10 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | | +| 0.3.9 | 2021-11-09 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Improve support for binary and varbinary data types | | +| 0.3.8 | 2021-10-26 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Fixed data type (smalldatetime, smallmoney) conversion from mssql source | | +| 0.3.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | | +| 0.3.6 | 2021-09-17 | [6318](https://github.com/airbytehq/airbyte/pull/6318) | Added option to connect to DB via SSH | | +| 0.3.4 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | | +| 0.3.3 | 2021-07-05 | [4689](https://github.com/airbytehq/airbyte/pull/4689) | Add CDC support | | +| 0.3.2 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | | +| 0.3.1 | 2021-06-08 | [3893](https://github.com/airbytehq/airbyte/pull/3893) | Enable SSL connection | | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | | +| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | | +| 0.1.11 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | \] | +| 0.1.10 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | | +| 0.1.9 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | | +| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | | +| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | | +| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | | +| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | | diff --git a/docs/integrations/sources/mysql.md b/docs/integrations/sources/mysql.md index ee84f6747f26..25e321436161 100644 --- a/docs/integrations/sources/mysql.md +++ b/docs/integrations/sources/mysql.md @@ -249,73 +249,74 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura ``` ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:-----------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:-----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | -| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | -| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | -| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | -| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | -| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | -| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | -| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | -| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | -| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | -| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | -| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | -| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | -| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | -| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | -| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | -| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | -| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | -| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | -| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | -| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | -| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | -| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | -| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | -| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | -| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | -| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | -| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | -| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | -| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | -| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | -| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | -| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | -| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | -| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | -| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | -| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | -| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | -| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | -| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | -| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | -| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | -| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | -| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | -| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | -| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | -| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | -| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | +| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | +| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | +| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | +| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | +| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | +| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | +| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | +| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | +| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | +| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | +| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | +| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | +| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | +| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | +| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | +| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | +| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | +| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | +| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | +| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | +| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | +| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | +| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | +| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | +| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | +| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | +| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | +| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | +| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | +| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | +| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | +| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | +| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | +| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | +| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | +| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | +| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | +| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | +| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | +| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | +| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | +| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | +| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | +| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | +| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | +| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | +| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index 4c0669ced2ce..b7b5f05858e1 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -398,8 +398,9 @@ The root causes is that the WALs needed for the incremental sync has been remove | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.16 | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | +| 1.0.17 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| 1.0.16 | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | | 1.0.14 | 2022-10-03 | [17515](https://github.com/airbytehq/airbyte/pull/17515) | Fix an issue preventing connection using client certificate | | 1.0.13 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | | 1.0.12 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt postgres source | From 75c367b38f047db64c8ea105d30e4e6eb4a479c5 Mon Sep 17 00:00:00 2001 From: Emilija <105725935+emilija-omnisend@users.noreply.github.com> Date: Wed, 19 Oct 2022 09:54:57 +0300 Subject: [PATCH 179/498] Update deployment.yaml (#18151) Do not ignore service account is webpp --- charts/airbyte-webapp/templates/deployment.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/charts/airbyte-webapp/templates/deployment.yaml b/charts/airbyte-webapp/templates/deployment.yaml index f1ad5c65900f..52532747602e 100644 --- a/charts/airbyte-webapp/templates/deployment.yaml +++ b/charts/airbyte-webapp/templates/deployment.yaml @@ -19,6 +19,7 @@ spec: {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} {{- end }} spec: + serviceAccountName: {{ .Values.global.serviceAccountName }} {{- if .Values.nodeSelector }} nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} {{- end }} From ac026ac7526822bb88de071ce9b5206ca4cb202d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 09:55:48 +0300 Subject: [PATCH 180/498] Bump helm chart version reference to 0.40.26 (#18094) Co-authored-by: xpuska513 Co-authored-by: Kyryl Skobylko --- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-metrics/Chart.yaml | 2 +- charts/airbyte-pod-sweeper/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 18 +++++++++--------- charts/airbyte/Chart.yaml | 16 ++++++++-------- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 48e97024970f..2d21fccf4ed7 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.24" +version: "0.40.26" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index 72aa0ca25ecd..767144e27cdb 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.24" +version: "0.40.26" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index 157c98c8c4bf..7d277013113a 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.24" +version: "0.40.26" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index abdc73e6e148..f281c06d6f28 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.24" +version: "0.40.26" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index 58734d675826..1328c5ca376b 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.24" +version: "0.40.26" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 9d4bc827a803..1253d9cb2ae9 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.24" +version: "0.40.26" # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index 443c3a07825b..900c5f4b9503 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -4,24 +4,24 @@ dependencies: version: 1.17.1 - name: airbyte-bootloader repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 + version: 0.40.26 - name: temporal repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 + version: 0.40.26 - name: webapp repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 + version: 0.40.26 - name: server repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 + version: 0.40.26 - name: worker repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 + version: 0.40.26 - name: pod-sweeper repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 + version: 0.40.26 - name: metrics repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.24 -digest: sha256:34f736f80f353eac15537f4b2a619773a1f768b575c4c29adfac9de2e3db2e9b -generated: "2022-10-17T20:40:52.086435729Z" + version: 0.40.26 +digest: sha256:cdcab12a15e123b7f42ca0d9ec06efc0438f70ba46197ad2e6a4e072dfe13b6b +generated: "2022-10-18T07:42:19.513120098Z" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index e652f82b2d94..388a78db8502 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.40.24 +version: 0.40.26 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -32,28 +32,28 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.24 + version: 0.40.26 From 8b683088dfdb5ae5cd197b0e00a84ca47d8b2973 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:05:37 +0300 Subject: [PATCH 181/498] Bump helm chart version reference to 0.40.27 (#18152) Co-authored-by: xpuska513 Co-authored-by: Kyryl Skobylko --- charts/airbyte-bootloader/Chart.yaml | 3 ++- charts/airbyte-metrics/Chart.yaml | 3 ++- charts/airbyte-pod-sweeper/Chart.yaml | 3 ++- charts/airbyte-temporal/Chart.yaml | 3 ++- charts/airbyte-webapp/Chart.yaml | 3 ++- charts/airbyte-worker/Chart.yaml | 3 ++- charts/airbyte/Chart.lock | 19 ++++++++++--------- charts/airbyte/Chart.yaml | 18 ++++++++++-------- 8 files changed, 32 insertions(+), 23 deletions(-) diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 2d21fccf4ed7..097ac5c7b8f8 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.26" +version: "0.40.27" + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index 767144e27cdb..20d195f11521 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.26" +version: "0.40.27" + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index 7d277013113a..1a944c4034c8 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.26" +version: "0.40.27" + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index f281c06d6f28..487a391ccfb6 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.26" +version: "0.40.27" + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index 1328c5ca376b..abe1441b92a9 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.26" +version: "0.40.27" + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 1253d9cb2ae9..683e7eea40b5 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.26" +version: "0.40.27" + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index 900c5f4b9503..bf4e6856b98d 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -4,24 +4,25 @@ dependencies: version: 1.17.1 - name: airbyte-bootloader repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 + version: 0.40.27 - name: temporal repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 + version: 0.40.27 - name: webapp repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 + version: 0.40.27 - name: server repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 + version: 0.40.27 - name: worker repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 + version: 0.40.27 - name: pod-sweeper repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 + version: 0.40.27 - name: metrics repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.26 -digest: sha256:cdcab12a15e123b7f42ca0d9ec06efc0438f70ba46197ad2e6a4e072dfe13b6b -generated: "2022-10-18T07:42:19.513120098Z" + version: 0.40.27 +digest: sha256:23e474cb8e2a7ff58cc911ef61eec81f7e2068c9a3b7ca6bfb22a45018a0fbed +generated: "2022-10-19T07:02:23.354665679Z" + diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 388a78db8502..47f4e6e59d0e 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,8 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.40.26 +version: 0.40.27 + # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -32,28 +33,29 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.26 + version: 0.40.27 + From 7682c99de243730be799d5b0a720f4655e022b61 Mon Sep 17 00:00:00 2001 From: Joey Marshment-Howell Date: Wed, 19 Oct 2022 12:47:44 +0200 Subject: [PATCH 182/498] =?UTF-8?q?=F0=9F=AA=9F=F0=9F=90=9B=20client=20sid?= =?UTF-8?q?e=20validation=20of=20cron=20expressions=20(#17887)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airbyte-webapp/src/locales/en.json | 1 + .../ConnectionReplicationTab.tsx | 1 + airbyte-webapp/src/utils/cron/index.ts | 1 + .../utils/cron/validateCronExpression.test.ts | 69 +++++++++++++++++++ .../src/utils/cron/validateCronExpression.ts | 42 +++++++++++ airbyte-webapp/src/utils/links.ts | 1 + .../ConnectionForm/ConnectionFormFields.tsx | 2 +- .../ScheduleField.module.scss | 0 .../ScheduleField.tsx | 24 ++++--- .../ConnectionForm/ScheduleField/index.ts | 1 + .../Connection/ConnectionForm/formConfig.tsx | 6 +- 11 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 airbyte-webapp/src/utils/cron/index.ts create mode 100644 airbyte-webapp/src/utils/cron/validateCronExpression.test.ts create mode 100644 airbyte-webapp/src/utils/cron/validateCronExpression.ts rename airbyte-webapp/src/views/Connection/ConnectionForm/{components => ScheduleField}/ScheduleField.module.scss (100%) rename airbyte-webapp/src/views/Connection/ConnectionForm/{components => ScheduleField}/ScheduleField.tsx (90%) create mode 100644 airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/index.ts diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 63560c4c801f..0a98546cda9f 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -384,6 +384,7 @@ "form.frequency": "Replication frequency*", "form.cronExpression": "Cron expression*", "form.cronExpression.placeholder": "Cron expression", + "form.cronExpression.error": "Invalid cron expression", "form.cronExpression.message": "Enter a cron expression for when syncs should run (ex. \"0 0 12 * * ?\" => Will sync at 12:00 PM every day)", "form.frequency.placeholder": "Select a frequency", "form.frequency.message": "Set how often data should sync to the destination", diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx index d5544c366499..51774de24265 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx @@ -157,6 +157,7 @@ export const ConnectionReplicationTab: React.FC = () => { { resetForm(); diff --git a/airbyte-webapp/src/utils/cron/index.ts b/airbyte-webapp/src/utils/cron/index.ts new file mode 100644 index 000000000000..16cc8122a85a --- /dev/null +++ b/airbyte-webapp/src/utils/cron/index.ts @@ -0,0 +1 @@ +export { validateCronExpression } from "./validateCronExpression"; diff --git a/airbyte-webapp/src/utils/cron/validateCronExpression.test.ts b/airbyte-webapp/src/utils/cron/validateCronExpression.test.ts new file mode 100644 index 000000000000..87354e96e704 --- /dev/null +++ b/airbyte-webapp/src/utils/cron/validateCronExpression.test.ts @@ -0,0 +1,69 @@ +import { validateCronExpression } from "./validateCronExpression"; + +// Test cases are taken from http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html +describe("validateCronExpression", () => { + it.each` + expression | isValid + ${"0 0 12 * * ?"} | ${true} + ${"0 0 12 * * ? "} | ${true} + ${" 0 0 12 * * ?"} | ${true} + ${"0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010"} | ${true} + ${"0 15 10 ? * *"} | ${true} + ${"0 15 10 * * ?"} | ${true} + ${"0 15 10 * * ? *"} | ${true} + ${"0 15 10 * * ? 2005"} | ${true} + ${"0 * 14 * * ?"} | ${true} + ${"0 0/5 14 * * ?"} | ${true} + ${"0 0/5 14,18 * * ?"} | ${true} + ${"0 0-5 14 * * ?"} | ${true} + ${"0 10,44 14 ? 3 WED"} | ${true} + ${"0 15 10 ? * MON-FRI"} | ${true} + ${"0 15 10 15 * ?"} | ${true} + ${"0 15 10 L * ?"} | ${true} + ${"0 15 10 L-2 * ?"} | ${true} + ${"0 15 10 ? * 6L"} | ${true} + ${"0 15 10 ? * 6L"} | ${true} + ${"0 15 10 ? * 6L 2002-2005"} | ${true} + ${"0 15 10 ? * 6#3"} | ${true} + ${"0 0 12 1/5 * ?"} | ${true} + ${"0 11 11 11 11 ?"} | ${true} + ${"* * * * * ?"} | ${true} + ${"0 0 0 * * ?"} | ${true} + ${"0 0 1 * * ?"} | ${true} + ${"0 0 10-19/10 ? * MON-FRI *"} | ${true} + ${"0 0 1 1/1 * ? *"} | ${true} + ${"0 0 12 * * ?"} | ${true} + ${"0 0 15 * * ?"} | ${true} + ${"0 0 17 * * ?"} | ${true} + ${"0 0 18 * * ?"} | ${true} + ${"0 0 18 1 * ?"} | ${true} + ${"0 0 18 2 * ?"} | ${true} + ${"0 0 2 * * ?"} | ${true} + ${"0 0 21 * * ?"} | ${true} + ${"0 0 2 L * ?"} | ${true} + ${"0 0 3 * * ?"} | ${true} + ${"0 0 4 * * ?"} | ${true} + ${"0 0 5 * * ?"} | ${true} + ${"0 0 6 * * ?"} | ${true} + ${"0 0 7 * * ?"} | ${true} + ${"0 0 9 * * ?"} | ${true} + ${"0 0 9 ? * 5"} | ${true} + ${"0 1 0 * * ?"} | ${true} + ${"0 15,45 7-17 ? * MON-FRI"} | ${true} + ${"0 15 6 * * ?"} | ${true} + ${"0 30 1 * * ?"} | ${true} + ${"0 30 2 * * ?"} | ${true} + ${"0 30 6 * * ?"} | ${true} + ${"0 30 8 ? * MON-FRI *"} | ${true} + ${"0 35 12 ? * 7 "} | ${true} + ${"0 40 4,16 * * ? *"} | ${true} + ${"0 45 6 * * ?"} | ${true} + ${"0 5 0 ? * 7"} | ${true} + ${"40 4,16 * * * ?"} | ${true} + ${"wildly invalid"} | ${false} + ${"* * * * *"} | ${false} + ${"0 0 0 0 0 0"} | ${false} + `("'$expression' is valid: $isValid", async ({ expression, isValid }) => { + expect(validateCronExpression(expression)).toEqual(isValid); + }); +}); diff --git a/airbyte-webapp/src/utils/cron/validateCronExpression.ts b/airbyte-webapp/src/utils/cron/validateCronExpression.ts new file mode 100644 index 000000000000..9020ab1bff98 --- /dev/null +++ b/airbyte-webapp/src/utils/cron/validateCronExpression.ts @@ -0,0 +1,42 @@ +const CRON_REGEX_MAP = [ + /^(([0-9]|,|-|\*|\/)+)$/, // seconds + /^(([0-9]|,|-|\*|\/)+)$/, // minutes + /^(([0-9]|,|-|\*|\/)+)$/, // hours + /^(([1-9]|,|-|\*|\/|\?|L|W)+)$/, // day of month + /^(([1-9]|,|-|\*|\/|JAN|FEB|MAR|APR|JUN|JUL|AUG|SEP|OCT|NOV|DEC)+)$/, // month + /^(([1-7]|,|-|\*|\/|\?|L|#|SUN|MON|TUE|WED|THU|FRI|SAT|SUN)+)$/, // day of week + /^(([0-9]|,|-|\*|\/)+)?/, // year +]; + +export function validateCronExpression(expression: string | undefined): boolean { + // yup passes string | undefined, this may be fixed in the future: https://github.com/jquense/yup/issues/1367 + if (expression === undefined) { + return false; + } + + try { + const cronFields = expression.trim().split(" "); + + if (cronFields.length < 6) { + throw new Error( + `Cron expression "${expression}" must contain at least 6 fields (${cronFields.length} fields found)` + ); + } + + if (cronFields.length > 7) { + throw new Error( + `Cron expression "${expression}" cannot be longer than 7 fields (${cronFields.length} fields found})` + ); + } + + cronFields.forEach((field, index) => { + if (!CRON_REGEX_MAP[index].test(field)) { + throw new Error(`"${field}" did not match regex at index ${index}`); + } + }); + } catch (e) { + return false; + } + + return true; +} diff --git a/airbyte-webapp/src/utils/links.ts b/airbyte-webapp/src/utils/links.ts index 33e6bdd2928a..ad99b8cd2f8b 100644 --- a/airbyte-webapp/src/utils/links.ts +++ b/airbyte-webapp/src/utils/links.ts @@ -26,6 +26,7 @@ export const links = { demoLink: "https://demo.airbyte.io", contactSales: "https://airbyte.com/talk-to-sales", webpageLink: "https://airbyte.com", + cronReferenceLink: "http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html", } as const; export type OutboundLinks = typeof links; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx index 67f194b4f9f7..74e7de6589bb 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx @@ -19,11 +19,11 @@ import { ValuesProps } from "hooks/services/useConnectionHook"; import { NamespaceDefinitionField } from "./components/NamespaceDefinitionField"; import { useRefreshSourceSchemaWithConfirmationOnDirty } from "./components/refreshSourceSchemaWithConfirmationOnDirty"; -import ScheduleField from "./components/ScheduleField"; import { Section } from "./components/Section"; import SchemaField from "./components/SyncCatalogField"; import styles from "./ConnectionFormFields.module.scss"; import { FormikConnectionFormValues } from "./formConfig"; +import { ScheduleField } from "./ScheduleField"; interface ConnectionFormFieldsProps { values: ValuesProps | FormikConnectionFormValues; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/ScheduleField.module.scss b/airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/ScheduleField.module.scss similarity index 100% rename from airbyte-webapp/src/views/Connection/ConnectionForm/components/ScheduleField.module.scss rename to airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/ScheduleField.module.scss diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/ScheduleField.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/ScheduleField.tsx similarity index 90% rename from airbyte-webapp/src/views/Connection/ConnectionForm/components/ScheduleField.tsx rename to airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/ScheduleField.tsx index 3bdf5c87b153..e6a25b2b777f 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/ScheduleField.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/ScheduleField.tsx @@ -1,6 +1,6 @@ -import { Field, FieldInputProps, FieldProps, FormikProps } from "formik"; +import { Field, FieldInputProps, FieldProps, FormikProps, useField } from "formik"; import { ChangeEvent, useCallback, useMemo } from "react"; -import { useIntl } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { ControlLabels, Link } from "components"; import { DropDown, DropDownOptionDataItem } from "components/ui/DropDown"; @@ -10,6 +10,8 @@ import { Action, Namespace } from "core/analytics"; import { ConnectionScheduleData, ConnectionScheduleType } from "core/request/AirbyteClient"; import { useAnalyticsService } from "hooks/services/Analytics"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { links } from "utils/links"; +import { PropertyError } from "views/Connector/ServiceForm/components/Property/PropertyError"; import availableCronTimeZones from "../../../../config/availableCronTimeZones.json"; import { FormikConnectionFormValues, useFrequencyDropdownData } from "../formConfig"; @@ -21,9 +23,7 @@ const CRON_DEFAULT_VALUE = { cronExpression: "0 0 12 * * ?", }; -const CRON_REFERENCE_LINK = "http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html"; - -const ScheduleField: React.FC = () => { +export const ScheduleField: React.FC = () => { const { formatMessage } = useIntl(); const { connection, mode } = useConnectionFormService(); const frequencies = useFrequencyDropdownData(connection.scheduleData); @@ -128,6 +128,8 @@ const ScheduleField: React.FC = () => { return form.values.scheduleType === ConnectionScheduleType.cron; }; + const [, { error: cronValidationError }] = useField("scheduleData.cron.cronExpression"); + return ( {({ field, meta, form }: FieldProps) => ( @@ -137,7 +139,6 @@ const ScheduleField: React.FC = () => { {
    { onScheduleChange(item, form); }} @@ -174,7 +175,7 @@ const ScheduleField: React.FC = () => { }, { lnk: (lnk: React.ReactNode) => ( - + {lnk} ), @@ -204,6 +205,11 @@ const ScheduleField: React.FC = () => { onChange={(item: DropDownOptionDataItem) => onCronChange(item, field, form, "cronTimeZone")} />
    + {cronValidationError && ( + + + + )}
    )} @@ -212,5 +218,3 @@ const ScheduleField: React.FC = () => { ); }; - -export default ScheduleField; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/index.ts b/airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/index.ts new file mode 100644 index 000000000000..ffc2c4463776 --- /dev/null +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ScheduleField/index.ts @@ -0,0 +1 @@ +export { ScheduleField } from "./ScheduleField"; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx index d0b3ba445089..996988201b41 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx @@ -27,6 +27,7 @@ import { import { ConnectionFormMode, ConnectionOrPartialConnection } from "hooks/services/ConnectionForm/ConnectionFormService"; import { ValuesProps } from "hooks/services/useConnectionHook"; import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; +import { validateCronExpression } from "utils/cron"; import calculateInitialCatalog from "./calculateInitialCatalog"; @@ -99,7 +100,10 @@ export const connectionValidationSchema = (mode: ConnectionFormMode) => return yup.object({ cron: yup .object({ - cronExpression: yup.string().required("form.empty.error"), + cronExpression: yup + .string() + .required("form.empty.error") + .test("validCron", "form.cronExpression.error", validateCronExpression), cronTimeZone: yup.string().required("form.empty.error"), }) .defined("form.empty.error"), From df858a51180325c1cd8becf909a68f580fb64f5c Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 19 Oct 2022 14:52:57 +0200 Subject: [PATCH 183/498] :window: :art: Improve empty state of resource pages (#18066) * Transfer WIP to other machine * Move bowtie image into component folder --- .../EmptyResourceListView.tsx | 23 +++++++++++-------- .../EmptyResourceListView}/bowtie-half.svg | 0 airbyte-webapp/src/locales/en.json | 3 +++ .../AllConnectionsPage/AllConnectionsPage.tsx | 9 ++++++-- .../AllDestinationsPage.tsx | 9 ++++++-- .../pages/AllSourcesPage/AllSourcesPage.tsx | 9 ++++++-- 6 files changed, 37 insertions(+), 16 deletions(-) rename airbyte-webapp/{public/images => src/components/EmptyResourceListView}/bowtie-half.svg (100%) diff --git a/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.tsx b/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.tsx index b6ba7f5d8ef3..f87234468aa3 100644 --- a/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.tsx +++ b/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.tsx @@ -5,24 +5,27 @@ import { FormattedMessage } from "react-intl"; import { Button } from "components/ui/Button"; import { Text } from "components/ui/Text"; +import { ReactComponent as BowtieHalf } from "./bowtie-half.svg"; import styles from "./EmptyResourceListView.module.scss"; interface EmptyResourceListViewProps { + buttonLabel: string; resourceType: "connections" | "destinations" | "sources"; onCreateClick: () => void; } -export const EmptyResourceListView: React.FC = ({ resourceType, onCreateClick }) => { - const { headingMessageId, buttonMessageId, singularResourceType } = useMemo(() => { +export const EmptyResourceListView: React.FC = ({ + resourceType, + onCreateClick, + buttonLabel, +}) => { + const { headingMessageId, singularResourceType } = useMemo(() => { const singularResourceType = resourceType.substring(0, resourceType.length - 1); const baseMessageId = resourceType === "connections" ? singularResourceType : resourceType; const headingMessageId = `${baseMessageId}.description`; - const buttonMessageId = `${baseMessageId}.new${ - singularResourceType.substring(0, 1).toUpperCase() + singularResourceType.substring(1) - }`; - return { headingMessageId, buttonMessageId, singularResourceType }; + return { headingMessageId, singularResourceType }; }, [resourceType]); return ( @@ -32,20 +35,20 @@ export const EmptyResourceListView: React.FC = ({ re
    {resourceType !== "destinations" && ( - Left Bowtie +
    ); diff --git a/airbyte-webapp/public/images/bowtie-half.svg b/airbyte-webapp/src/components/EmptyResourceListView/bowtie-half.svg similarity index 100% rename from airbyte-webapp/public/images/bowtie-half.svg rename to airbyte-webapp/src/components/EmptyResourceListView/bowtie-half.svg diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 0a98546cda9f..d59745ebb4b2 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -247,6 +247,7 @@ "sources.full_refresh": "Full refresh", "sources.incremental": "Incremental - based on...", "sources.newSource": "New source", + "sources.createFirst": "Connect your first source", "sources.newSourceTitle": "New Source", "sources.selectSource": "Select a source", "sources.status": "Status", @@ -310,6 +311,7 @@ "destination.destinationSettings": "Destination Settings", "destinations.newDestination": "New destination", + "destinations.createFirst": "Connect your first destination", "destinations.description": "Destinations are where you send or push your data to.", "destinations.noDestinations": "Destination list is empty", "destinations.noSources": "No sources yet", @@ -361,6 +363,7 @@ "connection.catalogTree.destinationSchema": "'", "connection.newConnection": "New connection", + "connection.createFirst": "Create your first connection", "connection.newConnectionTitle": "New connection", "connection.noConnections": "Connection list is empty", "connection.disabledConnection": "Disabled connection", diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx index 0fd292e47735..5dc45c5c2ee5 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx @@ -1,7 +1,7 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { Suspense } from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; import { LoadingPage, MainPageWithScroll } from "components"; @@ -18,6 +18,7 @@ import ConnectionsTable from "./components/ConnectionsTable"; const AllConnectionsPage: React.FC = () => { const navigate = useNavigate(); + const { formatMessage } = useIntl(); useTrackPage(PageTrackingCodes.CONNECTIONS_LIST); const { connections } = useConnectionList(); @@ -43,7 +44,11 @@ const AllConnectionsPage: React.FC = () => { ) : ( - + )} ); diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx index f6029b47780c..83bfd27f0883 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx @@ -1,7 +1,7 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; import { EmptyResourceListView } from "components/EmptyResourceListView"; @@ -18,6 +18,7 @@ import DestinationsTable from "./components/DestinationsTable"; const AllDestinationsPage: React.FC = () => { const navigate = useNavigate(); + const { formatMessage } = useIntl(); const { destinations } = useDestinationList(); useTrackPage(PageTrackingCodes.DESTINATION_LIST); @@ -45,7 +46,11 @@ const AllDestinationsPage: React.FC = () => { ) : ( - + ); }; diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx index 5adacbac7bb8..199dbec04fdb 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx @@ -1,7 +1,7 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React from "react"; -import { FormattedMessage } from "react-intl"; +import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; import { EmptyResourceListView } from "components/EmptyResourceListView"; @@ -18,6 +18,7 @@ import SourcesTable from "./components/SourcesTable"; const AllSourcesPage: React.FC = () => { const navigate = useNavigate(); + const { formatMessage } = useIntl(); const { sources } = useSourceList(); useTrackPage(PageTrackingCodes.SOURCE_LIST); const onCreateSource = () => navigate(`${RoutePaths.SourceNew}`); @@ -38,7 +39,11 @@ const AllSourcesPage: React.FC = () => { ) : ( - + ); }; From 4b0d1d4ac6bfd1c8d09311b7cd88f463ff856208 Mon Sep 17 00:00:00 2001 From: Delena Malan Date: Wed, 19 Oct 2022 15:20:40 +0200 Subject: [PATCH 184/498] Replace NoPaginator in the docs with NoPagination (#18142) --- docs/connector-development/config-based/source_schema.yaml | 4 ++-- .../config-based/understanding-the-yaml-file/pagination.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/connector-development/config-based/source_schema.yaml b/docs/connector-development/config-based/source_schema.yaml index 68d37b78e243..a9c8d46ff6cf 100644 --- a/docs/connector-development/config-based/source_schema.yaml +++ b/docs/connector-development/config-based/source_schema.yaml @@ -226,7 +226,7 @@ definitions: type: object anyOf: - "$ref": "#/definitions/DefaultPaginator" - - "$ref": "#/definitions/NoPaginator" + - "$ref": "#/definitions/NoPagination" DefaultPaginator: type: object additionalProperties: true @@ -247,7 +247,7 @@ definitions: "$ref": "#/definitions/PaginationStrategy" url_base: type: string - NoPaginator: + NoPagination: type: object additionalProperties: true RequestOption: diff --git a/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md b/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md index 71aaafbc92eb..afd6b8745981 100644 --- a/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md +++ b/docs/connector-development/config-based/understanding-the-yaml-file/pagination.md @@ -13,8 +13,8 @@ Schema: type: object anyOf: - "$ref": "#/definitions/DefaultPaginator" - - "$ref": "#/definitions/NoPaginator" - NoPaginator: + - "$ref": "#/definitions/NoPagination" + NoPagination: type: object additionalProperties: true ``` From b564f3eb78f80c1bbca11e27d0de56738dc81c57 Mon Sep 17 00:00:00 2001 From: Augustin Date: Wed, 19 Oct 2022 15:22:25 +0200 Subject: [PATCH 185/498] Protocol: make `supported_sync_modes` a required not empty list on `AirbyteStream` (#15591) --- airbyte-cdk/python/CHANGELOG.md | 4 + .../airbyte_cdk/models/airbyte_protocol.py | 2 +- .../sources/singer/singer_helpers.py | 2 +- airbyte-cdk/python/setup.py | 2 +- .../destinations/test_destination.py | 2 +- .../unit_tests/singer/test_singer_source.py | 2 +- .../python/unit_tests/sources/test_source.py | 10 ++- .../python/unit_tests/test_entrypoint.py | 3 +- .../resources/seed/source_definitions.yaml | 6 +- .../src/main/resources/seed/source_specs.yaml | 8 +- .../s3/util/S3OutputPathHelperTest.java | 19 ++-- .../bases/source-acceptance-test/CHANGELOG.md | 3 + .../bases/source-acceptance-test/Dockerfile | 2 +- .../bases/source-acceptance-test/setup.py | 2 +- .../unit_tests/test_asserts.py | 2 +- .../unit_tests/test_backward_compatibility.py | 29 ++++++ .../unit_tests/test_core.py | 88 +++++++++++++++---- .../unit_tests/test_json_schema_helper.py | 2 +- .../unit_tests/test_test_full_refresh.py | 12 ++- .../integration_tests/integration_test.py | 4 +- .../BigQueryDenormalizedTestDataUtils.java | 3 +- .../cassandra/TestDataFactory.java | 5 +- .../cassandra/TestDataFactory.java | 5 +- .../dynamodb/DynamodbDestinationTest.java | 6 +- .../integration_tests/integration_test.py | 2 +- .../unit_tests/test_firebolt_destination.py | 2 + .../integration_tests/integration_test.py | 4 +- .../gcs/avro/GcsAvroWriterTest.java | 4 +- .../jdbc/copy/s3/S3StreamCopierTest.java | 5 +- .../integration_tests/integration_test.py | 4 +- .../integration_tests/integration_test.py | 2 +- .../unit_tests/unit_test.py | 2 +- .../copiers/RedshiftStreamCopierTest.java | 2 + .../integration_tests/integration_test.py | 4 +- .../SnowflakeS3StreamCopierTest.java | 2 + .../integration_tests/integration_test.py | 2 +- .../connectors/source-azure-table/Dockerfile | 2 +- .../source_azure_table/source.py | 30 +++---- .../source_azure_table/spec.json | 1 - .../unit_tests/test_source.py | 4 +- .../connectors/source-e2e-test/Dockerfile | 2 +- .../source/e2e_test/ContinuousFeedConfig.java | 9 +- .../parse_mock_catalog_test_cases.json | 15 ++-- .../source-google-sheets/Dockerfile | 2 +- .../google_sheets_source/helpers.py | 4 +- .../unit_tests/test_helpers.py | 6 +- .../unit_tests/test_source.py | 2 +- .../state/StreamStateManagerTest.java | 41 ++++++--- .../protocol/models/CatalogHelpers.java | 6 +- .../airbyte_protocol/airbyte_protocol.yaml | 5 +- .../utils/AirbyteAcceptanceTestHarness.java | 2 +- docs/integrations/sources/azure-table.md | 23 ++--- docs/integrations/sources/e2e-test.md | 47 +++++----- docs/integrations/sources/google-sheets.md | 1 + 54 files changed, 307 insertions(+), 153 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index 6b4f48c6c082..ce3c484e39f5 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.1.104 + +- Protocol change: `supported_sync_modes` is now a required properties on AirbyteStream. [#15591](https://github.com/airbytehq/airbyte/pull/15591) + ## 0.1.103 - Low-code: added hash filter to jinja template diff --git a/airbyte-cdk/python/airbyte_cdk/models/airbyte_protocol.py b/airbyte-cdk/python/airbyte_cdk/models/airbyte_protocol.py index 8ccf522e6cdd..44c9c552c4a1 100644 --- a/airbyte-cdk/python/airbyte_cdk/models/airbyte_protocol.py +++ b/airbyte-cdk/python/airbyte_cdk/models/airbyte_protocol.py @@ -209,7 +209,7 @@ class Config: name: str = Field(..., description="Stream's name.") json_schema: Dict[str, Any] = Field(..., description="Stream schema using Json Schema specs.") - supported_sync_modes: Optional[List[SyncMode]] = None + supported_sync_modes: List[SyncMode] = Field(..., description="List of sync modes supported by this stream.", min_items=1) source_defined_cursor: Optional[bool] = Field( None, description="If the source defines the cursor field, then any other cursor field inputs will be ignored. If it does not, either the user_provided one is used, or the default one is used as a backup.", diff --git a/airbyte-cdk/python/airbyte_cdk/sources/singer/singer_helpers.py b/airbyte-cdk/python/airbyte_cdk/sources/singer/singer_helpers.py index fdfe9deb5d54..d5c6be46964a 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/singer/singer_helpers.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/singer/singer_helpers.py @@ -126,7 +126,7 @@ def singer_catalog_to_airbyte_catalog( for stream in singer_catalog.get("streams"): # type: ignore name = stream.get("stream") schema = stream.get("schema") - airbyte_stream = AirbyteStream(name=name, json_schema=schema) + airbyte_stream = AirbyteStream(name=name, json_schema=schema, supported_sync_modes=[SyncMode.full_refresh]) if name in sync_mode_overrides: override_sync_modes(airbyte_stream, sync_mode_overrides[name]) else: diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index 9363d3ae5598..b509896a01f5 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.1.103", + version="0.1.104", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/destinations/test_destination.py b/airbyte-cdk/python/unit_tests/destinations/test_destination.py index 66043b0365bb..a0cd9b35f6ec 100644 --- a/airbyte-cdk/python/unit_tests/destinations/test_destination.py +++ b/airbyte-cdk/python/unit_tests/destinations/test_destination.py @@ -192,7 +192,7 @@ def test_run_write(self, mocker, destination: Destination, tmp_path, monkeypatch dummy_catalog = ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( - stream=AirbyteStream(name="mystream", json_schema={"type": "object"}), + stream=AirbyteStream(name="mystream", json_schema={"type": "object"}, supported_sync_modes=[SyncMode.full_refresh]), sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-cdk/python/unit_tests/singer/test_singer_source.py b/airbyte-cdk/python/unit_tests/singer/test_singer_source.py index 58e53500595f..71156aad88ab 100644 --- a/airbyte-cdk/python/unit_tests/singer/test_singer_source.py +++ b/airbyte-cdk/python/unit_tests/singer/test_singer_source.py @@ -95,7 +95,7 @@ def test_singer_discover_metadata(mock_read_catalog): _user_stream = airbyte_catalog.streams[0] _roles_stream = airbyte_catalog.streams[1] - assert _user_stream.supported_sync_modes is None + # assert _user_stream.supported_sync_modes is None assert _user_stream.default_cursor_field is None assert _roles_stream.supported_sync_modes == [SyncMode.incremental] assert _roles_stream.default_cursor_field == ["name"] diff --git a/airbyte-cdk/python/unit_tests/sources/test_source.py b/airbyte-cdk/python/unit_tests/sources/test_source.py index a266b2756b8a..c81b794e5af6 100644 --- a/airbyte-cdk/python/unit_tests/sources/test_source.py +++ b/airbyte-cdk/python/unit_tests/sources/test_source.py @@ -60,12 +60,12 @@ def catalog(): configured_catalog = { "streams": [ { - "stream": {"name": "mock_http_stream", "json_schema": {}}, + "stream": {"name": "mock_http_stream", "json_schema": {}, "supported_sync_modes": ["full_refresh"]}, "destination_sync_mode": "overwrite", "sync_mode": "full_refresh", }, { - "stream": {"name": "mock_stream", "json_schema": {}}, + "stream": {"name": "mock_stream", "json_schema": {}, "supported_sync_modes": ["full_refresh"]}, "destination_sync_mode": "overwrite", "sync_mode": "full_refresh", }, @@ -317,7 +317,11 @@ def test_read_catalog(source): configured_catalog = { "streams": [ { - "stream": {"name": "mystream", "json_schema": {"type": "object", "properties": {"k": "v"}}}, + "stream": { + "name": "mystream", + "json_schema": {"type": "object", "properties": {"k": "v"}}, + "supported_sync_modes": ["full_refresh"], + }, "destination_sync_mode": "overwrite", "sync_mode": "full_refresh", } diff --git a/airbyte-cdk/python/unit_tests/test_entrypoint.py b/airbyte-cdk/python/unit_tests/test_entrypoint.py index 26bc5b855410..a71cd40eadd4 100644 --- a/airbyte-cdk/python/unit_tests/test_entrypoint.py +++ b/airbyte-cdk/python/unit_tests/test_entrypoint.py @@ -19,6 +19,7 @@ AirbyteStream, ConnectorSpecification, Status, + SyncMode, Type, ) from airbyte_cdk.sources import Source @@ -172,7 +173,7 @@ def test_run_check(entrypoint: AirbyteEntrypoint, mocker, spec_mock, config_mock def test_run_discover(entrypoint: AirbyteEntrypoint, mocker, spec_mock, config_mock): parsed_args = Namespace(command="discover", config="config_path") - expected = AirbyteCatalog(streams=[AirbyteStream(name="stream", json_schema={"k": "v"})]) + expected = AirbyteCatalog(streams=[AirbyteStream(name="stream", json_schema={"k": "v"}, supported_sync_modes=[SyncMode.full_refresh])]) mocker.patch.object(MockSource, "discover", return_value=expected) assert [_wrap_message(expected)] == list(entrypoint.run(parsed_args)) assert spec_mock.called diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 1d61824fdfd2..840676d25dad 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -96,7 +96,7 @@ - name: Azure Table Storage sourceDefinitionId: 798ae795-5189-42b6-b64e-3cb91db93338 dockerRepository: airbyte/source-azure-table - dockerImageTag: 0.1.2 + dockerImageTag: 0.1.3 documentationUrl: https://docs.airbyte.com/integrations/sources/azure-table icon: azureblobstorage.svg sourceType: database @@ -263,7 +263,7 @@ - name: E2E Testing sourceDefinitionId: d53f9084-fa6b-4a5a-976c-5b8392f4ad8a dockerRepository: airbyte/source-e2e-test - dockerImageTag: 2.1.1 + dockerImageTag: 2.1.3 documentationUrl: https://docs.airbyte.com/integrations/sources/e2e-test icon: airbyte.svg sourceType: api @@ -418,7 +418,7 @@ - name: Google Sheets sourceDefinitionId: 71607ba1-c0ac-4799-8049-7f4b90dd50f7 dockerRepository: airbyte/source-google-sheets - dockerImageTag: 0.2.20 + dockerImageTag: 0.2.21 documentationUrl: https://docs.airbyte.com/integrations/sources/google-sheets icon: google-sheets.svg sourceType: file diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 5650e0811966..29b0af1dc01f 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -1319,7 +1319,7 @@ - - "client_secret" oauthFlowOutputParameters: - - "refresh_token" -- dockerImage: "airbyte/source-azure-table:0.1.2" +- dockerImage: "airbyte/source-azure-table:0.1.3" spec: documentationUrl: "https://docsurl.com" connectionSpecification: @@ -1329,7 +1329,6 @@ required: - "storage_account_name" - "storage_access_key" - additionalProperties: false properties: storage_account_name: title: "Account Name" @@ -2410,7 +2409,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-e2e-test:2.1.1" +- dockerImage: "airbyte/source-e2e-test:2.1.3" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/e2e-test" connectionSpecification: @@ -2523,6 +2522,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] + protocol_version: "0.2.1" - dockerImage: "airbyte/source-exchange-rates:1.2.6" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/exchangeratesapi" @@ -4353,7 +4353,7 @@ oauthFlowOutputParameters: - - "access_token" - - "refresh_token" -- dockerImage: "airbyte/source-google-sheets:0.2.20" +- dockerImage: "airbyte/source-google-sheets:0.2.21" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-sheets" connectionSpecification: diff --git a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/util/S3OutputPathHelperTest.java b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/util/S3OutputPathHelperTest.java index d5a4fce7d923..aaa903b29a57 100644 --- a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/util/S3OutputPathHelperTest.java +++ b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/util/S3OutputPathHelperTest.java @@ -6,7 +6,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.google.common.collect.Lists; import io.airbyte.protocol.models.AirbyteStream; +import io.airbyte.protocol.models.SyncMode; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,32 +19,37 @@ class S3OutputPathHelperTest { public void testGetOutputPrefix() { // No namespace assertEquals("bucket_path/stream_name", S3OutputPathHelper - .getOutputPrefix("bucket_path", new AirbyteStream().withName("stream_name"))); + .getOutputPrefix("bucket_path", + new AirbyteStream().withName("stream_name").withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)))); // With namespace assertEquals("bucket_path/namespace/stream_name", S3OutputPathHelper .getOutputPrefix("bucket_path", - new AirbyteStream().withNamespace("namespace").withName("stream_name"))); + new AirbyteStream().withNamespace("namespace").withName("stream_name") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)))); // With empty namespace assertEquals("bucket_path/stream_name", S3OutputPathHelper .getOutputPrefix("bucket_path", - new AirbyteStream().withNamespace("").withName("stream_name"))); + new AirbyteStream().withNamespace("").withName("stream_name").withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)))); // With namespace with slash chart in the end assertEquals("bucket_path/namespace/stream_name", S3OutputPathHelper .getOutputPrefix("bucket_path", - new AirbyteStream().withNamespace("namespace/").withName("stream_name"))); + new AirbyteStream().withNamespace("namespace/").withName("stream_name") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)))); // With namespace with slash chart in the name assertEquals("bucket_path/namespace/subfolder/stream_name", S3OutputPathHelper .getOutputPrefix("bucket_path", - new AirbyteStream().withNamespace("namespace/subfolder/").withName("stream_name"))); + new AirbyteStream().withNamespace("namespace/subfolder/").withName("stream_name") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)))); // With an AWS Glue crawler assertEquals("bucket_path/namespace/date=2022-03-15", S3OutputPathHelper .getOutputPrefix("bucket_path", - new AirbyteStream().withNamespace("namespace").withName("date=2022-03-15"))); + new AirbyteStream().withNamespace("namespace").withName("date=2022-03-15") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)))); } } diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index 71fc49d543dc..7ec5868185dc 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.9 +Update tests after protocol change making `supported_sync_modes` a required property of `AirbyteStream` [#15591](https://github.com/airbytehq/airbyte/pull/15591/) + ## 0.2.8 Make full refresh tests tolerant to new records in a sequential read.[#17660](https://github.com/airbytehq/airbyte/pull/17660/) diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index 187f2a5c9138..516a33b4995b 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.8 +LABEL io.airbyte.version=0.2.9 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/setup.py b/airbyte-integrations/bases/source-acceptance-test/setup.py index 2fc14c1f7bbd..6bbbf11a2972 100644 --- a/airbyte-integrations/bases/source-acceptance-test/setup.py +++ b/airbyte-integrations/bases/source-acceptance-test/setup.py @@ -6,7 +6,7 @@ import setuptools MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1.104", "docker~=5.0.3", "PyYAML~=5.4", "icdiff~=1.9", diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_asserts.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_asserts.py index 413eb608a95f..46d495abbbb0 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_asserts.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_asserts.py @@ -31,7 +31,7 @@ def record_schema_fixture(): def catalog_fixture(request, record_schema) -> ConfiguredAirbyteCatalog: record_schema = request.param if hasattr(request, "param") else record_schema stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="my_stream", json_schema=record_schema), + stream=AirbyteStream(name="my_stream", json_schema=record_schema, supported_sync_modes=[SyncMode.full_refresh]), sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.append, ) diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_backward_compatibility.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_backward_compatibility.py index 011687ea44ed..f5105434b79c 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_backward_compatibility.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_backward_compatibility.py @@ -999,12 +999,14 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ), "other_test_stream": AirbyteStream.parse_obj( { "name": "other_test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ), }, @@ -1013,6 +1015,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1025,6 +1028,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1033,6 +1037,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "integer"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1045,6 +1050,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1053,6 +1059,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "new_test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1065,6 +1072,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], "default_cursor_field": ["a"], } ), @@ -1074,6 +1082,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], "default_cursor_field": ["b"], } ), @@ -1087,6 +1096,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], "default_cursor_field": ["a"], } ), @@ -1096,6 +1106,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], "default_cursor_field": ["a", "b"], } ), @@ -1109,6 +1120,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], "default_cursor_field": ["a", "b"], } ), @@ -1118,6 +1130,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], "default_cursor_field": ["a"], } ), @@ -1131,6 +1144,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["incremental"], "default_cursor_field": ["a"], } ), @@ -1140,6 +1154,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["incremental"], "default_cursor_field": ["b"], } ), @@ -1147,6 +1162,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "other_test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["incremental"], } ), }, @@ -1160,6 +1176,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "integer"}}}}}, "default_cursor_field": ["a"], + "supported_sync_modes": ["incremental"], } ), }, @@ -1171,6 +1188,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "properties": {"user": {"type": "object", "properties": {"username": {"type": ["integer", "string"]}}}} }, "default_cursor_field": ["b"], + "supported_sync_modes": ["incremental"], } ), }, @@ -1186,6 +1204,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1194,12 +1213,14 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ), "other_test_stream": AirbyteStream.parse_obj( { "name": "other_test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ), }, @@ -1212,6 +1233,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1219,6 +1241,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "test_stream": AirbyteStream.parse_obj( { "name": "test_stream", + "supported_sync_modes": ["full_refresh"], "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": ["string", "null"]}}}}}, } ) @@ -1231,6 +1254,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "test_stream": AirbyteStream.parse_obj( { "name": "test_stream", + "supported_sync_modes": ["full_refresh"], "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, } ) @@ -1240,6 +1264,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": ["string"]}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1256,6 +1281,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "user": {"type": "object", "properties": {"username": {"type": "string"}, "email": {"type": "string"}}} } }, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1264,6 +1290,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe { "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -1277,6 +1304,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, "default_cursor_field": ["a"], + "supported_sync_modes": ["full_refresh"], } ), }, @@ -1286,6 +1314,7 @@ def test_validate_previous_configs(previous_connector_spec, actual_connector_spe "name": "test_stream", "json_schema": {"properties": {"user": {"type": "object", "properties": {"username": {"type": "string"}}}}}, "default_cursor_field": ["a"], + "supported_sync_modes": ["full_refresh"], } ), }, diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py index 56b39d899564..6db6125eb4c8 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py @@ -2,6 +2,7 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from unittest import mock from unittest.mock import MagicMock, patch import pytest @@ -15,6 +16,7 @@ ConfiguredAirbyteCatalog, ConfiguredAirbyteStream, Level, + SyncMode, TraceType, Type, ) @@ -39,7 +41,14 @@ def test_discovery(schema, cursors, should_fail): t = _TestDiscovery() discovered_catalog = { - "test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": schema, "default_cursor_field": cursors}) + "test_stream": AirbyteStream.parse_obj( + { + "name": "test_stream", + "json_schema": schema, + "default_cursor_field": cursors, + "supported_sync_modes": ["full_refresh", "incremental"], + } + ) } if should_fail: with pytest.raises(AssertionError): @@ -71,7 +80,11 @@ def test_discovery(schema, cursors, should_fail): ) def test_ref_in_discovery_schemas(schema, should_fail): t = _TestDiscovery() - discovered_catalog = {"test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": schema})} + discovered_catalog = { + "test_stream": AirbyteStream.parse_obj( + {"name": "test_stream", "json_schema": schema, "supported_sync_modes": ["full_refresh", "incremental"]} + ) + } if should_fail: with pytest.raises(AssertionError): t.test_defined_refs_exist_in_schema(discovered_catalog) @@ -111,7 +124,11 @@ def test_ref_in_discovery_schemas(schema, should_fail): ) def test_keyword_in_discovery_schemas(schema, keyword, should_fail): t = _TestDiscovery() - discovered_catalog = {"test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": schema})} + discovered_catalog = { + "test_stream": AirbyteStream.parse_obj( + {"name": "test_stream", "json_schema": schema, "supported_sync_modes": ["full_refresh", "incremental"]} + ) + } if should_fail: with pytest.raises(AssertionError): t.test_defined_keyword_exist_in_schema(keyword, discovered_catalog) @@ -122,30 +139,30 @@ def test_keyword_in_discovery_schemas(schema, keyword, should_fail): @pytest.mark.parametrize( "discovered_catalog, expectation", [ - ({"test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": {}})}, pytest.raises(AssertionError)), + ({"test_stream": mock.MagicMock(name="test_stream", json_schema={}, supported_sync_modes=None)}, pytest.raises(AssertionError)), ( - {"test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": {}, "supported_sync_modes": []})}, + {"test_stream": mock.MagicMock(name="test_stream", json_schema={}, supported_sync_modes=[])}, pytest.raises(AssertionError), ), ( { - "test_stream": AirbyteStream.parse_obj( - {"name": "test_stream", "json_schema": {}, "supported_sync_modes": ["full_refresh", "incremental"]} + "test_stream": mock.MagicMock( + name="test_stream", json_schema={}, supported_sync_modes=[SyncMode.full_refresh, SyncMode.incremental] ) }, does_not_raise(), ), ( - {"test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": {}, "supported_sync_modes": ["full_refresh"]})}, + {"test_stream": mock.MagicMock(name="test_stream", json_schema={}, supported_sync_modes=[SyncMode.full_refresh])}, does_not_raise(), ), ( - {"test_stream": AirbyteStream.parse_obj({"name": "test_stream", "json_schema": {}, "supported_sync_modes": ["incremental"]})}, + {"test_stream": mock.MagicMock(name="test_stream", json_schema={}, supported_sync_modes=[SyncMode.incremental])}, does_not_raise(), ), ], ) -def test_supported_sync_modes_in_stream(discovered_catalog, expectation): +def test_supported_sync_modes_in_stream(mocker, discovered_catalog, expectation): t = _TestDiscovery() with expectation: t.test_streams_has_sync_modes(discovered_catalog) @@ -154,17 +171,36 @@ def test_supported_sync_modes_in_stream(discovered_catalog, expectation): @pytest.mark.parametrize( "discovered_catalog, expectation", [ - ({"test_stream_1": AirbyteStream.parse_obj({"name": "test_stream_1", "json_schema": {}})}, does_not_raise()), ( - {"test_stream_2": AirbyteStream.parse_obj({"name": "test_stream_2", "json_schema": {"additionalProperties": True}})}, + { + "test_stream_1": AirbyteStream.parse_obj( + {"name": "test_stream_1", "json_schema": {}, "supported_sync_modes": ["full_refresh"]} + ) + }, does_not_raise(), ), ( - {"test_stream_3": AirbyteStream.parse_obj({"name": "test_stream_3", "json_schema": {"additionalProperties": False}})}, + { + "test_stream_2": AirbyteStream.parse_obj( + {"name": "test_stream_2", "json_schema": {"additionalProperties": True}, "supported_sync_modes": ["full_refresh"]} + ) + }, + does_not_raise(), + ), + ( + { + "test_stream_3": AirbyteStream.parse_obj( + {"name": "test_stream_3", "json_schema": {"additionalProperties": False}, "supported_sync_modes": ["full_refresh"]} + ) + }, pytest.raises(AssertionError), ), ( - {"test_stream_4": AirbyteStream.parse_obj({"name": "test_stream_4", "json_schema": {"additionalProperties": "foo"}})}, + { + "test_stream_4": AirbyteStream.parse_obj( + {"name": "test_stream_4", "json_schema": {"additionalProperties": "foo"}, "supported_sync_modes": ["full_refresh"]} + ) + }, pytest.raises(AssertionError), ), ( @@ -173,6 +209,7 @@ def test_supported_sync_modes_in_stream(discovered_catalog, expectation): { "name": "test_stream_5", "json_schema": {"additionalProperties": True, "properties": {"my_object": {"additionalProperties": True}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -184,6 +221,7 @@ def test_supported_sync_modes_in_stream(discovered_catalog, expectation): { "name": "test_stream_6", "json_schema": {"additionalProperties": True, "properties": {"my_object": {"additionalProperties": False}}}, + "supported_sync_modes": ["full_refresh"], } ) }, @@ -217,7 +255,7 @@ def test_read(schema, record, should_fail): catalog = ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( - stream=AirbyteStream.parse_obj({"name": "test_stream", "json_schema": schema}), + stream=AirbyteStream.parse_obj({"name": "test_stream", "json_schema": schema, "supported_sync_modes": ["full_refresh"]}), sync_mode="full_refresh", destination_sync_mode="overwrite", ) @@ -334,7 +372,11 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur streams=[ ConfiguredAirbyteStream( stream=AirbyteStream.parse_obj( - {"name": "test1", "json_schema": {"type": "object", "properties": {"f1": {"type": "string"}}}} + { + "name": "test1", + "json_schema": {"type": "object", "properties": {"f1": {"type": "string"}}}, + "supported_sync_modes": ["full_refresh"], + } ), sync_mode="full_refresh", destination_sync_mode="overwrite", @@ -352,6 +394,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur { "name": "test1", "json_schema": {"type": "object", "properties": {"f1": {"type": "string"}, "f2": {"type": "string"}}}, + "supported_sync_modes": ["full_refresh"], } ), sync_mode="full_refresh", @@ -373,6 +416,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur { "name": "test1", "json_schema": {"type": "object", "properties": {"f1": {"type": "string"}, "f2": {"type": "string"}}}, + "supported_sync_modes": ["full_refresh"], } ), sync_mode="full_refresh", @@ -401,6 +445,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur "f3": {"type": "array", "items": {"type": "integer"}}, }, }, + "supported_sync_modes": ["full_refresh"], } ), sync_mode="full_refresh", @@ -429,6 +474,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur "f3": {"type": "array", "items": {"type": "integer"}}, }, }, + "supported_sync_modes": ["full_refresh"], } ), sync_mode="full_refresh", @@ -457,6 +503,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur "f3": {"type": "object", "properties": {"f4": {"type": "string"}, "f5": {"type": "array"}}}, }, }, + "supported_sync_modes": ["full_refresh"], } ), sync_mode="full_refresh", @@ -485,6 +532,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur "f3": {"type": "object", "properties": {"f4": {"type": "string"}, "f5": {"type": "array"}}}, }, }, + "supported_sync_modes": ["full_refresh"], } ), sync_mode="full_refresh", @@ -505,6 +553,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test1", + "supported_sync_modes": ["full_refresh"], "json_schema": { "type": "object", "properties": { @@ -552,6 +601,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test1", + "supported_sync_modes": ["full_refresh"], "json_schema": { "type": "object", "properties": { @@ -584,6 +634,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test2", + "supported_sync_modes": ["full_refresh"], "json_schema": {"type": "object", "properties": {"f8": {"type": "string"}, "f9": {"type": "string"}}}, } ), @@ -607,6 +658,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test1", + "supported_sync_modes": ["full_refresh"], "json_schema": { "type": "object", "properties": { @@ -639,6 +691,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test2", + "supported_sync_modes": ["full_refresh"], "json_schema": {"type": "object", "properties": {"f8": {"type": "string"}, "f9": {"type": "string"}}}, } ), @@ -661,6 +714,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test1", + "supported_sync_modes": ["full_refresh"], "json_schema": { "type": "object", "properties": { @@ -695,6 +749,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test1", + "supported_sync_modes": ["full_refresh"], "json_schema": { "type": "object", "properties": { @@ -739,6 +794,7 @@ def test_airbyte_trace_message_on_failure(output, expect_trace_message_on_failur stream=AirbyteStream.parse_obj( { "name": "test1", + "supported_sync_modes": ["full_refresh"], "json_schema": { "type": "object", "properties": { diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_json_schema_helper.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_json_schema_helper.py index 67043769f140..e4e789e174c8 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_json_schema_helper.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_json_schema_helper.py @@ -64,7 +64,7 @@ def stream_schema_fixture(): def stream_mapping_fixture(stream_schema): return { "my_stream": ConfiguredAirbyteStream( - stream=AirbyteStream(name="my_stream", json_schema=stream_schema), + stream=AirbyteStream(name="my_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.full_refresh]), sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.append, ) diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py index 54821dff87a3..24c6b7ff38c9 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_test_full_refresh.py @@ -7,7 +7,15 @@ import pytest from _pytest.outcomes import Failed -from airbyte_cdk.models import AirbyteMessage, AirbyteRecordMessage, AirbyteStream, ConfiguredAirbyteCatalog, ConfiguredAirbyteStream, Type +from airbyte_cdk.models import ( + AirbyteMessage, + AirbyteRecordMessage, + AirbyteStream, + ConfiguredAirbyteCatalog, + ConfiguredAirbyteStream, + SyncMode, + Type, +) from source_acceptance_test.config import ConnectionTestConfig from source_acceptance_test.tests.test_full_refresh import TestFullRefresh as _TestFullRefresh @@ -37,7 +45,7 @@ def get_default_catalog(schema, **kwargs): stream=AirbyteStream( name="test_stream", json_schema=schema, - supported_sync_modes=["full_refresh"], + supported_sync_modes=[SyncMode.full_refresh], ), **configured_catalog_kwargs, ) diff --git a/airbyte-integrations/connectors/destination-amazon-sqs/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-amazon-sqs/integration_tests/integration_test.py index cb9d6401ed8d..db237b32eeb4 100644 --- a/airbyte-integrations/connectors/destination-amazon-sqs/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-amazon-sqs/integration_tests/integration_test.py @@ -22,13 +22,13 @@ def configured_catalog_fixture() -> ConfiguredAirbyteCatalog: stream_schema = {"type": "object", "properties": {"string_col": {"type": "str"}, "int_col": {"type": "integer"}}} append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="append_stream", json_schema=stream_schema), + stream=AirbyteStream(name="append_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) overwrite_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema), + stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestDataUtils.java b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestDataUtils.java index 7f1c473c68dc..f501cbf9531d 100644 --- a/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestDataUtils.java +++ b/airbyte-integrations/connectors/destination-bigquery-denormalized/src/test-integration/java/io/airbyte/integrations/destination/bigquery/util/BigQueryDenormalizedTestDataUtils.java @@ -156,7 +156,8 @@ private static JsonNode getTestDataFromResourceJson(final String fileName) { public static ConfiguredAirbyteCatalog getCommonCatalog(final JsonNode schema, final String datasetId) { return new ConfiguredAirbyteCatalog().withStreams(Lists.newArrayList(new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(USERS_STREAM_NAME).withNamespace(datasetId).withJsonSchema(schema)) + .withStream(new AirbyteStream().withName(USERS_STREAM_NAME).withNamespace(datasetId).withJsonSchema(schema) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withSyncMode(SyncMode.FULL_REFRESH).withDestinationSyncMode(DestinationSyncMode.OVERWRITE))); } diff --git a/airbyte-integrations/connectors/destination-cassandra/src/test-integration/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java b/airbyte-integrations/connectors/destination-cassandra/src/test-integration/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java index af9405a317c6..a0b469672476 100644 --- a/airbyte-integrations/connectors/destination-cassandra/src/test-integration/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java +++ b/airbyte-integrations/connectors/destination-cassandra/src/test-integration/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import io.airbyte.commons.json.Jsons; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteRecordMessage; @@ -13,6 +14,7 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.DestinationSyncMode; +import io.airbyte.protocol.models.SyncMode; import java.time.Instant; import java.util.List; @@ -61,7 +63,8 @@ static AirbyteMessage createAirbyteMessage(AirbyteMessage.Type type, static AirbyteStream createAirbyteStream(String name, String namespace) { return new AirbyteStream() .withName(name) - .withNamespace(namespace); + .withNamespace(namespace) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)); } static ConfiguredAirbyteStream createConfiguredAirbyteStream(DestinationSyncMode syncMode, AirbyteStream stream) { diff --git a/airbyte-integrations/connectors/destination-cassandra/src/test/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java b/airbyte-integrations/connectors/destination-cassandra/src/test/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java index af9405a317c6..a0b469672476 100644 --- a/airbyte-integrations/connectors/destination-cassandra/src/test/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java +++ b/airbyte-integrations/connectors/destination-cassandra/src/test/java/io/airbyte/integrations/destination/cassandra/TestDataFactory.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import io.airbyte.commons.json.Jsons; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteRecordMessage; @@ -13,6 +14,7 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.DestinationSyncMode; +import io.airbyte.protocol.models.SyncMode; import java.time.Instant; import java.util.List; @@ -61,7 +63,8 @@ static AirbyteMessage createAirbyteMessage(AirbyteMessage.Type type, static AirbyteStream createAirbyteStream(String name, String namespace) { return new AirbyteStream() .withName(name) - .withNamespace(namespace); + .withNamespace(namespace) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)); } static ConfiguredAirbyteStream createConfiguredAirbyteStream(DestinationSyncMode syncMode, AirbyteStream stream) { diff --git a/airbyte-integrations/connectors/destination-dynamodb/src/test/java/io/airbyte/integrations/destination/dynamodb/DynamodbDestinationTest.java b/airbyte-integrations/connectors/destination-dynamodb/src/test/java/io/airbyte/integrations/destination/dynamodb/DynamodbDestinationTest.java index 6b43938707ae..25043993bd34 100644 --- a/airbyte-integrations/connectors/destination-dynamodb/src/test/java/io/airbyte/integrations/destination/dynamodb/DynamodbDestinationTest.java +++ b/airbyte-integrations/connectors/destination-dynamodb/src/test/java/io/airbyte/integrations/destination/dynamodb/DynamodbDestinationTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; import io.airbyte.commons.json.Jsons; import io.airbyte.protocol.models.*; import org.junit.jupiter.api.Test; @@ -21,9 +22,8 @@ void testGetOutputTableNameWithString() throws Exception { @Test void testGetOutputTableNameWithStream() throws Exception { - final var stream = new AirbyteStream(); - stream.setName("test_stream"); - stream.setNamespace("test_namespace"); + final var stream = + new AirbyteStream().withName("test_stream").withNamespace("test_namespace").withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)); final var actual = DynamodbOutputTableHelper.getOutputTableName("test_table", stream); assertEquals("test_table_test_namespace_test_stream", actual); } diff --git a/airbyte-integrations/connectors/destination-firebolt/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-firebolt/integration_tests/integration_test.py index 9c4856855410..9d5e9e1c0c76 100644 --- a/airbyte-integrations/connectors/destination-firebolt/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-firebolt/integration_tests/integration_test.py @@ -60,7 +60,7 @@ def table_schema() -> str: @fixture def configured_catalogue(test_table_name: str, table_schema: str) -> ConfiguredAirbyteCatalog: append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name=test_table_name, json_schema=table_schema), + stream=AirbyteStream(name=test_table_name, json_schema=table_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) diff --git a/airbyte-integrations/connectors/destination-firebolt/unit_tests/test_firebolt_destination.py b/airbyte-integrations/connectors/destination-firebolt/unit_tests/test_firebolt_destination.py index 8525c6114a02..64372a78f662 100644 --- a/airbyte-integrations/connectors/destination-firebolt/unit_tests/test_firebolt_destination.py +++ b/airbyte-integrations/connectors/destination-firebolt/unit_tests/test_firebolt_destination.py @@ -77,6 +77,7 @@ def configured_stream1() -> ConfiguredAirbyteStream: "type": "object", "properties": {"col1": {"type": "string"}, "col2": {"type": "integer"}}, }, + supported_sync_modes=[SyncMode.incremental], ), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, @@ -92,6 +93,7 @@ def configured_stream2() -> ConfiguredAirbyteStream: "type": "object", "properties": {"col1": {"type": "string"}, "col2": {"type": "integer"}}, }, + supported_sync_modes=[SyncMode.incremental], ), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, diff --git a/airbyte-integrations/connectors/destination-firestore/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-firestore/integration_tests/integration_test.py index 14987e41c5de..4602abb28e7a 100644 --- a/airbyte-integrations/connectors/destination-firestore/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-firestore/integration_tests/integration_test.py @@ -35,13 +35,13 @@ def configured_catalog_fixture() -> ConfiguredAirbyteCatalog: stream_schema = {"type": "object", "properties": {"string_col": {"type": "str"}, "int_col": {"type": "integer"}}} append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="append_stream", json_schema=stream_schema), + stream=AirbyteStream(name="append_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) overwrite_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema), + stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-integrations/connectors/destination-gcs/src/test/java/io/airbyte/integrations/destination/gcs/avro/GcsAvroWriterTest.java b/airbyte-integrations/connectors/destination-gcs/src/test/java/io/airbyte/integrations/destination/gcs/avro/GcsAvroWriterTest.java index c27da81fb152..05d5d27e697b 100644 --- a/airbyte-integrations/connectors/destination-gcs/src/test/java/io/airbyte/integrations/destination/gcs/avro/GcsAvroWriterTest.java +++ b/airbyte-integrations/connectors/destination-gcs/src/test/java/io/airbyte/integrations/destination/gcs/avro/GcsAvroWriterTest.java @@ -10,11 +10,13 @@ import com.amazonaws.services.s3.AmazonS3; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; import io.airbyte.integrations.destination.gcs.GcsDestinationConfig; import io.airbyte.integrations.destination.gcs.credential.GcsHmacKeyCredentialConfig; import io.airbyte.integrations.destination.s3.avro.S3AvroFormatConfig; import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.ConfiguredAirbyteStream; +import io.airbyte.protocol.models.SyncMode; import java.io.IOException; import java.sql.Timestamp; import java.time.Instant; @@ -35,7 +37,7 @@ public void generatesCorrectObjectPath() throws IOException { new ConfiguredAirbyteStream() .withStream(new AirbyteStream() .withNamespace("fake-namespace") - .withName("fake-stream")), + .withName("fake-stream").withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))), Timestamp.from(Instant.ofEpochMilli(1234)), null); diff --git a/airbyte-integrations/connectors/destination-jdbc/src/test/java/io/airbyte/integrations/destination/jdbc/copy/s3/S3StreamCopierTest.java b/airbyte-integrations/connectors/destination-jdbc/src/test/java/io/airbyte/integrations/destination/jdbc/copy/s3/S3StreamCopierTest.java index a0f487d59018..acd777b32fda 100644 --- a/airbyte-integrations/connectors/destination-jdbc/src/test/java/io/airbyte/integrations/destination/jdbc/copy/s3/S3StreamCopierTest.java +++ b/airbyte-integrations/connectors/destination-jdbc/src/test/java/io/airbyte/integrations/destination/jdbc/copy/s3/S3StreamCopierTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.verify; import com.amazonaws.services.s3.AmazonS3Client; +import com.google.common.collect.Lists; import io.airbyte.db.jdbc.JdbcDatabase; import io.airbyte.integrations.destination.ExtendedNameTransformer; import io.airbyte.integrations.destination.jdbc.SqlOperations; @@ -26,6 +27,7 @@ import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.DestinationSyncMode; +import io.airbyte.protocol.models.SyncMode; import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; @@ -54,7 +56,8 @@ public class S3StreamCopierTest { .withDestinationSyncMode(DestinationSyncMode.APPEND) .withStream(new AirbyteStream() .withName("fake-stream") - .withNamespace("fake-namespace")); + .withNamespace("fake-namespace") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))); private static final int UPLOAD_THREADS = 10; private static final int QUEUE_CAPACITY = 10; // equivalent to Thu, 09 Dec 2021 19:17:54 GMT diff --git a/airbyte-integrations/connectors/destination-kvdb/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-kvdb/integration_tests/integration_test.py index 73eba0b56631..8078180cb03d 100644 --- a/airbyte-integrations/connectors/destination-kvdb/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-kvdb/integration_tests/integration_test.py @@ -34,13 +34,13 @@ def configured_catalog_fixture() -> ConfiguredAirbyteCatalog: stream_schema = {"type": "object", "properties": {"string_col": {"type": "str"}, "int_col": {"type": "integer"}}} append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="append_stream", json_schema=stream_schema), + stream=AirbyteStream(name="append_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) overwrite_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema), + stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-integrations/connectors/destination-rabbitmq/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-rabbitmq/integration_tests/integration_test.py index 305817736c1e..db4958fc85f6 100644 --- a/airbyte-integrations/connectors/destination-rabbitmq/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-rabbitmq/integration_tests/integration_test.py @@ -25,7 +25,7 @@ def _configured_catalog() -> ConfiguredAirbyteCatalog: stream_schema = {"type": "object", "properties": {"name": {"type": "string"}}} append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name=TEST_STREAM, json_schema=stream_schema), + stream=AirbyteStream(name=TEST_STREAM, json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) diff --git a/airbyte-integrations/connectors/destination-rabbitmq/unit_tests/unit_test.py b/airbyte-integrations/connectors/destination-rabbitmq/unit_tests/unit_test.py index 59ae8d924bab..39c4341ee2bf 100644 --- a/airbyte-integrations/connectors/destination-rabbitmq/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/destination-rabbitmq/unit_tests/unit_test.py @@ -78,7 +78,7 @@ def _record(stream: str, data: Dict[str, Any]) -> AirbyteMessage: def _configured_catalog() -> ConfiguredAirbyteCatalog: stream_schema = {"type": "object", "properties": {"name": {"type": "string"}, "email": {"type": "string"}}} append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="people", json_schema=stream_schema), + stream=AirbyteStream(name="people", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) diff --git a/airbyte-integrations/connectors/destination-redshift/src/test/java/io/airbyte/integrations/destination/redshift/copiers/RedshiftStreamCopierTest.java b/airbyte-integrations/connectors/destination-redshift/src/test/java/io/airbyte/integrations/destination/redshift/copiers/RedshiftStreamCopierTest.java index 6681540b0425..820897e0f598 100644 --- a/airbyte-integrations/connectors/destination-redshift/src/test/java/io/airbyte/integrations/destination/redshift/copiers/RedshiftStreamCopierTest.java +++ b/airbyte-integrations/connectors/destination-redshift/src/test/java/io/airbyte/integrations/destination/redshift/copiers/RedshiftStreamCopierTest.java @@ -24,6 +24,7 @@ import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.DestinationSyncMode; +import io.airbyte.protocol.models.SyncMode; import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; @@ -86,6 +87,7 @@ public void setup() { .withDestinationSyncMode(DestinationSyncMode.APPEND) .withStream(new AirbyteStream() .withName("fake-stream") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)) .withNamespace("fake-namespace"))); } diff --git a/airbyte-integrations/connectors/destination-sftp-json/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-sftp-json/integration_tests/integration_test.py index 165bb8233805..2401b99cefed 100644 --- a/airbyte-integrations/connectors/destination-sftp-json/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-sftp-json/integration_tests/integration_test.py @@ -59,13 +59,13 @@ def configured_catalog_fixture() -> ConfiguredAirbyteCatalog: } append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="append_stream", json_schema=stream_schema), + stream=AirbyteStream(name="append_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) overwrite_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema), + stream=AirbyteStream(name="overwrite_stream", json_schema=stream_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-integrations/connectors/destination-snowflake/src/test/java/io/airbyte/integrations/destination/snowflake/SnowflakeS3StreamCopierTest.java b/airbyte-integrations/connectors/destination-snowflake/src/test/java/io/airbyte/integrations/destination/snowflake/SnowflakeS3StreamCopierTest.java index a899e7562d60..4d34b1bebef2 100644 --- a/airbyte-integrations/connectors/destination-snowflake/src/test/java/io/airbyte/integrations/destination/snowflake/SnowflakeS3StreamCopierTest.java +++ b/airbyte-integrations/connectors/destination-snowflake/src/test/java/io/airbyte/integrations/destination/snowflake/SnowflakeS3StreamCopierTest.java @@ -19,6 +19,7 @@ import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.DestinationSyncMode; +import io.airbyte.protocol.models.SyncMode; import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; @@ -65,6 +66,7 @@ public void setup() throws Exception { .withDestinationSyncMode(DestinationSyncMode.APPEND) .withStream(new AirbyteStream() .withName("fake-stream") + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)) .withNamespace("fake-namespace"))); } diff --git a/airbyte-integrations/connectors/destination-sqlite/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-sqlite/integration_tests/integration_test.py index 74ddb3466bb6..fcfba28b2b53 100644 --- a/airbyte-integrations/connectors/destination-sqlite/integration_tests/integration_test.py +++ b/airbyte-integrations/connectors/destination-sqlite/integration_tests/integration_test.py @@ -58,7 +58,7 @@ def table_schema() -> str: @pytest.fixture def configured_catalogue(test_table_name: str, table_schema: str) -> ConfiguredAirbyteCatalog: append_stream = ConfiguredAirbyteStream( - stream=AirbyteStream(name=test_table_name, json_schema=table_schema), + stream=AirbyteStream(name=test_table_name, json_schema=table_schema, supported_sync_modes=[SyncMode.incremental]), sync_mode=SyncMode.incremental, destination_sync_mode=DestinationSyncMode.append, ) diff --git a/airbyte-integrations/connectors/source-azure-table/Dockerfile b/airbyte-integrations/connectors/source-azure-table/Dockerfile index e23b8cfe7db7..6b59fb5dbbb7 100644 --- a/airbyte-integrations/connectors/source-azure-table/Dockerfile +++ b/airbyte-integrations/connectors/source-azure-table/Dockerfile @@ -34,5 +34,5 @@ COPY source_azure_table ./source_azure_table ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.2 +LABEL io.airbyte.version=0.1.3 LABEL io.airbyte.name=airbyte/source-azure-table diff --git a/airbyte-integrations/connectors/source-azure-table/source_azure_table/source.py b/airbyte-integrations/connectors/source-azure-table/source_azure_table/source.py index 1bd0c54cc740..d728ad4dd79b 100644 --- a/airbyte-integrations/connectors/source-azure-table/source_azure_table/source.py +++ b/airbyte-integrations/connectors/source-azure-table/source_azure_table/source.py @@ -2,7 +2,6 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # -import copy from typing import Any, Iterator, List, Mapping, MutableMapping, Optional, Tuple from airbyte_cdk.logger import AirbyteLogger @@ -10,13 +9,13 @@ AirbyteCatalog, AirbyteConnectionStatus, AirbyteMessage, - AirbyteStateMessage, AirbyteStream, ConfiguredAirbyteCatalog, Status, - Type, + SyncMode, ) from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.connector_state_manager import ConnectorStateManager from airbyte_cdk.sources.streams import Stream from airbyte_cdk.sources.utils.schema_helpers import split_config from airbyte_cdk.utils.event_timing import create_timer @@ -34,14 +33,6 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> def _as_airbyte_record(self, stream_name: str, data: Mapping[str, Any]): return data - def _checkpoint_state(self, stream, stream_state, connector_state): - try: - connector_state[stream.name] = stream.state - except AttributeError: - connector_state[stream.name] = stream_state - - return AirbyteMessage(type=Type.STATE, state=AirbyteStateMessage(data=connector_state)) - @property def get_typed_schema(self) -> object: """Static schema for tables""" @@ -71,10 +62,13 @@ def discover(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> AirbyteC streams = [] for table in tables: stream_name = table.name - stream = AirbyteStream(name=stream_name, json_schema=self.get_typed_schema) - stream.supported_sync_modes = ["full_refresh", "incremental"] - stream.source_defined_cursor = True - stream.default_cursor_field = ["PartitionKey"] + stream = AirbyteStream( + name=stream_name, + json_schema=self.get_typed_schema, + supported_sync_modes=[SyncMode.full_refresh, SyncMode.incremental], + source_defined_cursor=True, + default_cursor_field=["PartitionKey"], + ) streams.append(stream) logger.info(f"Total {streams.count} streams found.") @@ -106,10 +100,10 @@ def read( """ This method is overridden to check whether the stream `quotes` exists in the source, if not skip reading that stream. """ - connector_state = copy.deepcopy(state or {}) + stream_instances = {s.name: s for s in self.streams(logger=logger, config=config)} + state_manager = ConnectorStateManager(stream_instance_map=stream_instances, state=state) logger.info(f"Starting syncing {self.name}") config, internal_config = split_config(config) - stream_instances = {s.name: s for s in self.streams(logger=logger, config=config)} self._stream_to_instance_map = stream_instances with create_timer(self.name) as timer: for configured_stream in catalog.streams: @@ -128,7 +122,7 @@ def read( logger=logger, stream_instance=stream_instance, configured_stream=configured_stream, - connector_state=connector_state, + state_manager=state_manager, internal_config=internal_config, ) except Exception as e: diff --git a/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json b/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json index b163fa470114..c314e5eea6c2 100644 --- a/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json +++ b/airbyte-integrations/connectors/source-azure-table/source_azure_table/spec.json @@ -5,7 +5,6 @@ "title": "Azure Data Table Spec", "type": "object", "required": ["storage_account_name", "storage_access_key"], - "additionalProperties": false, "properties": { "storage_account_name": { "title": "Account Name", diff --git a/airbyte-integrations/connectors/source-azure-table/unit_tests/test_source.py b/airbyte-integrations/connectors/source-azure-table/unit_tests/test_source.py index 2fe472fb8f05..8df88a5c196b 100644 --- a/airbyte-integrations/connectors/source-azure-table/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-azure-table/unit_tests/test_source.py @@ -6,7 +6,7 @@ from unittest import mock import pytest -from airbyte_cdk.models import AirbyteCatalog +from airbyte_cdk.models import AirbyteCatalog, SyncMode from source_azure_table.source import SourceAzureTable from source_azure_table.streams import AzureTableStream @@ -49,7 +49,7 @@ def test_discover(mocker, config, tables): "type": "object", "properties": {"PartitionKey": {"type": "string"}}, } - assert stream.supported_sync_modes == ["full_refresh", "incremental"] + assert stream.supported_sync_modes == [SyncMode.full_refresh, SyncMode.incremental] assert stream.source_defined_cursor is True assert stream.default_cursor_field == ["PartitionKey"] diff --git a/airbyte-integrations/connectors/source-e2e-test/Dockerfile b/airbyte-integrations/connectors/source-e2e-test/Dockerfile index b38a53fab502..10bf69c85f64 100644 --- a/airbyte-integrations/connectors/source-e2e-test/Dockerfile +++ b/airbyte-integrations/connectors/source-e2e-test/Dockerfile @@ -17,5 +17,5 @@ ENV ENABLE_SENTRY true COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=2.1.2 +LABEL io.airbyte.version=2.1.3 LABEL io.airbyte.name=airbyte/source-e2e-test diff --git a/airbyte-integrations/connectors/source-e2e-test/src/main/java/io/airbyte/integrations/source/e2e_test/ContinuousFeedConfig.java b/airbyte-integrations/connectors/source-e2e-test/src/main/java/io/airbyte/integrations/source/e2e_test/ContinuousFeedConfig.java index 57b69f8ace10..c23fbeac61b6 100644 --- a/airbyte-integrations/connectors/source-e2e-test/src/main/java/io/airbyte/integrations/source/e2e_test/ContinuousFeedConfig.java +++ b/airbyte-integrations/connectors/source-e2e-test/src/main/java/io/airbyte/integrations/source/e2e_test/ContinuousFeedConfig.java @@ -5,12 +5,14 @@ package io.airbyte.integrations.source.e2e_test; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.resources.MoreResources; import io.airbyte.commons.string.Strings; import io.airbyte.commons.util.MoreIterators; import io.airbyte.protocol.models.AirbyteCatalog; import io.airbyte.protocol.models.AirbyteStream; +import io.airbyte.protocol.models.SyncMode; import io.airbyte.validation.json.JsonSchemaValidator; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; @@ -79,13 +81,15 @@ static AirbyteCatalog parseMockCatalog(final JsonNode config) throws JsonValidat checkSchema(streamName, streamSchema.get()); if (streamDuplication == 1) { - final AirbyteStream stream = new AirbyteStream().withName(streamName).withJsonSchema(streamSchema.get()); + final AirbyteStream stream = new AirbyteStream().withName(streamName).withJsonSchema(streamSchema.get()) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)); return new AirbyteCatalog().withStreams(Collections.singletonList(stream)); } else { final List streams = new ArrayList<>(streamDuplication); for (int i = 0; i < streamDuplication; ++i) { streams.add(new AirbyteStream() .withName(String.join("_", streamName, String.valueOf(i))) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)) .withJsonSchema(streamSchema.get())); } return new AirbyteCatalog().withStreams(streams); @@ -104,7 +108,8 @@ static AirbyteCatalog parseMockCatalog(final JsonNode config) throws JsonValidat final String streamName = entry.getKey(); final JsonNode streamSchema = entry.getValue(); checkSchema(streamName, streamSchema); - streams.add(new AirbyteStream().withName(streamName).withJsonSchema(streamSchema)); + streams.add(new AirbyteStream().withName(streamName).withJsonSchema(streamSchema) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))); } return new AirbyteCatalog().withStreams(streams); } diff --git a/airbyte-integrations/connectors/source-e2e-test/src/test/resources/parse_mock_catalog_test_cases.json b/airbyte-integrations/connectors/source-e2e-test/src/test/resources/parse_mock_catalog_test_cases.json index 92f3f961c5a7..4fd00b2a75db 100644 --- a/airbyte-integrations/connectors/source-e2e-test/src/test/resources/parse_mock_catalog_test_cases.json +++ b/airbyte-integrations/connectors/source-e2e-test/src/test/resources/parse_mock_catalog_test_cases.json @@ -20,7 +20,8 @@ "type": "number" } } - } + }, + "supported_sync_modes": ["full_refresh"] } ] } @@ -47,7 +48,8 @@ "type": "number" } } - } + }, + "supported_sync_modes": ["full_refresh"] }, { "name": "my_stream_1", @@ -61,7 +63,8 @@ "type": "number" } } - } + }, + "supported_sync_modes": ["full_refresh"] } ] } @@ -104,7 +107,8 @@ "type": "number" } } - } + }, + "supported_sync_modes": ["full_refresh"] }, { "name": "stream2", @@ -115,7 +119,8 @@ "type": "string" } } - } + }, + "supported_sync_modes": ["full_refresh"] } ] } diff --git a/airbyte-integrations/connectors/source-google-sheets/Dockerfile b/airbyte-integrations/connectors/source-google-sheets/Dockerfile index 83bdc7857668..eeea91de04c0 100644 --- a/airbyte-integrations/connectors/source-google-sheets/Dockerfile +++ b/airbyte-integrations/connectors/source-google-sheets/Dockerfile @@ -34,5 +34,5 @@ COPY google_sheets_source ./google_sheets_source ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.20 +LABEL io.airbyte.version=0.2.21 LABEL io.airbyte.name=airbyte/source-google-sheets diff --git a/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py b/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py index b22c4aa1521f..c321c416b8f9 100644 --- a/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py +++ b/airbyte-integrations/connectors/source-google-sheets/google_sheets_source/helpers.py @@ -10,7 +10,7 @@ from typing import Dict, FrozenSet, Iterable, List from airbyte_cdk.logger import AirbyteLogger -from airbyte_cdk.models.airbyte_protocol import AirbyteRecordMessage, AirbyteStream, ConfiguredAirbyteCatalog +from airbyte_cdk.models.airbyte_protocol import AirbyteRecordMessage, AirbyteStream, ConfiguredAirbyteCatalog, SyncMode from google.oauth2 import credentials as client_account from google.oauth2 import service_account from googleapiclient import discovery @@ -60,7 +60,7 @@ def headers_to_airbyte_stream(logger: AirbyteLogger, sheet_name: str, header_row "properties": {field: {"type": "string"} for field in fields}, } - return AirbyteStream(name=sheet_name, json_schema=sheet_json_schema, supported_sync_modes=["full_refresh"]) + return AirbyteStream(name=sheet_name, json_schema=sheet_json_schema, supported_sync_modes=[SyncMode.full_refresh]) @staticmethod def get_valid_headers_and_duplicates(header_row_values: List[str]) -> (List[str], List[str]): diff --git a/airbyte-integrations/connectors/source-google-sheets/unit_tests/test_helpers.py b/airbyte-integrations/connectors/source-google-sheets/unit_tests/test_helpers.py index 227ecfb5b5b9..decb7fe26fba 100644 --- a/airbyte-integrations/connectors/source-google-sheets/unit_tests/test_helpers.py +++ b/airbyte-integrations/connectors/source-google-sheets/unit_tests/test_helpers.py @@ -35,7 +35,7 @@ def test_headers_to_airbyte_stream(self): # For simplicity, the type of every cell is a string "properties": {header: {"type": "string"} for header in header_values}, }, - supported_sync_modes=["full_refresh"], + supported_sync_modes=[SyncMode.full_refresh], ) actual_stream = Helpers.headers_to_airbyte_stream(logger, sheet_name, header_values) @@ -66,7 +66,7 @@ def test_duplicate_headers_to_ab_stream_ignores_duplicates(self): # For simplicity, the type of every cell is a string "properties": {header: {"type": "string"} for header in expected_stream_header_values}, }, - supported_sync_modes=["full_refresh"], + supported_sync_modes=[SyncMode.full_refresh], ) actual_stream = Helpers.headers_to_airbyte_stream(logger, sheet_name, header_values) @@ -84,7 +84,7 @@ def test_headers_to_airbyte_stream_blank_values_terminate_row(self): # For simplicity, the type of every cell is a string "properties": {"h1": {"type": "string"}}, }, - supported_sync_modes=["full_refresh"], + supported_sync_modes=[SyncMode.full_refresh], ) actual_stream = Helpers.headers_to_airbyte_stream(logger, sheet_name, header_values) diff --git a/airbyte-integrations/connectors/source-instagram/unit_tests/test_source.py b/airbyte-integrations/connectors/source-instagram/unit_tests/test_source.py index a97387026e33..b2fe9ecbcd40 100644 --- a/airbyte-integrations/connectors/source-instagram/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-instagram/unit_tests/test_source.py @@ -65,7 +65,7 @@ def test_read(config): catalog = ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( - stream=AirbyteStream(name="users", json_schema={}), + stream=AirbyteStream(name="users", json_schema={}, supported_sync_modes=[SyncMode.full_refresh]), sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java index 7484202848b9..55df035c283d 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; +import com.google.common.collect.Lists; import io.airbyte.commons.json.Jsons; import io.airbyte.integrations.base.AirbyteStreamNameNamespacePair; import io.airbyte.integrations.source.relationaldb.models.DbState; @@ -29,6 +30,7 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConfiguredAirbyteStream; import io.airbyte.protocol.models.StreamDescriptor; +import io.airbyte.protocol.models.SyncMode; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -66,10 +68,12 @@ void testGetters() { final ConfiguredAirbyteCatalog catalog = new ConfiguredAirbyteCatalog() .withStreams(List.of( new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD1)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE)))); + .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))))); final StateManager stateManager = new StreamStateManager(state, catalog); @@ -89,13 +93,16 @@ void testToState() { final ConfiguredAirbyteCatalog catalog = new ConfiguredAirbyteCatalog() .withStreams(List.of( new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD1)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD2)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME3).withNamespace(NAMESPACE)))); + .withStream(new AirbyteStream().withName(STREAM_NAME3).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))))); final StateManager stateManager = new StreamStateManager(createDefaultState(), catalog); @@ -151,13 +158,16 @@ void testToStateWithoutCursorInfo() { final ConfiguredAirbyteCatalog catalog = new ConfiguredAirbyteCatalog() .withStreams(List.of( new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD1)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD2)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME3).withNamespace(NAMESPACE)))); + .withStream(new AirbyteStream().withName(STREAM_NAME3).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))))); final AirbyteStreamNameNamespacePair airbyteStreamNameNamespacePair = new AirbyteStreamNameNamespacePair("other", "other"); final StateManager stateManager = new StreamStateManager(createDefaultState(), catalog); @@ -172,13 +182,16 @@ void testToStateWithoutStreamPair() { final ConfiguredAirbyteCatalog catalog = new ConfiguredAirbyteCatalog() .withStreams(List.of( new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD1)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD2)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME3).withNamespace(NAMESPACE)))); + .withStream(new AirbyteStream().withName(STREAM_NAME3).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))))); final StateManager stateManager = new StreamStateManager(createDefaultState(), catalog); final AirbyteStateMessage airbyteStateMessage = stateManager.toState(Optional.empty()); @@ -193,10 +206,12 @@ void testToStateNullCursorField() { final ConfiguredAirbyteCatalog catalog = new ConfiguredAirbyteCatalog() .withStreams(List.of( new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE)) + .withStream(new AirbyteStream().withName(STREAM_NAME1).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withCursorField(List.of(CURSOR_FIELD1)), new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE)))); + .withStream(new AirbyteStream().withName(STREAM_NAME2).withNamespace(NAMESPACE) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))))); final StateManager stateManager = new StreamStateManager(createDefaultState(), catalog); final DbState expectedFirstDbState = new DbState() diff --git a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java index 9a816236ee6d..de776f3b129c 100644 --- a/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java +++ b/airbyte-protocol/protocol-models/src/main/java/io/airbyte/protocol/models/CatalogHelpers.java @@ -50,7 +50,8 @@ public static AirbyteStream createAirbyteStream(final String streamName, final S } public static AirbyteStream createAirbyteStream(final String streamName, final String namespace, final List fields) { - return new AirbyteStream().withName(streamName).withNamespace(namespace).withJsonSchema(fieldsToJsonSchema(fields)); + return new AirbyteStream().withName(streamName).withNamespace(namespace).withJsonSchema(fieldsToJsonSchema(fields)) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH)); } public static ConfiguredAirbyteCatalog createConfiguredAirbyteCatalog(final String streamName, final String namespace, final Field... fields) { @@ -67,7 +68,8 @@ public static ConfiguredAirbyteStream createConfiguredAirbyteStream(final String public static ConfiguredAirbyteStream createConfiguredAirbyteStream(final String streamName, final String namespace, final List fields) { return new ConfiguredAirbyteStream() - .withStream(new AirbyteStream().withName(streamName).withNamespace(namespace).withJsonSchema(fieldsToJsonSchema(fields))) + .withStream(new AirbyteStream().withName(streamName).withNamespace(namespace).withJsonSchema(fieldsToJsonSchema(fields)) + .withSupportedSyncModes(Lists.newArrayList(SyncMode.FULL_REFRESH))) .withSyncMode(SyncMode.FULL_REFRESH).withDestinationSyncMode(DestinationSyncMode.OVERWRITE); } diff --git a/airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml b/airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml index 92a5c0924eb8..731ab00a332d 100644 --- a/airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml +++ b/airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml @@ -228,8 +228,7 @@ definitions: required: - name - json_schema - # todo (cgardens) - make required once sources are migrated - # - supported_sync_modes + - supported_sync_modes properties: name: type: string @@ -239,7 +238,9 @@ definitions: type: object existingJavaType: com.fasterxml.jackson.databind.JsonNode supported_sync_modes: + description: List of sync modes supported by this stream. type: array + minItems: 1 items: "$ref": "#/definitions/SyncMode" source_defined_cursor: diff --git a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java index cc6c260848cd..761ef8f63a83 100644 --- a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java +++ b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java @@ -126,7 +126,7 @@ public class AirbyteAcceptanceTestHarness { private static final DockerImageName SOURCE_POSTGRES_IMAGE_NAME = DockerImageName.parse("debezium/postgres:13-alpine") .asCompatibleSubstituteFor("postgres"); - private static final String SOURCE_E2E_TEST_CONNECTOR_VERSION = "0.1.1"; + private static final String SOURCE_E2E_TEST_CONNECTOR_VERSION = "0.1.2"; private static final String DESTINATION_E2E_TEST_CONNECTOR_VERSION = "0.1.1"; public static final String POSTGRES_SOURCE_LEGACY_CONNECTOR_VERSION = "0.4.26"; diff --git a/docs/integrations/sources/azure-table.md b/docs/integrations/sources/azure-table.md index 78c3bc42f7cd..108b90361c94 100644 --- a/docs/integrations/sources/azure-table.md +++ b/docs/integrations/sources/azure-table.md @@ -33,13 +33,13 @@ Azure Table Storage uses different [property](https://docs.microsoft.com/en-us/r ### Features -| Feature | Supported? | -| :--- | :--- | -| Full Refresh Sync | Yes | -| Incremental - Append Sync | Yes | -| Incremental - Dedupe Sync | No | -| SSL connection | Yes | -| Namespaces | No | +| Feature | Supported? | +| :------------------------ | :--------- | +| Full Refresh Sync | Yes | +| Incremental - Append Sync | Yes | +| Incremental - Dedupe Sync | No | +| SSL connection | Yes | +| Namespaces | No | ### Performance considerations @@ -65,8 +65,9 @@ We recommend creating a restricted key specifically for Airbyte access. This wil ## Changelog -| Version | Date | Pull Request | Subject | -| :--- | :--- | :--- | :--- | -| 0.1.2 | 2021-12-23 | [14212](https://github.com/airbytehq/airbyte/pull/14212) | Adding incremental load capability | -| 0.1.1 | 2021-12-23 | [8434](https://github.com/airbytehq/airbyte/pull/8434) | Update fields in source-connectors specifications | +| Version | Date | Pull Request | Subject | +| :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------ | +| 0.1.3 | 2022-08-12 | [15591](https://github.com/airbytehq/airbyte/pull/15591) | Clean instantiation of AirbyteStream | +| 0.1.2 | 2021-12-23 | [14212](https://github.com/airbytehq/airbyte/pull/14212) | Adding incremental load capability | +| 0.1.1 | 2021-12-23 | [8434](https://github.com/airbytehq/airbyte/pull/8434) | Update fields in source-connectors specifications | diff --git a/docs/integrations/sources/e2e-test.md b/docs/integrations/sources/e2e-test.md index ab2a0a8f185c..12a9a7ce22df 100644 --- a/docs/integrations/sources/e2e-test.md +++ b/docs/integrations/sources/e2e-test.md @@ -16,15 +16,15 @@ The single-stream catalog config exists just for convenient, since in many testi Here is its configuration: -| Mock Catalog Type | Parameters | Type | Required | Default | Notes | -| --- | --- | --- | --- | --- | --- | -| Single-stream | stream name | string | yes | | Name of the stream in the catalog. | -| | stream schema | json | yes | | Json schema of the stream in the catalog. It must be a valid Json schema. | -| | stream duplication | integer | no | 1 | Duplicate the stream N times to quickly create a multi-stream catalog. | -| Multi-stream | streams and schemas | json | yes | | A Json object specifying multiple data streams and their schemas. Each key in this object is one stream name. Each value is the schema for that stream. | -| Both | max records | integer | yes | 100 | The number of record messages to emit from this connector. Min 1. Max 100 billion. | -| | random seed | integer | no | current time millis | The seed is used in random Json object generation. Min 0. Max 1 million. | -| | message interval | integer | no | 0 | The time interval between messages in millisecond. Min 0 ms. Max 60000 ms (1 minute). | +| Mock Catalog Type | Parameters | Type | Required | Default | Notes | +| ----------------- | ------------------- | ------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Single-stream | stream name | string | yes | | Name of the stream in the catalog. | +| | stream schema | json | yes | | Json schema of the stream in the catalog. It must be a valid Json schema. | +| | stream duplication | integer | no | 1 | Duplicate the stream N times to quickly create a multi-stream catalog. | +| Multi-stream | streams and schemas | json | yes | | A Json object specifying multiple data streams and their schemas. Each key in this object is one stream name. Each value is the schema for that stream. | +| Both | max records | integer | yes | 100 | The number of record messages to emit from this connector. Min 1. Max 100 billion. | +| | random seed | integer | no | current time millis | The seed is used in random Json object generation. Min 0. Max 1 million. | +| | message interval | integer | no | 0 | The time interval between messages in millisecond. Min 0 ms. Max 60000 ms (1 minute). | ### Legacy Infinite Feed @@ -46,10 +46,10 @@ This mode can generate infinite number of records, which can be dangerous. That' There are two configurable parameters: -| Parameters | Type | Required | Default | Notes | -| --- | --- | --- | --- | --- | -| max records | integer | no | `null` | Number of message records to emit. When it is left empty, the connector will generate infinite number of messages. | -| message interval | integer | no | `null` | Time interval between messages in millisecond. | +| Parameters | Type | Required | Default | Notes | +| ---------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------ | +| max records | integer | no | `null` | Number of message records to emit. When it is left empty, the connector will generate infinite number of messages. | +| message interval | integer | no | `null` | Time interval between messages in millisecond. | ### Exception after N @@ -61,13 +61,14 @@ This mode is also excluded from the Cloud variant of this connector. The OSS and Cloud variants have the same version number. The Cloud variant was initially released at version `1.0.0`. -| Version | Date | Pull request | Notes | -| --- | --- | --- | --- | -| 2.1.1 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 2.1.0 | 2021-02-12 | [\#10298](https://github.com/airbytehq/airbyte/pull/10298) | Support stream duplication to quickly create a multi-stream catalog. | -| 2.0.0 | 2021-02-01 | [\#9954](https://github.com/airbytehq/airbyte/pull/9954) | Remove legacy modes. Use more efficient Json generator. | -| 1.0.1 | 2021-01-29 | [\#9745](https://github.com/airbytehq/airbyte/pull/9745) | Integrate with Sentry. | -| 1.0.0 | 2021-01-23 | [\#9720](https://github.com/airbytehq/airbyte/pull/9720) | Add new continuous feed mode that supports arbitrary catalog specification. Initial release to cloud. | -| 0.1.2 | 2022-10-18 | [\#18100](https://github.com/airbytehq/airbyte/pull/18100) | Set supported sync mode on streams | -| 0.1.1 | 2021-12-16 | [\#8217](https://github.com/airbytehq/airbyte/pull/8217) | Fix sleep time in infinite feed mode. | -| 0.1.0 | 2021-07-23 | [\#3290](https://github.com/airbytehq/airbyte/pull/3290) [\#4939](https://github.com/airbytehq/airbyte/pull/4939) | Initial release. | +| Version | Date | Pull request | Notes | +| ------- | ---------- | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| 2.1.3 | 2022-08-25 | [15591](https://github.com/airbytehq/airbyte/pull/15591) | Declare supported sync modes in catalogs | +| 2.1.1 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 2.1.0 | 2021-02-12 | [\#10298](https://github.com/airbytehq/airbyte/pull/10298) | Support stream duplication to quickly create a multi-stream catalog. | +| 2.0.0 | 2021-02-01 | [\#9954](https://github.com/airbytehq/airbyte/pull/9954) | Remove legacy modes. Use more efficient Json generator. | +| 1.0.1 | 2021-01-29 | [\#9745](https://github.com/airbytehq/airbyte/pull/9745) | Integrate with Sentry. | +| 1.0.0 | 2021-01-23 | [\#9720](https://github.com/airbytehq/airbyte/pull/9720) | Add new continuous feed mode that supports arbitrary catalog specification. Initial release to cloud. | +| 0.1.2 | 2022-10-18 | [\#18100](https://github.com/airbytehq/airbyte/pull/18100) | Set supported sync mode on streams | +| 0.1.1 | 2021-12-16 | [\#8217](https://github.com/airbytehq/airbyte/pull/8217) | Fix sleep time in infinite feed mode. | +| 0.1.0 | 2021-07-23 | [\#3290](https://github.com/airbytehq/airbyte/pull/3290) [\#4939](https://github.com/airbytehq/airbyte/pull/4939) | Initial release. | diff --git a/docs/integrations/sources/google-sheets.md b/docs/integrations/sources/google-sheets.md index f78f741a8f62..976be4c54037 100644 --- a/docs/integrations/sources/google-sheets.md +++ b/docs/integrations/sources/google-sheets.md @@ -71,6 +71,7 @@ The [Google API rate limit](https://developers.google.com/sheets/api/limits) is | Version | Date | Pull Request | Subject | | ------- | ---------- | -------------------------------------------------------- | ----------------------------------------------------------------------------- | +| 0.2.21 | 2022-10-04 | [15591](https://github.com/airbytehq/airbyte/pull/15591) | Clean instantiation of AirbyteStream | | 0.2.20 | 2022-10-10 | [17766](https://github.com/airbytehq/airbyte/pull/17766) | Fix null pointer exception when parsing the spreadsheet id. | | 0.2.19 | 2022-09-29 | [17410](https://github.com/airbytehq/airbyte/pull/17410) | Use latest CDK. | | 0.2.18 | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states. | From 19e6fadd94808c8c4801aea10b1dcd860e1b949d Mon Sep 17 00:00:00 2001 From: Teal Larson Date: Wed, 19 Oct 2022 10:16:59 -0400 Subject: [PATCH 186/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=8E=A8=20Show=20Sc?= =?UTF-8?q?hema=20Update=20Errors=20to=20User=20(#18008)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * show schema update error * remove test code * use i18n for error message label and string * update snapshot of create connection * fix intl string parsing * update snapshot --- .../CreateConnection/SchemaError.tsx | 2 +- .../CreateConnection/TryAfterErrorBlock.tsx | 31 ++++++++++++------- .../CreateConnectionForm.test.tsx.snap | 5 +++ airbyte-webapp/src/locales/en.json | 1 + 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/airbyte-webapp/src/components/CreateConnection/SchemaError.tsx b/airbyte-webapp/src/components/CreateConnection/SchemaError.tsx index bfa7fc2d00ee..d49f791d9285 100644 --- a/airbyte-webapp/src/components/CreateConnection/SchemaError.tsx +++ b/airbyte-webapp/src/components/CreateConnection/SchemaError.tsx @@ -12,7 +12,7 @@ export const SchemaError = ({ schemaError }: { schemaError: SchemaErrorType }) = const { refreshSchema } = useConnectionFormService(); return ( - + {job && } ); diff --git a/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx b/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx index 276bf1364494..4bb6a7a091bf 100644 --- a/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx +++ b/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx @@ -8,18 +8,25 @@ import { Text } from "components/ui/Text"; import styles from "./TryAfterErrorBlock.module.scss"; interface TryAfterErrorBlockProps { - message?: React.ReactNode; + message?: string; onClick: () => void; } -export const TryAfterErrorBlock: React.FC = ({ message, onClick }) => ( -
    - - - {message || } - - -
    -); +export const TryAfterErrorBlock: React.FC = ({ message, onClick }) => { + return ( +
    + + + + + {message && ( + + + + )} + +
    + ); +}; diff --git a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap index 19aca020b0a1..bcb658201f49 100644 --- a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap +++ b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap @@ -996,6 +996,11 @@ exports[`CreateConnectionForm should render with an error 1`] = ` > Failed to fetch schema. Please try again

    +

    + Error: Test Error +

    + ), headerHighlighted: true, accessor: "name", @@ -90,14 +91,15 @@ const ConnectionTable: React.FC = ({ data, entity, onClickRow, onSync }) }, { Header: ( - + onSortClick("entityName")} + isActive={sortBy === "entityName"} + isAscending={sortOrder === SortOrderEnum.ASC} + > + + ), headerHighlighted: true, accessor: "entityName", @@ -112,14 +114,13 @@ const ConnectionTable: React.FC = ({ data, entity, onClickRow, onSync }) }, { Header: ( - + onSortClick("connectorName")} + isActive={sortBy === "connectorName"} + isAscending={sortOrder === SortOrderEnum.ASC} + > + + ), accessor: "connectorName", Cell: ({ cell, row }: CellProps) => ( @@ -136,10 +137,13 @@ const ConnectionTable: React.FC = ({ data, entity, onClickRow, onSync }) }, { Header: ( - + ), accessor: "lastSync", Cell: ({ cell, row }: CellProps) => ( diff --git a/airbyte-webapp/src/components/EntityTable/ImplementationTable.module.scss b/airbyte-webapp/src/components/EntityTable/ImplementationTable.module.scss index 2e7adc15fd8e..798678056755 100644 --- a/airbyte-webapp/src/components/EntityTable/ImplementationTable.module.scss +++ b/airbyte-webapp/src/components/EntityTable/ImplementationTable.module.scss @@ -3,14 +3,3 @@ padding-left: 32px !important; } } - -.tableHeaderButton { - display: inline-flex; - font-size: inherit; - text-transform: inherit; - color: inherit; - background-color: inherit; - border: none; - font-weight: inherit; - cursor: pointer; -} diff --git a/airbyte-webapp/src/components/EntityTable/ImplementationTable.tsx b/airbyte-webapp/src/components/EntityTable/ImplementationTable.tsx index 156e5d114dd5..6c06dee979a6 100644 --- a/airbyte-webapp/src/components/EntityTable/ImplementationTable.tsx +++ b/airbyte-webapp/src/components/EntityTable/ImplementationTable.tsx @@ -4,7 +4,7 @@ import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; import { CellProps } from "react-table"; -import { Table } from "components/ui/Table"; +import { Table, SortableTableHeader } from "components/ui/Table"; import { useQuery } from "hooks/useQuery"; @@ -13,7 +13,6 @@ import ConnectEntitiesCell from "./components/ConnectEntitiesCell"; import ConnectorCell from "./components/ConnectorCell"; import LastSyncCell from "./components/LastSyncCell"; import NameCell from "./components/NameCell"; -import SortIcon from "./components/SortIcon"; import styles from "./ImplementationTable.module.scss"; import { EntityTableDataItem, SortOrderEnum } from "./types"; @@ -69,10 +68,13 @@ const ImplementationTable: React.FC = ({ data, entity, onClickRow }) => () => [ { Header: ( - + ), headerHighlighted: true, accessor: "entityName", @@ -83,10 +85,13 @@ const ImplementationTable: React.FC = ({ data, entity, onClickRow }) => }, { Header: ( - + ), accessor: "connectorName", Cell: ({ cell, row }: CellProps) => ( @@ -102,10 +107,13 @@ const ImplementationTable: React.FC = ({ data, entity, onClickRow }) => }, { Header: ( - + ), accessor: "lastSync", Cell: ({ cell, row }: CellProps) => ( diff --git a/airbyte-webapp/src/components/EntityTable/components/SortIcon.module.scss b/airbyte-webapp/src/components/EntityTable/components/SortIcon.module.scss deleted file mode 100644 index 7e0dfe796b3a..000000000000 --- a/airbyte-webapp/src/components/EntityTable/components/SortIcon.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.sortIcon { - margin-left: 10px; -} diff --git a/airbyte-webapp/src/components/EntityTable/components/SortIcon.tsx b/airbyte-webapp/src/components/EntityTable/components/SortIcon.tsx deleted file mode 100644 index 4fa76b0023d8..000000000000 --- a/airbyte-webapp/src/components/EntityTable/components/SortIcon.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import * as React from "react"; - -import styles from "./SortIcon.module.scss"; - -interface SortIconProps { - lowToLarge?: boolean; - wasActive?: boolean; -} - -const SortIcon: React.FC = ({ wasActive, lowToLarge }) => ( - -); - -export default SortIcon; diff --git a/airbyte-webapp/src/components/ui/Table/SortableTableHeader/SortableTableHeader.module.scss b/airbyte-webapp/src/components/ui/Table/SortableTableHeader/SortableTableHeader.module.scss new file mode 100644 index 000000000000..07a102f655e1 --- /dev/null +++ b/airbyte-webapp/src/components/ui/Table/SortableTableHeader/SortableTableHeader.module.scss @@ -0,0 +1,14 @@ +.sortButton { + background-color: inherit; + border: none; + color: inherit; + cursor: pointer; + font-size: inherit; + font-weight: inherit; + text-transform: inherit; + white-space: nowrap; +} + +.sortIcon { + margin-left: 10px; +} diff --git a/airbyte-webapp/src/components/ui/Table/SortableTableHeader/index.tsx b/airbyte-webapp/src/components/ui/Table/SortableTableHeader/index.tsx new file mode 100644 index 000000000000..b83636615d25 --- /dev/null +++ b/airbyte-webapp/src/components/ui/Table/SortableTableHeader/index.tsx @@ -0,0 +1,23 @@ +import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React, { PropsWithChildren } from "react"; + +import styles from "./SortableTableHeader.module.scss"; + +interface SortableTableHeaderProps { + onClick: () => void; + isActive: boolean; + isAscending: boolean; +} + +export const SortableTableHeader: React.FC> = ({ + onClick, + isActive, + isAscending, + children, +}) => ( + +); diff --git a/airbyte-webapp/src/components/ui/Table/index.ts b/airbyte-webapp/src/components/ui/Table/index.ts index e40efa4761d0..11dec96097b6 100644 --- a/airbyte-webapp/src/components/ui/Table/index.ts +++ b/airbyte-webapp/src/components/ui/Table/index.ts @@ -1 +1,2 @@ export * from "./Table"; +export { SortableTableHeader } from "./SortableTableHeader"; diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.module.scss b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.module.scss deleted file mode 100644 index 2e032aaea465..000000000000 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.module.scss +++ /dev/null @@ -1,7 +0,0 @@ -.tableHeaderButton { - user-select: none; - - &:hover { - cursor: pointer; - } -} diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.tsx b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.tsx index e552120503c2..8765cc839d49 100644 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.tsx +++ b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/UsagePerConnectionTable.tsx @@ -5,9 +5,8 @@ import { useNavigate } from "react-router-dom"; import { CellProps } from "react-table"; import styled from "styled-components"; -import SortIcon from "components/EntityTable/components/SortIcon"; import { SortOrderEnum } from "components/EntityTable/types"; -import { Table } from "components/ui/Table"; +import { Table, SortableTableHeader } from "components/ui/Table"; import { useQuery } from "hooks/useQuery"; import { CreditConsumptionByConnector } from "packages/cloud/lib/domain/cloudWorkspaces/types"; @@ -16,7 +15,6 @@ import { useSourceDefinitionList } from "services/connector/SourceDefinitionServ import ConnectionCell from "./ConnectionCell"; import UsageCell from "./UsageCell"; -import styles from "./UsagePerConnectionTable.module.scss"; const Content = styled.div` padding: 0 60px 0 15px; @@ -112,10 +110,13 @@ const UsagePerConnectionTable: React.FC = ({ credi () => [ { Header: ( - + ), customWidth: 30, accessor: "sourceDefinitionName", @@ -130,10 +131,13 @@ const UsagePerConnectionTable: React.FC = ({ credi }, { Header: ( - + ), accessor: "creditsConsumed", collapse: true, From 141e0854c6932c0c7e2cfb2fc8dc284e7e52bd69 Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Wed, 19 Oct 2022 16:27:04 -0400 Subject: [PATCH 203/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=A7=B9=20Cleanup?= =?UTF-8?q?=20CatalogTree=20components=20(#18089)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update ConnectionForm Headings to be consistent Move styles in SyncCatalogField from styled-components to SCSS * Move CatalogTree related components from ConnectionForm to CatalogTree * Extract header and subheader from CatalogTree component, fix minor styling issues * Disable Refresh schema button on submit * Update test snapshots * CatalogTreeRows -> CatalogTreeBody * Cleanup StreamHeader implementation in CatalogSection * Fix provider and hook names in BulkEdit service --- .../CreateConnectionForm.test.tsx.snap | 64 +++--- .../services/BulkEdit/BulkEditService.tsx | 17 +- .../ConnectionReplicationTab.test.tsx.snap | 64 +++--- .../CatalogTree/CatalogSection.module.scss | 2 +- .../Connection/CatalogTree/CatalogSection.tsx | 29 ++- .../CatalogTree/CatalogTree.module.scss | 17 -- .../Connection/CatalogTree/CatalogTree.tsx | 84 +++---- .../CatalogTree/CatalogTreeBody.module.scss | 9 + .../CatalogTree/CatalogTreeBody.tsx | 59 +++++ .../CatalogTree/CatalogTreeHeader.module.scss | 28 +++ .../CatalogTree/CatalogTreeHeader.tsx | 76 ++++++ .../CatalogTreeSearch.tsx} | 6 +- .../CatalogTreeSubheader.module.scss | 19 ++ .../CatalogTree/CatalogTreeSubheader.tsx | 52 +++++ .../CatalogTree/StreamHeader.module.scss | 4 +- .../Connection/CatalogTree/StreamHeader.tsx | 34 +-- .../CatalogTree/components/BulkHeader.tsx | 4 +- .../views/Connection/CatalogTree/index.tsx | 4 +- .../views/Connection/CatalogTree/styles.tsx | 3 +- .../ConnectionForm/ConnectionFormFields.tsx | 8 +- .../ConnectionForm/components/Section.tsx | 2 +- .../components/SyncCatalogField.module.scss | 46 +--- .../components/SyncCatalogField.tsx | 216 ++---------------- 23 files changed, 439 insertions(+), 408 deletions(-) delete mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTree.module.scss create mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.module.scss create mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.tsx create mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.module.scss create mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.tsx rename airbyte-webapp/src/views/Connection/{ConnectionForm/components/Search.tsx => CatalogTree/CatalogTreeSearch.tsx} (84%) create mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.module.scss create mode 100644 airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.tsx diff --git a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap index bcb658201f49..b27da6f756ff 100644 --- a/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap +++ b/airbyte-webapp/src/components/CreateConnection/__snapshots__/CreateConnectionForm.test.tsx.snap @@ -71,11 +71,11 @@ exports[`CreateConnectionForm should render 1`] = `
    -
    Transfer -
    +
    @@ -192,11 +192,11 @@ exports[`CreateConnectionForm should render 1`] = `
    -
    Streams -
    + @@ -362,40 +362,40 @@ exports[`CreateConnectionForm should render 1`] = `
    -
    -
    - Activate the streams you want to sync -
    - -
    + + + Refresh source schema +
    + +
    +
    diff --git a/airbyte-webapp/src/hooks/services/BulkEdit/BulkEditService.tsx b/airbyte-webapp/src/hooks/services/BulkEdit/BulkEditService.tsx index 9d0289bef35d..1445d358c1bd 100644 --- a/airbyte-webapp/src/hooks/services/BulkEdit/BulkEditService.tsx +++ b/airbyte-webapp/src/hooks/services/BulkEdit/BulkEditService.tsx @@ -5,9 +5,9 @@ import { useSet } from "react-use"; import { SyncSchemaStream } from "core/domain/catalog"; import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; -const Context = React.createContext(null); +const Context = React.createContext(null); -interface BatchContext { +export interface BulkEditServiceContext { isActive: boolean; toggleNode: (id: string | undefined) => void; onCheckAll: () => void; @@ -28,7 +28,7 @@ const defaultOptions: Partial = { selected: false, }; -const BatchEditProvider: React.FC< +export const BulkEditServiceProvider: React.FC< React.PropsWithChildren<{ nodes: SyncSchemaStream[]; update: (streams: SyncSchemaStream[]) => void; @@ -59,7 +59,7 @@ const BatchEditProvider: React.FC< const isActive = selectedBatchNodes.size > 0; const allChecked = selectedBatchNodes.size === nodes.length; - const ctx: BatchContext = { + const ctx: BulkEditServiceContext = { isActive, toggleNode: toggle, onCheckAll: () => (allChecked ? reset() : nodes.forEach((n) => add(n.id))), @@ -75,7 +75,7 @@ const BatchEditProvider: React.FC< return {children}; }; -const useBulkEdit = (): BatchContext => { +export const useBulkEditService = (): BulkEditServiceContext => { const ctx = useContext(Context); if (!ctx) { @@ -85,12 +85,9 @@ const useBulkEdit = (): BatchContext => { return ctx; }; -const useBulkEditSelect = (id: string | undefined): [boolean, () => void] => { - const { selectedBatchNodeIds, toggleNode } = useBulkEdit(); +export const useBulkEditSelect = (id: string | undefined): [boolean, () => void] => { + const { selectedBatchNodeIds, toggleNode } = useBulkEditService(); const isIncluded = id !== undefined && selectedBatchNodeIds.includes(id); return useMemo(() => [isIncluded, () => toggleNode(id)], [isIncluded, toggleNode, id]); }; - -export type { BatchContext }; -export { useBulkEditSelect, useBulkEdit, BatchEditProvider }; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap index d0c19fcb223a..2c78822b81e5 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap @@ -18,11 +18,11 @@ exports[`ConnectionReplicationTab should render 1`] = `
    -
    Transfer -
    +
    @@ -139,11 +139,11 @@ exports[`ConnectionReplicationTab should render 1`] = `
    -
    Streams -
    + @@ -309,40 +309,40 @@ exports[`ConnectionReplicationTab should render 1`] = `
    -
    -
    - Activate the streams you want to sync -
    - -
    + + + Refresh source schema +
    + +
    +
    diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss index 480b35bb8e28..1ccdb8880366 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss @@ -1,7 +1,7 @@ @use "../../../scss/colors"; .streamFieldTableContainer { - margin-left: 83px; + margin-left: 85px; background: colors.$grey-50; } diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx index ab755a16a444..c408151a8426 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx @@ -44,6 +44,7 @@ const CatalogSectionInner: React.FC = ({ const { destDefinition: { supportedDestinationSyncModes }, } = useConnectionFormService(); + const { mode } = useConnectionFormService(); const [isRowExpanded, onExpand] = useToggle(false); const { stream, config } = streamNode; @@ -107,10 +108,11 @@ const CatalogSectionInner: React.FC = ({ [stream?.supportedSyncModes, supportedDestinationSyncModes] ); - const destNamespace = useDestinationNamespace({ - namespaceDefinition, - namespaceFormat, - }); + const destNamespace = + useDestinationNamespace({ + namespaceDefinition, + namespaceFormat, + }) ?? ""; const fields = useMemo(() => { const traversedFields = traverseSchemaToField(stream?.jsonSchema, stream?.name); @@ -125,31 +127,36 @@ const CatalogSectionInner: React.FC = ({ [flattenedFields] ); + const destName = prefix + (streamNode.stream?.name ?? ""); const configErrors = getIn(errors, `schema.streams[${streamNode.id}].config`); const hasError = configErrors && Object.keys(configErrors).length > 0; - const hasChildren = fields && fields.length > 0; + const pkType = getPathType(pkRequired, shouldDefinePk); + const cursorType = getPathType(cursorRequired, shouldDefineCursor); + const hasFields = fields?.length > 0; + const disabled = mode === "readonly"; return (
    - {isRowExpanded && hasChildren && ( + {isRowExpanded && hasFields && (
    void; + onStreamsChanged: (streams: SyncSchemaStream[]) => void; + isLoading: boolean; } -const CatalogTree: React.FC = ({ streams, onChangeStream }) => { +const CatalogTreeComponent: React.FC> = ({ + streams, + onStreamsChanged, + isLoading, +}) => { const { mode } = useConnectionFormService(); - const onUpdateStream = useCallback( - (id: string | undefined, newConfig: Partial) => { - const streamNode = streams.find((streamNode) => streamNode.id === id); - if (streamNode) { - const newStreamNode = setIn(streamNode, "config", { ...streamNode.config, ...newConfig }); + const [searchString, setSearchString] = useState(""); + + const onSingleStreamChanged = useCallback( + (newValue: SyncSchemaStream) => onStreamsChanged(streams.map((str) => (str.id === newValue.id ? newValue : str))), + [streams, onStreamsChanged] + ); - onChangeStream(newStreamNode); - } - }, - [streams, onChangeStream] + const sortedSchema = useMemo( + () => streams.sort(naturalComparatorBy((syncStream) => syncStream.stream?.name ?? "")), + [streams] ); - const { initialValues } = useFormikContext(); + const filteredStreams = useMemo(() => { + const filters: Array<(s: SyncSchemaStream) => boolean> = [ + (_: SyncSchemaStream) => true, + searchString + ? (stream: SyncSchemaStream) => stream.stream?.name.toLowerCase().includes(searchString.toLowerCase()) + : null, + ].filter(Boolean) as Array<(s: SyncSchemaStream) => boolean>; - const changedStreams = streams.filter((stream, idx) => { - return stream.config?.selected !== initialValues.syncCatalog.streams[idx].config?.selected; - }); + return sortedSchema.filter((stream) => filters.every((f) => f(stream))); + }, [searchString, sortedSchema]); return ( - <> - {streams.map((streamNode) => ( - - {({ form }: FieldProps) => ( - - )} - - ))} - + + + {mode !== "readonly" && } + + + + + + ); }; -export default CatalogTree; +export const CatalogTree = React.memo(CatalogTreeComponent); diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.module.scss b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.module.scss new file mode 100644 index 000000000000..ee90e5bb0977 --- /dev/null +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.module.scss @@ -0,0 +1,9 @@ +.container { + margin-bottom: 29px; + max-height: 600px; + overflow-y: auto; + + --webkit-overlay: true; + + width: 100%; +} diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.tsx new file mode 100644 index 000000000000..a61d55a9897e --- /dev/null +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.tsx @@ -0,0 +1,59 @@ +import { Field, FieldProps, setIn, useFormikContext } from "formik"; +import React, { useCallback } from "react"; + +import { SyncSchemaStream } from "core/domain/catalog"; +import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { ConnectionFormValues, FormikConnectionFormValues } from "views/Connection/ConnectionForm/formConfig"; + +import { CatalogSection } from "./CatalogSection"; +import styles from "./CatalogTreeBody.module.scss"; + +interface CatalogTreeBodyProps { + streams: SyncSchemaStream[]; + onStreamChanged: (stream: SyncSchemaStream) => void; +} + +export const CatalogTreeBody: React.FC = ({ streams, onStreamChanged }) => { + const { mode } = useConnectionFormService(); + + const onUpdateStream = useCallback( + (id: string | undefined, newConfig: Partial) => { + const streamNode = streams.find((streamNode) => streamNode.id === id); + + if (streamNode) { + const newStreamNode = setIn(streamNode, "config", { ...streamNode.config, ...newConfig }); + + onStreamChanged(newStreamNode); + } + }, + [streams, onStreamChanged] + ); + + const { initialValues } = useFormikContext(); + + const changedStreams = streams.filter((stream, idx) => { + return stream.config?.selected !== initialValues.syncCatalog.streams[idx].config?.selected; + }); + + return ( +
    + {streams.map((streamNode) => ( + + {({ form }: FieldProps) => ( + + )} + + ))} +
    + ); +}; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.module.scss b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.module.scss new file mode 100644 index 000000000000..50c61f200503 --- /dev/null +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.module.scss @@ -0,0 +1,28 @@ +%headerCell { + font-size: 10px; + line-height: 13px; +} + +.checkboxCell { + @extend %headerCell; + + max-width: 43px; + text-align: center; +} + +.catalogHeader, +%catalogHeader { + min-height: 33px; + margin-bottom: 0 !important; + margin-left: 20px; +} + +.readonlyCatalogHeader { + @extend %catalogHeader; + + margin-left: 50px; +} + +.nextLineText { + margin-top: 10px; +} diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.tsx new file mode 100644 index 000000000000..7d2749c9629a --- /dev/null +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.tsx @@ -0,0 +1,76 @@ +import classnames from "classnames"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { Cell, Header } from "components/SimpleTableComponents"; +import { CheckBox } from "components/ui/CheckBox"; +import { InfoTooltip, TooltipLearnMoreLink } from "components/ui/Tooltip"; + +import { useBulkEditService } from "hooks/services/BulkEdit/BulkEditService"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { links } from "utils/links"; + +import styles from "./CatalogTreeHeader.module.scss"; + +export const CatalogTreeHeader: React.FC = () => { + const { mode } = useConnectionFormService(); + const { onCheckAll, selectedBatchNodeIds, allChecked } = useBulkEditService(); + const catalogHeaderStyle = classnames({ + [styles.catalogHeader]: mode !== "readonly", + [styles.readonlyCatalogHeader]: mode === "readonly", + }); + + return ( +
    + {mode !== "readonly" && ( + + 0 && !allChecked} + checked={allChecked} + /> + + )} + {mode !== "readonly" && } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    +
    + +
    + ); +}; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/Search.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSearch.tsx similarity index 84% rename from airbyte-webapp/src/views/Connection/ConnectionForm/components/Search.tsx rename to airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSearch.tsx index 56ad662fcbc9..9194a870fbe3 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/Search.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSearch.tsx @@ -4,7 +4,7 @@ import styled from "styled-components"; import { Input } from "components/ui/Input"; -interface SearchProps { +interface CatalogTreeSearchProps { onSearch: (value: string) => void; } @@ -21,7 +21,7 @@ const SearchContent = styled.div` } `; -const Search: React.FC = ({ onSearch }) => { +export const CatalogTreeSearch: React.FC = ({ onSearch }) => { const { formatMessage } = useIntl(); return ( @@ -35,5 +35,3 @@ const Search: React.FC = ({ onSearch }) => { ); }; - -export default Search; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.module.scss b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.module.scss new file mode 100644 index 000000000000..f737708a899b --- /dev/null +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.module.scss @@ -0,0 +1,19 @@ +%catalogHeader { + min-height: 33px; + margin-bottom: 0 !important; + margin-left: 20px; +} + +.catalogSubheader { + @extend %catalogHeader; + + padding-top: 10px; + margin-left: 155px; +} + +.readonlyCatalogSubheader { + @extend %catalogHeader; + + padding-top: 10px; + margin-left: 115px; +} diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.tsx new file mode 100644 index 000000000000..a2d78f6c447c --- /dev/null +++ b/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.tsx @@ -0,0 +1,52 @@ +import classnames from "classnames"; +import React from "react"; +import { FormattedMessage } from "react-intl"; +import styled from "styled-components"; + +import { Cell, Header } from "components/SimpleTableComponents"; + +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; + +import styles from "./CatalogTreeSubheader.module.scss"; + +const SubtitleCell = styled(Cell).attrs(() => ({ lighter: true }))` + font-size: 10px; + line-height: 12px; + border-top: 1px solid ${({ theme }) => theme.greyColor0}; + padding-top: 5px; +`; + +const ClearSubtitleCell = styled(SubtitleCell)` + border-top: none; +`; + +export const CatalogTreeSubheader: React.FC = () => { + const { mode } = useConnectionFormService(); + + const catalogSubheaderStyle = classnames({ + [styles.catalogSubheader]: mode !== "readonly", + [styles.readonlyCatalogSubheader]: mode === "readonly", + }); + + return ( +
    + + + + + + + + + + + + + + + + + +
    + ); +}; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss b/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss index 880c7cc69ade..576fef3a3806 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss +++ b/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss @@ -1,6 +1,6 @@ @use "../../../scss/colors"; @use "../../../scss/variables"; -@forward "./CatalogTree.module.scss"; +@forward "./CatalogTreeBody.module.scss"; .icon { margin-right: 7px; @@ -16,10 +16,10 @@ } .streamRowCheckboxCell { - width: 100%; display: flex; flex-direction: row; justify-content: flex-end; + padding-left: 27px; } .streamHeaderContent { diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx index 04bb2900619b..8ce7252da1d5 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx @@ -13,7 +13,6 @@ import { Switch } from "components/ui/Switch"; import { Path, SyncSchemaField, SyncSchemaStream } from "core/domain/catalog"; import { DestinationSyncMode, SyncMode } from "core/request/AirbyteClient"; import { useBulkEditSelect } from "hooks/services/BulkEdit/BulkEditService"; -import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { Arrow as ArrowBlock } from "./components/Arrow"; import { IndexerType, PathPopout } from "./components/PathPopout"; @@ -30,7 +29,7 @@ interface SyncSchema { destinationSyncMode: DestinationSyncMode; } -interface StreamHeaderProps { +export interface StreamHeaderProps { stream: SyncSchemaStream; destName: string; destNamespace: string; @@ -45,10 +44,11 @@ interface StreamHeaderProps { cursorType: IndexerType; onCursorChange: (cursorPath: Path) => void; isRowExpanded: boolean; - hasFields: boolean; + fields: SyncSchemaField[]; onExpand: () => void; changedSelected: boolean; hasError: boolean; + disabled?: boolean; } export const StreamHeader: React.FC = ({ @@ -64,14 +64,14 @@ export const StreamHeader: React.FC = ({ primitiveFields, cursorType, isRowExpanded, - hasFields, + fields, onExpand, changedSelected, hasError, + disabled, }) => { - const { mode } = useConnectionFormService(); const { primaryKey, syncMode, cursorField, destinationSyncMode } = stream.config ?? {}; - const isEnabled = stream.config?.selected; + const isStreamEnabled = stream.config?.selected; const { defaultCursorField } = stream.stream ?? {}; const syncSchema = useMemo( @@ -86,14 +86,16 @@ export const StreamHeader: React.FC = ({ const paths = primitiveFields.map((field) => field.path); + const hasFields = fields && fields.length > 0; + const iconStyle = classnames(styles.icon, { - [styles.plus]: isEnabled, - [styles.minus]: !isEnabled, + [styles.plus]: isStreamEnabled, + [styles.minus]: !isStreamEnabled, }); const streamHeaderContentStyle = classnames(styles.streamHeaderContent, { - [styles.greenBackground]: changedSelected && isEnabled, - [styles.redBackground]: changedSelected && !isEnabled, + [styles.greenBackground]: changedSelected && isStreamEnabled, + [styles.redBackground]: changedSelected && !isStreamEnabled, [styles.purpleBackground]: isSelected, [styles.redBorder]: hasError, }); @@ -101,11 +103,11 @@ export const StreamHeader: React.FC = ({ return ( - {mode !== "readonly" && ( + {!disabled && (
    {changedSelected && (
    - {isEnabled ? ( + {isStreamEnabled ? ( ) : ( @@ -120,7 +122,7 @@ export const StreamHeader: React.FC = ({
    - + {stream.stream?.namespace || ( @@ -133,12 +135,12 @@ export const StreamHeader: React.FC = ({ {stream.stream?.name} - {mode !== "readonly" ? ( - - ) : ( + {disabled ? ( {syncSchema.syncMode} | {syncSchema.destinationSyncMode} + ) : ( + )} diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx index db1b31d85484..fabf20d4b6e3 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx @@ -9,7 +9,7 @@ import { Switch } from "components/ui/Switch"; import { SyncSchemaField, SyncSchemaFieldObject, SyncSchemaStream, traverseSchemaToField } from "core/domain/catalog"; import { DestinationSyncMode, SyncMode } from "core/request/AirbyteClient"; -import { useBulkEdit } from "hooks/services/BulkEdit/BulkEditService"; +import { useBulkEditService } from "hooks/services/BulkEdit/BulkEditService"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { SUPPORTED_MODES } from "../../ConnectionForm/formConfig"; @@ -57,7 +57,7 @@ export const BulkHeader: React.FC = () => { const { destDefinition: { supportedDestinationSyncModes }, } = useConnectionFormService(); - const { selectedBatchNodes, options, onChangeOption, onApply, isActive, onCancel } = useBulkEdit(); + const { selectedBatchNodes, options, onChangeOption, onApply, isActive, onCancel } = useBulkEditService(); const availableSyncModes = useMemo( () => diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/index.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/index.tsx index a4f0abfa2380..b702bdbaf9f5 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/index.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogTree/index.tsx @@ -1,3 +1 @@ -import CatalogTree from "./CatalogTree"; - -export default CatalogTree; +export { CatalogTree } from "./CatalogTree"; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/styles.tsx b/airbyte-webapp/src/views/Connection/CatalogTree/styles.tsx index 34ee58b6219f..47c7bf64fcd9 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/styles.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogTree/styles.tsx @@ -8,9 +8,8 @@ export const HeaderCell = styled(Cell)` `; export const CheckboxCell = styled(HeaderCell)` - max-width: 43px; + max-width: 55px; text-align: center; - margin-left: -43px; `; export const ArrowCell = styled(HeaderCell)` diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx index 74e7de6589bb..7e751d53434c 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx @@ -20,7 +20,7 @@ import { ValuesProps } from "hooks/services/useConnectionHook"; import { NamespaceDefinitionField } from "./components/NamespaceDefinitionField"; import { useRefreshSourceSchemaWithConfirmationOnDirty } from "./components/refreshSourceSchemaWithConfirmationOnDirty"; import { Section } from "./components/Section"; -import SchemaField from "./components/SyncCatalogField"; +import { SyncCatalogField } from "./components/SyncCatalogField"; import styles from "./ConnectionFormFields.module.scss"; import { FormikConnectionFormValues } from "./formConfig"; import { ScheduleField } from "./ScheduleField"; @@ -55,7 +55,7 @@ export const ConnectionFormFields: React.FC = ({ valu
    - + @@ -119,10 +119,10 @@ export const ConnectionFormFields: React.FC = ({ valu
    + diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/Section.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/Section.tsx index 40329d44f7f7..e9701a09f24b 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/Section.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/Section.tsx @@ -11,7 +11,7 @@ export const Section: React.FC> = ({ title
    {title && ( - + {title} )} diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.module.scss b/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.module.scss index dc2adc80d2d5..c9d8c72bec06 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.module.scss +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.module.scss @@ -1,38 +1,10 @@ -%headerCell { - font-size: 10px; - line-height: 13px; -} - -.checkboxCell { - @extend %headerCell; - - max-width: 43px; - text-align: center; -} - -.catalogHeader, -%catalogHeader { - min-height: 33px; - margin-bottom: 0 !important; - margin-left: 20px; -} - -.readonlyCatalogHeader { - @extend %catalogHeader; - - margin-left: 50px; -} - -.catalogSubheader { - @extend %catalogHeader; - - padding-top: 10px; - margin-left: 155px; -} - -.readonlyCatalogSubheader { - @extend %catalogHeader; - - padding-top: 10px; - margin-left: 115px; +.header { + margin: 10px 0 6px; + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + font-weight: 500; + font-size: 14px; + line-height: 17px; } diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx index ef7d38a12f26..c8d5603809d0 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx @@ -1,226 +1,52 @@ -import classnames from "classnames"; import { FieldProps } from "formik"; -import React, { useCallback, useMemo, useState } from "react"; +import React, { useCallback } from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; -import { H5 } from "components/base/Titles"; -import { Cell, Header } from "components/SimpleTableComponents"; -import { CheckBox } from "components/ui/CheckBox"; -import { LoadingBackdrop } from "components/ui/LoadingBackdrop"; -import { InfoTooltip, TooltipLearnMoreLink } from "components/ui/Tooltip"; +import { Text } from "components/ui/Text"; import { SyncSchemaStream } from "core/domain/catalog"; import { DestinationSyncMode } from "core/request/AirbyteClient"; -import { BatchEditProvider, useBulkEdit } from "hooks/services/BulkEdit/BulkEditService"; -import { ConnectionFormMode, useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; -import { links } from "utils/links"; -import { naturalComparatorBy } from "utils/objects"; -import CatalogTree from "views/Connection/CatalogTree"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { CatalogTree } from "views/Connection/CatalogTree"; -import { BulkHeader } from "../../CatalogTree/components/BulkHeader"; -import Search from "./Search"; import styles from "./SyncCatalogField.module.scss"; -const TreeViewContainer = styled.div` - margin-bottom: 29px; - max-height: 600px; - overflow-y: auto; - --webkit-overlay: true; - - width: 100%; -`; - -const SubtitleCell = styled(Cell).attrs(() => ({ lighter: true }))` - font-size: 10px; - line-height: 12px; - border-top: 1px solid ${({ theme }) => theme.greyColor0}; - padding-top: 5px; -`; - -const ClearSubtitleCell = styled(SubtitleCell)` - border-top: none; -`; - -const HeaderBlock = styled.div` - margin: 10px 0 6px; - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: row; - font-weight: 500; - font-size: 14px; - line-height: 17px; -`; - -const NextLineText = styled.div` - margin-top: 10px; -`; - interface SchemaViewProps extends FieldProps { additionalControl?: React.ReactNode; destinationSupportedSyncModes: DestinationSyncMode[]; isSubmitting: boolean; - mode?: ConnectionFormMode; } -const CatalogHeader: React.FC = () => { - const { mode } = useConnectionFormService(); - const { onCheckAll, selectedBatchNodeIds, allChecked } = useBulkEdit(); - const catalogHeaderStyle = classnames({ - [styles.catalogHeader]: mode !== "readonly", - [styles.readonlyCatalogHeader]: mode === "readonly", - }); - - return ( -
    - {mode !== "readonly" && ( - - 0 && !allChecked} - checked={allChecked} - /> - - )} - {mode !== "readonly" && } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - ); -}; - -const CatalogSubheader: React.FC = () => { - const { mode } = useConnectionFormService(); - - const catalogSubheaderStyle = classnames({ - [styles.catalogSubheader]: mode !== "readonly", - [styles.readonlyCatalogSubheader]: mode === "readonly", - }); - - return ( -
    - - - - - - - - - - - - - - - - - -
    - ); -}; - -const SyncCatalogField: React.FC = ({ additionalControl, field, form, isSubmitting }) => { +const SyncCatalogFieldComponent: React.FC> = ({ + additionalControl, + field, + form, + isSubmitting, +}) => { const { mode } = useConnectionFormService(); const { value: streams, name: fieldName } = field; - const [searchString, setSearchString] = useState(""); const setField = form.setFieldValue; - const onChangeSchema = useCallback( + const onStreamsUpdated = useCallback( (newValue: SyncSchemaStream[]) => { setField(fieldName, newValue); }, [fieldName, setField] ); - const onChangeStream = useCallback( - (newValue: SyncSchemaStream) => onChangeSchema(streams.map((str) => (str.id === newValue.id ? newValue : str))), - [streams, onChangeSchema] - ); - - const sortedSchema = useMemo( - () => streams.sort(naturalComparatorBy((syncStream) => syncStream.stream?.name ?? "")), - [streams] - ); - - const filteredStreams = useMemo(() => { - const filters: Array<(s: SyncSchemaStream) => boolean> = [ - (_: SyncSchemaStream) => true, - searchString - ? (stream: SyncSchemaStream) => stream.stream?.name.toLowerCase().includes(searchString.toLowerCase()) - : null, - ].filter(Boolean) as Array<(s: SyncSchemaStream) => boolean>; - - return sortedSchema.filter((stream) => filters.every((f) => f(stream))); - }, [searchString, sortedSchema]); - return ( - - - - {mode !== "readonly" ? ( - <> -
    - -
    - {additionalControl} - - ) : ( -
    - -
    - )} -
    - {mode !== "readonly" && } - - - - - - -
    -
    + <> +
    + + + + {mode !== "readonly" && additionalControl} +
    + + ); }; -export default React.memo(SyncCatalogField); +export const SyncCatalogField = React.memo(SyncCatalogFieldComponent); From 2b6730ec1c61928bd50fe51ca09b0fcccacf3737 Mon Sep 17 00:00:00 2001 From: "Roman Yermilov [GL]" <86300758+roman-yermilov-gl@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:50:49 +0400 Subject: [PATCH 204/498] Source Chargebee: fix transaction schema to be consistent with S3 (#17661) --- .../connectors/source-chargebee/Dockerfile | 2 +- .../acceptance-test-docker.sh | 2 +- .../source_chargebee/streams.py | 2 + docs/integrations/sources/chargebee.md | 37 ++++++++++--------- 4 files changed, 23 insertions(+), 20 deletions(-) mode change 100644 => 100755 airbyte-integrations/connectors/source-chargebee/acceptance-test-docker.sh diff --git a/airbyte-integrations/connectors/source-chargebee/Dockerfile b/airbyte-integrations/connectors/source-chargebee/Dockerfile index ed67f6ef3b9b..080196beff3f 100644 --- a/airbyte-integrations/connectors/source-chargebee/Dockerfile +++ b/airbyte-integrations/connectors/source-chargebee/Dockerfile @@ -13,5 +13,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.15 +LABEL io.airbyte.version=0.1.16 LABEL io.airbyte.name=airbyte/source-chargebee diff --git a/airbyte-integrations/connectors/source-chargebee/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-chargebee/acceptance-test-docker.sh old mode 100644 new mode 100755 index e4d8b1cef896..c51577d10690 --- a/airbyte-integrations/connectors/source-chargebee/acceptance-test-docker.sh +++ b/airbyte-integrations/connectors/source-chargebee/acceptance-test-docker.sh @@ -1,7 +1,7 @@ #!/usr/bin/env sh # Build latest connector image -docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2) +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) # Pull latest acctest image docker pull airbyte/source-acceptance-test:latest diff --git a/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py b/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py index 3cabd67ee4bb..d42ebd38def5 100644 --- a/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py +++ b/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py @@ -7,6 +7,7 @@ import pendulum from airbyte_cdk.models import SyncMode from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.utils.transform import TypeTransformer, TransformConfig from chargebee import APIError from chargebee.list_result import ListResult from chargebee.model import Model @@ -302,6 +303,7 @@ class Transaction(IncrementalChargebeeStream): cursor_field = "updated_at" api = TransactionModel + transformer: TypeTransformer = TypeTransformer(TransformConfig.DefaultSchemaNormalization) def request_params(self, **kwargs) -> MutableMapping[str, Any]: params = super().request_params(**kwargs) diff --git a/docs/integrations/sources/chargebee.md b/docs/integrations/sources/chargebee.md index 11ad4145fb18..c3fa7618e7ae 100644 --- a/docs/integrations/sources/chargebee.md +++ b/docs/integrations/sources/chargebee.md @@ -100,21 +100,22 @@ regardless of how many AttachedItems were actually changed or synced in a partic ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------| :--- |:----------------------------------------------------------------------------------------------------| -| 0.1.15 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | -| 0.1.14 | 2022-09-23 | [17056](https://github.com/airbytehq/airbyte/pull/17056) | Add "custom fields" to the relevant Chargebee source data streams | -| 0.1.13 | 2022-08-18 | [15743](https://github.com/airbytehq/airbyte/pull/15743) | Fix transaction `exchange_rate` field type | -| 0.1.12 | 2022-07-13 | [14672](https://github.com/airbytehq/airbyte/pull/14672) | Fix transaction sort by | -| 0.1.11 | 2022-03-03 | [10827](https://github.com/airbytehq/airbyte/pull/10827) | Fix Credit Note stream | -| 0.1.10 | 2022-03-02 | [10795](https://github.com/airbytehq/airbyte/pull/10795) | Add support for Credit Note stream | -| 0.1.9 | 2022-0224 | [10312](https://github.com/airbytehq/airbyte/pull/10312) | Add support for Transaction Stream | -| 0.1.8 | 2022-02-22 | [10366](https://github.com/airbytehq/airbyte/pull/10366) | Fix broken `coupon` stream + add unit tests | -| 0.1.7 | 2022-02-14 | [10269](https://github.com/airbytehq/airbyte/pull/10269) | Add support for Coupon stream | -| 0.1.6 | 2022-02-10 | [10143](https://github.com/airbytehq/airbyte/pull/10143) | Add support for Event stream | -| 0.1.5 | 2021-12-23 | [8434](https://github.com/airbytehq/airbyte/pull/8434) | Update fields in source-connectors specifications | -| 0.1.4 | 2021-09-27 | [6454](https://github.com/airbytehq/airbyte/pull/6454) | Fix examples in spec file | -| 0.1.3 | 2021-08-17 | [5421](https://github.com/airbytehq/airbyte/pull/5421) | Add support for "Product Catalog 2.0" specific streams: `Items`, `Item prices` and `Attached Items` | -| 0.1.2 | 2021-07-30 | [5067](https://github.com/airbytehq/airbyte/pull/5067) | Prepare connector for publishing | -| 0.1.1 | 2021-07-07 | [4539](https://github.com/airbytehq/airbyte/pull/4539) | Add entrypoint and bump version for connector | -| 0.1.0 | 2021-06-30 | [3410](https://github.com/airbytehq/airbyte/pull/3410) | New Source: Chargebee | +| Version | Date | Pull Request | Subject | +|:--------|:-----------| :--- |:----------------------------------------------------------------------------------------------------------------------------| +| 0.1.16 | 2022-10-06 | [17661](https://github.com/airbytehq/airbyte/pull/17661) | Make `transaction` stream to be consistent with `S3` by using type transformer | +| 0.1.15 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | +| 0.1.14 | 2022-09-23 | [17056](https://github.com/airbytehq/airbyte/pull/17056) | Add "custom fields" to the relevant Chargebee source data streams | +| 0.1.13 | 2022-08-18 | [15743](https://github.com/airbytehq/airbyte/pull/15743) | Fix transaction `exchange_rate` field type | +| 0.1.12 | 2022-07-13 | [14672](https://github.com/airbytehq/airbyte/pull/14672) | Fix transaction sort by | +| 0.1.11 | 2022-03-03 | [10827](https://github.com/airbytehq/airbyte/pull/10827) | Fix Credit Note stream | +| 0.1.10 | 2022-03-02 | [10795](https://github.com/airbytehq/airbyte/pull/10795) | Add support for Credit Note stream | +| 0.1.9 | 2022-0224 | [10312](https://github.com/airbytehq/airbyte/pull/10312) | Add support for Transaction Stream | +| 0.1.8 | 2022-02-22 | [10366](https://github.com/airbytehq/airbyte/pull/10366) | Fix broken `coupon` stream + add unit tests | +| 0.1.7 | 2022-02-14 | [10269](https://github.com/airbytehq/airbyte/pull/10269) | Add support for Coupon stream | +| 0.1.6 | 2022-02-10 | [10143](https://github.com/airbytehq/airbyte/pull/10143) | Add support for Event stream | +| 0.1.5 | 2021-12-23 | [8434](https://github.com/airbytehq/airbyte/pull/8434) | Update fields in source-connectors specifications | +| 0.1.4 | 2021-09-27 | [6454](https://github.com/airbytehq/airbyte/pull/6454) | Fix examples in spec file | +| 0.1.3 | 2021-08-17 | [5421](https://github.com/airbytehq/airbyte/pull/5421) | Add support for "Product Catalog 2.0" specific streams: `Items`, `Item prices` and `Attached Items` | +| 0.1.2 | 2021-07-30 | [5067](https://github.com/airbytehq/airbyte/pull/5067) | Prepare connector for publishing | +| 0.1.1 | 2021-07-07 | [4539](https://github.com/airbytehq/airbyte/pull/4539) | Add entrypoint and bump version for connector | +| 0.1.0 | 2021-06-30 | [3410](https://github.com/airbytehq/airbyte/pull/3410) | New Source: Chargebee | From db0227e051159dcf93851c640c092e7622faa701 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Wed, 19 Oct 2022 17:18:30 -0400 Subject: [PATCH 205/498] publish: increase delay to delete temp arch tags (#18137) --- tools/integrations/manage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integrations/manage.sh b/tools/integrations/manage.sh index 2215daede757..4029d8db834d 100755 --- a/tools/integrations/manage.sh +++ b/tools/integrations/manage.sh @@ -300,7 +300,7 @@ cmd_publish() { docker manifest rm $versioned_image # delete the temporary image tags made with arch_versioned_image - sleep 5 + sleep 10 for arch in $(echo $build_arch | sed "s/,/ /g") do local arch_versioned_tag=`echo $arch | sed "s/\//-/g"`-$image_version From 388810cf907426769db299a91ea21237ab7c981d Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:08:22 +0200 Subject: [PATCH 206/498] Mark the sendgrid api key secret in the spec (#18182) * mark the sendgrid api key secret in the connector spec * bump veresion on sendgrid source connector * update changelog with sendgrid release notes * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- airbyte-config/init/src/main/resources/seed/source_specs.yaml | 3 ++- airbyte-integrations/connectors/source-sendgrid/Dockerfile | 2 +- .../connectors/source-sendgrid/source_sendgrid/spec.json | 1 + docs/integrations/sources/sendgrid.md | 1 + 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 2f23f33459f9..50b5333148c8 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -954,7 +954,7 @@ - name: Sendgrid sourceDefinitionId: fbb5fbe2-16ad-4cf4-af7d-ff9d9c316c87 dockerRepository: airbyte/source-sendgrid - dockerImageTag: 0.2.14 + dockerImageTag: 0.2.15 documentationUrl: https://docs.airbyte.com/integrations/sources/sendgrid icon: sendgrid.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 6519106e9834..c94acb9d438a 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9990,7 +9990,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-sendgrid:0.2.14" +- dockerImage: "airbyte/source-sendgrid:0.2.15" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/sendgrid" connectionSpecification: @@ -10003,6 +10003,7 @@ properties: apikey: title: "Sendgrid API key" + airbyte_secret: true type: "string" description: "API Key, use admin to generate this key." diff --git a/airbyte-integrations/connectors/source-sendgrid/Dockerfile b/airbyte-integrations/connectors/source-sendgrid/Dockerfile index 7271481a4204..895d3da5f3e5 100644 --- a/airbyte-integrations/connectors/source-sendgrid/Dockerfile +++ b/airbyte-integrations/connectors/source-sendgrid/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.14 +LABEL io.airbyte.version=0.2.15 LABEL io.airbyte.name=airbyte/source-sendgrid diff --git a/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json b/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json index c253b6f9fe87..214e5045dc40 100644 --- a/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json +++ b/airbyte-integrations/connectors/source-sendgrid/source_sendgrid/spec.json @@ -9,6 +9,7 @@ "properties": { "apikey": { "title": "Sendgrid API key", + "airbyte_secret": true, "type": "string", "description": "API Key, use admin to generate this key.", "order": 0 diff --git a/docs/integrations/sources/sendgrid.md b/docs/integrations/sources/sendgrid.md index 2169598b22b4..eae045fef59a 100644 --- a/docs/integrations/sources/sendgrid.md +++ b/docs/integrations/sources/sendgrid.md @@ -78,6 +78,7 @@ The connector is restricted by normal Sendgrid [requests limitation](https://sen | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:------------------------------------------------------| +| 0.2.15 | 2022-10-19 | [18182](https://github.com/airbytehq/airbyte/pull/18182) | Mark the sendgrid api key secret in the spec | | 0.2.14 | 2022-09-07 | [16400](https://github.com/airbytehq/airbyte/pull/16400) | Change Start Time config parameter to datetime string | | 0.2.13 | 2022-08-29 | [16112](https://github.com/airbytehq/airbyte/pull/16112) | Revert back to Python CDK | | 0.2.12 | 2022-08-24 | [15911](https://github.com/airbytehq/airbyte/pull/15911) | Bugfix to allowing reading schemas at runtime | From 25feff7af28a1288d3eeaa511329ab58d7832ec6 Mon Sep 17 00:00:00 2001 From: Isaac Harris-Holt <47423046+isaacharrisholt@users.noreply.github.com> Date: Wed, 19 Oct 2022 23:36:24 +0100 Subject: [PATCH 207/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20GoCardle?= =?UTF-8?q?ss=20(#17792)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit * Reformat JSON * Remove block from acceptance test yaml * Remove references to lookback_window_days * Make gocardless_environment an enum * Add ordering to spec * Address final comments * Make commit to change email associated with PR * Revert previous * add seed config * add docs * auto-bump connector version Co-authored-by: Marcos Marx Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 7 + .../src/main/resources/seed/source_specs.yaml | 48 ++ .../source-gocardless/.dockerignore | 6 + .../connectors/source-gocardless/Dockerfile | 38 ++ .../connectors/source-gocardless/README.md | 79 +++ .../connectors/source-gocardless/__init__.py | 3 + .../acceptance-test-config.yml | 20 + .../acceptance-test-docker.sh | 16 + .../connectors/source-gocardless/build.gradle | 9 + .../integration_tests/__init__.py | 3 + .../integration_tests/abnormal_state.json | 5 + .../integration_tests/acceptance.py | 16 + .../integration_tests/catalog.json | 515 ++++++++++++++++++ .../integration_tests/configured_catalog.json | 40 ++ .../integration_tests/invalid_config.json | 6 + .../integration_tests/sample_config.json | 6 + .../integration_tests/sample_state.json | 5 + .../connectors/source-gocardless/main.py | 13 + .../source-gocardless/requirements.txt | 2 + .../connectors/source-gocardless/setup.py | 29 + .../source_gocardless/__init__.py | 8 + .../source_gocardless/gocardless.yaml | 102 ++++ .../source_gocardless/schemas/mandates.json | 47 ++ .../source_gocardless/schemas/payments.json | 72 +++ .../source_gocardless/schemas/payouts.json | 71 +++ .../source_gocardless/schemas/refunds.json | 54 ++ .../source_gocardless/source.py | 18 + .../source_gocardless/spec.yaml | 45 ++ docs/integrations/sources/gocardless.md | 35 ++ 29 files changed, 1318 insertions(+) create mode 100644 airbyte-integrations/connectors/source-gocardless/.dockerignore create mode 100644 airbyte-integrations/connectors/source-gocardless/Dockerfile create mode 100644 airbyte-integrations/connectors/source-gocardless/README.md create mode 100644 airbyte-integrations/connectors/source-gocardless/__init__.py create mode 100644 airbyte-integrations/connectors/source-gocardless/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-gocardless/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-gocardless/build.gradle create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/abnormal_state.json create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-gocardless/integration_tests/sample_state.json create mode 100644 airbyte-integrations/connectors/source-gocardless/main.py create mode 100644 airbyte-integrations/connectors/source-gocardless/requirements.txt create mode 100644 airbyte-integrations/connectors/source-gocardless/setup.py create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/__init__.py create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/source.py create mode 100644 airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml create mode 100644 docs/integrations/sources/gocardless.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 50b5333148c8..a383b443942d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -382,6 +382,13 @@ icon: glassfrog.svg sourceType: api releaseStage: alpha +- name: GoCardless + sourceDefinitionId: ba15ac82-5c6a-4fb2-bf24-925c23a1180c + dockerRepository: airbyte/source-gocardless + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/gocardless + sourceType: api + releaseStage: alpha - name: Google Ads sourceDefinitionId: 253487c0-2246-43ba-a21f-5116b20a2c50 dockerRepository: airbyte/source-google-ads diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index c94acb9d438a..758fdad057fe 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3723,6 +3723,54 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-gocardless:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/sources/gocardless" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Gocardless Spec" + type: "object" + required: + - "access_token" + - "gocardless_environment" + - "gocardless_version" + - "start_date" + properties: + access_token: + title: "Access Token" + type: "string" + pattern: "^(sandbox|live)_.+$" + description: "Gocardless API TOKEN" + airbyte_secret: true + order: 0 + gocardless_environment: + title: "GoCardless API Environment" + type: "string" + enum: + - "sandbox" + - "live" + default: "sandbox" + description: "Environment you are trying to connect to." + order: 1 + gocardless_version: + title: "GoCardless API Version" + type: "string" + pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" + description: "GoCardless version. This is a date. You can find the latest\ + \ here: \nhttps://developer.gocardless.com/api-reference/#api-usage-making-requests\n" + order: 2 + start_date: + title: "Start Date" + type: "string" + pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + description: "UTC date and time in the format 2017-01-25T00:00:00Z. Any\ + \ data\nbefore this date will not be replicated.\n" + examples: + - "2017-01-25T00:00:00Z" + order: 3 + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-google-ads:0.2.1" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-ads" diff --git a/airbyte-integrations/connectors/source-gocardless/.dockerignore b/airbyte-integrations/connectors/source-gocardless/.dockerignore new file mode 100644 index 000000000000..62e971986725 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_gocardless +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-gocardless/Dockerfile b/airbyte-integrations/connectors/source-gocardless/Dockerfile new file mode 100644 index 000000000000..2cc4567f8f1f --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.11-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_gocardless ./source_gocardless + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-gocardless diff --git a/airbyte-integrations/connectors/source-gocardless/README.md b/airbyte-integrations/connectors/source-gocardless/README.md new file mode 100644 index 000000000000..2a1a7615978e --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/README.md @@ -0,0 +1,79 @@ +# Gocardless Source + +This is the repository for the Gocardless configuration based source connector. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/gocardless). + +## Local development + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-gocardless:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/gocardless) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_gocardless/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source gocardless test creds` +and place them into `secrets/config.json`. + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-gocardless:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-gocardless:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-gocardless:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-gocardless:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-gocardless:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-gocardless:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing + +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. + +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-gocardless:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-gocardless:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-gocardless/__init__.py b/airbyte-integrations/connectors/source-gocardless/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-gocardless/acceptance-test-config.yml b/airbyte-integrations/connectors/source-gocardless/acceptance-test-config.yml new file mode 100644 index 000000000000..69f9b9ffc612 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/acceptance-test-config.yml @@ -0,0 +1,20 @@ +# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-gocardless:dev +tests: + spec: + - spec_path: "source_gocardless/spec.yaml" + connection: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-gocardless/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-gocardless/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-gocardless/build.gradle b/airbyte-integrations/connectors/source-gocardless/build.gradle new file mode 100644 index 000000000000..ee320315ba9f --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_gocardless' +} diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/__init__.py b/airbyte-integrations/connectors/source-gocardless/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/abnormal_state.json new file mode 100644 index 000000000000..52b0f2c2118f --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/abnormal_state.json @@ -0,0 +1,5 @@ +{ + "todo-stream-name": { + "todo-field-name": "todo-abnormal-value" + } +} diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-gocardless/integration_tests/acceptance.py new file mode 100644 index 000000000000..1302b2f57e10 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/acceptance.py @@ -0,0 +1,16 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + # TODO: setup test dependencies if needed. otherwise remove the TODO comments + yield + # TODO: clean up test dependencies diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json new file mode 100644 index 000000000000..80905e66c30f --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json @@ -0,0 +1,515 @@ +{ + "streams": [ + { + "name": "payments", + "supported_sync_modes": [ + "full_refresh" + ], + "source_defined_cursor": true, + "default_cursor_field": "id", + "json_schema": { + "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-payments", + "type": "object", + "properties": { + "id": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "type": [ + "null", + "string" + ], + "format": "date-time" + }, + "charge_date": { + "type": [ + "null", + "string" + ] + }, + "amount": { + "type": [ + "null", + "integer" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "currency": { + "type": [ + "null", + "string" + ] + }, + "status": { + "type": [ + "null", + "string" + ] + }, + "reference": { + "type": [ + "null", + "string" + ] + }, + "metadata": { + "type": [ + "null", + "object" + ], + "properties": { + "order_dispatch_date": { + "type": [ + "null", + "string" + ] + } + } + }, + "amount_refunded": { + "type": [ + "null", + "integer" + ] + }, + "fx": { + "type": [ + "null", + "object" + ], + "properties": { + "fx_currency": { + "type": [ + "null", + "string" + ] + }, + "fx_amount": { + "type": [ + "null", + "integer" + ] + }, + "exchange_rate": { + "type": [ + "null", + "string" + ] + }, + "estimated_exchange_rate": { + "type": [ + "null", + "string" + ] + } + } + }, + "links": { + "type": [ + "null", + "object" + ], + "properties": { + "mandate": { + "type": [ + "null", + "string" + ] + }, + "creditor": { + "type": [ + "null", + "string" + ] + } + } + }, + "retry_if_possible": { + "type": [ + "null", + "boolean" + ] + } + } + } + }, + { + "name": "refunds", + "supported_sync_modes": [ + "full_refresh", + "incremental" + ], + "source_defined_cursor": true, + "default_cursor_field": "id", + "json_schema": { + "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-refunds", + "type": "object", + "properties": { + "type": [ + "null", + "object" + ], + "properties": { + "id": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "type": [ + "null", + "string" + ], + "format": "date-time" + }, + "amount": { + "type": [ + "null", + "integer" + ] + }, + "currency": { + "type": [ + "null", + "string" + ] + }, + "reference": { + "type": [ + "null", + "string" + ] + }, + "metadata": { + "type": [ + "null", + "object" + ], + "properties": { + "reason": { + "type": [ + "null", + "string" + ] + } + } + }, + "fx": { + "type": [ + "null", + "object" + ], + "properties": { + "fx_currency": { + "type": [ + "null", + "string" + ] + }, + "fx_amount": { + "type": [ + "null", + "integer" + ] + }, + "exchange_rate": { + "type": [ + "null", + "string" + ] + }, + "estimated_exchange_rate": { + "type": [ + "null", + "string" + ] + } + } + }, + "links": { + "type": [ + "null", + "object" + ], + "properties": { + "payment": { + "type": [ + "null", + "string" + ] + } + } + } + } + } + } + }, + { + "name": "mandates", + "supported_sync_modes": [ + "full_refresh", + "incremental" + ], + "source_defined_cursor": true, + "default_cursor_field": "id", + "json_schema": { + "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-mandates", + "type": "object", + "properties": { + "type": [ + "null", + "object" + ], + "properties": { + "type": [ + "null", + "object" + ], + "properties": { + "id": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "type": [ + "null", + "string" + ], + "format": "date-time" + }, + "reference": { + "type": [ + "null", + "string" + ] + }, + "status": { + "type": [ + "null", + "string" + ] + }, + "scheme": { + "type": [ + "null", + "string" + ] + }, + "next_possible_charge_date": { + "type": [ + "null", + "string" + ], + "format": "date" + }, + "metadata": { + "type": [ + "null", + "object" + ], + "properties": { + "contract": { + "type": [ + "null", + "string" + ] + } + } + }, + "links": { + "type": [ + "null", + "object" + ], + "properties": { + "customer_bank_account": { + "type": [ + "null", + "string" + ] + }, + "creditor": { + "type": [ + "null", + "string" + ] + }, + "customer": { + "type": [ + "null", + "string" + ] + } + } + } + } + } + } + } + }, + { + "name": "payouts", + "supported_sync_modes": [ + "full_refresh", + "incremental" + ], + "source_defined_cursor": true, + "default_cursor_field": "id", + "json_schema": { + "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-payouts", + "type": "object", + "properties": { + "type": [ + "null", + "object" + ], + "properties": { + "type": [ + "null", + "object" + ], + "properties": { + "id": { + "type": [ + "null", + "string" + ] + }, + "amount": { + "type": [ + "null", + "integer" + ] + }, + "arrival_date": { + "type": [ + "null", + "string" + ], + "format": "date" + }, + "deducted_fees": { + "type": [ + "null", + "integer" + ] + }, + "currency": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "type": [ + "null", + "string" + ], + "format": "date-time" + }, + "payout_type": { + "type": [ + "null", + "string" + ] + }, + "reference": { + "type": [ + "null", + "string" + ] + }, + "status": { + "type": [ + "null", + "string" + ] + }, + "fx": { + "type": [ + "null", + "object" + ], + "properties": { + "fx_currency": { + "type": [ + "null", + "string" + ] + }, + "fx_amount": { + "type": [ + "null", + "integer" + ] + }, + "exchange_rate": { + "type": [ + "null", + "string" + ] + }, + "estimated_exchange_rate": { + "type": [ + "null", + "string" + ] + } + } + }, + "tax_currency": { + "type": [ + "null", + "string" + ] + }, + "metadata": { + "type": [ + "null", + "object" + ] + }, + "amount_refunded": { + "type": [ + "null", + "integer" + ] + }, + "links": { + "type": [ + "null", + "object" + ], + "properties": { + "creditor_bank_account": { + "type": [ + "null", + "string" + ] + }, + "creditor": { + "type": [ + "null", + "string" + ] + } + } + } + } + } + } + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..2e0a04f41ff1 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/configured_catalog.json @@ -0,0 +1,40 @@ +{ + "streams": [ + { + "stream": { + "name": "payments", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "refunds", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "mandates", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "payouts", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/invalid_config.json new file mode 100644 index 000000000000..681a2a2bd84c --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/invalid_config.json @@ -0,0 +1,6 @@ +{ + "access_token": "sandbox_invalid_key", + "gocardless_environment": "sandbox", + "gocardless_version": "2015-07-06", + "start_date": "2022-01-01T00:00:00Z" +} diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/sample_config.json new file mode 100644 index 000000000000..59d53d53e17e --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/sample_config.json @@ -0,0 +1,6 @@ +{ + "access_token": "sandbox_token", + "gocardless_environment": "sandbox", + "gocardless_version": "2015-07-06", + "start_date": "2022-01-01T00:00:00Z" +} diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/sample_state.json new file mode 100644 index 000000000000..3587e579822d --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/sample_state.json @@ -0,0 +1,5 @@ +{ + "todo-stream-name": { + "todo-field-name": "value" + } +} diff --git a/airbyte-integrations/connectors/source-gocardless/main.py b/airbyte-integrations/connectors/source-gocardless/main.py new file mode 100644 index 000000000000..ee59d89e7dea --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_gocardless import SourceGocardless + +if __name__ == "__main__": + source = SourceGocardless() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-gocardless/requirements.txt b/airbyte-integrations/connectors/source-gocardless/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-gocardless/setup.py b/airbyte-integrations/connectors/source-gocardless/setup.py new file mode 100644 index 000000000000..36b135317450 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.1", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_gocardless", + description="Source implementation for Gocardless.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/__init__.py b/airbyte-integrations/connectors/source-gocardless/source_gocardless/__init__.py new file mode 100644 index 000000000000..3024e3505f09 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceGocardless + +__all__ = ["SourceGocardless"] diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml b/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml new file mode 100644 index 000000000000..92c614baf017 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml @@ -0,0 +1,102 @@ +version: "0.1.0" + +definitions: + schema_loader: + type: JsonSchema + file_path: "./source_gocardless/schemas/{{ options['name'] }}.json" + selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_pointer: + - "{{ options['name'] }}" + requester: + type: HttpRequester + name: "{{ options['name'] }}" + http_method: "GET" + authenticator: + type: BearerAuthenticator + api_token: "{{ config['access_token'] }}" + request_options_provider: + request_headers: + GoCardless-Version: "{{ config['gocardless_version'] }}" + request_parameters: + created_at[gte]: "{{ config['start_date'] }}" + retriever: + type: SimpleRetriever + $options: + url_base: "{{ 'https://api.gocardless.com' if config['gocardless_environment'] == 'live' else 'https://api-sandbox.gocardless.com' }}" + name: "{{ options['name'] }}" + primary_key: "{{ options['primary_key'] }}" + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + type: "DefaultPaginator" + pagination_strategy: + type: "CursorPagination" + cursor_value: "{{ response['meta']['cursors']['after'] }}" + page_size: 500 + limit_option: + inject_into: request_parameter + field_name: limit + page_token_option: + field_name: "after" + inject_into: "request_parameter" + page_size_option: + field_name: "limit" + inject_into: "request_parameter" + + +streams: + - type: DeclarativeStream + $options: + name: "payments" + stream_cursor_field: "created_at" + primary_key: "id" + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/payments" + - type: DeclarativeStream + $options: + name: "refunds" + stream_cursor_field: "created_at" + primary_key: "id" + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/refunds" + - type: DeclarativeStream + $options: + name: "mandates" + stream_cursor_field: "created_at" + primary_key: "id" + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/mandates" + - type: DeclarativeStream + $options: + name: "payouts" + stream_cursor_field: "created_at" + primary_key: "id" + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/payouts" +check: + type: CheckStream + stream_names: + - "payments" diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json new file mode 100644 index 000000000000..18cbdb36e22b --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json @@ -0,0 +1,47 @@ +{ + "type": ["null", "object"], + "properties": { + "id": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "reference": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "scheme": { + "type": ["null", "string"] + }, + "next_possible_charge_date": { + "type": ["null", "string"], + "format": "date" + }, + "metadata": { + "type": ["null", "object"], + "properties":{ + "contract": { + "type": ["null", "string"] + } + } + }, + "links": { + "type": ["null", "object"], + "properties": { + "customer_bank_account": { + "type": ["null", "string"] + }, + "creditor": { + "type": ["null", "string"] + }, + "customer": { + "type": ["null", "string"] + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json new file mode 100644 index 000000000000..e85fdd2123ff --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json @@ -0,0 +1,72 @@ +{ + "type": ["null", "object"], + "properties": { + "id": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "charge_date": { + "type": ["null", "string"] + }, + "amount": { + "type": ["null", "integer"] + }, + "description": { + "type": ["null", "string"] + }, + "currency": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "reference": { + "type": ["null", "string"] + }, + "metadata": { + "type": ["null", "object"], + "properties":{ + "order_dispatch_date": { + "type": ["null", "string"] + } + } + }, + "amount_refunded": { + "type": ["null", "integer"] + }, + "fx": { + "type": ["null", "object"], + "properties": { + "fx_currency": { + "type": ["null", "string"] + }, + "fx_amount": { + "type": ["null", "integer"] + }, + "exchange_rate": { + "type": ["null", "string"] + }, + "estimated_exchange_rate": { + "type": ["null", "string"] + } + } + }, + "links": { + "type": ["null", "object"], + "properties": { + "mandate": { + "type": ["null", "string"] + }, + "creditor": { + "type": ["null", "string"] + } + } + }, + "retry_if_possible": { + "type": ["null", "boolean"] + } + } +} diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json new file mode 100644 index 000000000000..6ceaa0f98422 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json @@ -0,0 +1,71 @@ +{ + "type": ["null", "object"], + "properties": { + "id": { + "type": ["null", "string"] + }, + "amount": { + "type": ["null", "integer"] + }, + "arrival_date": { + "type": ["null", "string"], + "format": "date" + }, + "deducted_fees": { + "type": ["null", "integer"] + }, + "currency": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "payout_type": { + "type": ["null", "string"] + }, + "reference": { + "type": ["null", "string"] + }, + "status": { + "type": ["null", "string"] + }, + "fx": { + "type": ["null", "object"], + "properties": { + "fx_currency": { + "type": ["null", "string"] + }, + "fx_amount": { + "type": ["null", "integer"] + }, + "exchange_rate": { + "type": ["null", "string"] + }, + "estimated_exchange_rate": { + "type": ["null", "string"] + } + } + }, + "tax_currency": { + "type": ["null", "string"] + }, + "metadata": { + "type": ["null", "object"] + }, + "amount_refunded": { + "type": ["null", "integer"] + }, + "links": { + "type": ["null", "object"], + "properties": { + "creditor_bank_account": { + "type": ["null", "string"] + }, + "creditor": { + "type": ["null", "string"] + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json new file mode 100644 index 000000000000..7484e680f53f --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json @@ -0,0 +1,54 @@ +{ + "type": ["null", "object"], + "properties": { + "id": { + "type": ["null", "string"] + }, + "created_at": { + "type": ["null", "string"], + "format": "date-time" + }, + "amount": { + "type": ["null", "integer"] + }, + "currency": { + "type": ["null", "string"] + }, + "reference": { + "type": ["null", "string"] + }, + "metadata": { + "type": ["null", "object"], + "properties":{ + "reason": { + "type": ["null", "string"] + } + } + }, + "fx": { + "type": ["null", "object"], + "properties": { + "fx_currency": { + "type": ["null", "string"] + }, + "fx_amount": { + "type": ["null", "integer"] + }, + "exchange_rate": { + "type": ["null", "string"] + }, + "estimated_exchange_rate": { + "type": ["null", "string"] + } + } + }, + "links": { + "type": ["null", "object"], + "properties": { + "payment": { + "type": ["null", "string"] + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/source.py b/airbyte-integrations/connectors/source-gocardless/source_gocardless/source.py new file mode 100644 index 000000000000..01118b2bbe3e --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/source.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource + +""" +This file provides the necessary constructs to interpret a provided declarative YAML configuration file into +source connector. + +WARNING: Do not modify this file. +""" + + +# Declarative Source +class SourceGocardless(YamlDeclarativeSource): + def __init__(self): + super().__init__(**{"path_to_yaml": "gocardless.yaml"}) diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml b/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml new file mode 100644 index 000000000000..8ec92a712000 --- /dev/null +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml @@ -0,0 +1,45 @@ +documentationUrl: https://docs.airbyte.com/integrations/sources/gocardless +connectionSpecification: + "$schema": http://json-schema.org/draft-07/schema# + title: Gocardless Spec + type: object + required: + - access_token + - gocardless_environment + - gocardless_version + - start_date + properties: + access_token: + title: Access Token + type: string + pattern: "^(sandbox|live)_.+$" + description: Gocardless API TOKEN + airbyte_secret: true + order: 0 + gocardless_environment: + title: GoCardless API Environment + type: string + enum: + - sandbox + - live + default: sandbox + description: Environment you are trying to connect to. + order: 1 + gocardless_version: + title: GoCardless API Version + type: string + pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" + description: | + GoCardless version. This is a date. You can find the latest here: + https://developer.gocardless.com/api-reference/#api-usage-making-requests + order: 2 + start_date: + title: Start Date + type: string + pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + description: | + UTC date and time in the format 2017-01-25T00:00:00Z. Any data + before this date will not be replicated. + examples: + - '2017-01-25T00:00:00Z' + order: 3 diff --git a/docs/integrations/sources/gocardless.md b/docs/integrations/sources/gocardless.md new file mode 100644 index 000000000000..8e58c9142941 --- /dev/null +++ b/docs/integrations/sources/gocardless.md @@ -0,0 +1,35 @@ +# GoCardless + +## Overview + +The GoCardless source can sync data from the [GoCardless API](https://gocardless.com/) + +#### Output schema + +This source is capable of syncing the following streams: +* Mandates +* Payments +* Payouts +* Refunds + + +#### Features + +| Feature | Supported? | +| :--- | :--- | +| Full Refresh Sync | Yes | +| Incremental - Append Sync | No | +| Namespaces | No | + +### Requirements / Setup Guide +* Access Token +* GoCardless Environment +* GoCardless Version +* Start Date + + +## Changelog + +| Version | Date | Pull Request | Subject | +| :--- | :--- | :--- | :--- | +| 0.1.0 | 2022-10-19 | [17792](https://github.com/airbytehq/airbyte/pull/17792) | Initial release supporting the GoCardless | \ No newline at end of file From 8972fbd646813e7ba1da9ac0bf60a88f9e5f8e7c Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Wed, 19 Oct 2022 15:45:19 -0700 Subject: [PATCH 208/498] Avoid generating too many logs (#18190) --- .../java/io/airbyte/metrics/lib/DogStatsDMetricClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/DogStatsDMetricClient.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/DogStatsDMetricClient.java index 7e5fec725c99..e44818703c73 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/DogStatsDMetricClient.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/DogStatsDMetricClient.java @@ -103,7 +103,7 @@ public void gauge(final MetricsRegistry metric, final double val, final MetricAt return; } - log.info("publishing gauge, name: {}, value: {}, attributes: {}", metric, val, attributes); + log.debug("publishing gauge, name: {}, value: {}, attributes: {}", metric, val, attributes); statsDClient.gauge(metric.getMetricName(), val, toTags(attributes)); } } @@ -117,7 +117,7 @@ public void distribution(final MetricsRegistry metric, final double val, final M return; } - log.info("recording distribution, name: {}, value: {}, attributes: {}", metric, val, attributes); + log.debug("recording distribution, name: {}, value: {}, attributes: {}", metric, val, attributes); statsDClient.distribution(metric.getMetricName(), val, toTags(attributes)); } } From 702154da7672cb0b057b0b51aa29e1da47ed81ea Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Wed, 19 Oct 2022 18:49:25 -0400 Subject: [PATCH 209/498] Update to Micronaut 3.7.2 and related dependecies (#18191) --- deps.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deps.toml b/deps.toml index 5e3e650d3904..5d2079050562 100644 --- a/deps.toml +++ b/deps.toml @@ -10,8 +10,8 @@ slf4j = "1.7.36" lombok = "1.18.24" jooq = "3.13.4" junit-jupiter = "5.9.0" -micronaut = "3.7.1" -micronaut-test = "3.6.2" +micronaut = "3.7.2" +micronaut-test = "3.7.0" postgresql = "42.3.5" connectors-testcontainers = "1.15.3" connectors-testcontainers-cassandra = "1.16.0" @@ -102,7 +102,7 @@ hibernate-types = { module = "com.vladmihalcea:hibernate-types-52", version = "2 jakarta-inject = { module = "jakarta.annotation:jakarta.annotation-api", version = "2.1.1'" } javax-transaction = { module = "javax.transaction:javax.transaction-api", version = "1.3" } micronaut-bom = { module = "io.micronaut:micronaut-bom", version.ref = "micronaut" } -micronaut-data-processor = { module = "io.micronaut.data:micronaut-data-processor", version = "3.8.0" } +micronaut-data-processor = { module = "io.micronaut.data:micronaut-data-processor", version = "3.8.1" } micronaut-flyway = { module = "io.micronaut.flyway:micronaut-flyway", version = "5.4.1" } micronaut-inject = { module = "io.micronaut:micronaut-inject" } micronaut-http-client = { module = "io.micronaut:micronaut-http-client" } From 3d053e32ee9710cafa4e0613821527cf5ad6a4cc Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Wed, 19 Oct 2022 15:52:01 -0700 Subject: [PATCH 210/498] Use Nginx + Basic Auth to secure OSS Airbyte (#17694) * Use Nginx + Basic Auth to secure OSS Airbyte * use local passwords * Use gradle builds * K8s setup and source values from ENV * note about disabling * add back defaults * custom 401 page * update http message * update docs * remove kube files * additional doc updates * Add a test suite * fix failure exit codes * doc updates * Add docs * bump to re-test * add more sleep in tests for CI * better sleep in test * Update docs/operator-guides/security.md Co-authored-by: Davin Chia * PR updates * test comment * change test host on CI * update tests and nginx to boot without backend * proxy updates for docker DNS * simpler test for uptime * acceptance test skips PWs * remove resolver madness * fixup tests * more proxy_pass revert * update acceptance test exit codes * relax test expectations * add temporal mount back for testing * Update docs/operator-guides/security.md Co-authored-by: swyx * Update airbyte-proxy/401.html Co-authored-by: swyx * more doc updates * Octavia CLI uses Basic Auth (#17982) * [WIP] Octavia CLI uses Basic Auth * readme * augustin: add basic auth headers to clien * augustin: add basic auth headers to client * tests passing * lint * docs * Move monkey patch to test * coerce headers into strings * monkey patch get_basic_auth_token Co-authored-by: alafanechere * fix launch permissions * Keep worker port internal * more readme Co-authored-by: Davin Chia Co-authored-by: swyx Co-authored-by: alafanechere --- .bumpversion.cfg | 30 +- .env | 4 + airbyte-proxy/401.html | 19 + airbyte-proxy/Dockerfile | 27 + airbyte-proxy/LICENSE | 21 + airbyte-proxy/README.md | 14 + airbyte-proxy/build.gradle | 21 + airbyte-proxy/gradle.properties | 1 + airbyte-proxy/nginx-auth.conf.template | 45 + airbyte-proxy/nginx-no-auth.conf.template | 27 + airbyte-proxy/run.sh | 23 + airbyte-proxy/test.sh | 125 + docker-compose.debug.yaml | 6 + docker-compose.yaml | 38 +- docs/deploying-airbyte/local-deployment.md | 29 +- docs/deploying-airbyte/on-aws-ec2.md | 57 +- docs/operator-guides/configuring-airbyte.md | 24 +- docs/operator-guides/security.md | 35 +- docs/quickstart/deploy-airbyte.md | 9 +- octavia-cli/README.md | 7 +- .../test_api_http_headers.yaml | 48312 ++++++++-------- octavia-cli/integration_tests/conftest.py | 2 +- .../test_api_http_headers.py | 22 +- octavia-cli/octavia_cli/entrypoint.py | 36 +- octavia-cli/unit_tests/test_entrypoint.py | 29 +- settings.gradle | 1 + tools/bin/acceptance_test.sh | 4 +- tools/bin/publish_docker.sh | 15 +- 28 files changed, 25412 insertions(+), 23571 deletions(-) create mode 100644 airbyte-proxy/401.html create mode 100644 airbyte-proxy/Dockerfile create mode 100644 airbyte-proxy/LICENSE create mode 100644 airbyte-proxy/README.md create mode 100644 airbyte-proxy/build.gradle create mode 100644 airbyte-proxy/gradle.properties create mode 100644 airbyte-proxy/nginx-auth.conf.template create mode 100644 airbyte-proxy/nginx-no-auth.conf.template create mode 100644 airbyte-proxy/run.sh create mode 100755 airbyte-proxy/test.sh diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 0a09e042299e..e1934af9317b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -3,7 +3,7 @@ current_version = 0.40.15 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-[a-z]+)? -serialize = +serialize = {major}.{minor}.{patch} [bumpversion:file:.bumpversion.cfg] @@ -18,42 +18,44 @@ serialize = [bumpversion:file:airbyte-metrics/reporter/Dockerfile] -[bumpversion:file:airbyte-server/Dockerfile] +[bumpversion:file:airbyte-proxy/Dockerfile] -[bumpversion:file:airbyte-webapp/package.json] +[bumpversion:file:airbyte-server/Dockerfile] [bumpversion:file:airbyte-webapp/package-lock.json] +[bumpversion:file:airbyte-webapp/package.json] + [bumpversion:file:airbyte-workers/Dockerfile] -[bumpversion:file:charts/airbyte/Chart.yaml] +[bumpversion:file:charts/airbyte-bootloader/Chart.yaml] -[bumpversion:file:charts/airbyte-worker/Chart.yaml] +[bumpversion:file:charts/airbyte-server/Chart.yaml] [bumpversion:file:charts/airbyte-temporal/Chart.yaml] [bumpversion:file:charts/airbyte-webapp/Chart.yaml] -[bumpversion:file:charts/airbyte-server/Chart.yaml] - -[bumpversion:file:charts/airbyte-bootloader/Chart.yaml] +[bumpversion:file:charts/airbyte-worker/Chart.yaml] [bumpversion:file:charts/airbyte/README.md] -[bumpversion:file:docs/operator-guides/upgrading-airbyte.md] +[bumpversion:file:charts/airbyte/values.yaml] -[bumpversion:file:kube/overlays/stable/.env] - -[bumpversion:file:kube/overlays/stable/kustomization.yaml] +[bumpversion:file:docs/operator-guides/upgrading-airbyte.md] [bumpversion:file:kube/overlays/stable-with-resource-limits/.env] [bumpversion:file:kube/overlays/stable-with-resource-limits/kustomization.yaml] -[bumpversion:file:octavia-cli/install.sh] +[bumpversion:file:kube/overlays/stable/.env] -[bumpversion:file:octavia-cli/README.md] +[bumpversion:file:kube/overlays/stable/kustomization.yaml] [bumpversion:file:octavia-cli/Dockerfile] +[bumpversion:file:octavia-cli/README.md] + +[bumpversion:file:octavia-cli/install.sh] + [bumpversion:file:octavia-cli/setup.py] diff --git a/.env b/.env index 96ca2dc1ab9a..e2b27523a2c8 100644 --- a/.env +++ b/.env @@ -32,6 +32,10 @@ LOCAL_DOCKER_MOUNT=/tmp/airbyte_local # Issue: https://github.com/airbytehq/airbyte/issues/577 HACK_LOCAL_ROOT_PARENT=/tmp +# Proxy Configuration +# Set to empty values, e.g. "" to disable basic auth +BASIC_AUTH_USERNAME=airbyte +BASIC_AUTH_PASSWORD=password ### DATABASE ### # Airbyte Internal Job Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db diff --git a/airbyte-proxy/401.html b/airbyte-proxy/401.html new file mode 100644 index 000000000000..69e19668f51b --- /dev/null +++ b/airbyte-proxy/401.html @@ -0,0 +1,19 @@ + + + + Airbyte - Access Denied + + +

    🐙 Nope.

    +

    HTTP Error Code: 401

    + +

    + This deployment of Airbyte is protected by HTTP Basic Authentication. + Please refer to the Airbyte docs to learn more about: +

    +

    + + diff --git a/airbyte-proxy/Dockerfile b/airbyte-proxy/Dockerfile new file mode 100644 index 000000000000..419843cdc044 --- /dev/null +++ b/airbyte-proxy/Dockerfile @@ -0,0 +1,27 @@ +# Inspired by https://medium.com/pernod-ricard-tech/adding-basic-authentication-with-nginx-as-a-reverse-proxy-a229f9d12b73 + +FROM nginx:latest + +ARG VERSION=0.40.12 + +ENV APPLICATION airbyte-proxy +ENV VERSION ${VERSION} + +RUN apt-get update -y && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/* + +# This variable can be used to update the destintion containers that Nginx proxies to. +ENV PROXY_PASS_WEB "http://airbyte-webapp:80" +ENV PROXY_PASS_API "http://airbyte-server:8001" + +# Nginx config file +WORKDIR / +RUN mkdir -p /etc/nginx/templates +COPY nginx-auth.conf.template /etc/nginx/templates/nginx-auth.conf.template +COPY nginx-no-auth.conf.template /etc/nginx/templates/nginx-no-auth.conf.template +COPY 401.html /etc/nginx/401.html + +# Startup script +COPY run.sh ./ +RUN chmod 0755 ./run.sh +CMD [ "./run.sh" ] +ENTRYPOINT ["./run.sh"] diff --git a/airbyte-proxy/LICENSE b/airbyte-proxy/LICENSE new file mode 100644 index 000000000000..387ec9606473 --- /dev/null +++ b/airbyte-proxy/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Airbyte, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/airbyte-proxy/README.md b/airbyte-proxy/README.md new file mode 100644 index 000000000000..1bb4f873fb37 --- /dev/null +++ b/airbyte-proxy/README.md @@ -0,0 +1,14 @@ +# Airbyte Proxy + +This service uses Nginx to front the Aribyte `webapp` and `server` services to add Authentication via HTTP basic auth. + +Authentication is controlled by 2 environment variables, `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` which can be modified in the `.env` file for your Airbyte deployment. You can disable authentication by setting both `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` to empty strings. Changes in your environment variables will be applied when the service (re)boots. + +This service is intended to work in conjunction with the `airbyte_internal` network defined in the default docker compose file. By default, this application forwards requesting coming in on 8000 and 8001 to the PROXY_PASS_WEB and PROXY_PASS_API accordingly - which are also configured by environment variables within this container (see Dockerfile). The deafults are configured to work with the default `docker-compose.yaml` file for Airbyte OSS deployments. + +``` +ENV PROXY_PASS_WEB "http://airbyte-webapp:80" +ENV PROXY_PASS_API "http://airbyte-server:8001" +``` + +🐙 diff --git a/airbyte-proxy/build.gradle b/airbyte-proxy/build.gradle new file mode 100644 index 000000000000..297f75c8f4c4 --- /dev/null +++ b/airbyte-proxy/build.gradle @@ -0,0 +1,21 @@ +task prepareBuild(type: Copy) { + from layout.projectDirectory.file("nginx-auth.conf.template") + from layout.projectDirectory.file("nginx-no-auth.conf.template") + from layout.projectDirectory.file("run.sh") + from layout.projectDirectory.file("401.html") + + into layout.buildDirectory.dir("docker") +} + +tasks.named("buildDockerImage") { + dependsOn prepareBuild + dependsOn copyDocker +} + +task bashTest(type: Exec) { + dependsOn buildDockerImage + commandLine "./test.sh" +} + +// we can't override the 'test' command, so we can make our bash test a dependency +test.dependsOn(project.tasks.bashTest) diff --git a/airbyte-proxy/gradle.properties b/airbyte-proxy/gradle.properties new file mode 100644 index 000000000000..f28927f52ca6 --- /dev/null +++ b/airbyte-proxy/gradle.properties @@ -0,0 +1 @@ +dockerImageName=proxy diff --git a/airbyte-proxy/nginx-auth.conf.template b/airbyte-proxy/nginx-auth.conf.template new file mode 100644 index 000000000000..9dcf9804cd4a --- /dev/null +++ b/airbyte-proxy/nginx-auth.conf.template @@ -0,0 +1,45 @@ +events {} + +http { + server { + listen 8000 default_server; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + auth_basic "Welcome to Airbyte"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_pass "${PROXY_PASS_WEB}"; + + error_page 401 /etc/nginx/401.html; + location ~ (401.html)$ { + alias /etc/nginx/$1; + auth_basic off; + } + } + } + + server { + listen 8001; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + auth_basic "Welcome to Airbyte"; + auth_basic_user_file /etc/nginx/.htpasswd; + + proxy_pass "${PROXY_PASS_API}"; + + error_page 401 /etc/nginx/401.html; + location ~ (401.html)$ { + alias /etc/nginx/$1; + auth_basic off; + } + } + } +} diff --git a/airbyte-proxy/nginx-no-auth.conf.template b/airbyte-proxy/nginx-no-auth.conf.template new file mode 100644 index 000000000000..577dd7ef7f11 --- /dev/null +++ b/airbyte-proxy/nginx-no-auth.conf.template @@ -0,0 +1,27 @@ +events {} + +http { + server { + listen 8000 default_server; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass "${PROXY_PASS_WEB}"; + } + } + + server { + listen 8001; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass "${PROXY_PASS_API}"; + } + } +} diff --git a/airbyte-proxy/run.sh b/airbyte-proxy/run.sh new file mode 100644 index 000000000000..afa840adb70e --- /dev/null +++ b/airbyte-proxy/run.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +rm /etc/nginx/nginx.conf + +if [[ -z "${BASIC_AUTH_USERNAME}" ]]; then + echo "BASIC_AUTH_USERNAME is not set, skipping nginx auth" + + TEMPLATE_PATH="/etc/nginx/templates/nginx-no-auth.conf.template" +else + echo "BASIC_AUTH_USERNAME is set, requiring auth for user '$BASIC_AUTH_USERNAME'" + + # htpasswd for basic authentication + rm -rf /etc/nginx/.htpasswd + htpasswd -c -b /etc/nginx/.htpasswd $BASIC_AUTH_USERNAME $BASIC_AUTH_PASSWORD + + TEMPLATE_PATH="/etc/nginx/templates/nginx-auth.conf.template" +fi + +envsubst '${PROXY_PASS_WEB} ${PROXY_PASS_API} ${PROXY_PASS_RESOLVER}' < $TEMPLATE_PATH > /etc/nginx/nginx.conf + +echo "starting nginx..." +nginx -v +nginx -g "daemon off;" diff --git a/airbyte-proxy/test.sh b/airbyte-proxy/test.sh new file mode 100755 index 000000000000..339358d5beeb --- /dev/null +++ b/airbyte-proxy/test.sh @@ -0,0 +1,125 @@ +#!/bin/bash + +NAME="airbyte-proxy-test-container" +PORT=18000 +BASIC_AUTH_USERNAME=airbyte +BASIC_AUTH_PASSWORD=password +BASIC_AUTH_UPDATED_PASSWORD=pa55w0rd +TEST_HOST=localhost + + +function start_container () { + CMD="docker run -d -p $PORT:8000 --env BASIC_AUTH_USERNAME=$1 --env BASIC_AUTH_PASSWORD=$2 --env PROXY_PASS_WEB=http://localhost --env PROXY_PASS_API=http://localhost --name $NAME airbyte/proxy:dev" + echo $CMD + eval $CMD + wait_for_docker; +} + +function start_container_with_proxy () { + CMD="docker run -d -p $PORT:8000 --env PROXY_PASS_WEB=$1 --env PROXY_PASS_API=$1 --name $NAME airbyte/proxy:dev" + echo $CMD + eval $CMD + wait_for_docker; +} + +function stop_container () { + echo "Stopping $NAME" + docker kill $NAME + docker rm $NAME +} + +function wait_for_docker() { + until [ "`docker inspect -f {{.State.Running}} $NAME`"=="true" ]; do + sleep 1; + done; + sleep 1; +} + +echo "Testing airbyte proxy..." + +stop_container; # just in case there was a failure of a previous test run + +echo "Starting $NAME" +start_container $BASIC_AUTH_USERNAME $BASIC_AUTH_PASSWORD + +echo "Testing access without auth" +RESPONSE=`curl "http://$TEST_HOST:$PORT" -i --silent` +if [[ $RESPONSE == *"401 Unauthorized"* ]]; then + echo "✔️ access without auth blocked" +else + echo "Auth not working" + echo $RESPONSE + exit 1 +fi + +echo "Testing access with auth" +RESPONSE=`curl "http://$BASIC_AUTH_USERNAME:$BASIC_AUTH_PASSWORD@$TEST_HOST:$PORT" -i --silent` +if [[ $RESPONSE != *"401 Unauthorized"* ]]; then + echo "✔️ access with auth worked" +else + echo "Auth not working" + echo $RESPONSE + exit 1 +fi + +stop_container; + +echo "Starting $NAME with updated password" +start_container $BASIC_AUTH_USERNAME $BASIC_AUTH_UPDATED_PASSWORD + +echo "Testing access with orignial paassword" +RESPONSE=`curl "http://$BASIC_AUTH_USERNAME:$BASIC_AUTH_PASSWORD@$TEST_HOST:$PORT" -i --silent` +if [[ $RESPONSE == *"401 Unauthorized"* ]]; then + echo "✔️ access with original auth blocked" +else + echo "Auth not working" + echo $RESPONSE + exit 1 +fi + +echo "Testing access updated auth" +RESPONSE=`curl "http://$BASIC_AUTH_USERNAME:$BASIC_AUTH_UPDATED_PASSWORD@$TEST_HOST:$PORT" -i --silent` +if [[ $RESPONSE != *"401 Unauthorized"* ]]; then + echo "✔️ access with updated auth worked" +else + echo "Auth not working" + echo $RESPONSE + exit 1 +fi + +stop_container; + +echo "Starting $NAME with no password" +start_container "" "" + +echo "Testing access without auth" +RESPONSE=`curl "http://$TEST_HOST:$PORT" -i --silent` +if [[ $RESPONSE != *"401 Unauthorized"* ]]; then + echo "✔️ access without auth allowed when configured" +else + echo "Auth not working" + echo $RESPONSE + exit 1 +fi + +stop_container; + + +# TODO: We can't test external URLs without a resolver, but adding a resolver that isn't dynamic+local doesn't work with docker. + +# echo "Testing that PROXY_PASS can be used to change the backend" +# start_container_with_proxy "http://www.google.com" + +# RESPONSE=`curl "http://$TEST_HOST:$PORT" -i --silent` +# if [[ $RESPONSE == *"google.com"* ]]; then +# echo "✔️ proxy backends can be changed" +# else +# echo "Proxy update not working" +# echo $RESPONSE +# exit 1 +# fi + +# stop_container; + +echo "Tests Passed ✅" +exit 0 diff --git a/docker-compose.debug.yaml b/docker-compose.debug.yaml index e62acc104f63..1ba4e41e7737 100644 --- a/docker-compose.debug.yaml +++ b/docker-compose.debug.yaml @@ -11,6 +11,9 @@ services: db: ports: - 8011:5432 + networks: + - airbyte_internal + - airbyte_public airbyte-temporal-ui: image: temporalio/web:1.13.0 logging: *default-logging @@ -21,3 +24,6 @@ services: - TEMPORAL_PERMIT_WRITE_API=true ports: - 8012:8088 + networks: + - airbyte_internal + - airbyte_public diff --git a/docker-compose.yaml b/docker-compose.yaml index b137d983c819..eae83cc01b70 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -32,6 +32,8 @@ services: - DATABASE_USER=${DATABASE_USER} - LOG_LEVEL=${LOG_LEVEL} - RUN_DATABASE_MIGRATION_ON_STARTUP=${RUN_DATABASE_MIGRATION_ON_STARTUP} + networks: + - airbyte_internal db: image: airbyte/db:${VERSION} logging: *default-logging @@ -48,6 +50,8 @@ services: - POSTGRES_USER=${DATABASE_USER} volumes: - db:/var/lib/postgresql/data + networks: + - airbyte_internal worker: image: airbyte/worker:${VERSION} logging: *default-logging @@ -106,7 +110,9 @@ services: - workspace:${WORKSPACE_ROOT} - ${LOCAL_ROOT}:${LOCAL_ROOT} ports: - - 9000:9000 + - 9000 + networks: + - airbyte_internal server: image: airbyte/server:${VERSION} logging: *default-logging @@ -140,18 +146,20 @@ services: - WORKSPACE_ROOT=${WORKSPACE_ROOT} - GITHUB_STORE_BRANCH=${GITHUB_STORE_BRANCH} ports: - - 8001:8001 + - 8001 volumes: - workspace:${WORKSPACE_ROOT} - data:${CONFIG_ROOT} - ${LOCAL_ROOT}:${LOCAL_ROOT} + networks: + - airbyte_internal webapp: image: airbyte/webapp:${VERSION} logging: *default-logging container_name: airbyte-webapp restart: unless-stopped ports: - - 8000:80 + - 80 environment: - AIRBYTE_ROLE=${AIRBYTE_ROLE:-} - AIRBYTE_VERSION=${VERSION} @@ -160,6 +168,8 @@ services: - OPENREPLAY=${OPENREPLAY:-} - PAPERCUPS_STORYTIME=${PAPERCUPS_STORYTIME:-} - TRACKING_STRATEGY=${TRACKING_STRATEGY} + networks: + - airbyte_internal airbyte-temporal: image: airbyte/temporal:${VERSION} logging: *default-logging @@ -175,6 +185,8 @@ services: - POSTGRES_USER=${DATABASE_USER} volumes: - ./temporal/dynamicconfig:/etc/temporal/config/dynamicconfig + networks: + - airbyte_internal airbyte-cron: image: airbyte/cron:${VERSION} logging: *default-logging @@ -195,6 +207,23 @@ services: - MICRONAUT_ENVIRONMENTS=${CRON_MICRONAUT_ENVIRONMENTS} volumes: - workspace:${WORKSPACE_ROOT} + networks: + - airbyte_internal + airbyte-proxy: + image: airbyte/proxy:${VERSION} + container_name: airbyte-proxy + ports: + - 8000:8000 + - 8001:8001 + environment: + - BASIC_AUTH_USERNAME=${BASIC_AUTH_USERNAME} + - BASIC_AUTH_PASSWORD=${BASIC_AUTH_PASSWORD} + networks: + - airbyte_internal + - airbyte_public + depends_on: + - webapp + - server volumes: workspace: name: ${WORKSPACE_DOCKER_MOUNT} @@ -205,3 +234,6 @@ volumes: name: ${DATA_DOCKER_MOUNT} db: name: ${DB_DOCKER_MOUNT} +networks: + airbyte_public: + airbyte_internal: diff --git a/docs/deploying-airbyte/local-deployment.md b/docs/deploying-airbyte/local-deployment.md index 91d433322237..65153e46f5e7 100644 --- a/docs/deploying-airbyte/local-deployment.md +++ b/docs/deploying-airbyte/local-deployment.md @@ -7,8 +7,8 @@ These instructions have been tested on MacOS, Windows 10 and Ubuntu 20.04. ## Setup & launch Airbyte -* Install Docker on your workstation \(see [instructions](https://www.docker.com/products/docker-desktop)\). Make sure you're on the latest version of `docker-compose`. -* After Docker is installed, you can immediately get started locally by running: +- Install Docker on your workstation \(see [instructions](https://www.docker.com/products/docker-desktop)\). Make sure you're on the latest version of `docker-compose`. +- After Docker is installed, you can immediately get started locally by running: ```bash git clone https://github.com/airbytehq/airbyte.git @@ -16,8 +16,17 @@ cd airbyte docker-compose up ``` -* In your browser, just visit [http://localhost:8000](http://localhost:8000) -* Start moving some data! +- In your browser, just visit [http://localhost:8000](http://localhost:8000) +- You will be asked for a username and password. By default, that's username `airbyte` and password `password`. Once you deploy airbyte to your servers, be sure to [change these](/operator-guides/security): + +```yaml +# Proxy Configuration +# Set to empty values, e.g. "" to disable basic auth +BASIC_AUTH_USERNAME=your_new_username_here +BASIC_AUTH_PASSWORD=your_new_password_here +``` + +- Start moving some data! ## Deploy on Windows @@ -34,8 +43,9 @@ Follow the steps on the system requirements, and necessarily, download and insta Install [Docker Desktop](https://docs.docker.com/desktop/windows/install/) from here. Make sure to select the options: -1. *Enable Hyper-V Windows Features* -2. *Install required Windows components for WSL 2*\ + +1. _Enable Hyper-V Windows Features_ +2. _Install required Windows components for WSL 2_\ when prompted. After installation, it will require to reboot your computer. **3. You're done!** @@ -45,10 +55,11 @@ git clone https://github.com/airbytehq/airbyte.git cd airbyte docker-compose up ``` -* In your browser, just visit [http://localhost:8000](http://localhost:8000) -* Start moving some data! + +- In your browser, just visit [http://localhost:8000](http://localhost:8000) +- You will be asked for a username and password. By default, that's username `airbyte` and password `password`. Once you deploy airbyte to your servers, be sure to [change these](/operator-guides/security). +- Start moving some data! ## Troubleshooting If you encounter any issues, just connect to our [Slack](https://slack.airbyte.io). Our community will help! We also have a [troubleshooting](../troubleshooting/on-deploying.md) section in our docs for common problems. - diff --git a/docs/deploying-airbyte/on-aws-ec2.md b/docs/deploying-airbyte/on-aws-ec2.md index 18da4e1608da..ded1b5757b3c 100644 --- a/docs/deploying-airbyte/on-aws-ec2.md +++ b/docs/deploying-airbyte/on-aws-ec2.md @@ -8,43 +8,43 @@ The instructions have been tested on `Amazon Linux 2 AMI (HVM)` ## Create a new instance -* Launch a new instance +- Launch a new instance ![](../.gitbook/assets/aws_ec2_launch.png) -* Select instance AMI +- Select instance AMI ![](../.gitbook/assets/aws_ec2_ami.png) -* Select instance type - * For testing out Airbyte, a `t2.medium` instance is likely sufficient. Airbyte uses a lot of disk space with images and logs, so make sure to provision at least 30GBs of disk per node. - * For long-running Airbyte installations, we recommend a `t2.large` instance. +- Select instance type + - For testing out Airbyte, a `t2.medium` instance is likely sufficient. Airbyte uses a lot of disk space with images and logs, so make sure to provision at least 30GBs of disk per node. + - For long-running Airbyte installations, we recommend a `t2.large` instance. ![](../.gitbook/assets/aws_ec2_instance_type.png) -* `Next: Configure Instance Details` - * You can tune parameters or keep the defaults -* `Next: Add Storage` - * You can tune parameters or keep the defaults -* `Next: Add Tags` - * You can tune parameters or keep the defaults -* `Next: Configure Security Groups` - * We are going to allow network for `ssh` +- `Next: Configure Instance Details` + - You can tune parameters or keep the defaults +- `Next: Add Storage` + - You can tune parameters or keep the defaults +- `Next: Add Tags` + - You can tune parameters or keep the defaults +- `Next: Configure Security Groups` + - We are going to allow network for `ssh` ![](../.gitbook/assets/aws_ec2_security_group.png) -* `Review and Launch` -* `Launch` -* Create a ssh key so you can connect to the instance - * Download the key \(and don't lose it or you won't be able to connect to the instance\) +- `Review and Launch` +- `Launch` +- Create a ssh key so you can connect to the instance + - Download the key \(and don't lose it or you won't be able to connect to the instance\) ![](../.gitbook/assets/aws_ec2_ssh_key.png) -* `Launch Instances` +- `Launch Instances` ![](../.gitbook/assets/aws_ec2_instance_view.png) -* Wait for the instance to become `Running` +- Wait for the instance to become `Running` ## Install environment @@ -54,7 +54,7 @@ Note: The following commands will be entered either on your local terminal or in ::: -* Connect to your instance +- Connect to your instance ```bash # In your workstation terminal @@ -64,7 +64,7 @@ chmod 400 $SSH_KEY # or ssh will complain that the key has the wrong permissions ssh -i $SSH_KEY ec2-user@$INSTANCE_IP ``` -* Install `docker` +- Install `docker` ```bash # In your ssh session on the instance terminal @@ -74,7 +74,7 @@ sudo service docker start sudo usermod -a -G docker $USER ``` -* Install `docker-compose` +- Install `docker-compose` ```bash # In your ssh session on the instance terminal @@ -83,7 +83,7 @@ sudo chmod +x /usr/local/bin/docker-compose docker-compose --version ``` -* Close the ssh connection to ensure the group modification is taken into account +- Close the ssh connection to ensure the group modification is taken into account ```bash # In your ssh session on the instance terminal @@ -92,14 +92,14 @@ logout ## Install & start Airbyte -* Connect to your instance +- Connect to your instance ```bash # In your workstation terminal ssh -i $SSH_KEY ec2-user@$INSTANCE_IP ``` -* Install Airbyte +- Install Airbyte ```bash # In your ssh session on the instance terminal @@ -116,11 +116,11 @@ For security reasons, we strongly recommend to not expose Airbyte on Internet av ::: -* Create ssh tunnel for port 8000 +- Create ssh tunnel for port 8000 :::info -If you want to use different ports you will need to modify `API_URL` in your `.env` file and restart Airbyte. +If you want to use different ports or change the HTTP basic auth username and password, you will need to modify `API_URL` in your `.env` file and restart Airbyte. ::: @@ -129,7 +129,7 @@ If you want to use different ports you will need to modify `API_URL` in your `.e ssh -i $SSH_KEY -L 8000:localhost:8000 -N -f ec2-user@$INSTANCE_IP ``` -* Just visit [http://localhost:8000](http://localhost:8000) in your browser and start moving some data! +- Just visit [http://localhost:8000](http://localhost:8000) in your browser and start moving some data! ## Pushing Airbyte logs to CloudWatch @@ -138,4 +138,3 @@ If you want to get your logs from your Airbyte Docker containers in CloudWatch, ## Troubleshooting If you encounter any issues, just connect to our [Slack](https://slack.airbyte.io). Our community will help! We also have a [FAQ](../troubleshooting/on-deploying.md) section in our docs for common problems. - diff --git a/docs/operator-guides/configuring-airbyte.md b/docs/operator-guides/configuring-airbyte.md index 36e33c268dea..c3b406a34052 100644 --- a/docs/operator-guides/configuring-airbyte.md +++ b/docs/operator-guides/configuring-airbyte.md @@ -37,13 +37,22 @@ Be careful using variables marked as `alpha` as they aren't meant for public con The following variables are relevant to both Docker and Kubernetes. #### Core + 1. `AIRBYTE_VERSION` - Defines the Airbyte deployment version. 2. `SPEC_CACHE_BUCKET` - Defines the bucket for caching specs. This immensely speeds up spec operations. This is updated when new versions are published. 3. `WORKER_ENVIRONMENT` - Defines if the deployment is Docker or Kubernetes. Airbyte behaves accordingly. 4. `CONFIG_ROOT` - Defines the configs directory. Applies only to Docker, and is present in Kubernetes for backward compatibility. 5. `WORKSPACE_ROOT` - Defines the Airbyte workspace directory. Applies only to Docker, and is present in Kubernetes for backward compatibility. +#### Access + +Set to empty values, e.g. "" to disable basic auth. **Be sure to change these values**. + +1. BASIC_AUTH_USERNAME=airbyte +2. BASIC_AUTH_PASSWORD=password + #### Secrets + 1. `SECRET_STORE_GCP_PROJECT_ID` - Defines the GCP Project to store secrets in. Alpha support. 2. `SECRET_STORE_GCP_CREDENTIALS` - Define the JSON credentials used to read/write Airbyte Configuration to Google Secret Manager. These credentials must have Secret Manager Read/Write access. Alpha support. 3. `VAULT_ADDRESS` - Define the vault address to read/write Airbyte Configuration to Hashicorp Vault. Alpha Support. @@ -53,6 +62,7 @@ The following variables are relevant to both Docker and Kubernetes. 7. `SECRET_PERSISTENCE` - Defines the Secret Persistence type. Defaults to NONE. Set to GOOGLE_SECRET_MANAGER to use Google Secret Manager. Set to TESTING_CONFIG_DB_TABLE to use the database as a test. Set to VAULT to use Hashicorp Vault, currently only the token based authentication is supported. Alpha support. Undefined behavior will result if this is turned on and then off. #### Database + 1. `DATABASE_USER` - Define the Jobs Database user. 2. `DATABASE_PASSWORD` - Define the Jobs Database password. 3. `DATABASE_URL` - Define the Jobs Database url in the form of `jdbc:postgresql://${DATABASE_HOST}:${DATABASE_PORT/${DATABASE_DB}`. Do not include username or password. @@ -64,22 +74,26 @@ The following variables are relevant to both Docker and Kubernetes. 9. `RUN_DATABASE_MIGRATION_ON_STARTUP` - Define if the Bootloader should run migrations on start up. #### Airbyte Services + 1. `TEMPORAL_HOST` - Define the url where Temporal is hosted at. Please include the port. Airbyte services use this information. 2. `INTERNAL_API_HOST` - Define the url where the Airbyte Server is hosted at. Please include the port. Airbyte services use this information. 3. `WEBAPP_URL` - Define the url the Airbyte Webapp is hosted at. Please include the port. Airbyte services use this information. #### Jobs + 1. `SYNC_JOB_MAX_ATTEMPTS` - Define the number of attempts a sync will attempt before failing. 2. `SYNC_JOB_MAX_TIMEOUT_DAYS` - Define the number of days a sync job will execute for before timing out. -3. `JOB_MAIN_CONTAINER_CPU_REQUEST` - Define the job container's minimum CPU usage. Units follow either Docker or Kubernetes, depending on the deployment. Defaults to none. +3. `JOB_MAIN_CONTAINER_CPU_REQUEST` - Define the job container's minimum CPU usage. Units follow either Docker or Kubernetes, depending on the deployment. Defaults to none. 4. `JOB_MAIN_CONTAINER_CPU_LIMIT` - Define the job container's maximum CPU usage. Units follow either Docker or Kubernetes, depending on the deployment. Defaults to none. 5. `JOB_MAIN_CONTAINER_MEMORY_REQUEST` - Define the job container's minimum RAM usage. Units follow either Docker or Kubernetes, depending on the deployment. Defaults to none. 6. `JOB_MAIN_CONTAINER_MEMORY_LIMIT` - Define the job container's maximum RAM usage. Units follow either Docker or Kubernetes, depending on the deployment. Defaults to none. #### Logging + 1. `LOG_LEVEL` - Define log levels. Defaults to INFO. This value is expected to be one of the various Log4J log levels. #### Monitoring + 1. `PUBLISH_METRICS` - Define whether to publish metrics collected by the Metrics Reporter. Defaults to false. 2. `METRIC_CLIENT` - Defines which metrics client to use. Only relevant if `PUBLISH_METRICS` is set to true. Accepts either `datadog` or `otel`. Default to none. 3. `DD_AGENT_HOST` - Defines the ip the Datadog metric client sends metrics to. Only relevant if `METRIC_CLIENT` is set to `datadog`. Defaults to none. @@ -87,6 +101,7 @@ The following variables are relevant to both Docker and Kubernetes. 5. `OTEL_COLLECTOR_ENDPOIN` - Define the ip:port the OTEL metric client sends metrics to. Only relevant if `METRIC_CLIENT` is set to `otel`. Defaults to none. #### Worker + 1. `MAX_SPEC_WORKERS` - Define the maximum number of Spec workers each Airbyte Worker container can support. Defaults to 5. 2. `MAX_CHECK_WORKERS` - Define the maximum number of Check workers each Airbyte Worker container can support. Defaults to 5. 3. `MAX_SYNC_WORKERS` - Define the maximum number of Sync workers each Airbyte Worker container can support. Defaults to 5. @@ -94,12 +109,15 @@ The following variables are relevant to both Docker and Kubernetes. 5. `SENTRY_DSN` - Define the [DSN](https://docs.sentry.io/product/sentry-basics/dsn-explainer/) of necessary Sentry instance. Defaults to empty. Integration with Sentry is explained [here](./sentry-integration.md) ### Docker-Only + 1. `WORKSPACE_DOCKER_MOUNT` - Defines the name of the Airbyte docker volume. 2. `DOCKER_NETWORK` - Defines the docker network the new Scheduler launches jobs on. 3. `LOCAL_DOCKER_MOUNT` - Defines the name of the docker mount that is used for local file handling. On Docker, this allows connector pods to interact with a volume for "local file" operations. ### Kubernetes-Only + #### Jobs + 1. `JOB_KUBE_TOLERATIONS` - Define one or more Job pod tolerations. Tolerations are separated by ';'. Each toleration contains k=v pairs mentioning some/all of key, effect, operator and value and separated by `,`. 2. `JOB_KUBE_NODE_SELECTORS` - Define one or more Job pod node selectors. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2`. It is the pod node selectors of the sync job and the default pod node selectors fallback for others jobs. 3. `JOB_KUBE_ANNOTATIONS` - Define one or more Job pod annotations. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2`. It is the pod annotations of the sync job and the default pod annotations fallback for others jobs. @@ -116,16 +134,18 @@ The following variables are relevant to both Docker and Kubernetes. A job specific variable overwrites the default sync job variable defined above. 1. `SPEC_JOB_KUBE_NODE_SELECTORS` - Define one or more pod node selectors for the spec job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` -2. `CHECK_JOB_KUBE_NODE_SELECTORS` - Define one or more pod node selectors for the check job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` +2. `CHECK_JOB_KUBE_NODE_SELECTORS` - Define one or more pod node selectors for the check job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` 3. `DISCOVER_JOB_KUBE_NODE_SELECTORS` - Define one or more pod node selectors for the discover job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` 4. `SPEC_JOB_KUBE_ANNOTATIONS` - Define one or more pod annotations for the spec job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` 5. `CHECK_JOB_KUBE_ANNOTATIONS` - Define one or more pod annotations for the check job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` 6. `DISCOVER_JOB_KUBE_ANNOTATIONS` - Define one or more pod annotations for the discover job. Each k=v pair is separated by a `,`. For example: `key1=value1,key2=value2` #### Worker + 1. `TEMPORAL_WORKER_PORTS` - Define the local ports the Airbyte Worker pod uses to connect to the various Job pods. Port 9001 - 9040 are exposed by default in the Kustomize deployments. #### Logging + Note that Airbyte does not support logging to separate Cloud Storage providers. Please see [here](https://docs.airbyte.com/deploying-airbyte/on-kubernetes#configure-logs) for more information on configuring Kuberentes logging. diff --git a/docs/operator-guides/security.md b/docs/operator-guides/security.md index 39a676ce59db..baa70aed94d3 100644 --- a/docs/operator-guides/security.md +++ b/docs/operator-guides/security.md @@ -1,22 +1,22 @@ # Airbyte Security -Airbyte is committed to keeping your data safe by following industry-standard practices for securing physical deployments, setting access policies, and leveraging the security features of leading Cloud providers. +Airbyte is committed to keeping your data safe by following industry-standard practices for securing physical deployments, setting access policies, and leveraging the security features of leading Cloud providers. If you have any security concerns with Airbyte or believe you have uncovered a vulnerability, contact us at [security@airbyte.io](mailto:security@airbyte.io) ## Securing your data -Airbyte connectors operate as the data pipes moving data from Point A to point B: Extracting data from data sources (APIs, files, databases) and loading it into destination platforms (warehouses, data lakes) with optional transformation performed at the data destination. As soon as data is transferred from the source to the destination, it is purged from an Airbyte deployment. +Airbyte connectors operate as the data pipes moving data from Point A to point B: Extracting data from data sources (APIs, files, databases) and loading it into destination platforms (warehouses, data lakes) with optional transformation performed at the data destination. As soon as data is transferred from the source to the destination, it is purged from an Airbyte deployment. An Airbyte deployment stores the following data: ### Technical Logs -Technical logs are stored for troubleshooting purposes and may contain sensitive data based on the connection’s `state` data. If your connection is set to an Incremental sync mode, users choose which column is the cursor for their connection. While we strongly recommend a timestamp like an `updated_at` column, users can choose any column they want to be the cursor. +Technical logs are stored for troubleshooting purposes and may contain sensitive data based on the connection’s `state` data. If your connection is set to an Incremental sync mode, users choose which column is the cursor for their connection. While we strongly recommend a timestamp like an `updated_at` column, users can choose any column they want to be the cursor. ### Configuration Metadata -Airbyte retains configuration details and data points such as table and column names for each integration. +Airbyte retains configuration details and data points such as table and column names for each integration. ### Sensitive Data​ @@ -24,10 +24,10 @@ As Airbyte is not aware of the data being transferred, users are required to fol For more information, see [Airbyte’s Privacy Policy](https://airbyte.com/privacy-policy) -## Securing Airbyte Open Source +## Securing Airbyte Open Source :::note -Our security and reliability commitments are only applicable to Airbyte Cloud. Airbyte Open Source security and reliability depend on your development and production setups. +Our security and reliability commitments are only applicable to Airbyte Cloud. Airbyte Open Source security and reliability depend on your development and production setups. ::: ### Network Security @@ -38,9 +38,16 @@ You can secure access to Airbyte using the following methods: - Deploy Airbyte in a private network or use a firewall to filter which IP is allowed to access your host. - Deploy Airbyte behind a reverse proxy and handle the access control on the reverse proxy side. +- Change the default username and password in your environment's `.env` file: + ``` + # Proxy Configuration + # Set to empty values, e.g. "" to disable basic auth + BASIC_AUTH_USERNAME=your_new_username_here + BASIC_AUTH_PASSWORD=your_new_password_here + ``` - If you deployed Airbyte on a cloud provider: - - GCP: use the [Identity-Aware proxy](https://cloud.google.com/iap) service - - AWS: use the [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) service + - GCP: use the [Identity-Aware proxy](https://cloud.google.com/iap) service + - AWS: use the [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html) service ### Credential management @@ -60,6 +67,7 @@ Note that this process is not reversible. Once you have converted to a secret st Most Airbyte Open Source connectors support encryption-in-transit (SSL or HTTPS). We recommend configuring your connectors to use the encryption option whenever available. ### Telemetry + Airbyte does send anonymized data to our services to improve the product (especially connector reliability and scale). To disable telemetry, modify the .env file and define the following environment variable: ``` @@ -72,11 +80,12 @@ Airbyte Cloud leverages the security features of leading Cloud providers and set ### Physical infrastructure -Airbyte Cloud is currently deployed on GCP with all servers located in the United States. We use isolated pods to ensure your data is kept separate from other customers’ data. +Airbyte Cloud is currently deployed on GCP with all servers located in the United States. We use isolated pods to ensure your data is kept separate from other customers’ data. Only certain Airbyte staff can access Airbyte infrastructure and technical logs for deployments, upgrades, configuration changes, and troubleshooting. ### Network security + - You may need to allowlist one of our IP addresses to enable access to Airbyte: - 34.106.109.131 - 34.106.196.165 @@ -84,7 +93,7 @@ Only certain Airbyte staff can access Airbyte infrastructure and technical logs ### Credential management -Most Airbyte Cloud connectors require keys, secrets, or passwords to allow the connectors to continually sync without prompting credentials on every refresh. Airbyte Cloud fetches credentials using HTTPS and stores them in Google Cloud’s [Secret Manager](https://cloud.google.com/secret-manager). When persisting connector configurations to disk or the database, we store a version of the configuration that points to the secret in Google Secret Manager instead of the secret itself to limit the parts of the system interacting with secrets. +Most Airbyte Cloud connectors require keys, secrets, or passwords to allow the connectors to continually sync without prompting credentials on every refresh. Airbyte Cloud fetches credentials using HTTPS and stores them in Google Cloud’s [Secret Manager](https://cloud.google.com/secret-manager). When persisting connector configurations to disk or the database, we store a version of the configuration that points to the secret in Google Secret Manager instead of the secret itself to limit the parts of the system interacting with secrets. ### Encryption @@ -94,7 +103,7 @@ All Airbyte Cloud connectors (APIs, files, databases) pull data through encrypte ### Authentication -Airbyte Cloud allows you to log in to the platform using your email and password, Google account, or GitHub account. +Airbyte Cloud allows you to log in to the platform using your email and password, Google account, or GitHub account. ### Access Control @@ -104,7 +113,7 @@ Airbyte Cloud supports [user management](https://docs.airbyte.com/cloud/managing Our compliance efforts for Airbyte Cloud include: -- SOC 2 Type II assessment: An independent third-party completed a SOC2 Type II assessment and found effective operational controls in place. Independent third-party audits will continue at a regular cadence, and the most recent report is available upon request. +- SOC 2 Type II assessment: An independent third-party completed a SOC2 Type II assessment and found effective operational controls in place. Independent third-party audits will continue at a regular cadence, and the most recent report is available upon request. - ISO 27001 certification: We are currently pursuing ISO 27001 certification and will continue to align the evolution of our security program with its standards as we grow. - Assessments and penetration tests: We use tools provided by the Cloud platforms as well as third-party assessments and penetration tests. @@ -118,4 +127,4 @@ Airbyte takes security issues very seriously. If you have any concerns about Air Use this security address only for undisclosed vulnerabilities. For fixed issues or general questions on how to use the security features, use the [Discourse forum](https://discuss.airbyte.io/) or [Community Slack](https://slack.airbyte.com/). -Please report any security problems to us before disclosing it publicly. +Please report any security problems to us before disclosing it publicly. diff --git a/docs/quickstart/deploy-airbyte.md b/docs/quickstart/deploy-airbyte.md index 38163974c68a..007c31cdb1bc 100644 --- a/docs/quickstart/deploy-airbyte.md +++ b/docs/quickstart/deploy-airbyte.md @@ -11,7 +11,7 @@ cd airbyte docker-compose up ``` -Once you see an Airbyte banner, the UI is ready to go at [http://localhost:8000](http://localhost:8000)! +Once you see an Airbyte banner, the UI is ready to go at [http://localhost:8000](http://localhost:8000)! You will be asked for a username and password. By default, that's username `airbyte` and password `password`. Once you deploy airbyte to your servers, **be sure to change these** in your `.env` file. Alternatively, if you have an Airbyte Cloud invite, just follow [these steps.](../deploying-airbyte/on-cloud.md) @@ -19,9 +19,8 @@ Alternatively, if you have an Airbyte Cloud invite, just follow [these steps.](. If you have any questions about the Airbyte Open-Source setup and deployment process, head over to our [Getting Started FAQ](https://discuss.airbyte.io/c/faq/15) on our Discourse that answers the following questions and more: -* How long does it take to set up Airbyte? -* Where can I see my data once I've run a sync? -* Can I set a start time for my sync? +- How long does it take to set up Airbyte? +- Where can I see my data once I've run a sync? +- Can I set a start time for my sync? If there are any questions that we couldn't answer here, we'd love to help you get started. [Join our Slack](https://airbytehq.slack.com/ssb/redirect) and feel free to ask your questions in the \#getting-started channel. - diff --git a/octavia-cli/README.md b/octavia-cli/README.md index a53ea31c5d9d..52a9489fc63d 100644 --- a/octavia-cli/README.md +++ b/octavia-cli/README.md @@ -140,6 +140,8 @@ docker-compose run octavia-cli ` | **Flag** | **Description** | **Env Variable** | **Default** | | ---------------------------------------- | --------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------ | | `--airbyte-url` | Airbyte instance URL. | `AIRBYTE_URL` | `http://localhost:8000` | +| `--airbyte-username` | Airbyte instance username (basic auth). | `AIRBYTE_URL` | `airbyte` | +| `--airbyte-password` | Airbyte instance password (basic auth). | `AIRBYTE_URL` | `password` | | `--workspace-id` | Airbyte workspace id. | `AIRBYTE_WORKSPACE_ID` | The first workspace id found on your Airbyte instance. | | `--enable-telemetry/--disable-telemetry` | Enable or disable the sending of telemetry data. | `OCTAVIA_ENABLE_TELEMETRY` | True | | `--api-http-header` | HTTP Header value pairs passed while calling Airbyte's API | None | None | @@ -690,7 +692,7 @@ $ octavia apply 3. Activate the virtualenv: `source .venv/bin/activate`. 4. Install dev dependencies: `pip install -e .\[tests\]`. 5. Install `pre-commit` hooks: `pre-commit install`. -6. Run the unittest suite: `pytest --cov=octavia_cli`. +6. Run the unittest suite: `pytest --cov=octavia_cli`. Note, a local version of airbyte needs to be running (e.g. `docker compose up` from the root directory of the project) 7. Make sure the build passes (step 0) before opening a PR. ## Telemetry @@ -709,7 +711,8 @@ You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment | Version | Date | Description | PR | | ------- | ---------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- | -| 0.40.15 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | +| 0.41.0 | 2022-10-13 | Use Basic Authentication for making API requests | [#17982](https://github.com/airbytehq/airbyte/pull/17982) | +| 0.40.15 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | | 0.39.33 | 2022-07-05 | Add `octavia import all` command | [#14374](https://github.com/airbytehq/airbyte/pull/14374) | | 0.39.32 | 2022-06-30 | Create import command to import and manage existing Airbyte resource from octavia-cli | [#14137](https://github.com/airbytehq/airbyte/pull/14137) | | 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | diff --git a/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml b/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml index a7316d679e0c..bb2c25757c34 100644 --- a/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml +++ b/octavia-cli/integration_tests/cassettes/test_api_http_headers/test_api_http_headers.yaml @@ -6,10 +6,12 @@ interactions: - application/json Another-Custom-Header: - Bar + Authorization: + - Basic YWlyYnl0ZTpwYXNzd29yZA== Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.42 + - octavia-cli/0.40.14 method: GET uri: http://localhost:8000/api/v1/health response: @@ -31,7 +33,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 11 Aug 2022 08:55:08 GMT + - Fri, 14 Oct 2022 18:12:50 GMT Server: - nginx/1.23.1 status: @@ -44,17 +46,19 @@ interactions: - application/json Another-Custom-Header: - Bar + Authorization: + - Basic YWlyYnl0ZTpwYXNzd29yZA== Content-Type: - application/json Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.42 + - octavia-cli/0.40.14 method: POST uri: http://localhost:8000/api/v1/workspaces/list response: body: - string: '{"workspaces":[{"workspaceId":"a70357d2-f753-41a4-a0cc-22944c59e5d2","customerId":"3bc0b3a1-bd3d-4cf6-8bd8-8273addacb42","name":"a70357d2-f753-41a4-a0cc-22944c59e5d2","slug":"a70357d2-f753-41a4-a0cc-22944c59e5d2","initialSetupComplete":false,"displaySetupWizard":true,"notifications":[]}]}' + string: '{"workspaces":[{"workspaceId":"df3ad9d3-5d41-4fa9-993e-c6723552c26a","customerId":"96d8f0d5-762c-4c38-9580-966f4c887173","email":"evan@airbyte.io","name":"df3ad9d3-5d41-4fa9-993e-c6723552c26a","slug":"df3ad9d3-5d41-4fa9-993e-c6723552c26a","initialSetupComplete":true,"displaySetupWizard":true,"anonymousDataCollection":false,"news":false,"securityUpdates":true,"notifications":[],"defaultGeography":"auto"}]}' headers: Access-Control-Allow-Headers: - Origin, Content-Type, Accept, Content-Encoding @@ -65,36 +69,38 @@ interactions: Connection: - keep-alive Content-Length: - - "289" + - "408" Content-Security-Policy: - script-src * 'unsafe-inline'; worker-src self blob:; Content-Type: - application/json Date: - - Thu, 11 Aug 2022 08:55:08 GMT + - Fri, 14 Oct 2022 18:12:50 GMT Server: - nginx/1.23.1 status: code: 200 message: OK - request: - body: '{"workspaceId": "a70357d2-f753-41a4-a0cc-22944c59e5d2"}' + body: '{"workspaceId": "df3ad9d3-5d41-4fa9-993e-c6723552c26a"}' headers: Accept: - application/json Another-Custom-Header: - Bar + Authorization: + - Basic YWlyYnl0ZTpwYXNzd29yZA== Content-Type: - application/json Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.42 + - octavia-cli/0.40.14 method: POST uri: http://localhost:8000/api/v1/workspaces/get response: body: - string: '{"workspaceId":"a70357d2-f753-41a4-a0cc-22944c59e5d2","customerId":"3bc0b3a1-bd3d-4cf6-8bd8-8273addacb42","name":"a70357d2-f753-41a4-a0cc-22944c59e5d2","slug":"a70357d2-f753-41a4-a0cc-22944c59e5d2","initialSetupComplete":false,"displaySetupWizard":true,"notifications":[]}' + string: '{"workspaceId":"df3ad9d3-5d41-4fa9-993e-c6723552c26a","customerId":"96d8f0d5-762c-4c38-9580-966f4c887173","email":"evan@airbyte.io","name":"df3ad9d3-5d41-4fa9-993e-c6723552c26a","slug":"df3ad9d3-5d41-4fa9-993e-c6723552c26a","initialSetupComplete":true,"displaySetupWizard":true,"anonymousDataCollection":false,"news":false,"securityUpdates":true,"notifications":[],"defaultGeography":"auto"}' headers: Access-Control-Allow-Headers: - Origin, Content-Type, Accept, Content-Encoding @@ -105,13 +111,13 @@ interactions: Connection: - keep-alive Content-Length: - - "272" + - "391" Content-Security-Policy: - script-src * 'unsafe-inline'; worker-src self blob:; Content-Type: - application/json Date: - - Thu, 11 Aug 2022 08:55:08 GMT + - Fri, 14 Oct 2022 18:12:50 GMT Server: - nginx/1.23.1 status: @@ -124,27 +130,29 @@ interactions: - application/json Another-Custom-Header: - Bar + Authorization: + - Basic YWlyYnl0ZTpwYXNzd29yZA== Content-Type: - application/json Custom-Header: - Foo User-Agent: - - octavia-cli/0.39.42 + - octavia-cli/0.40.14 method: POST uri: http://localhost:8000/api/v1/source_definitions/list response: body: string: - "{\"sourceDefinitions\":[{\"sourceDefinitionId\":\"fbb5fbe2-16ad-4cf4-af7d-ff9d9c316c87\",\"name\":\"Sendgrid\",\"dockerRepository\":\"airbyte/source-sendgrid\",\"dockerImageTag\":\"0.2.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sendgrid\",\"icon\":\"\\nimage/svg+xmlimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d6f73702-d7a0-4e95-9758-b0fb1af0bfba\",\"name\":\"Jenkins\",\"dockerRepository\":\"farosai/airbyte-jenkins-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/jenkins\",\"icon\":\"\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cc88c43f-6f53-4e8a-8c4d-b284baaf9635\",\"name\":\"Delighted\",\"dockerRepository\":\"airbyte/source-delighted\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/delighted\",\"icon\":\"\\n \\n \\n + \ \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"008b2e26-11a3-11ec-82a8-0242ac130003\",\"name\":\"Commercetools\",\"dockerRepository\":\"airbyte/source-commercetools\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/commercetools\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d917a47b-8537-4d0d-8c10-36a9928d4265\",\"name\":\"Kafka\",\"dockerRepository\":\"airbyte/source-kafka\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kafka\",\"icon\":\"\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"47f25999-dd5e-4636-8c39-e7cea2453331\",\"name\":\"Bing + Ads\",\"dockerRepository\":\"airbyte/source-bing-ads\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bing-ads\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2817b3f0-04e4-4c7a-9f32-7a5e8a83db95\",\"name\":\"PagerDuty\",\"dockerRepository\":\"farosai/airbyte-pagerduty-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pagerduty\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"008b2e26-11a3-11ec-82a8-0242ac130003\",\"name\":\"Commercetools\",\"dockerRepository\":\"airbyte/source-commercetools\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/commercetools\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"200330b2-ea62-4d11-ac6d-cfe3e3f8ab2b\",\"name\":\"Snapchat - Marketing\",\"dockerRepository\":\"airbyte/source-snapchat-marketing\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snapchat-marketing\",\"icon\":\"\\n\\n\\n\\n - \ \\n \\n \\n - \ \\n \\n image/svg+xml\\n - \ \\n \\n \\n \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n \\n \\n \\n \\n \\n - \ \\n\\t\\n\\t\\t\\n\\n\\t\\n\\n\\t\\n\\n\\n \\n \\n\\t.st0{fill:#FFFFFF;}\\n\\n\\n\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cf40a7f8-71f8-45ce-a7fa-fca053e4028c\",\"name\":\"Confluence\",\"dockerRepository\":\"airbyte/source-confluence\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/confluence\",\"icon\":\"\\n\\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n \\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"447e0381-3780-4b46-bb62-00a4e3c8b8e2\",\"name\":\"IBM - Db2\",\"dockerRepository\":\"airbyte/source-db2\",\"dockerImageTag\":\"0.1.13\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/db2\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"2af123bf-0aaf-4e0d-9784-cb497f23741a\",\"name\":\"Appstore\",\"dockerRepository\":\"airbyte/source-appstore-singer\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/appstore\",\"icon\":\"\\n\\n - \ \\n \\n \\n \\n - \ \\n \\n\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c8630570-086d-4a40-99ae-ea5b18673071\",\"name\":\"Zendesk - Talk\",\"dockerRepository\":\"airbyte/source-zendesk-talk\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-talk\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bfd1ddf8-ae8a-4620-b1d7-55597d2ba08c\",\"name\":\"BigQuery\",\"dockerRepository\":\"airbyte/source-bigquery\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bigquery\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"eca08d79-7b92-4065-b7f3-79c14836ebe7\",\"name\":\"Freshsales\",\"dockerRepository\":\"airbyte/source-freshsales\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshsales\",\"icon\":\"freshsales_logo_color\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b39a7370-74c3-45a6-ac3a-380d48520a83\",\"name\":\"Oracle - DB\",\"dockerRepository\":\"airbyte/source-oracle\",\"dockerImageTag\":\"0.3.19\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/oracle\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"2fed2292-5586-480c-af92-9944e39fe12d\",\"name\":\"Short.io\",\"dockerRepository\":\"airbyte/source-shortio\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/shortio\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"79c1aa37-dae3-42ae-b333-d1c105477715\",\"name\":\"Zendesk - Support\",\"dockerRepository\":\"airbyte/source-zendesk-support\",\"dockerImageTag\":\"0.2.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-support\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"010eb12f-837b-4685-892d-0a39f76a98f5\",\"name\":\"Facebook - Pages\",\"dockerRepository\":\"airbyte/source-facebook-pages\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/facebook-pages\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"fa9f58c6-2d03-4237-aaa4-07d75e0c1396\",\"name\":\"Amplitude\",\"dockerRepository\":\"airbyte/source-amplitude\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amplitude\",\"icon\":\"\\n\\t\\n\\t\\n\\t\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"492b56d1-937c-462e-8076-21ad2031e784\",\"name\":\"Hellobaton\",\"dockerRepository\":\"airbyte/source-hellobaton\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hellobaton\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8d7ef552-2c0f-11ec-8d3d-0242ac130003\",\"name\":\"SearchMetrics\",\"dockerRepository\":\"airbyte/source-search-metrics\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/search-metrics\",\"icon\":\"\\n\\n\\n\\nCreated by potrace 1.16, written by Peter Selinger - 2001-2019\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bb6afd81-87d5-47e3-97c4-e2c2901b1cf8\",\"name\":\"OneSignal\",\"dockerRepository\":\"airbyte/source-onesignal\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/onesignal\",\"icon\":\"\\n\\n \\n \\n - \ \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"dfffecb7-9a13-43e9-acdc-b92af7997ca9\",\"name\":\"Close.com\",\"dockerRepository\":\"airbyte/source-close-com\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/close-com\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"29b409d9-30a5-4cc8-ad50-886eb846fea3\",\"name\":\"QuickBooks\",\"dockerRepository\":\"airbyte/source-quickbooks-singer\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/quickbooks\",\"icon\":\" qb-logoCreated with Sketch. - \",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cd42861b-01fc-4658-a8ab-5d11d0510f01\",\"name\":\"Recurly\",\"dockerRepository\":\"airbyte/source-recurly\",\"dockerImageTag\":\"0.4.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/recurly\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"eff3616a-f9c3-11eb-9a03-0242ac130003\",\"name\":\"Google - Analytics\",\"dockerRepository\":\"airbyte/source-google-analytics-v4\",\"dockerImageTag\":\"0.1.25\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/google-analytics-universal-analytics\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d8313939-3782-41b0-be29-b3ca20d8dd3a\",\"name\":\"Intercom\",\"dockerRepository\":\"airbyte/source-intercom\",\"dockerImageTag\":\"0.1.24\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/intercom\",\"icon\":\"\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ef580275-d9a9-48bb-af5e-db0f5855be04\",\"name\":\"Webflow\",\"dockerRepository\":\"airbyte/source-webflow\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/webflow\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c2281cee-86f9-4a86-bb48-d23286b4c7bd\",\"name\":\"Slack\",\"dockerRepository\":\"airbyte/source-slack\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/slack\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7b86879e-26c5-4ef6-a5ce-2be5c7b46d1e\",\"name\":\"Linnworks\",\"dockerRepository\":\"airbyte/source-linnworks\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linnworks\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6e00b415-b02e-4160-bf02-58176a0ae687\",\"name\":\"Notion\",\"dockerRepository\":\"airbyte/source-notion\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/notion\",\"icon\":\"\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"14c6e7ea-97ed-4f5e-a7b5-25e9a80b8212\",\"name\":\"Airtable\",\"dockerRepository\":\"airbyte/source-airtable\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/airtable\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6371b14b-bc68-4236-bfbd-468e8df8e968\",\"name\":\"PokeAPI\",\"dockerRepository\":\"airbyte/source-pokeapi\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pokeapi\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"80a54ea2-9959-4040-aac1-eee42423ec9b\",\"name\":\"Monday\",\"dockerRepository\":\"airbyte/source-monday\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/monday\",\"icon\":\"\\n\\n - \ \\n \\n \\n image/svg+xml\\n - \ \\n \\n \\n \\n \\n \\n Logo / monday.com\\n \\n - \ \\n \\n \\n \\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6ff047c0-f5d5-4ce5-8c81-204a830fa7e1\",\"name\":\"AWS - CloudTrail\",\"dockerRepository\":\"airbyte/source-aws-cloudtrail\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/aws-cloudtrail\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c47d6804-8b98-449f-970a-5ddb5cb5d7aa\",\"name\":\"Customer.io\",\"dockerRepository\":\"farosai/airbyte-customer-io-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/customer-io\",\"icon\":\"Logo-Color-NEW\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"686473f1-76d9-4994-9cc7-9b13da46147c\",\"name\":\"Chargebee\",\"dockerRepository\":\"airbyte/source-chargebee\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargebee\",\"icon\":\"\\n\\n - \ \\n \\n - \ \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"778daa7c-feaf-4db6-96f3-70fd645acc77\",\"name\":\"File\",\"dockerRepository\":\"airbyte/source-file\",\"dockerImageTag\":\"0.2.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/file\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"b117307c-14b6-41aa-9422-947e34922962\",\"name\":\"Salesforce\",\"dockerRepository\":\"airbyte/source-salesforce\",\"dockerImageTag\":\"1.0.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesforce\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b2e713cd-cc36-4c0a-b5bd-b47cb8a0561e\",\"name\":\"MongoDb\",\"dockerRepository\":\"airbyte/source-mongodb-v2\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mongodb-v2\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"253487c0-2246-43ba-a21f-5116b20a2c50\",\"name\":\"Google - Ads\",\"dockerRepository\":\"airbyte/source-google-ads\",\"dockerImageTag\":\"0.1.44\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-ads\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"95e8cffd-b8c4-4039-968e-d32fb4a69bde\",\"name\":\"Klaviyo\",\"dockerRepository\":\"airbyte/source-klaviyo\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/klaviyo\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"90916976-a132-4ce9-8bce-82a03dd58788\",\"name\":\"BambooHR\",\"dockerRepository\":\"airbyte/source-bamboo-hr\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bamboo-hr\",\"icon\":\"\\n\\n \\n BambooHR\\n Created - with Sketch.\\n \\n \\n \\n - \ \\n \\n \\n \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"eb4c9e00-db83-4d63-a386-39cfa91012a8\",\"name\":\"Google - Search Console\",\"dockerRepository\":\"airbyte/source-google-search-console\",\"dockerImageTag\":\"0.1.13\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-search-console\",\"icon\":\"\\n\\n \\n Artboard\\n - \ Created with Sketch.\\n \\n \\n - \ \\n \\n - \ \\n \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n \\n \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n \\n \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bad83517-5e54-4a3d-9b53-63e85fbd4d7c\",\"name\":\"ClickHouse\",\"dockerRepository\":\"airbyte/source-clickhouse\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/clickhouse\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"5b9cb09e-1003-4f9c-983d-5779d1b2cd51\",\"name\":\"Mailgun\",\"dockerRepository\":\"airbyte/source-mailgun\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailgun\",\"icon\":\"\\n - \ \\n \\n - \ \\n \\n \\n \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n \\n - \ \\n - \ \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n - \ \\n \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d913b0f2-cc51-4e55-a44c-8ba1697b9239\",\"name\":\"Paypal - Transaction\",\"dockerRepository\":\"airbyte/source-paypal-transaction\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paypal-transaction\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"374ebc65-6636-4ea0-925c-7d35999a8ffc\",\"name\":\"Smartsheets\",\"dockerRepository\":\"airbyte/source-smartsheets\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/smartsheets\",\"icon\":\"\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"47f25999-dd5e-4636-8c39-e7cea2453331\",\"name\":\"Bing - Ads\",\"dockerRepository\":\"airbyte/source-bing-ads\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bing-ads\",\"icon\":\"\\n\\n \\n \\n \\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d60a46d4-709f-4092-a6b7-2457f7d455f5\",\"name\":\"Prestashop\",\"dockerRepository\":\"airbyte/source-prestashop\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/presta-shop\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"71607ba1-c0ac-4799-8049-7f4b90dd50f7\",\"name\":\"Google - Sheets\",\"dockerRepository\":\"airbyte/source-google-sheets\",\"dockerImageTag\":\"0.2.17\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-sheets\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"d8540a80-6120-485d-b7d6-272bca477d9b\",\"name\":\"OpenWeather\",\"dockerRepository\":\"airbyte/source-openweather\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/openweather\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"45d2e135-2ede-49e1-939f-3e3ec357a65e\",\"name\":\"Recharge\",\"dockerRepository\":\"airbyte/source-recharge\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/recharge\",\"icon\":\"\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d53f9084-fa6b-4a5a-976c-5b8392f4ad8a\",\"name\":\"E2E - Testing\",\"dockerRepository\":\"airbyte/source-e2e-test\",\"dockerImageTag\":\"2.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/e2e-test\",\"icon\":\"\\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cd06e646-31bf-4dc8-af48-cbc6530fcad3\",\"name\":\"Kustomer\",\"dockerRepository\":\"airbyte/source-kustomer-singer\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kustomer\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b9dc6155-672e-42ea-b10d-9f1f1fb95ab1\",\"name\":\"Twilio\",\"dockerRepository\":\"airbyte/source-twilio\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/twilio\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3052c77e-8b91-47e2-97a0-a29a22794b4b\",\"name\":\"PersistIq\",\"dockerRepository\":\"airbyte/source-persistiq\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/persistiq\",\"icon\":\"\\n - \ \\n \\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"1d4fdb25-64fc-4569-92da-fcdca79a8372\",\"name\":\"Okta\",\"dockerRepository\":\"airbyte/source-okta\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/okta\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"193bdcb8-1dd9-48d1-aade-91cadfd74f9b\",\"name\":\"Paystack\",\"dockerRepository\":\"airbyte/source-paystack\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paystack\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6f2ac653-8623-43c4-8950-19218c7caf3d\",\"name\":\"Firebolt\",\"dockerRepository\":\"airbyte/source-firebolt\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/firebolt\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"63cea06f-1c75-458d-88fe-ad48c7cb27fd\",\"name\":\"Braintree\",\"dockerRepository\":\"airbyte/source-braintree\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/braintree\",\"icon\":\"\\n\\n - \ \\n \\n - \ \\n \\n - \ \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b6604cbd-1b12-4c08-8767-e140d0fb0877\",\"name\":\"Chartmogul\",\"dockerRepository\":\"airbyte/source-chartmogul\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chartmogul\",\"icon\":\"\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d8286229-c680-4063-8c59-23b9b391c700\",\"name\":\"Pipedrive\",\"dockerRepository\":\"airbyte/source-pipedrive\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pipedrive\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3dc3037c-5ce8-4661-adc2-f7a9e3c5ece5\",\"name\":\"Zuora\",\"dockerRepository\":\"airbyte/source-zuora\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zuora\",\"icon\":\"\\n\\n\\nimage/svg+xml\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"59f1e50a-331f-4f09-b3e8-2e8d4d355f44\",\"name\":\"Greenhouse\",\"dockerRepository\":\"airbyte/source-greenhouse\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/greenhouse\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"789f8e7a-2d28-11ec-8d3d-0242ac130003\",\"name\":\"Lemlist\",\"dockerRepository\":\"airbyte/source-lemlist\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lemlist\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"f1e4c7f6-db5c-4035-981f-d35ab4998794\",\"name\":\"Zenloop\",\"dockerRepository\":\"airbyte/source-zenloop\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zenloop\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9e0556f4-69df-4522-a3fb-03264d36b348\",\"name\":\"Marketo\",\"dockerRepository\":\"airbyte/source-marketo\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/marketo\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7f0455fb-4518-4ec0-b7a3-d808bf8081cc\",\"name\":\"Orb\",\"dockerRepository\":\"airbyte/source-orb\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/orb\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"435bb9a5-7887-4809-aa58-28c27df0d7ad\",\"name\":\"MySQL\",\"dockerRepository\":\"airbyte/source-mysql\",\"dockerImageTag\":\"0.6.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mysql\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"4942d392-c7b5-4271-91f9-3b4f4e51eb3e\",\"name\":\"ZohoCRM\",\"dockerRepository\":\"airbyte/source-zoho-crm\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/zoho-crm\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9b2d3607-7222-4709-9fa2-c2abdebbdd88\",\"name\":\"Chargify\",\"dockerRepository\":\"airbyte/source-chargify\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargify\",\"icon\":\"\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b5ea17b1-f170-46dc-bc31-cc744ca984c1\",\"name\":\"Microsoft - SQL Server (MSSQL)\",\"dockerRepository\":\"airbyte/source-mssql\",\"dockerImageTag\":\"0.4.13\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mssql\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4\",\"name\":\"Zendesk - Chat\",\"dockerRepository\":\"airbyte/source-zendesk-chat\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-chat\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"dfd88b22-b603-4c3d-aad7-3701784586b1\",\"name\":\"Faker\",\"dockerRepository\":\"airbyte/source-faker\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/source-faker\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"12928b32-bf0a-4f1e-964f-07e12e37153a\",\"name\":\"Mixpanel\",\"dockerRepository\":\"airbyte/source-mixpanel\",\"dockerImageTag\":\"0.1.18\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mixpanel\",\"icon\":\"\\n\\n\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d19ae824-e289-4b14-995a-0632eb46d246\",\"name\":\"Google - Directory\",\"dockerRepository\":\"airbyte/source-google-directory\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-directory\",\"icon\":\"\\n\\n\\n\\n - \ \\n\\n\\n \\n\\n\\n - \ \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n - \ \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n - \ \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n - \ \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e2b40e36-aa0e-4bed-b41b-bcea6fa348b1\",\"name\":\"Exchange - Rates Api\",\"dockerRepository\":\"airbyte/source-exchange-rates\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/exchangeratesapi\",\"icon\":\"\\n\\n \\n logo\\n - \ Created with Sketch.\\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ef69ef6e-aa7f-4af1-a01d-ef775033524e\",\"name\":\"GitHub\",\"dockerRepository\":\"airbyte/source-github\",\"dockerImageTag\":\"0.2.44\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/github\",\"icon\":\"\\n\\n\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"0dad1a35-ccf8-4d03-b73e-6788c00b13ae\",\"name\":\"TiDB\",\"dockerRepository\":\"airbyte/source-tidb\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tidb\",\"icon\":\"\\n - \ \\n \\n - \ \\n \\n \\n \\n \\n - \ \\n \\n - \ \\n - \ \\n - \ \\n - \ \\n \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"db04ecd1-42e7-4115-9cec-95812905c626\",\"name\":\"Retently\",\"dockerRepository\":\"airbyte/source-retently\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/retently\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d917a47b-8537-4d0d-8c10-36a9928d4265\",\"name\":\"Kafka\",\"dockerRepository\":\"airbyte/source-kafka\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kafka\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"137ece28-5434-455c-8f34-69dc3782f451\",\"name\":\"LinkedIn - Ads\",\"dockerRepository\":\"airbyte/source-linkedin-ads\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linkedin-ads\",\"icon\":\"\\n\\n\\n - \ \\n \\n \\n - \ \\n\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d0243522-dccf-4978-8ba0-37ed47a0bdbf\",\"name\":\"Asana\",\"dockerRepository\":\"airbyte/source-asana\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/asana\",\"icon\":\"\\n\\n - \ \\n \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n - \ \\n - \ \\n - \ \\n \\n - \ \\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9fa5862c-da7c-11eb-8d19-0242ac130003\",\"name\":\"Cockroachdb\",\"dockerRepository\":\"airbyte/source-cockroachdb\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cockroachdb\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"36c891d9-4bd9-43ac-bad2-10e12756272c\",\"name\":\"HubSpot\",\"dockerRepository\":\"airbyte/source-hubspot\",\"dockerImageTag\":\"0.1.81\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hubspot\",\"icon\":\"\\n\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e094cb9a-26de-4645-8761-65c0c425d1de\",\"name\":\"Stripe\",\"dockerRepository\":\"airbyte/source-stripe\",\"dockerImageTag\":\"0.1.35\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/stripe\",\"icon\":\"Asset - 32Stone - Hub\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c4cfaeda-c757-489a-8aba-859fb08b6970\",\"name\":\"US - Census\",\"dockerRepository\":\"airbyte/source-us-census\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/us-census\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"fe2b4084-3386-4d3b-9ad6-308f61a6f1e6\",\"name\":\"Harvest\",\"dockerRepository\":\"airbyte/source-harvest\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harvest\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b03a9f3e-22a5-11eb-adc1-0242ac120002\",\"name\":\"Mailchimp\",\"dockerRepository\":\"airbyte/source-mailchimp\",\"dockerImageTag\":\"0.2.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailchimp\",\"icon\":\"\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6fe89830-d04d-401b-aad6-6552ffa5c4af\",\"name\":\"Harness\",\"dockerRepository\":\"farosai/airbyte-harness-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harness\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"983fd355-6bf3-4709-91b5-37afa391eeb6\",\"name\":\"Amazon - SQS\",\"dockerRepository\":\"airbyte/source-amazon-sqs\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-sqs\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bc617b5f-1b9e-4a2d-bebe-782fd454a771\",\"name\":\"Timely\",\"dockerRepository\":\"airbyte/source-timely\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/timely\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"0b5c867e-1b12-4d02-ab74-97b2184ff6d7\",\"name\":\"Dixa\",\"dockerRepository\":\"airbyte/source-dixa\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dixa\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"decd338e-5647-4c0b-adf4-da0e75f5a750\",\"name\":\"Postgres\",\"dockerRepository\":\"airbyte/source-postgres\",\"dockerImageTag\":\"1.0.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/postgres\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"47f17145-fe20-4ef5-a548-e29b048adf84\",\"name\":\"Apify - Dataset\",\"dockerRepository\":\"airbyte/source-apify-dataset\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/apify-dataset\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e7eff203-90bf-43e5-a240-19ea3056c474\",\"name\":\"Typeform\",\"dockerRepository\":\"airbyte/source-typeform\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/typeform\",\"icon\":\"\\n - \ \\n \\n \\n - \ \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d78e5de0-aa44-4744-aa4f-74c818ccfe19\",\"name\":\"RKI - Covid\",\"dockerRepository\":\"airbyte/source-rki-covid\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/rki-covid\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8da67652-004c-11ec-9a03-0242ac130003\",\"name\":\"Trello\",\"dockerRepository\":\"airbyte/source-trello\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/trello\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"445831eb-78db-4b1f-8f1f-0d96ad8739e2\",\"name\":\"Drift\",\"dockerRepository\":\"airbyte/source-drift\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/drift\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"5e6175e5-68e1-4c17-bff9-56103bbb0d80\",\"name\":\"Gitlab\",\"dockerRepository\":\"airbyte/source-gitlab\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/gitlab\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\tH: 2.5 x\\n\\t1/2 - x\\n\\t1x\\n\\t1x\\n\\t\\n\\t1x\\n\\t\\n\\t1x\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3490c201-5d95-4783-b600-eaf07a4c7787\",\"name\":\"Outreach\",\"dockerRepository\":\"airbyte/source-outreach\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/outreach\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3cc2eafd-84aa-4dca-93af-322d9dfeec1a\",\"name\":\"Google - Analytics Data API\",\"dockerRepository\":\"airbyte/source-google-analytics-data-api\",\"dockerImageTag\":\"0.0.2\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/google-analytics-v4\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"325e0640-e7b3-4e24-b823-3361008f603f\",\"name\":\"Zendesk - Sunshine\",\"dockerRepository\":\"airbyte/source-zendesk-sunshine\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-sunshine\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"69589781-7828-43c5-9f63-8925b1c1ccc2\",\"name\":\"S3\",\"dockerRepository\":\"airbyte/source-s3\",\"dockerImageTag\":\"0.1.18\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/s3\",\"icon\":\"\\n\\n Icon-Resource/Storage/Res_Amazon-Simple-Storage_Service-Standard_48_Light\\n - \ \\n - \ \\n \\n\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"7a4327c4-315a-11ec-8d3d-0242ac130003\",\"name\":\"Strava\",\"dockerRepository\":\"airbyte/source-strava\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/strava\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"77225a51-cd15-4a13-af02-65816bd0ecf4\",\"name\":\"Square\",\"dockerRepository\":\"airbyte/source-square\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/square\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"eaf50f04-21dd-4620-913b-2a83f5635227\",\"name\":\"Microsoft - teams\",\"dockerRepository\":\"airbyte/source-microsoft-teams\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/microsoft-teams\",\"icon\":\"\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n]>\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e55879a8-0ef8-4557-abcf-ab34c53ec460\",\"name\":\"Amazon - Seller Partner\",\"dockerRepository\":\"airbyte/source-amazon-seller-partner\",\"dockerImageTag\":\"0.2.24\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-seller-partner\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"00405b19-9768-4e0c-b1ae-9fc2ee2b2a8c\",\"name\":\"Looker\",\"dockerRepository\":\"airbyte/source-looker\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/looker\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ed799e2b-2158-4c66-8da4-b40fe63bc72a\",\"name\":\"Plaid\",\"dockerRepository\":\"airbyte/source-plaid\",\"dockerImageTag\":\"0.3.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/plaid\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7cf88806-25f5-4e1a-b422-b2fa9e1b0090\",\"name\":\"Elasticsearch\",\"dockerRepository\":\"airbyte/source-elasticsearch\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/elasticsearch\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e2d65910-8c8b-40a1-ae7d-ee2416b2bfa2\",\"name\":\"Snowflake\",\"dockerRepository\":\"airbyte/source-snowflake\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snowflake\",\"icon\":\"\\n\\n \\n Group\\n Created - with Sketch.\\n \\n \\n - \ \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n - \ \\n - \ \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"72d405a3-56d8-499f-a571-667c03406e43\",\"name\":\"Dockerhub\",\"dockerRepository\":\"airbyte/source-dockerhub\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dockerhub\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9bb85338-ea95-4c93-b267-6be89125b267\",\"name\":\"Freshservice\",\"dockerRepository\":\"airbyte/source-freshservice\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshservice\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c7cb421b-942e-4468-99ee-e369bcabaec5\",\"name\":\"Metabase\",\"dockerRepository\":\"airbyte/source-metabase\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/metabase\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ec4b9503-13cb-48ab-a4ab-6ade4be46567\",\"name\":\"Freshdesk\",\"dockerRepository\":\"airbyte/source-freshdesk\",\"dockerImageTag\":\"0.3.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshdesk\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d60f5393-f99e-4310-8d05-b1876820f40e\",\"name\":\"Pivotal - Tracker\",\"dockerRepository\":\"airbyte/source-pivotal-tracker\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pivotal-tracker\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"afa734e4-3571-11ec-991a-1e0031268139\",\"name\":\"YouTube - Analytics\",\"dockerRepository\":\"airbyte/source-youtube-analytics\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/youtube-analytics\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6acf6b55-4f1e-4fca-944e-1a3caef8aba8\",\"name\":\"Instagram\",\"dockerRepository\":\"airbyte/source-instagram\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/instagram\",\"icon\":\"\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b08e4776-d1de-4e80-ab5c-1e51dad934a2\",\"name\":\"Qualaroo\",\"dockerRepository\":\"airbyte/source-qualaroo\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/qualaroo\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"badc5925-0485-42be-8caa-b34096cb71b5\",\"name\":\"SurveyMonkey\",\"dockerRepository\":\"airbyte/source-surveymonkey\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/surveymonkey\",\"icon\":\"Horizontal_Sabaeus_RGB\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"722ba4bf-06ec-45a4-8dd5-72e4a5cf3903\",\"name\":\"My - Hours\",\"dockerRepository\":\"airbyte/source-my-hours\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/my-hours\",\"icon\":\"\\n\\n - \ \\n \\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"547dc08e-ab51-421d-953b-8f3745201a8c\",\"name\":\"Kyriba\",\"dockerRepository\":\"airbyte/source-kyriba\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kyriba\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e7778cfc-e97c-4458-9ecb-b4f2bba8946c\",\"name\":\"Facebook - Marketing\",\"dockerRepository\":\"airbyte/source-facebook-marketing\",\"dockerImageTag\":\"0.2.58\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/facebook-marketing\",\"icon\":\"\\nimage/svg+xml\\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bb1a6d31-6879-4819-a2bd-3eed299ea8e2\",\"name\":\"Cart.com\",\"dockerRepository\":\"airbyte/source-cart\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cart\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"f00d2cf4-3c28-499a-ba93-b50b6f26359e\",\"name\":\"TalkDesk - Explore\",\"dockerRepository\":\"airbyte/source-talkdesk-explore\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/talkdesk-explore\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7e20ce3e-d820-4327-ad7a-88f3927fd97a\",\"name\":\"VictorOps\",\"dockerRepository\":\"farosai/airbyte-victorops-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/victorops\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"41991d12-d4b5-439e-afd0-260a31d4c53f\",\"name\":\"SalesLoft\",\"dockerRepository\":\"airbyte/source-salesloft\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesloft\",\"icon\":\"\\n\\n - \ \\n \\n \\n image/svg+xml\\n - \ \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n \\n \\n - \ \\n \\n \\n - \ \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"4bfac00d-ce15-44ff-95b9-9e3c3e8fbd35\",\"name\":\"TikTok - Marketing\",\"dockerRepository\":\"airbyte/source-tiktok-marketing\",\"dockerImageTag\":\"0.1.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tiktok-marketing\",\"icon\":\"\\n\\n \\n \u7F16\u7EC4\\n - \ Created with Sketch.\\n \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n - \ \\n - \ \\n \\n \\n \\n\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2e875208-0c0b-4ee4-9e92-1cb3156ea799\",\"name\":\"Iterable\",\"dockerRepository\":\"airbyte/source-iterable\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/iterable\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3981c999-bd7d-4afc-849b-e53dea90c948\",\"name\":\"Lever - Hiring\",\"dockerRepository\":\"airbyte/source-lever-hiring\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lever-hiring\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"798ae795-5189-42b6-b64e-3cb91db93338\",\"name\":\"Azure - Table Storage\",\"dockerRepository\":\"airbyte/source-azure-table\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/azure-table\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"9da77001-af33-4bcd-be46-6252bf9342b9\",\"name\":\"Shopify\",\"dockerRepository\":\"airbyte/source-shopify\",\"dockerImageTag\":\"0.1.37\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/shopify\",\"icon\":\"\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"5cb7e5fe-38c2-11ec-8d3d-0242ac130003\",\"name\":\"Pinterest\",\"dockerRepository\":\"airbyte/source-pinterest\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pinterest\",\"icon\":\"\\nimage/svg+xml\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"68e63de2-bb83-4c7e-93fa-a8a9051e3993\",\"name\":\"Jira\",\"dockerRepository\":\"airbyte/source-jira\",\"dockerImageTag\":\"0.2.20\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/jira\",\"icon\":\"\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"af6d50ee-dddf-4126-a8ee-7faee990774f\",\"name\":\"PostHog\",\"dockerRepository\":\"airbyte/source-posthog\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/posthog\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cc88c43f-6f53-4e8a-8c4d-b284baaf9635\",\"name\":\"Delighted\",\"dockerRepository\":\"airbyte/source-delighted\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/delighted\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"aea2fd0d-377d-465e-86c0-4fdc4f688e51\",\"name\":\"Zoom\",\"dockerRepository\":\"airbyte/source-zoom-singer\",\"dockerImageTag\":\"0.2.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zoom\",\"icon\":\"\\n\\n \\n \\n - \ \\n image/svg+xml\\n - \ \\n \\n \\n \\n - \ \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"a827c52e-791c-4135-a245-e233c5255199\",\"name\":\"SFTP\",\"dockerRepository\":\"airbyte/source-sftp\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/sftp\",\"releaseStage\":\"alpha\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"95bcc041-1d1a-4c2e-8802-0ca5b1bfa36a\",\"name\":\"Orbit\",\"dockerRepository\":\"airbyte/source-orbit\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/orbit\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d1aa448b-7c54-498e-ad95-263cbebcd2db\",\"name\":\"Tempo\",\"dockerRepository\":\"airbyte/source-tempo\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tempo\",\"icon\":\"\\n\\n\\n \\n \\n \\n \\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cdaf146a-9b75-49fd-9dd2-9d64a0bb4781\",\"name\":\"Sentry\",\"dockerRepository\":\"airbyte/source-sentry\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sentry\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ed9dfefa-1bbc-419d-8c5e-4d78f0ef6734\",\"name\":\"Google - Workspace Admin Reports\",\"dockerRepository\":\"airbyte/source-google-workspace-admin-reports\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-workspace-admin-reports\",\"icon\":\"\\n \\n \\n - \ \\n \\n - \ \\n \\n \\n - \ \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n - \ \\n \\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cf8ff320-6272-4faa-89e6-4402dc17e5d5\",\"name\":\"Glassfrog\",\"dockerRepository\":\"airbyte/source-glassfrog\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/glassfrog\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e87ffa8e-a3b5-f69c-9076-6011339de1f6\",\"name\":\"Redshift\",\"dockerRepository\":\"airbyte/source-redshift\",\"dockerImageTag\":\"0.3.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/redshift\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"c6b0a29e-1da9-4512-9002-7bfd0cba2246\",\"name\":\"Amazon - Ads\",\"dockerRepository\":\"airbyte/source-amazon-ads\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-ads\",\"icon\":\"\\n \\n \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"eb4c9e00-db83-4d63-a386-39cfa91012a8\",\"name\":\"Google + Search Console\",\"dockerRepository\":\"airbyte/source-google-search-console\",\"dockerImageTag\":\"0.1.17\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/google-search-console\",\"icon\":\"\\n\\n \\n Artboard\\n + \ Created with Sketch.\\n \\n \\n + \ \\n \\n + \ \\n \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n \\n \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"a5f2c853-6e44-4aed-a9b6-7d1390c8a827\",\"name\":\"E2E + Test Source\",\"dockerRepository\":\"airbyte/source-e2e-test\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://example.com\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"custom\"},{\"sourceDefinitionId\":\"9b2d3607-7222-4709-9fa2-c2abdebbdd88\",\"name\":\"Chargify\",\"dockerRepository\":\"airbyte/source-chargify\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargify\",\"icon\":\"\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2a2552ca-9f78-4c1c-9eb7-4d0dc66d72df\",\"name\":\"WooCommerce\",\"dockerRepository\":\"airbyte/source-woocommerce\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/woocommerce\",\"icon\":\"\\n\\n + \\n \\n \\n image/svg+xml\\n + \ \\n + \ \\n \\n \\n \\n \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9da77001-af33-4bcd-be46-6252bf9342b9\",\"name\":\"Shopify\",\"dockerRepository\":\"airbyte/source-shopify\",\"dockerImageTag\":\"0.1.38\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/shopify\",\"icon\":\"\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c4cfaeda-c757-489a-8aba-859fb08b6970\",\"name\":\"US + Census\",\"dockerRepository\":\"airbyte/source-us-census\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/us-census\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7865dce4-2211-4f6a-88e5-9d0fe161afe7\",\"name\":\"Yandex + Metrica\",\"dockerRepository\":\"airbyte/source-yandex-metrica\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/yandex-metrica\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"fe2b4084-3386-4d3b-9ad6-308f61a6f1e6\",\"name\":\"Harvest\",\"dockerRepository\":\"airbyte/source-harvest\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harvest\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"90916976-a132-4ce9-8bce-82a03dd58788\",\"name\":\"BambooHR\",\"dockerRepository\":\"airbyte/source-bamboo-hr\",\"dockerImageTag\":\"0.2.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bamboo-hr\",\"icon\":\"\\n\\n \\n BambooHR\\n Created + with Sketch.\\n \\n \\n \\n + \ \\n \\n \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d8313939-3782-41b0-be29-b3ca20d8dd3a\",\"name\":\"Intercom\",\"dockerRepository\":\"airbyte/source-intercom\",\"dockerImageTag\":\"0.1.27\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/intercom\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6e00b415-b02e-4160-bf02-58176a0ae687\",\"name\":\"Notion\",\"dockerRepository\":\"airbyte/source-notion\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/notion\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"1fa90628-2b9e-11ed-a261-0242ac120002\",\"name\":\"AlloyDB + for PostgreSQL\",\"dockerRepository\":\"airbyte/source-alloydb\",\"dockerImageTag\":\"1.0.15\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/alloydb\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"137ece28-5434-455c-8f34-69dc3782f451\",\"name\":\"LinkedIn + Ads\",\"dockerRepository\":\"airbyte/source-linkedin-ads\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linkedin-ads\",\"icon\":\"\\n\\n\\n + \ \\n \\n \\n + \ \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9bb85338-ea95-4c93-b267-6be89125b267\",\"name\":\"Freshservice\",\"dockerRepository\":\"airbyte/source-freshservice\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshservice\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bad83517-5e54-4a3d-9b53-63e85fbd4d7c\",\"name\":\"ClickHouse\",\"dockerRepository\":\"airbyte/source-clickhouse\",\"dockerImageTag\":\"0.1.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/clickhouse\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"eff3616a-f9c3-11eb-9a03-0242ac130003\",\"name\":\"Google + Analytics (Universal Analytics)\",\"dockerRepository\":\"airbyte/source-google-analytics-v4\",\"dockerImageTag\":\"0.1.30\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/google-analytics-universal-analytics\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3981c999-bd7d-4afc-849b-e53dea90c948\",\"name\":\"Lever + Hiring\",\"dockerRepository\":\"airbyte/source-lever-hiring\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lever-hiring\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"a827c52e-791c-4135-a245-e233c5255199\",\"name\":\"SFTP\",\"dockerRepository\":\"airbyte/source-sftp\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/sftp\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"1d4fdb25-64fc-4569-92da-fcdca79a8372\",\"name\":\"Okta\",\"dockerRepository\":\"airbyte/source-okta\",\"dockerImageTag\":\"0.1.13\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/okta\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"547dc08e-ab51-421d-953b-8f3745201a8c\",\"name\":\"Kyriba\",\"dockerRepository\":\"airbyte/source-kyriba\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kyriba\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"79c1aa37-dae3-42ae-b333-d1c105477715\",\"name\":\"Zendesk + Support\",\"dockerRepository\":\"airbyte/source-zendesk-support\",\"dockerImageTag\":\"0.2.16\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-support\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ef69ef6e-aa7f-4af1-a01d-ef775033524e\",\"name\":\"GitHub\",\"dockerRepository\":\"airbyte/source-github\",\"dockerImageTag\":\"0.3.6\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/github\",\"icon\":\"\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e55879a8-0ef8-4557-abcf-ab34c53ec460\",\"name\":\"Amazon + Seller Partner\",\"dockerRepository\":\"airbyte/source-amazon-seller-partner\",\"dockerImageTag\":\"0.2.27\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-seller-partner\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"722ba4bf-06ec-45a4-8dd5-72e4a5cf3903\",\"name\":\"My + Hours\",\"dockerRepository\":\"airbyte/source-my-hours\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/my-hours\",\"icon\":\"\\n\\n + \ \\n \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2af123bf-0aaf-4e0d-9784-cb497f23741a\",\"name\":\"Appstore\",\"dockerRepository\":\"airbyte/source-appstore-singer\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/appstore\",\"icon\":\"\\n\\n + \ \\n \\n \\n \\n + \ \\n \\n\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d53f9084-fa6b-4a5a-976c-5b8392f4ad8a\",\"name\":\"E2E + Testing\",\"dockerRepository\":\"airbyte/source-e2e-test\",\"dockerImageTag\":\"2.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/e2e-test\",\"icon\":\"\\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9fa5862c-da7c-11eb-8d19-0242ac130003\",\"name\":\"Cockroachdb\",\"dockerRepository\":\"airbyte/source-cockroachdb\",\"dockerImageTag\":\"0.1.18\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cockroachdb\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"253487c0-2246-43ba-a21f-5116b20a2c50\",\"name\":\"Google + Ads\",\"dockerRepository\":\"airbyte/source-google-ads\",\"dockerImageTag\":\"0.2.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-ads\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6ff047c0-f5d5-4ce5-8c81-204a830fa7e1\",\"name\":\"AWS + CloudTrail\",\"dockerRepository\":\"airbyte/source-aws-cloudtrail\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/aws-cloudtrail\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"68e63de2-bb83-4c7e-93fa-a8a9051e3993\",\"name\":\"Jira\",\"dockerRepository\":\"airbyte/source-jira\",\"dockerImageTag\":\"0.2.21\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/jira\",\"icon\":\"\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"36c891d9-4bd9-43ac-bad2-10e12756272c\",\"name\":\"HubSpot\",\"dockerRepository\":\"airbyte/source-hubspot\",\"dockerImageTag\":\"0.2.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hubspot\",\"icon\":\"\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e7eff203-90bf-43e5-a240-19ea3056c474\",\"name\":\"Typeform\",\"dockerRepository\":\"airbyte/source-typeform\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/typeform\",\"icon\":\"\\n + \ \\n \\n \\n + \ \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d19ae824-e289-4b14-995a-0632eb46d246\",\"name\":\"Google + Directory\",\"dockerRepository\":\"airbyte/source-google-directory\",\"dockerImageTag\":\"0.1.9\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-directory\",\"icon\":\"\\n\\n\\n\\n + \ \\n\\n\\n \\n\\n\\n + \ \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n + \ \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n + \ \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n \\n\\n\\n\\n \\n\\n\\n + \ \\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"010eb12f-837b-4685-892d-0a39f76a98f5\",\"name\":\"Facebook + Pages\",\"dockerRepository\":\"airbyte/source-facebook-pages\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/facebook-pages\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b5ea17b1-f170-46dc-bc31-cc744ca984c1\",\"name\":\"Microsoft + SQL Server (MSSQL)\",\"dockerRepository\":\"airbyte/source-mssql\",\"dockerImageTag\":\"0.4.20\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mssql\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"badc5925-0485-42be-8caa-b34096cb71b5\",\"name\":\"SurveyMonkey\",\"dockerRepository\":\"airbyte/source-surveymonkey\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/surveymonkey\",\"icon\":\"Horizontal_Sabaeus_RGB\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3825db3e-c94b-42ac-bd53-b5a9507ace2b\",\"name\":\"Fauna\",\"dockerRepository\":\"airbyte/source-fauna\",\"dockerImageTag\":\"dev\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/fauna\",\"icon\":\"\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"bb1a6d31-6879-4819-a2bd-3eed299ea8e2\",\"name\":\"Cart.com\",\"dockerRepository\":\"airbyte/source-cart\",\"dockerImageTag\":\"0.2.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/cart\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\t\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"f1e4c7f6-db5c-4035-981f-d35ab4998794\",\"name\":\"Zenloop\",\"dockerRepository\":\"airbyte/source-zenloop\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zenloop\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b2e713cd-cc36-4c0a-b5bd-b47cb8a0561e\",\"name\":\"MongoDb\",\"dockerRepository\":\"airbyte/source-mongodb-v2\",\"dockerImageTag\":\"0.1.19\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mongodb-v2\",\"icon\":\"\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"f00d2cf4-3c28-499a-ba93-b50b6f26359e\",\"name\":\"TalkDesk + Explore\",\"dockerRepository\":\"airbyte/source-talkdesk-explore\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/talkdesk-explore\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"5cb7e5fe-38c2-11ec-8d3d-0242ac130003\",\"name\":\"Pinterest\",\"dockerRepository\":\"airbyte/source-pinterest\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pinterest\",\"icon\":\"\\nimage/svg+xml\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d1aa448b-7c54-498e-ad95-263cbebcd2db\",\"name\":\"Tempo\",\"dockerRepository\":\"airbyte/source-tempo\",\"dockerImageTag\":\"0.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tempo\",\"icon\":\"\\n\\n\\n \\n \\n \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b6604cbd-1b12-4c08-8767-e140d0fb0877\",\"name\":\"Chartmogul\",\"dockerRepository\":\"airbyte/source-chartmogul\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chartmogul\",\"icon\":\"\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"0b5c867e-1b12-4d02-ab74-97b2184ff6d7\",\"name\":\"Dixa\",\"dockerRepository\":\"airbyte/source-dixa\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dixa\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bb6afd81-87d5-47e3-97c4-e2c2901b1cf8\",\"name\":\"OneSignal\",\"dockerRepository\":\"airbyte/source-onesignal\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/onesignal\",\"icon\":\"\\n\\n \\n \\n + \ \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"5b9cb09e-1003-4f9c-983d-5779d1b2cd51\",\"name\":\"Mailgun\",\"dockerRepository\":\"airbyte/source-mailgun\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailgun\",\"icon\":\"\\n + \ \\n \\n + \ \\n \\n \\n \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n \\n + \ \\n + \ \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n + \ \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b4375641-e270-41d3-9c20-4f9cecad87a8\",\"name\":\"Appfollow\",\"dockerRepository\":\"airbyte/source-appfollow\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/appfollow\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d60a46d4-709f-4092-a6b7-2457f7d455f5\",\"name\":\"Prestashop\",\"dockerRepository\":\"airbyte/source-prestashop\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/presta-shop\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8a5d48f6-03bb-4038-a942-a8d3f175cca3\",\"name\":\"Freshcaller\",\"dockerRepository\":\"airbyte/source-freshcaller\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshcaller\",\"protocolVersion\":\"0.2.0\"},{\"sourceDefinitionId\":\"45d2e135-2ede-49e1-939f-3e3ec357a65e\",\"name\":\"Recharge\",\"dockerRepository\":\"airbyte/source-recharge\",\"dockerImageTag\":\"0.2.4\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/recharge\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"325e0640-e7b3-4e24-b823-3361008f603f\",\"name\":\"Zendesk + Sunshine\",\"dockerRepository\":\"airbyte/source-zendesk-sunshine\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-sunshine\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"40d24d0f-b8f9-4fe0-9e6c-b06c0f3f45e4\",\"name\":\"Zendesk + Chat\",\"dockerRepository\":\"airbyte/source-zendesk-chat\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-chat\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"72d405a3-56d8-499f-a571-667c03406e43\",\"name\":\"Dockerhub\",\"dockerRepository\":\"airbyte/source-dockerhub\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dockerhub\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"80a54ea2-9959-4040-aac1-eee42423ec9b\",\"name\":\"Monday\",\"dockerRepository\":\"airbyte/source-monday\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/monday\",\"icon\":\"\\n\\n + \ \\n \\n \\n image/svg+xml\\n + \ \\n \\n \\n \\n \\n \\n Logo / monday.com\\n \\n + \ \\n \\n \\n \\n \\n \\n \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"fa9f58c6-2d03-4237-aaa4-07d75e0c1396\",\"name\":\"Amplitude\",\"dockerRepository\":\"airbyte/source-amplitude\",\"dockerImageTag\":\"0.1.16\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/amplitude\",\"icon\":\"\\n\\t\\n\\t\\n\\t\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"789f8e7a-2d28-11ec-8d3d-0242ac130003\",\"name\":\"Lemlist\",\"dockerRepository\":\"airbyte/source-lemlist\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/lemlist\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"41991d12-d4b5-439e-afd0-260a31d4c53f\",\"name\":\"SalesLoft\",\"dockerRepository\":\"airbyte/source-salesloft\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/salesloft\",\"icon\":\"\\n\\n + \ \\n \\n \\n image/svg+xml\\n + \ \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d8286229-c680-4063-8c59-23b9b391c700\",\"name\":\"Pipedrive\",\"dockerRepository\":\"airbyte/source-pipedrive\",\"dockerImageTag\":\"0.1.13\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pipedrive\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6fe89830-d04d-401b-aad6-6552ffa5c4af\",\"name\":\"Harness\",\"dockerRepository\":\"farosai/airbyte-harness-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/harness\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"4f2f093d-ce44-4121-8118-9d13b7bfccd0\",\"name\":\"Netsuite\",\"dockerRepository\":\"airbyte/source-netsuite\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/netsuite\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c6b0a29e-1da9-4512-9002-7bfd0cba2246\",\"name\":\"Amazon + Ads\",\"dockerRepository\":\"airbyte/source-amazon-ads\",\"dockerImageTag\":\"0.1.22\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-ads\",\"icon\":\"\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"59c5501b-9f95-411e-9269-7143c939adbd\",\"name\":\"BigCommerce\",\"dockerRepository\":\"airbyte/source-bigcommerce\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bigcommerce\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b9dc6155-672e-42ea-b10d-9f1f1fb95ab1\",\"name\":\"Twilio\",\"dockerRepository\":\"airbyte/source-twilio\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/twilio\",\"icon\":\"\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3052c77e-8b91-47e2-97a0-a29a22794b4b\",\"name\":\"PersistIq\",\"dockerRepository\":\"airbyte/source-persistiq\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/persistiq\",\"icon\":\"\\n + \ \\n \\n \\n \\n \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"200330b2-ea62-4d11-ac6d-cfe3e3f8ab2b\",\"name\":\"Snapchat + Marketing\",\"dockerRepository\":\"airbyte/source-snapchat-marketing\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snapchat-marketing\",\"icon\":\"\\n\\n\\n\\n + \ \\n \\n \\n + \ \\n \\n image/svg+xml\\n + \ \\n \\n \\n \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n \\n \\n \\n \\n \\n + \ \\n\\t\\n\\t\\t\\n\\n\\t\\n\\n\\t\\n\\n\\n \\n \\n\\t.st0{fill:#FFFFFF;}\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"4bfac00d-ce15-44ff-95b9-9e3c3e8fbd35\",\"name\":\"TikTok + Marketing\",\"dockerRepository\":\"airbyte/source-tiktok-marketing\",\"dockerImageTag\":\"0.1.17\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tiktok-marketing\",\"icon\":\"\\n\\n \\n \u7F16\u7EC4\\n + \ Created with Sketch.\\n \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d6f73702-d7a0-4e95-9758-b0fb1af0bfba\",\"name\":\"Jenkins\",\"dockerRepository\":\"farosai/airbyte-jenkins-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/jenkins\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8d7ef552-2c0f-11ec-8d3d-0242ac130003\",\"name\":\"SearchMetrics\",\"dockerRepository\":\"airbyte/source-search-metrics\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/search-metrics\",\"icon\":\"\\n\\n\\n\\nCreated by potrace 1.16, written by Peter Selinger + 2001-2019\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"983fd355-6bf3-4709-91b5-37afa391eeb6\",\"name\":\"Amazon + SQS\",\"dockerRepository\":\"airbyte/source-amazon-sqs\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/amazon-sqs\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"6acf6b55-4f1e-4fca-944e-1a3caef8aba8\",\"name\":\"Instagram\",\"dockerRepository\":\"airbyte/source-instagram\",\"dockerImageTag\":\"1.0.0\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/instagram\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9e0556f4-69df-4522-a3fb-03264d36b348\",\"name\":\"Marketo\",\"dockerRepository\":\"airbyte/source-marketo\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/marketo\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c47d6804-8b98-449f-970a-5ddb5cb5d7aa\",\"name\":\"Customer.io\",\"dockerRepository\":\"farosai/airbyte-customer-io-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/customer-io\",\"icon\":\"Logo-Color-NEW\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e2d65910-8c8b-40a1-ae7d-ee2416b2bfa2\",\"name\":\"Snowflake\",\"dockerRepository\":\"airbyte/source-snowflake\",\"dockerImageTag\":\"0.1.24\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/snowflake\",\"icon\":\"\\n\\n \\n Group\\n Created + with Sketch.\\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n + \ \\n + \ \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"374ebc65-6636-4ea0-925c-7d35999a8ffc\",\"name\":\"Smartsheets\",\"dockerRepository\":\"airbyte/source-smartsheets\",\"dockerImageTag\":\"0.1.12\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/smartsheets\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"95bcc041-1d1a-4c2e-8802-0ca5b1bfa36a\",\"name\":\"Orbit\",\"dockerRepository\":\"airbyte/source-orbit\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/orbit\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ed9dfefa-1bbc-419d-8c5e-4d78f0ef6734\",\"name\":\"Google + Workspace Admin Reports\",\"dockerRepository\":\"airbyte/source-google-workspace-admin-reports\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/google-workspace-admin-reports\",\"icon\":\"\\n \\n \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n + \ \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cf40a7f8-71f8-45ce-a7fa-fca053e4028c\",\"name\":\"Confluence\",\"dockerRepository\":\"airbyte/source-confluence\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/confluence\",\"icon\":\"\\n\\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n \\n \\n\\t\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"47f17145-fe20-4ef5-a548-e29b048adf84\",\"name\":\"Apify + Dataset\",\"dockerRepository\":\"airbyte/source-apify-dataset\",\"dockerImageTag\":\"0.1.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/apify-dataset\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3cc2eafd-84aa-4dca-93af-322d9dfeec1a\",\"name\":\"Google + Analytics 4 (GA4)\",\"dockerRepository\":\"airbyte/source-google-analytics-data-api\",\"dockerImageTag\":\"0.0.3\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/google-analytics-v4\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\t\\n\\t\\t\\n\\t\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3490c201-5d95-4783-b600-eaf07a4c7787\",\"name\":\"Outreach\",\"dockerRepository\":\"airbyte/source-outreach\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/outreach\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"f636c3c6-4077-45ac-b109-19fc62a283c1\",\"name\":\"Primetric\",\"dockerRepository\":\"airbyte/source-primetric\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/presta-shop\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7f0455fb-4518-4ec0-b7a3-d808bf8081cc\",\"name\":\"Orb\",\"dockerRepository\":\"airbyte/source-orb\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/orb\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"f95337f1-2ad1-4baf-922f-2ca9152de630\",\"name\":\"Flexport\",\"dockerRepository\":\"airbyte/source-flexport\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/flexport\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d60f5393-f99e-4310-8d05-b1876820f40e\",\"name\":\"Pivotal + Tracker\",\"dockerRepository\":\"airbyte/source-pivotal-tracker\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pivotal-tracker\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"492b56d1-937c-462e-8076-21ad2031e784\",\"name\":\"Hellobaton\",\"dockerRepository\":\"airbyte/source-hellobaton\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hellobaton\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"eca08d79-7b92-4065-b7f3-79c14836ebe7\",\"name\":\"Freshsales\",\"dockerRepository\":\"airbyte/source-freshsales\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshsales\",\"icon\":\"freshsales_logo_color\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8097ceb9-383f-42f6-9f92-d3fd4bcc7689\",\"name\":\"Hubplanner\",\"dockerRepository\":\"airbyte/source-hubplanner\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/hubplanner\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b08e4776-d1de-4e80-ab5c-1e51dad934a2\",\"name\":\"Qualaroo\",\"dockerRepository\":\"airbyte/source-qualaroo\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/qualaroo\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"decd338e-5647-4c0b-adf4-da0e75f5a750\",\"name\":\"Postgres\",\"dockerRepository\":\"airbyte/source-postgres\",\"dockerImageTag\":\"0.4.26\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/postgres\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"193bdcb8-1dd9-48d1-aade-91cadfd74f9b\",\"name\":\"Paystack\",\"dockerRepository\":\"airbyte/source-paystack\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paystack\",\"icon\":\"\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e87ffa8e-a3b5-f69c-9076-6011339de1f6\",\"name\":\"Redshift\",\"dockerRepository\":\"airbyte/source-redshift\",\"dockerImageTag\":\"0.3.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/redshift\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"447e0381-3780-4b46-bb62-00a4e3c8b8e2\",\"name\":\"IBM + Db2\",\"dockerRepository\":\"airbyte/source-db2\",\"dockerImageTag\":\"0.1.16\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/db2\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"ed799e2b-2158-4c66-8da4-b40fe63bc72a\",\"name\":\"Plaid\",\"dockerRepository\":\"airbyte/source-plaid\",\"dockerImageTag\":\"0.3.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/plaid\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"fbb5fbe2-16ad-4cf4-af7d-ff9d9c316c87\",\"name\":\"Sendgrid\",\"dockerRepository\":\"airbyte/source-sendgrid\",\"dockerImageTag\":\"0.2.14\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sendgrid\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"29b409d9-30a5-4cc8-ad50-886eb846fea3\",\"name\":\"QuickBooks\",\"dockerRepository\":\"airbyte/source-quickbooks-singer\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/quickbooks\",\"icon\":\" qb-logoCreated with Sketch. + \",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b117307c-14b6-41aa-9422-947e34922962\",\"name\":\"Salesforce\",\"dockerRepository\":\"airbyte/source-salesforce\",\"dockerImageTag\":\"1.0.22\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/salesforce\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b39a7370-74c3-45a6-ac3a-380d48520a83\",\"name\":\"Oracle + DB\",\"dockerRepository\":\"airbyte/source-oracle\",\"dockerImageTag\":\"0.3.21\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/oracle\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"afa734e4-3571-11ec-991a-1e0031268139\",\"name\":\"YouTube + Analytics\",\"dockerRepository\":\"airbyte/source-youtube-analytics\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/youtube-analytics\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"71607ba1-c0ac-4799-8049-7f4b90dd50f7\",\"name\":\"Google + Sheets\",\"dockerRepository\":\"airbyte/source-google-sheets\",\"dockerImageTag\":\"0.2.20\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/google-sheets\",\"icon\":\"\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"c8630570-086d-4a40-99ae-ea5b18673071\",\"name\":\"Zendesk + Talk\",\"dockerRepository\":\"airbyte/source-zendesk-talk\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zendesk-talk\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"ec4b9503-13cb-48ab-a4ab-6ade4be46567\",\"name\":\"Freshdesk\",\"dockerRepository\":\"airbyte/source-freshdesk\",\"dockerImageTag\":\"0.3.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/freshdesk\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"dfd88b22-b603-4c3d-aad7-3701784586b1\",\"name\":\"Faker\",\"dockerRepository\":\"airbyte/source-faker\",\"dockerImageTag\":\"0.1.8\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/faker\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"00405b19-9768-4e0c-b1ae-9fc2ee2b2a8c\",\"name\":\"Looker\",\"dockerRepository\":\"airbyte/source-looker\",\"dockerImageTag\":\"0.2.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/looker\",\"icon\":\"\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"59c5501b-9f95-411e-9269-7143c939adbd\",\"name\":\"BigCommerce\",\"dockerRepository\":\"airbyte/source-bigcommerce\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bigcommerce\",\"icon\":\"\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"}]}" + 27 10.4-27c.1-.4.5-.6.9-.6h8.6c.3 0 .5.2.5.5v38.4c0 .3-.2.5-.5.5h-6c-.2 0-.4-.2-.4-.5z\\\"/>\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d0243522-dccf-4978-8ba0-37ed47a0bdbf\",\"name\":\"Asana\",\"dockerRepository\":\"airbyte/source-asana\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/asana\",\"icon\":\"\\n\\n + \ \\n \\n \\n \\n + \ \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n + \ \\n + \ \\n + \ \\n + \ \\n \\n + \ \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"beta\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2fed2292-5586-480c-af92-9944e39fe12d\",\"name\":\"Short.io\",\"dockerRepository\":\"airbyte/source-shortio\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/shortio\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8da67652-004c-11ec-9a03-0242ac130003\",\"name\":\"Trello\",\"dockerRepository\":\"airbyte/source-trello\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/trello\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d3b7fa46-111b-419a-998a-d7f046f6d66d\",\"name\":\"Adjust\",\"dockerRepository\":\"airbyte/source-adjust\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/adjust\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d913b0f2-cc51-4e55-a44c-8ba1697b9239\",\"name\":\"Paypal + Transaction\",\"dockerRepository\":\"airbyte/source-paypal-transaction\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/paypal-transaction\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"1356e1d9-977f-4057-ad4b-65f25329cf61\",\"name\":\"DV + 360\",\"dockerRepository\":\"airbyte/source-dv-360\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/dv-360\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"db04ecd1-42e7-4115-9cec-95812905c626\",\"name\":\"Retently\",\"dockerRepository\":\"airbyte/source-retently\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/retently\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"b03a9f3e-22a5-11eb-adc1-0242ac120002\",\"name\":\"Mailchimp\",\"dockerRepository\":\"airbyte/source-mailchimp\",\"dockerImageTag\":\"0.2.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mailchimp\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"0dad1a35-ccf8-4d03-b73e-6788c00b13ae\",\"name\":\"TiDB\",\"dockerRepository\":\"airbyte/source-tidb\",\"dockerImageTag\":\"0.2.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/tidb\",\"icon\":\"\\n + \ \\n \\n + \ \\n \\n \\n \\n \\n + \ \\n \\n + \ \\n + \ \\n + \ \\n + \ \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"ef580275-d9a9-48bb-af5e-db0f5855be04\",\"name\":\"Webflow\",\"dockerRepository\":\"airbyte/source-webflow\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/webflow\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cf8ff320-6272-4faa-89e6-4402dc17e5d5\",\"name\":\"Glassfrog\",\"dockerRepository\":\"airbyte/source-glassfrog\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/glassfrog\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e2b40e36-aa0e-4bed-b41b-bcea6fa348b1\",\"name\":\"Exchange + Rates Api\",\"dockerRepository\":\"airbyte/source-exchange-rates\",\"dockerImageTag\":\"1.2.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/exchangeratesapi\",\"icon\":\"\\n\\n \\n logo\\n + \ Created with Sketch.\\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n \\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n + \ \\n \\n \\n + \ \\n \\n \\n \\n + \ \\n \\n \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cd06e646-31bf-4dc8-af48-cbc6530fcad3\",\"name\":\"Kustomer\",\"dockerRepository\":\"airbyte/source-kustomer-singer\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/kustomer\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7e20ce3e-d820-4327-ad7a-88f3927fd97a\",\"name\":\"VictorOps\",\"dockerRepository\":\"farosai/airbyte-victorops-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/victorops\",\"icon\":\"\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"445831eb-78db-4b1f-8f1f-0d96ad8739e2\",\"name\":\"Drift\",\"dockerRepository\":\"airbyte/source-drift\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/drift\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"8baba53d-2fe3-4e33-bc85-210d0eb62884\",\"name\":\"Zenefits\",\"dockerRepository\":\"airbyte/source-zenefits\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zenefits\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bfd1ddf8-ae8a-4620-b1d7-55597d2ba08c\",\"name\":\"BigQuery\",\"dockerRepository\":\"airbyte/source-bigquery\",\"dockerImageTag\":\"0.2.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/bigquery\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"7b86879e-26c5-4ef6-a5ce-2be5c7b46d1e\",\"name\":\"Linnworks\",\"dockerRepository\":\"airbyte/source-linnworks\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linnworks\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"3dc3037c-5ce8-4661-adc2-f7a9e3c5ece5\",\"name\":\"Zuora\",\"dockerRepository\":\"airbyte/source-zuora\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zuora\",\"icon\":\"\\n\\n\\nimage/svg+xml\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"686473f1-76d9-4994-9cc7-9b13da46147c\",\"name\":\"Chargebee\",\"dockerRepository\":\"airbyte/source-chargebee\",\"dockerImageTag\":\"0.1.15\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/chargebee\",\"icon\":\"\\n\\n + \ \\n \\n + \ \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"9c13f986-a13b-4988-b808-4705badf71c2\",\"name\":\"Wrike\",\"dockerRepository\":\"airbyte/source-wrike\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/wrike\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2817b3f0-04e4-4c7a-9f32-7a5e8a83db95\",\"name\":\"PagerDuty\",\"dockerRepository\":\"farosai/airbyte-pagerduty-source\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pagerduty\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"12928b32-bf0a-4f1e-964f-07e12e37153a\",\"name\":\"Mixpanel\",\"dockerRepository\":\"airbyte/source-mixpanel\",\"dockerImageTag\":\"0.1.28\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/mixpanel\",\"icon\":\"\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"bc617b5f-1b9e-4a2d-bebe-782fd454a771\",\"name\":\"Timely\",\"dockerRepository\":\"airbyte/source-timely\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/timely\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"af54297c-e8f8-4d63-a00d-a94695acc9d3\",\"name\":\"LinkedIn + Pages\",\"dockerRepository\":\"airbyte/source-linkedin-pages\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/linkedin-pages\",\"icon\":\"\\n\\n\\n + \ \\n \\n \\n + \ \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d8540a80-6120-485d-b7d6-272bca477d9b\",\"name\":\"OpenWeather\",\"dockerRepository\":\"airbyte/source-openweather\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/openweather\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"95e8cffd-b8c4-4039-968e-d32fb4a69bde\",\"name\":\"Klaviyo\",\"dockerRepository\":\"airbyte/source-klaviyo\",\"dockerImageTag\":\"0.1.10\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/klaviyo\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7cf88806-25f5-4e1a-b422-b2fa9e1b0090\",\"name\":\"Elasticsearch\",\"dockerRepository\":\"airbyte/source-elasticsearch\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/elasticsearch\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"d78e5de0-aa44-4744-aa4f-74c818ccfe19\",\"name\":\"RKI + Covid\",\"dockerRepository\":\"airbyte/source-rki-covid\",\"dockerImageTag\":\"0.1.1\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/rki-covid\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"798ae795-5189-42b6-b64e-3cb91db93338\",\"name\":\"Azure + Table Storage\",\"dockerRepository\":\"airbyte/source-azure-table\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/azure-table\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"6f2ac653-8623-43c4-8950-19218c7caf3d\",\"name\":\"Firebolt\",\"dockerRepository\":\"airbyte/source-firebolt\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/firebolt\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"e7778cfc-e97c-4458-9ecb-b4f2bba8946c\",\"name\":\"Facebook + Marketing\",\"dockerRepository\":\"airbyte/source-facebook-marketing\",\"dockerImageTag\":\"0.2.68\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/facebook-marketing\",\"icon\":\"\\nimage/svg+xml\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"7a4327c4-315a-11ec-8d3d-0242ac130003\",\"name\":\"Strava\",\"dockerRepository\":\"airbyte/source-strava\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/strava\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"63cea06f-1c75-458d-88fe-ad48c7cb27fd\",\"name\":\"Braintree\",\"dockerRepository\":\"airbyte/source-braintree\",\"dockerImageTag\":\"0.1.3\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/braintree\",\"icon\":\"\\n\\n + \ \\n \\n + \ \\n \\n + \ \\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"2e875208-0c0b-4ee4-9e92-1cb3156ea799\",\"name\":\"Iterable\",\"dockerRepository\":\"airbyte/source-iterable\",\"dockerImageTag\":\"0.1.19\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/iterable\",\"icon\":\"\\r\\n\\r\\n\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\t\\r\\n\\t\\t\\r\\n\\r\\n\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\t\\r\\n\\r\\n\\r\\n\\r\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"dfffecb7-9a13-43e9-acdc-b92af7997ca9\",\"name\":\"Close.com\",\"dockerRepository\":\"airbyte/source-close-com\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/close-com\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"4942d392-c7b5-4271-91f9-3b4f4e51eb3e\",\"name\":\"ZohoCRM\",\"dockerRepository\":\"airbyte/source-zoho-crm\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/zoho-crm\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"e094cb9a-26de-4645-8761-65c0c425d1de\",\"name\":\"Stripe\",\"dockerRepository\":\"airbyte/source-stripe\",\"dockerImageTag\":\"0.1.39\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/stripe\",\"icon\":\"Asset + 32Stone + Hub\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"69589781-7828-43c5-9f63-8925b1c1ccc2\",\"name\":\"S3\",\"dockerRepository\":\"airbyte/source-s3\",\"dockerImageTag\":\"0.1.23\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/s3\",\"icon\":\"\\n\\n Icon-Resource/Storage/Res_Amazon-Simple-Storage_Service-Standard_48_Light\\n + \ \\n + \ \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"c7cb421b-942e-4468-99ee-e369bcabaec5\",\"name\":\"Metabase\",\"dockerRepository\":\"airbyte/source-metabase\",\"dockerImageTag\":\"0.1.0\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/metabase\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"eaf50f04-21dd-4620-913b-2a83f5635227\",\"name\":\"Microsoft + teams\",\"dockerRepository\":\"airbyte/source-microsoft-teams\",\"dockerImageTag\":\"0.2.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/microsoft-teams\",\"icon\":\"\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n\\t\\n]>\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n\\t\\n\\t\\n\\t\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"5e6175e5-68e1-4c17-bff9-56103bbb0d80\",\"name\":\"Gitlab\",\"dockerRepository\":\"airbyte/source-gitlab\",\"dockerImageTag\":\"0.1.6\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/gitlab\",\"icon\":\"\\n\\n\\n\\n\\n\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\n\\n\\n\\t\\n\\t\\n\\t\\n\\t\\n\\tH: 2.5 x\\n\\t1/2 + x\\n\\t1x\\n\\t1x\\n\\t\\n\\t1x\\n\\t\\n\\t1x\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"aea2fd0d-377d-465e-86c0-4fdc4f688e51\",\"name\":\"Zoom\",\"dockerRepository\":\"airbyte/source-zoom-singer\",\"dockerImageTag\":\"0.2.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/zoom\",\"icon\":\"\\n\\n \\n \\n + \ \\n image/svg+xml\\n + \ \\n \\n \\n \\n + \ \\n \\n \\n \\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"435bb9a5-7887-4809-aa58-28c27df0d7ad\",\"name\":\"MySQL\",\"dockerRepository\":\"airbyte/source-mysql\",\"dockerImageTag\":\"1.0.4\",\"documentationUrl\":\"https://docs.airbyte.com/integrations/sources/mysql\",\"icon\":\"\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"beta\",\"sourceType\":\"database\"},{\"sourceDefinitionId\":\"6371b14b-bc68-4236-bfbd-468e8df8e968\",\"name\":\"PokeAPI\",\"dockerRepository\":\"airbyte/source-pokeapi\",\"dockerImageTag\":\"0.1.5\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/pokeapi\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"cdaf146a-9b75-49fd-9dd2-9d64a0bb4781\",\"name\":\"Sentry\",\"dockerRepository\":\"airbyte/source-sentry\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/sentry\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"c2281cee-86f9-4a86-bb48-d23286b4c7bd\",\"name\":\"Slack\",\"dockerRepository\":\"airbyte/source-slack\",\"dockerImageTag\":\"0.1.18\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/slack\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"778daa7c-feaf-4db6-96f3-70fd645acc77\",\"name\":\"File\",\"dockerRepository\":\"airbyte/source-file\",\"dockerImageTag\":\"0.2.24\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/file\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"beta\",\"sourceType\":\"file\"},{\"sourceDefinitionId\":\"77225a51-cd15-4a13-af02-65816bd0ecf4\",\"name\":\"Square\",\"dockerRepository\":\"airbyte/source-square\",\"dockerImageTag\":\"0.1.4\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/square\",\"icon\":\"\\n\\n\\n\\n\\n\\n\\n\\n\\n\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"14c6e7ea-97ed-4f5e-a7b5-25e9a80b8212\",\"name\":\"Airtable\",\"dockerRepository\":\"airbyte/source-airtable\",\"dockerImageTag\":\"0.1.2\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/airtable\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"af6d50ee-dddf-4126-a8ee-7faee990774f\",\"name\":\"PostHog\",\"dockerRepository\":\"airbyte/source-posthog\",\"dockerImageTag\":\"0.1.7\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/posthog\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"alpha\",\"sourceType\":\"api\"},{\"sourceDefinitionId\":\"59f1e50a-331f-4f09-b3e8-2e8d4d355f44\",\"name\":\"Greenhouse\",\"dockerRepository\":\"airbyte/source-greenhouse\",\"dockerImageTag\":\"0.2.11\",\"documentationUrl\":\"https://docs.airbyte.io/integrations/sources/greenhouse\",\"icon\":\"\",\"protocolVersion\":\"0.2.0\",\"releaseStage\":\"generally_available\",\"sourceType\":\"api\"}]}" headers: Access-Control-Allow-Headers: - Origin, Content-Type, Accept, Content-Encoding @@ -25459,7 +26831,7 @@ interactions: Content-Type: - application/json Date: - - Thu, 11 Aug 2022 08:55:10 GMT + - Fri, 14 Oct 2022 18:12:50 GMT Server: - nginx/1.23.1 Transfer-Encoding: diff --git a/octavia-cli/integration_tests/conftest.py b/octavia-cli/integration_tests/conftest.py index 856e9059327a..8af1dc381c7e 100644 --- a/octavia-cli/integration_tests/conftest.py +++ b/octavia-cli/integration_tests/conftest.py @@ -35,7 +35,7 @@ def octavia_test_project_directory(): @pytest.fixture(scope="session") def api_client(): - return get_api_client("http://localhost:8000", "octavia-cli/integration-tests", None) + return get_api_client("http://localhost:8000", "airbyte", "password", "octavia-cli/integration-tests", None) @pytest.fixture(scope="session") diff --git a/octavia-cli/integration_tests/test_api_http_headers.py b/octavia-cli/integration_tests/test_api_http_headers.py index 0ba9a62a59d4..93c6a298abe8 100644 --- a/octavia-cli/integration_tests/test_api_http_headers.py +++ b/octavia-cli/integration_tests/test_api_http_headers.py @@ -2,6 +2,7 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +import base64 import logging import pytest @@ -13,6 +14,8 @@ vcr_log.setLevel(logging.WARN) AIRBYTE_URL = "http://localhost:8000" +AIRBYTE_USERNAME = "airbyte" +AIRBYTE_PASSWORD = "password" @pytest.fixture(scope="module") @@ -44,10 +47,25 @@ def option_based_headers(): def test_api_http_headers(vcr, file_based_headers, option_based_headers): raw_option_based_headers, expected_option_based_headers = option_based_headers custom_api_http_headers_yaml_file_path, expected_file_based_headers = file_based_headers - expected_headers = expected_option_based_headers + expected_file_based_headers + basic_auth_header_value = f"Basic {base64.b64encode(f'{AIRBYTE_USERNAME}:{AIRBYTE_PASSWORD}'.encode()).decode()}" + expected_headers = ( + expected_option_based_headers + + expected_file_based_headers + + [api_http_headers.ApiHttpHeader("Authorization", basic_auth_header_value)] + ) runner = CliRunner() command_options = ( - ["--airbyte-url", AIRBYTE_URL, "--api-http-headers-file-path", custom_api_http_headers_yaml_file_path, "--api-http-header"] + [ + "--airbyte-url", + AIRBYTE_URL, + "--airbyte-username", + AIRBYTE_USERNAME, + "--airbyte-password", + AIRBYTE_PASSWORD, + "--api-http-headers-file-path", + custom_api_http_headers_yaml_file_path, + "--api-http-header", + ] + raw_option_based_headers + ["list", "connectors", "sources"] ) diff --git a/octavia-cli/octavia_cli/entrypoint.py b/octavia-cli/octavia_cli/entrypoint.py index 2aa28bb0f584..5dcae22e1862 100644 --- a/octavia-cli/octavia_cli/entrypoint.py +++ b/octavia-cli/octavia_cli/entrypoint.py @@ -33,6 +33,8 @@ def set_context_object( ctx: click.Context, airbyte_url: str, + airbyte_username: str, + airbyte_password: str, workspace_id: str, enable_telemetry: bool, option_based_api_http_headers: Optional[List[Tuple[str, str]]], @@ -44,6 +46,8 @@ def set_context_object( Args: ctx (click.Context): Current command context. airbyte_url (str): The airbyte instance url. + airbyte_username (str): The OSS airbyte instance username. + airbyte_password (str): The OSS airbyte instance password. workspace_id (str): The user_defined workspace id. enable_telemetry (bool): Whether the telemetry should send data. option_based_api_http_headers (Optional[List[Tuple[str, str]]]): Option based headers. @@ -63,7 +67,7 @@ def set_context_object( ctx.obj["TELEMETRY_CLIENT"] = telemetry_client user_agent = build_user_agent(ctx.obj["OCTAVIA_VERSION"]) api_http_headers = merge_api_headers(option_based_api_http_headers, api_http_headers_file_path) - api_client = get_api_client(airbyte_url, user_agent, api_http_headers) + api_client = get_api_client(airbyte_url, airbyte_username, airbyte_password, user_agent, api_http_headers) ctx.obj["WORKSPACE_ID"] = get_workspace_id(api_client, workspace_id) ctx.obj["ANONYMOUS_DATA_COLLECTION"] = get_anonymous_data_collection(api_client, ctx.obj["WORKSPACE_ID"]) ctx.obj["API_CLIENT"] = api_client @@ -76,6 +80,8 @@ def set_context_object( @click.group() @click.option("--airbyte-url", envvar="AIRBYTE_URL", default="http://localhost:8000", help="The URL of your Airbyte instance.") +@click.option("--airbyte-username", envvar="AIRBYTE_USERNAME", default="airbyte", help="The username for your Airbyte OSS instance.") +@click.option("--airbyte-password", envvar="AIRBYTE_PASSWORD", default="password", help="The password for your Airbyte OSS instance.") @click.option( "--workspace-id", envvar="AIRBYTE_WORKSPACE_ID", @@ -107,13 +113,24 @@ def set_context_object( def octavia( ctx: click.Context, airbyte_url: str, + airbyte_username: str, + airbyte_password: str, workspace_id: str, enable_telemetry: bool, option_based_api_http_headers: Optional[List[Tuple[str, str]]] = None, api_http_headers_file_path: Optional[str] = None, ) -> None: - ctx = set_context_object(ctx, airbyte_url, workspace_id, enable_telemetry, option_based_api_http_headers, api_http_headers_file_path) + ctx = set_context_object( + ctx, + airbyte_url, + airbyte_username, + airbyte_password, + workspace_id, + enable_telemetry, + option_based_api_http_headers, + api_http_headers_file_path, + ) click.echo( click.style( @@ -124,13 +141,18 @@ def octavia( click.echo(click.style("🐙 - Project is not yet initialized.", fg="red", bold=True)) -def get_api_client(airbyte_url: str, user_agent: str, api_http_headers: Optional[List[ApiHttpHeader]]): - client_configuration = airbyte_api_client.Configuration(host=f"{airbyte_url}/api") +def get_api_client( + airbyte_url: str, airbyte_username: str, airbyte_password: str, user_agent: str, api_http_headers: Optional[List[ApiHttpHeader]] +): + client_configuration = airbyte_api_client.Configuration(host=f"{airbyte_url}/api", username=airbyte_username, password=airbyte_password) api_client = airbyte_api_client.ApiClient(client_configuration) api_client.user_agent = user_agent - if api_http_headers: - set_api_headers_on_api_client(api_client, api_http_headers) - + api_http_headers = api_http_headers if api_http_headers else [] + has_existing_authorization_headers = bool([header for header in api_http_headers if header.name.lower() == "authorization"]) + if not has_existing_authorization_headers: + basic_auth_token = client_configuration.get_basic_auth_token() + api_http_headers.append(ApiHttpHeader("Authorization", basic_auth_token)) + set_api_headers_on_api_client(api_client, api_http_headers) check_api_health(api_client) return api_client diff --git a/octavia-cli/unit_tests/test_entrypoint.py b/octavia-cli/unit_tests/test_entrypoint.py index e6dca8877090..c1af732a3893 100644 --- a/octavia-cli/unit_tests/test_entrypoint.py +++ b/octavia-cli/unit_tests/test_entrypoint.py @@ -37,7 +37,14 @@ def test_set_context_object(mocker, option_based_api_http_headers, api_http_head mocker.patch.object(entrypoint, "get_anonymous_data_collection") mock_ctx = mocker.Mock(obj={}) built_context = entrypoint.set_context_object( - mock_ctx, "my_airbyte_url", "my_workspace_id", "enable_telemetry", option_based_api_http_headers, api_http_headers_file_path + mock_ctx, + "my_airbyte_url", + "my_airbyte_username", + "my_airbyte_password", + "my_workspace_id", + "enable_telemetry", + option_based_api_http_headers, + api_http_headers_file_path, ) entrypoint.TelemetryClient.assert_called_with("enable_telemetry") mock_ctx.ensure_object.assert_called_with(dict) @@ -52,7 +59,11 @@ def test_set_context_object(mocker, option_based_api_http_headers, api_http_head entrypoint.build_user_agent.assert_called_with(built_context.obj["OCTAVIA_VERSION"]) entrypoint.merge_api_headers.assert_called_with(option_based_api_http_headers, api_http_headers_file_path) entrypoint.get_api_client.assert_called_with( - "my_airbyte_url", entrypoint.build_user_agent.return_value, entrypoint.merge_api_headers.return_value + "my_airbyte_url", + "my_airbyte_username", + "my_airbyte_password", + entrypoint.build_user_agent.return_value, + entrypoint.merge_api_headers.return_value, ) @@ -62,7 +73,14 @@ def test_set_context_object_error(mocker): mock_ctx.ensure_object.side_effect = NotImplementedError() with pytest.raises(NotImplementedError): entrypoint.set_context_object( - mock_ctx, "my_airbyte_url", "my_workspace_id", "enable_telemetry", [("foo", "bar")], "api_http_headers_file_path" + mock_ctx, + "my_airbyte_url", + "my_airbyte_username", + "my_airbyte_password", + "my_workspace_id", + "enable_telemetry", + [("foo", "bar")], + "api_http_headers_file_path", ) entrypoint.TelemetryClient.return_value.send_command_telemetry.assert_called_with( mock_ctx, error=mock_ctx.ensure_object.side_effect @@ -154,10 +172,11 @@ def test_octavia_not_initialized(mocker): ) def test_get_api_client(mocker, api_http_headers: Optional[List[str]]): mocker.patch.object(entrypoint, "airbyte_api_client") + entrypoint.airbyte_api_client.Configuration.return_value.get_basic_auth_token.return_value = "my_basic_auth_token" mocker.patch.object(entrypoint, "check_api_health") mocker.patch.object(entrypoint, "set_api_headers_on_api_client") - api_client = entrypoint.get_api_client("test-url", "test-user-agent", api_http_headers) - entrypoint.airbyte_api_client.Configuration.assert_called_with(host="test-url/api") + api_client = entrypoint.get_api_client("test-url", "test-username", "test-password", "test-user-agent", api_http_headers) + entrypoint.airbyte_api_client.Configuration.assert_called_with(host="test-url/api", username="test-username", password="test-password") entrypoint.airbyte_api_client.ApiClient.assert_called_with(entrypoint.airbyte_api_client.Configuration.return_value) assert entrypoint.airbyte_api_client.ApiClient.return_value.user_agent == "test-user-agent" if api_http_headers: diff --git a/settings.gradle b/settings.gradle index a9d7fcea98fc..073a53c4b0e1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -92,6 +92,7 @@ if (!System.getenv().containsKey("SUB_BUILD") || System.getenv().get("SUB_BUILD" include ':airbyte-container-orchestrator' include ':airbyte-cron' include ':airbyte-metrics:reporter' + include ':airbyte-proxy' include ':airbyte-server' include ':airbyte-temporal' include ':airbyte-tests' diff --git a/tools/bin/acceptance_test.sh b/tools/bin/acceptance_test.sh index 188a13c7156b..0ec5f9f5806d 100755 --- a/tools/bin/acceptance_test.sh +++ b/tools/bin/acceptance_test.sh @@ -13,7 +13,7 @@ get_epoch_time() { } check_success() { - docker-compose ps | grep "^$1" | grep 'Exit 0' >/dev/null || (echo "$1 didn't run successfully"; exit 1) + docker-compose ps | grep "^$1" | grep -e 'Exit 0' -e 'exited (0)' >/dev/null || (echo "$1 didn't run successfully"; exit 1) } ## @@ -21,7 +21,7 @@ check_success() { echo "Starting app..." # Detach so we can run subsequent commands -VERSION=dev TRACKING_STRATEGY=logging USE_STREAM_CAPABLE_STATE=true docker-compose -f docker-compose.yaml -f docker-compose.acceptance-test.yaml up -d +VERSION=dev TRACKING_STRATEGY=logging USE_STREAM_CAPABLE_STATE=true BASIC_AUTH_USERNAME="" BASIC_AUTH_PASSWORD="" docker-compose -f docker-compose.yaml -f docker-compose.acceptance-test.yaml up -d # Sometimes source/dest containers using airbyte volumes survive shutdown, which need to be killed in order to shut down properly. shutdown_cmd="docker-compose down -v || docker kill \$(docker ps -a -f volume=airbyte_workspace -f volume=airbyte_data -f volume=airbyte_db -q) && docker-compose down -v" diff --git a/tools/bin/publish_docker.sh b/tools/bin/publish_docker.sh index dfd36944b1cb..ed7dbace80f7 100755 --- a/tools/bin/publish_docker.sh +++ b/tools/bin/publish_docker.sh @@ -3,17 +3,18 @@ set -e # List of directories without "airbyte-" prefix. projectDir=( - "workers" + "bootloader" "cli" + "config/init" + "container-orchestrator" "cron" - "webapp" + "db/db-lib" + "metrics/reporter" + "proxy" "server" "temporal" - "container-orchestrator" - "config/init" - "bootloader" - "metrics/reporter" - "db/db-lib" + "webapp" + "workers" ) # Set default values to required vars. If set in env, values will be taken from there. From e361ef6d8343cf4212c17ca4d9e1a7931b91e369 Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Wed, 19 Oct 2022 16:19:39 -0700 Subject: [PATCH 211/498] JDBC sources: Improve source column type logs (#18193) --- .../io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java index 6cd6b925e982..c14662f4d1f2 100644 --- a/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java +++ b/airbyte-integrations/connectors/source-jdbc/src/main/java/io/airbyte/integrations/source/jdbc/AbstractJdbcSource.java @@ -193,7 +193,7 @@ protected List>> discoverInternal(final JdbcData .map(f -> { final Datatype datatype = getFieldType(f); final JsonSchemaType jsonType = getType(datatype); - LOGGER.info("Table {} column {} (type {}[{}]) -> {}", + LOGGER.info("Table {} column {} (type {}[{}], nullable {}) -> {}", fields.get(0).get(INTERNAL_TABLE_NAME).asText(), f.get(INTERNAL_COLUMN_NAME).asText(), f.get(INTERNAL_COLUMN_TYPE_NAME).asText(), From 6f88bdba39d80a352363a0a90ce7177faaa5eb59 Mon Sep 17 00:00:00 2001 From: Xiaohan Song Date: Wed, 19 Oct 2022 16:21:05 -0700 Subject: [PATCH 212/498] query to find out jobs running unusally long (#17978) * query to find out jobs running unusally long * comments fix * add more indents for better readability * Update airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java Co-authored-by: Davin Chia * formatting Co-authored-by: Davin Chia --- .../metrics/lib/OssMetricsRegistry.java | 50 ++++---- .../io/airbyte/metrics/reporter/Emitter.java | 18 +++ .../metrics/reporter/MetricRepository.java | 60 +++++++++ .../reporter/MetricRepositoryTest.java | 121 ++++++++++++++++++ ...4979-af48-eaa545e5a9ff_destination_ids.txt | 0 5 files changed, 227 insertions(+), 22 deletions(-) create mode 100644 tools/bin/load_test/cleanup/d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java index 3c5f81b0296c..3c830e2a85f1 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java @@ -67,10 +67,6 @@ public enum OssMetricsRegistry implements MetricsRegistry { MetricEmittingApps.WORKER, "job_succeeded_by_release_stage", "increments when a job succeeds. jobs are double counted as this is tagged by release stage."), - KUBE_POD_PROCESS_CREATE_TIME_MILLISECS( - MetricEmittingApps.WORKER, - "kube_pod_process_create_time_millisecs", - "time taken to create a new kube pod process"), JSON_STRING_LENGTH( MetricEmittingApps.WORKER, "json_string_length", @@ -79,30 +75,43 @@ public enum OssMetricsRegistry implements MetricsRegistry { MetricEmittingApps.WORKER, "json_size", "size of the json object"), + KUBE_POD_PROCESS_CREATE_TIME_MILLISECS( + MetricEmittingApps.WORKER, + "kube_pod_process_create_time_millisecs", + "time taken to create a new kube pod process"), + NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY( + MetricEmittingApps.METRICS_REPORTER, + "num_abnormal_scheduled_syncs_last_day", + "number of abnormal syncs that have skipped at least 1 scheduled run in last day."), + NUM_ACTIVE_CONN_PER_WORKSPACE( + MetricEmittingApps.METRICS_REPORTER, + "num_active_conn_per_workspace", + "number of active connections per workspace"), NUM_PENDING_JOBS( MetricEmittingApps.METRICS_REPORTER, "num_pending_jobs", "number of pending jobs"), - NUM_RUNNING_JOBS( - MetricEmittingApps.METRICS_REPORTER, - "num_running_jobs", - "number of running jobs"), NUM_ORPHAN_RUNNING_JOBS( MetricEmittingApps.METRICS_REPORTER, "num_orphan_running_jobs", "number of jobs reported as running that as associated to connection inactive or deprecated"), - NUM_ACTIVE_CONN_PER_WORKSPACE( - MetricEmittingApps.METRICS_REPORTER, - "num_active_conn_per_workspace", - "number of active connections per workspace"), - NUM_ABNORMAL_SCHEDULED_SYNCS_IN_LAST_DAY( + NUM_RUNNING_JOBS( MetricEmittingApps.METRICS_REPORTER, - "num_abnormal_scheduled_syncs_last_day", - "number of abnormal syncs that have skipped at least 1 scheduled run in last day."), + "num_running_jobs", + "number of running jobs"), + NUM_SOURCE_STREAMS_WITH_RECORD_SCHEMA_VALIDATION_ERRORS(MetricEmittingApps.WORKER, + "record_schema_validation_error", + "number of record schema validation errors"), NUM_TOTAL_SCHEDULED_SYNCS_IN_LAST_DAY( MetricEmittingApps.METRICS_REPORTER, "num_total_scheduled_syncs_last_day", "number of total syncs runs in last day."), + + NUM_UNUSUALLY_LONG_SYNCS( + MetricEmittingApps.METRICS_REPORTER, + "num_unusually_long_syncs", + "number of unusual long syncs compared to their historic performance."), + OLDEST_PENDING_JOB_AGE_SECS(MetricEmittingApps.METRICS_REPORTER, "oldest_pending_job_age_secs", "oldest pending job in seconds"), @@ -112,6 +121,9 @@ public enum OssMetricsRegistry implements MetricsRegistry { OVERALL_JOB_RUNTIME_IN_LAST_HOUR_BY_TERMINAL_STATE_SECS(MetricEmittingApps.METRICS_REPORTER, "overall_job_runtime_in_last_hour_by_terminal_state_secs", "overall job runtime - scheduling and execution for all attempts - for jobs that reach terminal states in the last hour. tagged by terminal states."), + STATE_METRIC_TRACKER_ERROR(MetricEmittingApps.WORKER, + "state_timestamp_metric_tracker_error", + "number of syncs where the state timestamp metric tracker ran out of memory or was unable to match destination state message to source state message"), TEMPORAL_WORKFLOW_ATTEMPT(MetricEmittingApps.WORKER, "temporal_workflow_attempt", "count of the number of workflow attempts"), @@ -120,13 +132,7 @@ public enum OssMetricsRegistry implements MetricsRegistry { "count of the number of successful workflow syncs."), TEMPORAL_WORKFLOW_FAILURE(MetricEmittingApps.WORKER, "temporal_workflow_failure", - "count of the number of workflow failures"), - NUM_SOURCE_STREAMS_WITH_RECORD_SCHEMA_VALIDATION_ERRORS(MetricEmittingApps.WORKER, - "record_schema_validation_error", - "number of record schema validation errors"), - STATE_METRIC_TRACKER_ERROR(MetricEmittingApps.WORKER, - "state_timestamp_metric_tracker_error", - "number of syncs where the state timestamp metric tracker ran out of memory or was unable to match destination state message to source state message"); + "count of the number of workflow failures"); private final MetricEmittingApp application; private final String metricName; diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java index 12b2900a3e81..56ab764c0a7b 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java @@ -113,6 +113,24 @@ public Duration getDuration() { } +@Singleton +final class NumUnusuallyLongSyncs extends Emitter { + + NumUnusuallyLongSyncs(final MetricClient client, final MetricRepository db) { + super(client, () -> { + final var count = db.numberOfJobsRunningUnusuallyLong(); + client.gauge(OssMetricsRegistry.NUM_UNUSUALLY_LONG_SYNCS, count); + return null; + }); + } + + @Override + public Duration getDuration() { + return Duration.ofMinutes(15); + } + +} + @Singleton final class TotalScheduledSyncs extends Emitter { diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java index 8dda0ba29869..c30d0111e501 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java @@ -140,6 +140,66 @@ having count(*) < 1440 / cast(c.schedule::jsonb->'units' as integer) + ctx.fetchOne(queryForAbnormalSyncInMinutesInLastDay).get("cnt", long.class); } + long numberOfJobsRunningUnusuallyLong() { + // Definition of unusually long means runtime is more than 2x historic avg run time or 15 + // minutes more than avg run time, whichever is greater. + // It will skip jobs with fewer than 4 runs in last week to make sure the historic avg run is + // meaningful and consistent. + final var query = + """ + -- pick average running time and last sync running time in attempts table. + select + current_running_attempts.connection_id, + current_running_attempts.running_time, + historic_avg_running_attempts.avg_run_sec + from + ( + -- Sub-query-1: query the currently running attempt's running time. + ( + select + jobs.scope as connection_id, + extract(epoch from age(NOW(), attempts.created_at)) as running_time + from + jobs + join attempts on + jobs.id = attempts.job_id + where + jobs.status = 'running' + and attempts.status = 'running' + and jobs.config_type = 'sync' ) + as current_running_attempts + join + -- Sub-query-2: query historic attempts' average running time within last week. + ( + select + jobs.scope as connection_id, + avg(extract(epoch from age(attempts.updated_at, attempts.created_at))) as avg_run_sec + from + jobs + join attempts on + jobs.id = attempts.job_id + where + -- 168 hours is 1 week: we look for all attempts in last week to calculate its average running time. + attempts.updated_at >= NOW() - interval '168 HOUR' + and jobs.status = 'succeeded' + and attempts.status = 'succeeded' + and jobs.config_type = 'sync' + group by + connection_id + having + count(*) > 4 + ) as historic_avg_running_attempts + on + current_running_attempts.connection_id = historic_avg_running_attempts.connection_id) + where + -- Find if currently running time takes 2x more time than average running time, + -- and it's 15 minutes (900 seconds) more than average running time so it won't alert on noises for quick sync jobs. + current_running_attempts.running_time > greatest(historic_avg_running_attempts.avg_run_sec * 2, historic_avg_running_attempts.avg_run_sec + 900) + """; + final var queryResults = ctx.fetch(query); + return queryResults.getValues("connection_id").size(); + } + Map overallJobRuntimeForTerminalJobsInLastHour() { final var query = """ SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java index 8ff5f2e8cd69..4510cd01098e 100644 --- a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java @@ -13,6 +13,7 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; import static io.airbyte.db.instance.configs.jooq.generated.Tables.WORKSPACE; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.ATTEMPTS; import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -23,6 +24,7 @@ import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.AttemptStatus; import io.airbyte.db.instance.jobs.jooq.generated.enums.JobConfigType; import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; import io.airbyte.db.instance.test.TestDatabaseProviders; @@ -92,6 +94,7 @@ void setUp() { ctx.truncate(ACTOR).execute(); ctx.truncate(CONNECTION).cascade().execute(); ctx.truncate(JOBS).cascade().execute(); + ctx.truncate(ATTEMPTS).cascade().execute(); ctx.truncate(WORKSPACE).cascade().execute(); } @@ -503,4 +506,122 @@ void shouldNotCountNormalJobsInAbnormalMetric() { } + @Nested + class UnusuallyLongJobs { + + @Test + void shouldCountInJobsWithUnusuallyLongTime() throws SQLException { + final var connectionId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + // Current job has been running for 12 hours while the previous 5 jobs runs 2 hours. Avg will be 2 + // hours. + // Thus latest job will be counted as an unusually long-running job. + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), + OffsetDateTime.now().minus(26, ChronoUnit.HOURS), syncConfigType) + .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), + OffsetDateTime.now().minus(18, ChronoUnit.HOURS), syncConfigType) + .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(18, ChronoUnit.HOURS), + OffsetDateTime.now().minus(16, ChronoUnit.HOURS), syncConfigType) + .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(16, ChronoUnit.HOURS), + OffsetDateTime.now().minus(14, ChronoUnit.HOURS), syncConfigType) + .values(4L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(14, ChronoUnit.HOURS), + OffsetDateTime.now().minus(12, ChronoUnit.HOURS), syncConfigType) + .values(5L, connectionId.toString(), JobStatus.running, OffsetDateTime.now().minus(12, ChronoUnit.HOURS), + OffsetDateTime.now().minus(12, ChronoUnit.HOURS), syncConfigType) + .execute(); + + ctx.insertInto(ATTEMPTS, ATTEMPTS.ID, ATTEMPTS.JOB_ID, ATTEMPTS.STATUS, ATTEMPTS.CREATED_AT, ATTEMPTS.UPDATED_AT) + .values(100L, 100L, AttemptStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), + OffsetDateTime.now().minus(26, ChronoUnit.HOURS)) + .values(1L, 1L, AttemptStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), + OffsetDateTime.now().minus(18, ChronoUnit.HOURS)) + .values(2L, 2L, AttemptStatus.succeeded, OffsetDateTime.now().minus(18, ChronoUnit.HOURS), + OffsetDateTime.now().minus(16, ChronoUnit.HOURS)) + .values(3L, 3L, AttemptStatus.succeeded, OffsetDateTime.now().minus(16, ChronoUnit.HOURS), + OffsetDateTime.now().minus(14, ChronoUnit.HOURS)) + .values(4L, 4L, AttemptStatus.succeeded, OffsetDateTime.now().minus(14, ChronoUnit.HOURS), + OffsetDateTime.now().minus(12, ChronoUnit.HOURS)) + .values(5L, 5L, AttemptStatus.running, OffsetDateTime.now().minus(12, ChronoUnit.HOURS), + OffsetDateTime.now().minus(12, ChronoUnit.HOURS)) + .execute(); + + final var numOfJubsRunningUnusallyLong = db.numberOfJobsRunningUnusuallyLong(); + assertEquals(1, numOfJubsRunningUnusallyLong); + } + + @Test + void shouldNotCountInJobsWithinFifteenMinutes() throws SQLException { + final var connectionId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + // Latest job runs 14 minutes while the previous 5 jobs runs average about 3 minutes. + // Despite it has been more than 2x than avg it's still within 15 minutes threshold, thus this + // shouldn't be + // counted in. + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(26, ChronoUnit.MINUTES), syncConfigType) + .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(18, ChronoUnit.MINUTES), syncConfigType) + .values(2L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(18, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(16, ChronoUnit.MINUTES), syncConfigType) + .values(3L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(16, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(14, ChronoUnit.MINUTES), syncConfigType) + .values(4L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(14, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(2, ChronoUnit.MINUTES), syncConfigType) + .values(5L, connectionId.toString(), JobStatus.running, OffsetDateTime.now().minus(14, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(2, ChronoUnit.MINUTES), syncConfigType) + .execute(); + + ctx.insertInto(ATTEMPTS, ATTEMPTS.ID, ATTEMPTS.JOB_ID, ATTEMPTS.STATUS, ATTEMPTS.CREATED_AT, ATTEMPTS.UPDATED_AT) + .values(100L, 100L, AttemptStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(26, ChronoUnit.MINUTES)) + .values(1L, 1L, AttemptStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(18, ChronoUnit.MINUTES)) + .values(2L, 2L, AttemptStatus.succeeded, OffsetDateTime.now().minus(18, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(16, ChronoUnit.MINUTES)) + .values(3L, 3L, AttemptStatus.succeeded, OffsetDateTime.now().minus(26, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(14, ChronoUnit.MINUTES)) + .values(4L, 4L, AttemptStatus.succeeded, OffsetDateTime.now().minus(18, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(17, ChronoUnit.MINUTES)) + .values(5L, 5L, AttemptStatus.running, OffsetDateTime.now().minus(14, ChronoUnit.MINUTES), + OffsetDateTime.now().minus(14, ChronoUnit.MINUTES)) + .execute(); + + final var numOfJubsRunningUnusallyLong = db.numberOfJobsRunningUnusuallyLong(); + assertEquals(0, numOfJubsRunningUnusallyLong); + } + + @Test + void shouldSkipInsufficientJobRuns() throws SQLException { + final var connectionId = UUID.randomUUID(); + final var syncConfigType = JobConfigType.sync; + + // Require at least 5 runs in last week to get meaningful average runtime. + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT, JOBS.UPDATED_AT, JOBS.CONFIG_TYPE) + .values(100L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), + OffsetDateTime.now().minus(26, ChronoUnit.HOURS), syncConfigType) + .values(1L, connectionId.toString(), JobStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), + OffsetDateTime.now().minus(18, ChronoUnit.HOURS), syncConfigType) + .values(2L, connectionId.toString(), JobStatus.running, OffsetDateTime.now().minus(18, ChronoUnit.HOURS), + OffsetDateTime.now().minus(1, ChronoUnit.HOURS), syncConfigType) + .execute(); + + ctx.insertInto(ATTEMPTS, ATTEMPTS.ID, ATTEMPTS.JOB_ID, ATTEMPTS.STATUS, ATTEMPTS.CREATED_AT, ATTEMPTS.UPDATED_AT) + .values(100L, 100L, AttemptStatus.succeeded, OffsetDateTime.now().minus(28, ChronoUnit.HOURS), + OffsetDateTime.now().minus(26, ChronoUnit.HOURS)) + .values(1L, 1L, AttemptStatus.succeeded, OffsetDateTime.now().minus(20, ChronoUnit.HOURS), + OffsetDateTime.now().minus(18, ChronoUnit.HOURS)) + .values(2L, 2L, AttemptStatus.running, OffsetDateTime.now().minus(18, ChronoUnit.HOURS), + OffsetDateTime.now().minus(1, ChronoUnit.HOURS)) + .execute(); + + final var numOfJubsRunningUnusallyLong = db.numberOfJobsRunningUnusuallyLong(); + assertEquals(0, numOfJubsRunningUnusallyLong); + } + + } + } diff --git a/tools/bin/load_test/cleanup/d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt b/tools/bin/load_test/cleanup/d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt new file mode 100644 index 000000000000..e69de29bb2d1 From 27a7878b8e20b0cc492dc2128bbe1c999eb669f9 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Wed, 19 Oct 2022 17:03:21 -0700 Subject: [PATCH 213/498] Manually bump proxy docker version tag to current version (#18198) --- airbyte-proxy/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-proxy/Dockerfile b/airbyte-proxy/Dockerfile index 419843cdc044..619b4eddf074 100644 --- a/airbyte-proxy/Dockerfile +++ b/airbyte-proxy/Dockerfile @@ -2,7 +2,7 @@ FROM nginx:latest -ARG VERSION=0.40.12 +ARG VERSION=0.40.15 ENV APPLICATION airbyte-proxy ENV VERSION ${VERSION} From 6b1c5ee384926885613cfbd420d68e8150391f86 Mon Sep 17 00:00:00 2001 From: Xiaohan Song Date: Wed, 19 Oct 2022 17:10:37 -0700 Subject: [PATCH 214/498] save queue name into Attempts table (#17921) * save queue name * make input nullable because we changed signature * PR Comments fix --- airbyte-api/src/main/openapi/config.yaml | 3 ++ .../job/DefaultJobPersistence.java | 9 +++-- .../persistence/job/JobPersistence.java | 2 +- .../job/DefaultJobPersistenceTest.java | 12 +++++-- .../server/handlers/AttemptHandler.java | 4 +-- .../server/handlers/AttemptHandlerTest.java | 22 ++++++++---- .../temporal/TemporalAttemptExecution.java | 34 +++++++++++++++++-- .../temporal/sync/ReplicationActivity.java | 3 +- .../sync/ReplicationActivityImpl.java | 10 ++++-- .../temporal/sync/SyncWorkflowImpl.java | 4 ++- .../TemporalAttemptExecutionTest.java | 4 ++- .../temporal/sync/SyncWorkflowTest.java | 26 +++++++------- .../api/generated-api-html/index.html | 1 + 13 files changed, 100 insertions(+), 34 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index ab7ac43ab5bb..73b65e2634a1 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -4813,6 +4813,9 @@ components: $ref: "#/components/schemas/AttemptNumber" workflowId: $ref: "#/components/schemas/WorkflowId" + processingTaskQueue: + type: string + default: "" InternalOperationResult: type: object required: diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java index 32eaa4d6037e..e4b10f6ceb43 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/DefaultJobPersistence.java @@ -304,10 +304,15 @@ public void succeedAttempt(final long jobId, final int attemptNumber) throws IOE } @Override - public void setAttemptTemporalWorkflowId(final long jobId, final int attemptNumber, final String temporalWorkflowId) throws IOException { + public void setAttemptTemporalWorkflowInfo(final long jobId, + final int attemptNumber, + final String temporalWorkflowId, + final String processingTaskQueue) + throws IOException { jobDatabase.query(ctx -> ctx.execute( - " UPDATE attempts SET temporal_workflow_id = ? WHERE job_id = ? AND attempt_number = ?", + " UPDATE attempts SET temporal_workflow_id = ? , processing_task_queue = ? WHERE job_id = ? AND attempt_number = ?", temporalWorkflowId, + processingTaskQueue, jobId, attemptNumber)); } diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java index 5744dd695496..5063e98bcdb2 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/JobPersistence.java @@ -121,7 +121,7 @@ public interface JobPersistence { /** * Sets an attempt's temporal workflow id. Later used to cancel the workflow. */ - void setAttemptTemporalWorkflowId(long jobId, int attemptNumber, String temporalWorkflowId) throws IOException; + void setAttemptTemporalWorkflowInfo(long jobId, int attemptNumber, String temporalWorkflowId, String processingTaskQueue) throws IOException; /** * Retrieves an attempt's temporal workflow id. Used to cancel the workflow. diff --git a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java index 8629b307ddf4..456d3eaf53d3 100644 --- a/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java +++ b/airbyte-persistence/job-persistence/src/test/java/io/airbyte/persistence/job/DefaultJobPersistenceTest.java @@ -617,7 +617,7 @@ private long createJobAt(final Instant created_at) throws IOException { } @Nested - class TemporalWorkflowId { + class TemporalWorkflowInfo { @Test void testSuccessfulGet() throws IOException, SQLException { @@ -640,15 +640,21 @@ void testGetMissingAttempt() throws IOException { } @Test - void testSuccessfulSet() throws IOException { + void testSuccessfulSet() throws IOException, SQLException { final long jobId = jobPersistence.enqueueJob(SCOPE, SPEC_JOB_CONFIG).orElseThrow(); final var attemptNumber = jobPersistence.createAttempt(jobId, LOG_PATH); final var temporalWorkflowId = "test-id-usually-uuid"; + final var syncQueue = "SYNC"; - jobPersistence.setAttemptTemporalWorkflowId(jobId, attemptNumber, temporalWorkflowId); + jobPersistence.setAttemptTemporalWorkflowInfo(jobId, attemptNumber, temporalWorkflowId, syncQueue); final var workflowId = jobPersistence.getAttemptTemporalWorkflowId(jobId, attemptNumber).get(); assertEquals(workflowId, temporalWorkflowId); + + final var taskQueue = jobDatabase.query(ctx -> ctx.fetch( + "SELECT processing_task_queue FROM attempts WHERE job_id = ? AND attempt_number =?", jobId, + attemptNumber)).stream().findFirst().get().get("processing_task_queue", String.class); + assertEquals(syncQueue, taskQueue); } } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/AttemptHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/AttemptHandler.java index 34a19aec6f21..83f86861d6b3 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/AttemptHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/AttemptHandler.java @@ -24,8 +24,8 @@ public AttemptHandler(JobPersistence jobPersistence) { public InternalOperationResult setWorkflowInAttempt( SetWorkflowInAttemptRequestBody requestBody) { try { - jobPersistence.setAttemptTemporalWorkflowId(requestBody.getJobId(), - requestBody.getAttemptNumber(), requestBody.getWorkflowId().toString()); + jobPersistence.setAttemptTemporalWorkflowInfo(requestBody.getJobId(), + requestBody.getAttemptNumber(), requestBody.getWorkflowId().toString(), requestBody.getProcessingTaskQueue()); } catch (IOException ioe) { LOGGER.error("IOException when setting temporal workflow in attempt;", ioe); return new InternalOperationResult().succeeded(false); diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/AttemptHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/AttemptHandlerTest.java index 368fba11636f..4698ed5c6387 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/AttemptHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/AttemptHandlerTest.java @@ -29,6 +29,8 @@ class AttemptHandlerTest { private static final long JOB_ID = 10002L; private static final int ATTEMPT_NUMBER = 1; + private static final String PROCESSING_TASK_QUEUE = "SYNC"; + @BeforeEach public void init() { jobPersistence = Mockito.mock(JobPersistence.class); @@ -42,40 +44,48 @@ void testInternalWorkerHandlerSetsTemporalWorkflowId() throws Exception { final ArgumentCaptor attemptNumberCapture = ArgumentCaptor.forClass(Integer.class); final ArgumentCaptor jobIdCapture = ArgumentCaptor.forClass(Long.class); final ArgumentCaptor workflowIdCapture = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor queueCapture = ArgumentCaptor.forClass(String.class); SetWorkflowInAttemptRequestBody requestBody = - new SetWorkflowInAttemptRequestBody().attemptNumber(ATTEMPT_NUMBER).jobId(JOB_ID).workflowId(workflowId); + new SetWorkflowInAttemptRequestBody().attemptNumber(ATTEMPT_NUMBER).jobId(JOB_ID).workflowId(workflowId) + .processingTaskQueue(PROCESSING_TASK_QUEUE); assertTrue(handler.setWorkflowInAttempt(requestBody).getSucceeded()); - Mockito.verify(jobPersistence).setAttemptTemporalWorkflowId(jobIdCapture.capture(), attemptNumberCapture.capture(), workflowIdCapture.capture()); + Mockito.verify(jobPersistence).setAttemptTemporalWorkflowInfo(jobIdCapture.capture(), attemptNumberCapture.capture(), workflowIdCapture.capture(), + queueCapture.capture()); assertEquals(ATTEMPT_NUMBER, attemptNumberCapture.getValue()); assertEquals(JOB_ID, jobIdCapture.getValue()); assertEquals(workflowId, workflowIdCapture.getValue()); + assertEquals(PROCESSING_TASK_QUEUE, queueCapture.getValue()); } @Test void testInternalWorkerHandlerSetsTemporalWorkflowIdThrows() throws Exception { String workflowId = UUID.randomUUID().toString(); - doThrow(IOException.class).when(jobPersistence).setAttemptTemporalWorkflowId(anyLong(), anyInt(), - any()); + doThrow(IOException.class).when(jobPersistence).setAttemptTemporalWorkflowInfo(anyLong(), anyInt(), + any(), any()); final ArgumentCaptor attemptNumberCapture = ArgumentCaptor.forClass(Integer.class); final ArgumentCaptor jobIdCapture = ArgumentCaptor.forClass(Long.class); final ArgumentCaptor workflowIdCapture = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor queueCapture = ArgumentCaptor.forClass(String.class); SetWorkflowInAttemptRequestBody requestBody = - new SetWorkflowInAttemptRequestBody().attemptNumber(ATTEMPT_NUMBER).jobId(JOB_ID).workflowId(workflowId); + new SetWorkflowInAttemptRequestBody().attemptNumber(ATTEMPT_NUMBER).jobId(JOB_ID).workflowId(workflowId) + .processingTaskQueue(PROCESSING_TASK_QUEUE); assertFalse(handler.setWorkflowInAttempt(requestBody).getSucceeded()); - Mockito.verify(jobPersistence).setAttemptTemporalWorkflowId(jobIdCapture.capture(), attemptNumberCapture.capture(), workflowIdCapture.capture()); + Mockito.verify(jobPersistence).setAttemptTemporalWorkflowInfo(jobIdCapture.capture(), attemptNumberCapture.capture(), workflowIdCapture.capture(), + queueCapture.capture()); assertEquals(ATTEMPT_NUMBER, attemptNumberCapture.getValue()); assertEquals(JOB_ID, jobIdCapture.getValue()); assertEquals(workflowId, workflowIdCapture.getValue()); + assertEquals(PROCESSING_TASK_QUEUE, queueCapture.getValue()); } } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java index b463b8fc8ce5..c8a476482dfd 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/TemporalAttemptExecution.java @@ -19,6 +19,7 @@ import io.temporal.activity.Activity; import io.temporal.activity.ActivityExecutionContext; import java.nio.file.Path; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -50,6 +51,7 @@ public class TemporalAttemptExecution implements Supplier private final Supplier workflowIdProvider; private final AirbyteApiClient airbyteApiClient; private final String airbyteVersion; + private final Optional workflowTaskQueue; public TemporalAttemptExecution(final Path workspaceRoot, final WorkerEnvironment workerEnvironment, @@ -70,7 +72,32 @@ public TemporalAttemptExecution(final Path workspaceRoot, cancellationHandler, airbyteApiClient, () -> activityContext.get().getInfo().getWorkflowId(), - airbyteVersion); + airbyteVersion, + Optional.empty()); + } + + public TemporalAttemptExecution(final Path workspaceRoot, + final WorkerEnvironment workerEnvironment, + final LogConfigs logConfigs, + final JobRunConfig jobRunConfig, + final CheckedSupplier, Exception> workerSupplier, + final Supplier inputSupplier, + final CancellationHandler cancellationHandler, + final AirbyteApiClient airbyteApiClient, + final String airbyteVersion, + final Supplier activityContext, + final Optional workflowTaskQueue) { + this( + workspaceRoot, workerEnvironment, logConfigs, + jobRunConfig, + workerSupplier, + inputSupplier, + (path -> LogClientSingleton.getInstance().setJobMdc(workerEnvironment, logConfigs, path)), + cancellationHandler, + airbyteApiClient, + () -> activityContext.get().getInfo().getWorkflowId(), + airbyteVersion, + workflowTaskQueue); } @VisibleForTesting @@ -84,7 +111,8 @@ public TemporalAttemptExecution(final Path workspaceRoot, final CancellationHandler cancellationHandler, final AirbyteApiClient airbyteApiClient, final Supplier workflowIdProvider, - final String airbyteVersion) { + final String airbyteVersion, + final Optional workflowTaskQueue) { this.jobRunConfig = jobRunConfig; this.jobRoot = TemporalUtils.getJobRoot(workspaceRoot, jobRunConfig.getJobId(), jobRunConfig.getAttemptId()); @@ -96,6 +124,7 @@ public TemporalAttemptExecution(final Path workspaceRoot, this.airbyteApiClient = airbyteApiClient; this.airbyteVersion = airbyteVersion; + this.workflowTaskQueue = workflowTaskQueue; } @Override @@ -148,6 +177,7 @@ private void saveWorkflowIdForCancellation(final AirbyteApiClient airbyteApiClie airbyteApiClient.getAttemptApi().setWorkflowInAttempt(new SetWorkflowInAttemptRequestBody() .jobId(Long.parseLong(jobRunConfig.getJobId())) .attemptNumber(jobRunConfig.getAttemptId().intValue()) + .processingTaskQueue(workflowTaskQueue.orElse("")) .workflowId(workflowId)); } } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivity.java index 5b9ee83713ed..6b123f6fd963 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivity.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivity.java @@ -18,6 +18,7 @@ public interface ReplicationActivity { StandardSyncOutput replicate(JobRunConfig jobRunConfig, IntegrationLauncherConfig sourceLauncherConfig, IntegrationLauncherConfig destinationLauncherConfig, - StandardSyncInput syncInput); + StandardSyncInput syncInput, + final String taskQueue); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java index 349aa5f1ae12..f0fbc5b075f3 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java @@ -10,6 +10,7 @@ import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; import datadog.trace.api.Trace; +import edu.umd.cs.findbugs.annotations.Nullable; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.api.client.invoker.generated.ApiException; import io.airbyte.api.client.model.generated.JobIdRequestBody; @@ -111,12 +112,16 @@ public ReplicationActivityImpl(@Named("containerOrchestratorConfig") final Optio this.airbyteApiClient = airbyteApiClient; } + // Marking task queue as nullable because we changed activity signature; thus runs started before + // this new change will have taskQueue set to null. We should remove it after the old runs are all + // finished in next release. @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override public StandardSyncOutput replicate(final JobRunConfig jobRunConfig, final IntegrationLauncherConfig sourceLauncherConfig, final IntegrationLauncherConfig destinationLauncherConfig, - final StandardSyncInput syncInput) { + final StandardSyncInput syncInput, + @Nullable final String taskQueue) { ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage(), SOURCE_DOCKER_IMAGE_KEY, sourceLauncherConfig.getDockerImage())); final ActivityExecutionContext context = Activity.getExecutionContext(); @@ -161,7 +166,8 @@ public StandardSyncOutput replicate(final JobRunConfig jobRunConfig, new CancellationHandler.TemporalCancellationHandler(context), airbyteApiClient, airbyteVersion, - () -> context); + () -> context, + Optional.ofNullable(taskQueue)); final ReplicationOutput attemptOutput = temporalAttempt.get(); final StandardSyncOutput standardSyncOutput = reduceReplicationOutput(attemptOutput); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java index b53a201e407f..f2c6b3f021d1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java @@ -70,7 +70,9 @@ public StandardSyncOutput run(final JobRunConfig jobRunConfig, DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage())); final int version = Workflow.getVersion(VERSION_LABEL, Workflow.DEFAULT_VERSION, CURRENT_VERSION); - StandardSyncOutput syncOutput = replicationActivity.replicate(jobRunConfig, sourceLauncherConfig, destinationLauncherConfig, syncInput); + final String taskQueue = Workflow.getInfo().getTaskQueue(); + StandardSyncOutput syncOutput = + replicationActivity.replicate(jobRunConfig, sourceLauncherConfig, destinationLauncherConfig, syncInput, taskQueue); if (version > Workflow.DEFAULT_VERSION) { // the state is persisted immediately after the replication succeeded, because the diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalAttemptExecutionTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalAttemptExecutionTest.java index f88252e5003a..c39f2a9bf07d 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalAttemptExecutionTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/TemporalAttemptExecutionTest.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Consumer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -89,7 +90,8 @@ void setup() throws IOException, DatabaseInitializationException { mdcSetter, mock(CancellationHandler.class), airbyteApiClient, - () -> "workflow_id", configs.getAirbyteVersionOrWarning()); + () -> "workflow_id", configs.getAirbyteVersionOrWarning(), + Optional.of("SYNC")); } @AfterAll diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java index cb419e3e0d51..31933b9c734d 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/SyncWorkflowTest.java @@ -80,8 +80,6 @@ class SyncWorkflowTest { private NormalizationSummaryCheckActivityImpl normalizationSummaryCheckActivity; private WebhookOperationActivityImpl webhookOperationActivity; - private static final String SYNC_TASK_QUEUE = "SYNC_TASK_QUEUE"; - // AIRBYTE CONFIGURATION private static final long JOB_ID = 11L; private static final int ATTEMPT_ID = 21; @@ -99,6 +97,8 @@ class SyncWorkflowTest { .withAttemptId((long) ATTEMPT_ID) .withDockerImage(IMAGE_NAME2); + private static final String SYNC_QUEUE = "SYNC"; + private StandardSync sync; private StandardSyncInput syncInput; private NormalizationInput normalizationInput; @@ -116,7 +116,7 @@ class SyncWorkflowTest { @BeforeEach void setUp() throws IOException { testEnv = TestWorkflowEnvironment.newInstance(); - syncWorker = testEnv.newWorker(SYNC_TASK_QUEUE); + syncWorker = testEnv.newWorker(SYNC_QUEUE); client = testEnv.getWorkflowClient(); final ImmutablePair syncPair = TestConfigHelpers.createSyncConfig(); @@ -193,7 +193,7 @@ private StandardSyncOutput execute() { persistStateActivity, normalizationSummaryCheckActivity, webhookOperationActivity); testEnv.start(); final SyncWorkflow workflow = - client.newWorkflowStub(SyncWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(SYNC_TASK_QUEUE).build()); + client.newWorkflowStub(SyncWorkflow.class, WorkflowOptions.newBuilder().setTaskQueue(SYNC_QUEUE).build()); return workflow.run(JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, syncInput, sync.getConnectionId()); } @@ -204,7 +204,7 @@ void testSuccess() { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); doReturn(normalizationSummary).when(normalizationActivity).normalize( JOB_RUN_CONFIG, @@ -229,7 +229,7 @@ void testReplicationFailure() { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); assertThrows(WorkflowFailedException.class, this::execute); @@ -245,7 +245,7 @@ void testReplicationFailedGracefully() { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); doReturn(normalizationSummary).when(normalizationActivity).normalize( JOB_RUN_CONFIG, @@ -270,7 +270,7 @@ void testNormalizationFailure() { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); doThrow(new IllegalArgumentException("induced exception")).when(normalizationActivity).normalize( JOB_RUN_CONFIG, @@ -294,7 +294,7 @@ void testCancelDuringReplication() { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); assertThrows(WorkflowFailedException.class, this::execute); @@ -310,7 +310,7 @@ void testCancelDuringNormalization() { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); doAnswer(ignored -> { cancelWorkflow(); @@ -341,7 +341,7 @@ void testSkipNormalization() throws IOException { JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); execute(); @@ -354,7 +354,7 @@ void testSkipNormalization() throws IOException { @Test void testWebhookOperation() { - when(replicationActivity.replicate(any(), any(), any(), any())).thenReturn(new StandardSyncOutput()); + when(replicationActivity.replicate(any(), any(), any(), any(), any())).thenReturn(new StandardSyncOutput()); final StandardSyncOperation webhookOperation = new StandardSyncOperation() .withOperationId(UUID.randomUUID()) .withOperatorType(OperatorType.WEBHOOK) @@ -393,7 +393,7 @@ private static void verifyReplication(final ReplicationActivity replicationActiv JOB_RUN_CONFIG, SOURCE_LAUNCHER_CONFIG, DESTINATION_LAUNCHER_CONFIG, - syncInput); + syncInput, SYNC_QUEUE); } private static void verifyPersistState(final PersistStateActivity persistStateActivity, diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 210e5416f003..8bf8380da920 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -11097,6 +11097,7 @@

    SetWorkflowInAttemptRequestB
    jobId
    Long format: int64
    attemptNumber
    Integer format: int32
    workflowId
    +
    processingTaskQueue (optional)

    From 1f21f75e8a8fa29e0ea949c8f0c43925fe4ec1eb Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Wed, 19 Oct 2022 17:13:00 -0700 Subject: [PATCH 215/498] Fix bumpfile merge (#18199) --- .bumpversion.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e1934af9317b..28e83fc2c91a 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -38,9 +38,9 @@ serialize = [bumpversion:file:charts/airbyte-worker/Chart.yaml] -[bumpversion:file:charts/airbyte/README.md] +[bumpversion:file:charts/airbyte/Chart.yaml] -[bumpversion:file:charts/airbyte/values.yaml] +[bumpversion:file:charts/airbyte/README.md] [bumpversion:file:docs/operator-guides/upgrading-airbyte.md] From d75604adb4aef94bbc17bd36711aeb011bfb719b Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Wed, 19 Oct 2022 17:19:42 -0700 Subject: [PATCH 216/498] add a span interceptor to treat expected errors as non-errors (#18192) * wip; tracer interceptor * fix span check * update logs for testing * more debugging * more debugging * more debugging * more debugging * remove debugging; add test * address one pmd error and ignore another --- .../workers/ApplicationInitializer.java | 74 +++++--- .../workers/StorageObjectGetInterceptor.java | 51 +++++ .../StorageObjectGetInterceptorTest.java | 176 ++++++++++++++++++ 3 files changed, 277 insertions(+), 24 deletions(-) create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/StorageObjectGetInterceptor.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java b/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java index bef334c310dc..c6f6f4c9c4f0 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java @@ -133,6 +133,7 @@ public class ApplicationInitializer implements ApplicationEventListener onTraceComplete( + final Collection trace) { + final var filtered = new ArrayList(); + trace.forEach(s -> { + final var tags = s.getTags(); + // if no tags, then keep the span and move on to the next one + if (tags == null || tags.isEmpty()) { + filtered.add(s); + return; + } + + // There are two different errors spans that we want to ignore, both of which are specific to + // "storage.googleapis.com". One returns a http status code of 404 and the other has an error + // message + // that begins with "404 Not Found" + final var is404 = tags.getOrDefault("http.status_code", "").equals(404) || + ((String) tags.getOrDefault("error.msg", "")).startsWith("404 Not Found"); + if (s.isError() && "storage.googleapis.com".equals(tags.getOrDefault("peer.hostname", "")) + && is404) { + // Mark these spans as non-errors as this is expected behavior based on our + // current google storage usage. + s.setError(false); + } + + filtered.add(s); + }); + + return filtered; + } + + @Override + public int priority() { + return 404; + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java new file mode 100644 index 000000000000..fbe4271762c7 --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import datadog.trace.api.interceptor.MutableSpan; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class StorageObjectGetInterceptorTest { + + private static class DummySpan implements MutableSpan { + + @SuppressWarnings("PMD") + private final Map tags = new HashMap<>(); + private boolean error = false; + + @Override + public long getStartTime() { + return 0; + } + + @Override + public long getDurationNano() { + return 0; + } + + @Override + public CharSequence getOperationName() { + return null; + } + + @Override + public MutableSpan setOperationName(final CharSequence serviceName) { + return null; + } + + @Override + public String getServiceName() { + return null; + } + + @Override + public MutableSpan setServiceName(final String serviceName) { + return null; + } + + @Override + public CharSequence getResourceName() { + return null; + } + + @Override + public MutableSpan setResourceName(final CharSequence resourceName) { + return null; + } + + @Override + public Integer getSamplingPriority() { + return null; + } + + @Override + public MutableSpan setSamplingPriority(final int newPriority) { + return null; + } + + @Override + public String getSpanType() { + return null; + } + + @Override + public MutableSpan setSpanType(final CharSequence type) { + return null; + } + + @Override + public Map getTags() { + return tags; + } + + @Override + public MutableSpan setTag(final String tag, final String value) { + tags.put(tag, value); + return this; + } + + @Override + public MutableSpan setTag(final String tag, final boolean value) { + tags.put(tag, value); + return this; + } + + @Override + public MutableSpan setTag(final String tag, final Number value) { + tags.put(tag, value); + return this; + } + + @Override + public MutableSpan setMetric(final CharSequence metric, final int value) { + return null; + } + + @Override + public MutableSpan setMetric(final CharSequence metric, final long value) { + return null; + } + + @Override + public MutableSpan setMetric(final CharSequence metric, final double value) { + return null; + } + + @Override + public boolean isError() { + return error; + } + + @Override + public MutableSpan setError(final boolean value) { + error = value; + return this; + } + + @Override + public MutableSpan getRootSpan() { + return null; + } + + @Override + public MutableSpan getLocalRootSpan() { + return null; + } + + } + + @Test + void testOnTraceComplete() { + final var simple = new DummySpan(); + + final var unmodifiedError = new DummySpan(); + unmodifiedError.setError(true); + unmodifiedError.setTag("unmodified", true); + + final var statusCodeError = new DummySpan(); + statusCodeError.setError(true); + statusCodeError.setTag("peer.hostname", "storage.googleapis.com"); + statusCodeError.setTag("http.status_code", 404); + + final var errorMsgError = new DummySpan(); + errorMsgError.setError(true); + errorMsgError.setTag("peer.hostname", "storage.googleapis.com"); + errorMsgError.setTag("error.msg", "404 Not Found and is still missing!"); + + final var spans = List.of( + simple, unmodifiedError, statusCodeError, errorMsgError); + + final var interceptor = new StorageObjectGetInterceptor(); + final var actual = interceptor.onTraceComplete(spans); + + assertEquals(spans, actual); + assertTrue(unmodifiedError.isError()); + assertFalse(statusCodeError.isError()); + assertFalse(errorMsgError.isError()); + } + +} From a4474ce33835199014b611c5553d29eec3921e65 Mon Sep 17 00:00:00 2001 From: Greg Solovyev Date: Wed, 19 Oct 2022 17:53:54 -0700 Subject: [PATCH 217/498] Add back destination-databricks (#18196) --- airbyte-webapp/src/core/domain/connector/constants.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/airbyte-webapp/src/core/domain/connector/constants.ts b/airbyte-webapp/src/core/domain/connector/constants.ts index e01850fd84d5..aba8be639b46 100644 --- a/airbyte-webapp/src/core/domain/connector/constants.ts +++ b/airbyte-webapp/src/core/domain/connector/constants.ts @@ -19,7 +19,6 @@ export const getExcludedConnectorIds = (workspaceId: string) => isCloudApp() ? [ "707456df-6f4f-4ced-b5c6-03f73bcad1c5", // hide Cassandra Destination https://github.com/airbytehq/airbyte-cloud/issues/2606 - "072d5540-f236-4294-ba7c-ade8fd918496", // hide Databricks Destination https://github.com/airbytehq/airbyte-cloud/issues/2607 "8ccd8909-4e99-4141-b48d-4984b70b2d89", // hide DynamoDB Destination https://github.com/airbytehq/airbyte-cloud/issues/2608 "68f351a7-2745-4bef-ad7f-996b8e51bb8c", // hide ElasticSearch Destination https://github.com/airbytehq/airbyte-cloud/issues/2594 "9f760101-60ae-462f-9ee6-b7a9dafd454d", // hide Kafka Destination https://github.com/airbytehq/airbyte-cloud/issues/2610 From 7cedfa48de4bd0970fcac86a3b13cd7407231b08 Mon Sep 17 00:00:00 2001 From: Parker Mossman Date: Wed, 19 Oct 2022 17:56:12 -0700 Subject: [PATCH 218/498] Load Testing Script (#18020) * script skeleton * add API call to source_definitions to fetch E2E Test Source definition ID * createSource implementation * add destination creation logic implementation * get definition IDs, catalogId, and implement connection creation * add cleanup script and write created ids to a file that can be cleaned up * make cloud header a command-line argument, other cleanup * script comments fix * remove kube references and fix indentation * temp commit - don't push * remove discover catalog function * more cleanups * more cleanups * cleanup help text * exit codes and show how many connections left * add README Co-authored-by: Xiaohan Song --- tools/bin/load_test/.gitignore | 1 + tools/bin/load_test/README.md | 103 ++++++++++ tools/bin/load_test/cleanup_load_test.sh | 152 ++++++++++++++ tools/bin/load_test/connection_spec.json | 47 +++++ tools/bin/load_test/destination_spec.json | 8 + tools/bin/load_test/load_test_airbyte.sh | 240 ++++++++++++++++++++++ tools/bin/load_test/load_test_utils.sh | 45 ++++ tools/bin/load_test/source_spec.json | 17 ++ 8 files changed, 613 insertions(+) create mode 100644 tools/bin/load_test/.gitignore create mode 100644 tools/bin/load_test/README.md create mode 100755 tools/bin/load_test/cleanup_load_test.sh create mode 100644 tools/bin/load_test/connection_spec.json create mode 100644 tools/bin/load_test/destination_spec.json create mode 100755 tools/bin/load_test/load_test_airbyte.sh create mode 100644 tools/bin/load_test/load_test_utils.sh create mode 100644 tools/bin/load_test/source_spec.json diff --git a/tools/bin/load_test/.gitignore b/tools/bin/load_test/.gitignore new file mode 100644 index 000000000000..9a748532e4be --- /dev/null +++ b/tools/bin/load_test/.gitignore @@ -0,0 +1 @@ +cleanup/ diff --git a/tools/bin/load_test/README.md b/tools/bin/load_test/README.md new file mode 100644 index 000000000000..ef45405e8917 --- /dev/null +++ b/tools/bin/load_test/README.md @@ -0,0 +1,103 @@ +# Load Testing Airbyte + +## Overview +To perform a stress test of an Airbyte deployment, the `load_test_airbyte.sh` shell script is useful to quickly and easily create many connections. +This script creates a new E2E Test Source, E2E Test Destination, and a configurable number of connections in the indicated workspace. + +## Instructions +From your top-level `/airbyte` directory, run the following to perform a load test: + +``` +./tools/bin/load_test/load_test_airbyte.sh -W -C +``` + + +By default, the script assumes that the Airbyte instance's server is accessible at `localhost:8001`. This is the default server location when +deploying Airbyte with `docker-compose up`. + +Additionally, the E2E Test Source created by the script will take 10 minutes to complete a sync by default. + +These defaults can be overridden with flags. All available flags are described as follows: + +``` + -h + Display help + + -W + Specify the workspace ID where new connectors and connections should be created. + Required. + + -H + Specify the Airbyte API server hostname that the script should call to create new connectors and connections. + Defaults to 'localhost'. + + -P + Specify the port for the Airbyte server. + Defaults to '8001'. + + -X
    + Specify the X-Endpoint-API-UserInfo header value for API authentication. + For Google Cloud Endpoint authentication only. + + -C + Specify the number of connections that should be created by the script. + Defaults to '1'. + + -T + Specify the time in minutes that each connection should sync for. + Defaults to '10'. +``` + + +### Load Testing on Kubernetes + +To load test a deployment of Airbyte running on Kubernetes, you will need to set up port-forwarding to the `airbyte-server` deployment. +This can be accomplished with the following command: + +``` +kubectl port-forward deployment/airbyte-server -n ab 8001:8001 +``` + +This will make the Airbyte server available at `localhost:8001` + + +### Authentication + +If your deployment of Airbyte happens to use Google Cloud Endpoints for authentication, you can use the `-X` option to pass +an `X-Endpoint-API-UserInfo` header value. + + +## Cleanup +The `load_test_airbyte.sh` script writes created IDs to files in the script's `/cleanup` directory. To delete resources that were created by the load +test script, you can run `cleanup_load_test.sh`, which reads IDs from the `/cleanup` directory and calls the Airbyte API to delete them. + + +### Cleanup Instructions +To run the cleanup script, from the top-level `airbyte` directory, run the following: + +``` +./tools/bin/load_test/cleanup_load_test.sh -W +``` + +All available cleanup script flags are described as follows: + +``` + -h + Display help + + -W + Specify the workspace ID from where connectors and connections should be deleted. + Required. + + -H + Specify the Airbyte API server hostname that the script should call to delete connectors and connections. + Defaults to 'localhost'. + + -P + Specify the port for the Airbyte server. + Defaults to '8001'. + + -X
    + Specify the X-Endpoint-API-UserInfo header value for API authentication. + For Google Cloud Endpoint authentication only. +``` diff --git a/tools/bin/load_test/cleanup_load_test.sh b/tools/bin/load_test/cleanup_load_test.sh new file mode 100755 index 000000000000..03e60bc2558b --- /dev/null +++ b/tools/bin/load_test/cleanup_load_test.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +set -o errexit +set -o nounset + +< + ${GREEN}Specify the workspace ID from where connectors and connections should be deleted. + Required. + + ${CLEAR}-H + ${GREEN}Specify the Airbyte API server hostname that the script should call to delete connectors and connections. + Defaults to 'localhost'. + + ${CLEAR}-P + ${GREEN}Specify the port for the Airbyte server. + Defaults to '8001'. + + ${CLEAR}-X
    + ${GREEN}Specify the X-Endpoint-API-UserInfo header value for API authentication. + For Google Cloud Endpoint authentication only. + """ && exit 1 +} + +hostname=localhost +api_port=8001 +x_endpoint_header="" + +while getopts "hW:H:P:X:kN:" options ; do + case "${options}" in + h) + showhelp + ;; + W) + workspace_id="${OPTARG}" + ;; + H) + hostname="${OPTARG}" + ;; + P) + api_port="${OPTARG}" + ;; + X) + x_endpoint_header="${OPTARG}" + ;; + *) + showhelp + ;; + esac +done + +function setup { + if test -z "$workspace_id"; then + echo "error: must set a workspace id with -W" + exit 1 + fi + + echo "set workspace_id to ${workspace_id}" + echo "set hostname to ${hostname}" + echo "set api_port to ${api_port}" + + setCleanupFilesForWorkspace $workspace_id +} + +function deleteConnections { + while test -s $CONNECTION_CLEANUP_FILE + do + connectionId=$(readFirstLineFromFile $CONNECTION_CLEANUP_FILE) + callApi "connections/delete" "{\"connectionId\":\"$connectionId\"}" + echo "deleted connection with ID $connectionId" + + # deletion succeeded, so remove the ID from the cleanup file + removeFirstLineFromFile $CONNECTION_CLEANUP_FILE + done + + if ! test -s $CONNECTION_CLEANUP_FILE + then + rm $CONNECTION_CLEANUP_FILE + echo "removed cleanup file $CONNECTION_CLEANUP_FILE" + fi +} + +function deleteSources { + while test -s $SOURCE_CLEANUP_FILE + do + sourceId=$(readFirstLineFromFile $SOURCE_CLEANUP_FILE) + callApi "sources/delete" "{\"sourceId\":\"$sourceId\"}" + echo "deleted source with ID $sourceId" + + # deletion succeeded, so remove the ID from the cleanup file + removeFirstLineFromFile $SOURCE_CLEANUP_FILE + done + + if ! test -s $SOURCE_CLEANUP_FILE + then + rm $SOURCE_CLEANUP_FILE + echo "removed cleanup file $SOURCE_CLEANUP_FILE" + fi +} + +function deleteDestinations { + while test -s $DESTINATION_CLEANUP_FILE + do + destinationId=$(readFirstLineFromFile $DESTINATION_CLEANUP_FILE) + callApi "destinations/delete" "{\"destinationId\":\"$destinationId\"}" + echo "deleted destination with ID $destinationId" + + # deletion succeeded, so remove the ID from the cleanup file + removeFirstLineFromFile $DESTINATION_CLEANUP_FILE + done + + if test -z $DESTINATION_CLEANUP_FILE + then + rm $DESTINATION_CLEANUP_FILE + echo "removed cleanup file $DESTINATION_CLEANUP_FILE" + fi +} + +############ +## MAIN ## +############ + +if [[ $# -eq 0 ]] ; then + showhelp + exit 0 +fi + +setup + +deleteConnections + +deleteSources + +deleteDestinations + +echo "Finished!" diff --git a/tools/bin/load_test/connection_spec.json b/tools/bin/load_test/connection_spec.json new file mode 100644 index 000000000000..b4678cf58a98 --- /dev/null +++ b/tools/bin/load_test/connection_spec.json @@ -0,0 +1,47 @@ +{ + "sourceId": "replace_source_id", + "destinationId": "replace_destination_id", + "syncCatalog": { + "streams": [ + { + "config": { + "syncMode": "full_refresh", + "cursorField": [], + "destinationSyncMode": "overwrite", + "primaryKey": [], + "aliasName": "data_stream", + "selected": true + }, + "stream": { + "name": "data_stream", + "jsonSchema": { + "type": "object", + "properties": { + "column1": { + "type": "string" + } + } + }, + "supportedSyncModes": [ + "full_refresh" + ], + "defaultCursorField": [], + "sourceDefinedPrimaryKey": [] + } + } + ] + }, + "prefix": "", + "namespaceDefinition": "source", + "namespaceFormat": "${SOURCE_NAMESPACE}", + "scheduleType": "basic", + "scheduleData": { + "basicSchedule": { + "units": 24, + "timeUnit": "hours" + } + }, + "name": "replace_connection_name", + "operations": [], + "status": "active" +} diff --git a/tools/bin/load_test/destination_spec.json b/tools/bin/load_test/destination_spec.json new file mode 100644 index 000000000000..dc645d969d55 --- /dev/null +++ b/tools/bin/load_test/destination_spec.json @@ -0,0 +1,8 @@ +{ + "name": "End-to-End Testing (/dev/null)", + "destinationDefinitionId": "replace_destination_definition_id", + "workspaceId": "replace_workspace_id", + "connectionConfiguration": { + "type": "SILENT" + } +} diff --git a/tools/bin/load_test/load_test_airbyte.sh b/tools/bin/load_test/load_test_airbyte.sh new file mode 100755 index 000000000000..dc7f893b651e --- /dev/null +++ b/tools/bin/load_test/load_test_airbyte.sh @@ -0,0 +1,240 @@ +#!/usr/bin/env bash +set -o errexit +set -o nounset + +< + ${GREEN}Specify the workspace ID where new connectors and connections should be created. + Required. + + ${CLEAR}-H + ${GREEN}Specify the Airbyte API server hostname that the script should call to create new connectors and connections. + Defaults to 'localhost'. + + ${CLEAR}-P + ${GREEN}Specify the port for the Airbyte server. + Defaults to '8001'. + + ${CLEAR}-X
    + ${GREEN}Specify the X-Endpoint-API-UserInfo header value for API authentication. + For Google Cloud Endpoint authentication only. + + ${CLEAR}-C + ${GREEN}Specify the number of connections that should be created by the script. + Defaults to '1'. + + ${CLEAR}-T + ${GREEN}Specify the time in minutes that each connection should sync for. + Defaults to '10'. + """ +} + +hostname=localhost +api_port=8001 +x_endpoint_header= +num_connections=1 +sync_minutes=10 + +while getopts "hW:H:P:X:C:T:kN:-:" options ; do + case "${options}" in + -) + case "${OPTARG}" in + debug) + PS4="$GREEN"'${BASH_SOURCE}:${LINENO}:$CLEAR ' + set -o xtrace #xtrace calls the PS4 string and show all lines as executed + ;; + *) + showhelp + exit 0 + ;; + esac;; + h) + showhelp + ;; + W) + workspace_id="${OPTARG}" + ;; + H) + hostname="${OPTARG}" + ;; + P) + api_port="${OPTARG}" + ;; + X) + x_endpoint_header="${OPTARG}" + ;; + C) + num_connections="${OPTARG}" + ;; + T) + sync_minutes="${OPTARG}" + ;; + *) + showhelp + exit 1 + ;; + esac +done + +function setup { + set -e + if test -z "$workspace_id"; then + echo "error: must set a workspace id with -W" + exit 1 + fi + + echo "set workspace_id to ${workspace_id}" + echo "set hostname to ${hostname}" + echo "set api_port to ${api_port}" + echo "set x_endpoint_header to ${x_endpoint_header}" + echo "set num_connections to ${num_connections}" + echo "set sync_minutes to ${sync_minutes}" + + setCleanupFilesForWorkspace $workspace_id + + mkdir -p cleanup + + touch $CONNECTION_CLEANUP_FILE + touch $SOURCE_CLEANUP_FILE + touch $DESTINATION_CLEANUP_FILE +} + +function getE2ETestSourceDefinitionId { + # call source_definitions/list and search response for the E2E Test dockerRepository to get the ID. + # local uses `source-e2e-test`, while cloud uses `source-e2e-test-cloud` + sourceDefinitionId=$( + callApi "source_definitions/list" | + jq -r '.sourceDefinitions[] | + select( + (.dockerRepository == "airbyte/source-e2e-test") or + (.dockerRepository == "airbyte/source-e2e-test-cloud") + ) | + .sourceDefinitionId' + ) + export sourceDefinitionId +} + +function getE2ETestDestinationDefinition { + # call destination_definitions/list and search response for the E2E Test dockerRepository to get the ID. + # local uses `destination-dev-null`, while cloud uses `destination-e2e-test-cloud` + destinationDefinitionId=$( + callApi "destination_definitions/list" | + jq -r '.destinationDefinitions[] | + select( + (.dockerRepository == "airbyte/destination-e2e-test") or + (.dockerRepository == "airbyte/destination-dev-null") + ) | + .destinationDefinitionId' + ) + export destinationDefinitionId +} + +function createSource { + body=$( + sed " + s/replace_source_read_secs/$(( 60*sync_minutes ))/g ; + s/replace_source_definition_id/$sourceDefinitionId/g ; + s/replace_workspace_id/$workspace_id/g" source_spec.json | + tr -d '\n' | + tr -d ' ' + ) + + sourceId=$( + callApi "sources/create" $body | + jq -r '.sourceId' + ) + export sourceId + echo $sourceId >> $SOURCE_CLEANUP_FILE +} + +function createDestination { + body=$( + sed " + s/replace_destination_definition_id/$destinationDefinitionId/g ; + s/replace_workspace_id/$workspace_id/g" destination_spec.json | + tr -d '\n' | + tr -d ' ' + ) + destinationId=$( + callApi "destinations/create" $body | + jq -r '.destinationId' + ) + export destinationId + echo $destinationId >> $DESTINATION_CLEANUP_FILE +} + +function createMultipleConnections { + for i in $(seq 1 $num_connections) + do + echo "Creating connection number $i (out of $num_connections)..." + createConnection $i + done + echo "Finished creating $num_connections connections." +} + +# Call the API to create a connection. Replace strings in connection_spec.json with real IDs. +# $1 arg is the connection count, which is used in the name of the created connection +# Connection spec might change and this function could break in the future. If that happens, we need +# to update the connection spec. +function createConnection { + body=$( + sed " + s/replace_source_id/$sourceId/g ; + s/replace_destination_id/$destinationId/g ; + s/replace_connection_name/load_test_connection_$1/g" connection_spec.json | + tr -d '\n' | + tr -d ' ' + ) + + connectionId=$( + callApi "web_backend/connections/create" $body | + jq -r '.connectionId' + ) + echo $connectionId >> $CONNECTION_CLEANUP_FILE +} + +############ +## MAIN ## +############ + +if [[ $# -eq 0 ]] ; then + showhelp + exit 0 +fi + +setup + +getE2ETestSourceDefinitionId +echo "Retrieved E2E Test Source Definition ID: ${sourceDefinitionId}" + +getE2ETestDestinationDefinition +echo "Retrieved E2E Test Destination Definition ID: ${destinationDefinitionId}" + +createSource +echo "Created Source with ID: ${sourceId}" + +createDestination +echo "Created Destination with ID: ${destinationId}" + +createMultipleConnections + +echo "Finished!" diff --git a/tools/bin/load_test/load_test_utils.sh b/tools/bin/load_test/load_test_utils.sh new file mode 100644 index 000000000000..1d70b506590c --- /dev/null +++ b/tools/bin/load_test/load_test_utils.sh @@ -0,0 +1,45 @@ +< Date: Wed, 19 Oct 2022 18:31:48 -0700 Subject: [PATCH 219/498] Support testing airbyte-proxy with $VERSION (#18203) --- airbyte-proxy/test.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/airbyte-proxy/test.sh b/airbyte-proxy/test.sh index 339358d5beeb..6d26885912e7 100755 --- a/airbyte-proxy/test.sh +++ b/airbyte-proxy/test.sh @@ -6,17 +6,19 @@ BASIC_AUTH_USERNAME=airbyte BASIC_AUTH_PASSWORD=password BASIC_AUTH_UPDATED_PASSWORD=pa55w0rd TEST_HOST=localhost +VERSION="${VERSION:-dev}" # defaults to "dev", otherwise it is set by environment's $VERSION +echo "testing with proxy container airbyte/proxy:$VERSION" function start_container () { - CMD="docker run -d -p $PORT:8000 --env BASIC_AUTH_USERNAME=$1 --env BASIC_AUTH_PASSWORD=$2 --env PROXY_PASS_WEB=http://localhost --env PROXY_PASS_API=http://localhost --name $NAME airbyte/proxy:dev" + CMD="docker run -d -p $PORT:8000 --env BASIC_AUTH_USERNAME=$1 --env BASIC_AUTH_PASSWORD=$2 --env PROXY_PASS_WEB=http://localhost --env PROXY_PASS_API=http://localhost --name $NAME airbyte/proxy:$VERSION" echo $CMD eval $CMD wait_for_docker; } function start_container_with_proxy () { - CMD="docker run -d -p $PORT:8000 --env PROXY_PASS_WEB=$1 --env PROXY_PASS_API=$1 --name $NAME airbyte/proxy:dev" + CMD="docker run -d -p $PORT:8000 --env PROXY_PASS_WEB=$1 --env PROXY_PASS_API=$1 --name $NAME airbyte/proxy:$VERSION" echo $CMD eval $CMD wait_for_docker; From a71b7ca025d97a3f44abea56e0c33e80fcbdb453 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Thu, 20 Oct 2022 07:58:26 +0300 Subject: [PATCH 220/498] Source file secure 0.2.26 (#18180) Signed-off-by: Sergey Chvalyuk --- airbyte-integrations/connectors/source-file-secure/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-integrations/connectors/source-file-secure/Dockerfile b/airbyte-integrations/connectors/source-file-secure/Dockerfile index 793605451fdb..e38177292b4c 100644 --- a/airbyte-integrations/connectors/source-file-secure/Dockerfile +++ b/airbyte-integrations/connectors/source-file-secure/Dockerfile @@ -1,4 +1,4 @@ -FROM airbyte/source-file:0.2.25 +FROM airbyte/source-file:0.2.26 WORKDIR /airbyte/integration_code COPY source_file_secure ./source_file_secure @@ -9,5 +9,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.25 +LABEL io.airbyte.version=0.2.26 LABEL io.airbyte.name=airbyte/source-file-secure From 5466a68c74735dd05c8ac8dab0591c6801a23255 Mon Sep 17 00:00:00 2001 From: letiescanciano <45267095+letiescanciano@users.noreply.github.com> Date: Thu, 20 Oct 2022 08:56:03 +0200 Subject: [PATCH 221/498] =?UTF-8?q?=F0=9F=AA=9F=F0=9F=A7=AA=20[Experiment]?= =?UTF-8?q?=20Incentivize=20speedy=20first=20connection=20(aka=20activatio?= =?UTF-8?q?n)=20(#17593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🪟🧪 [Experiment] Incentivize speedy first connection (aka activation) --- .../hooks/services/Experiment/experiments.ts | 1 + .../src/packages/cloud/cloudRoutes.tsx | 5 +- .../CountDownTimer/CountDownTimer.module.scss | 14 ++ .../CountDownTimer/CountDownTimer.tsx | 15 ++ .../SpeedyConnection/CountDownTimer/index.ts | 1 + .../CountDownTimer/useCountdown.ts | 26 +++ .../SpeedyConnectionBanner.module.scss | 39 ++++ .../SpeedyConnectionBanner.tsx | 51 +++++ .../SpeedyConnectionBanner/credits.svg | 196 ++++++++++++++++++ .../SpeedyConnectionBanner/index.ts | 1 + .../hooks/useExperimentSpeedyConnection.ts | 15 ++ .../cloud/services/auth/AuthService.tsx | 10 + .../layout/MainView/MainView.module.scss | 8 + .../cloud/views/layout/MainView/MainView.tsx | 16 +- .../pages/OnboardingPage/OnboardingPage.tsx | 8 +- .../OnboardingPage/components/WelcomeStep.tsx | 1 + .../src/pages/OnboardingPage/types.ts | 5 + .../pages/OnboardingPage/useStepsConfig.tsx | 10 +- 18 files changed, 410 insertions(+), 12 deletions(-) create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/CountDownTimer.module.scss create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/CountDownTimer.tsx create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/index.ts create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/useCountdown.ts create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.module.scss create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.tsx create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/credits.svg create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/index.ts create mode 100644 airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection.ts diff --git a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts index a06231626681..d7f4e8274f44 100644 --- a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts +++ b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts @@ -18,4 +18,5 @@ export interface Experiments { "authPage.oauth.github": boolean; "authPage.oauth.google.signUpPage": boolean; "authPage.oauth.github.signUpPage": boolean; + "onboarding.speedyConnection": boolean; } diff --git a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx index d0daaefe5484..0d68f80e01ee 100644 --- a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx +++ b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx @@ -11,6 +11,7 @@ import { FeatureItem, FeatureSet, useFeatureService } from "hooks/services/Featu import { useApiHealthPoll } from "hooks/services/Health"; import { OnboardingServiceProvider } from "hooks/services/Onboarding"; import { useQuery } from "hooks/useQuery"; +import { useExperimentSpeedyConnection } from "packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection"; import { useAuthService } from "packages/cloud/services/auth/AuthService"; import { useIntercom } from "packages/cloud/services/thirdParty/intercom/useIntercom"; import { Auth } from "packages/cloud/views/auth"; @@ -86,6 +87,8 @@ const MainRoutes: React.FC = () => { const mainNavigate = workspace.displaySetupWizard && !hideOnboardingExperiment ? RoutePaths.Onboarding : RoutePaths.Connections; + // exp-speedy-connection + const { isExperimentVariant } = useExperimentSpeedyConnection(); return ( @@ -95,7 +98,7 @@ const MainRoutes: React.FC = () => { } /> } /> - {workspace.displaySetupWizard && !hideOnboardingExperiment && ( + {(workspace.displaySetupWizard || isExperimentVariant) && !hideOnboardingExperiment && ( = ({ expiredOfferDate }) => { + const [hours, minutes, seconds] = useCountdown(expiredOfferDate); + + return ( +
    + {hours.toString().padStart(2, "0")}h + {minutes.toString().padStart(2, "0")}m + {seconds.toString().padStart(2, "0")}s +
    + ); +}; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/index.ts b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/index.ts new file mode 100644 index 000000000000..002ded4737c3 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/index.ts @@ -0,0 +1 @@ +export * from "./CountDownTimer"; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/useCountdown.ts b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/useCountdown.ts new file mode 100644 index 000000000000..9c695a456c30 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/CountDownTimer/useCountdown.ts @@ -0,0 +1,26 @@ +import { useEffect, useState } from "react"; + +export const useCountdown = (targetDate: string) => { + const countDownDate = new Date(targetDate).getTime(); + + const [countDown, setCountDown] = useState(countDownDate - new Date().getTime()); + + useEffect(() => { + const interval = setInterval(() => { + setCountDown(countDownDate - new Date().getTime()); + }, 1000); + + return () => clearInterval(interval); + }, [countDownDate]); + + return getReturnValues(countDown); +}; + +const getReturnValues = (countDown: number): number[] => { + // calculate time left + const hours = Math.floor((countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((countDown % (1000 * 60)) / 1000); + + return [hours, minutes, seconds]; +}; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.module.scss b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.module.scss new file mode 100644 index 000000000000..2519746129c0 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.module.scss @@ -0,0 +1,39 @@ +@use "../../../../../../../src/scss/variables"; +@use "../../../../../../../src/scss/colors"; + +.container { + height: 43px; + width: 100%; + position: fixed; + top: 0; + z-index: 3; + font-size: 12px; + line-height: 15px; + color: colors.$black; + padding: 8px; + display: flex; + align-items: center; + background-color: colors.$beige-100; + + @media (min-width: 1280px) { + height: 50px; + } +} + +.innerContainer { + display: flex; + width: 80%; + flex-direction: row; + gap: variables.$spacing-md; + justify-content: center; + align-items: center; + margin: auto; +} + +.linkCta { + color: colors.$dark-blue; +} + +.textDecorationNone { + text-decoration: none; +} diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.tsx b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.tsx new file mode 100644 index 000000000000..01eca3ab1cb2 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/SpeedyConnectionBanner.tsx @@ -0,0 +1,51 @@ +import classnames from "classnames"; +import classNames from "classnames"; +import { FormattedMessage } from "react-intl"; +import { Link, useLocation } from "react-router-dom"; + +import { Text } from "components/ui/Text"; + +import { useExperiment } from "hooks/services/Experiment"; +import { CountDownTimer } from "packages/cloud/components/experiments/SpeedyConnection/CountDownTimer"; +import { StepType } from "pages/OnboardingPage/types"; +import { RoutePaths } from "pages/routePaths"; + +import { useExperimentSpeedyConnection } from "../hooks/useExperimentSpeedyConnection"; +import credits from "./credits.svg"; +import styles from "./SpeedyConnectionBanner.module.scss"; + +export const SpeedyConnectionBanner = () => { + const { expiredOfferDate } = useExperimentSpeedyConnection(); + const location = useLocation(); + const hideOnboardingExperiment = useExperiment("onboarding.hideOnboarding", false); + + return ( +
    +
    + + + ( + + {link} + + ), + timer: () => , + }} + /> + +
    +
    + ); +}; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/credits.svg b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/credits.svg new file mode 100644 index 000000000000..ec9380be9b1b --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/credits.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/index.ts b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/index.ts new file mode 100644 index 000000000000..b3709edb7b26 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner/index.ts @@ -0,0 +1 @@ +export * from "./SpeedyConnectionBanner"; diff --git a/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection.ts b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection.ts new file mode 100644 index 000000000000..c6727215bbd8 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection.ts @@ -0,0 +1,15 @@ +import { useExperiment } from "hooks/services/Experiment"; +import { useCurrentWorkspaceState } from "services/workspaces/WorkspacesService"; + +export const useExperimentSpeedyConnection = () => { + const { hasConnections } = useCurrentWorkspaceState(); + const isVariantEnabled = useExperiment("onboarding.speedyConnection", false); + + const timestamp = localStorage.getItem("exp-speedy-connection-timestamp"); + const expiredOfferDate = timestamp ? String(timestamp) : String(0); + + const now = new Date(); + const isExperimentVariant = + !hasConnections && expiredOfferDate && new Date(expiredOfferDate) >= now && isVariantEnabled; + return { isExperimentVariant, expiredOfferDate }; +}; diff --git a/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx b/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx index 539ac9f276a4..2cfac872ef42 100644 --- a/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx +++ b/airbyte-webapp/src/packages/cloud/services/auth/AuthService.tsx @@ -127,6 +127,11 @@ export const AuthenticationProvider: React.FC> // also happen for email/password users if they closed their browser or got some network // errors in between creating the firebase user and the database user originally. const user = await createAirbyteUser(currentUser); + // exp-speedy-connection + localStorage.setItem( + "exp-speedy-connection-timestamp", + String(new Date(new Date().getTime() + 24 * 60 * 60 * 1000)) + ); await onAfterAuth(currentUser, user); } else { throw e; @@ -266,6 +271,11 @@ export const AuthenticationProvider: React.FC> await authService.sendEmailVerifiedLink(); if (auth.currentUser) { + // exp-speedy-connection + localStorage.setItem( + "exp-speedy-connection-timestamp", + String(new Date(new Date().getTime() + 24 * 60 * 60 * 1000)) + ); await onAfterAuth(auth.currentUser); } }, diff --git a/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.module.scss b/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.module.scss index f7b8ce4a4c4f..0d803d6f2d53 100644 --- a/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.module.scss +++ b/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.module.scss @@ -25,5 +25,13 @@ $banner-height: 30px; height: calc(100% - #{$banner-height}); } } + + &.speedyConnectionBanner { + margin-top: 50px; + + .dataBlock { + height: calc(100% - 50px); + } + } } } diff --git a/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx b/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx index 460fe334b61b..3b715057d52a 100644 --- a/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx @@ -7,6 +7,8 @@ import { LoadingPage } from "components"; import { AlertBanner } from "components/ui/Banner/AlertBanner"; import { CloudRoutes } from "packages/cloud/cloudRoutes"; +import { useExperimentSpeedyConnection } from "packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection"; +import { SpeedyConnectionBanner } from "packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner"; import { CreditStatus } from "packages/cloud/lib/domain/cloudWorkspaces/types"; import { useGetCloudWorkspace } from "packages/cloud/services/workspaces/CloudWorkspacesService"; import SideBar from "packages/cloud/views/layout/SideBar"; @@ -21,7 +23,6 @@ const MainView: React.FC> = (props) => { const { formatMessage } = useIntl(); const workspace = useCurrentWorkspace(); const cloudWorkspace = useGetCloudWorkspace(workspace.workspaceId); - const showCreditsBanner = cloudWorkspace.creditStatus && [ @@ -32,6 +33,10 @@ const MainView: React.FC> = (props) => { !cloudWorkspace.trialExpiryTimestamp; const alertToShow = showCreditsBanner ? "credits" : cloudWorkspace.trialExpiryTimestamp ? "trial" : undefined; + // exp-speedy-connection + const { isExperimentVariant } = useExperimentSpeedyConnection(); + const isTrial = Boolean(cloudWorkspace.trialExpiryTimestamp); + const showExperimentBanner = isExperimentVariant && isTrial; const alertMessage = useMemo(() => { if (alertToShow === "credits") { @@ -62,8 +67,13 @@ const MainView: React.FC> = (props) => {
    }> -
    - {alertToShow && } +
    + {showExperimentBanner ? : alertToShow && }
    }> }>{props.children ?? } diff --git a/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx b/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx index 33010f626b0f..b45a2faab7c8 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx @@ -1,7 +1,6 @@ import React, { Suspense, useState } from "react"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { useEffectOnce } from "react-use"; import styled from "styled-components"; import ApiErrorBoundary from "components/ApiErrorBoundary"; @@ -9,7 +8,7 @@ import HeadTitle from "components/HeadTitle"; import LoadingPage from "components/LoadingPage"; import { Button } from "components/ui/Button"; -import { useAnalyticsService, useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; +import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; import useWorkspace from "hooks/services/useWorkspace"; import { useCurrentWorkspaceState } from "services/workspaces/WorkspacesService"; import { ConnectorDocumentationWrapper } from "views/Connector/ConnectorDocumentationLayout"; @@ -60,15 +59,10 @@ const TITLE_BY_STEP: Partial> = { }; const OnboardingPage: React.FC = () => { - const analyticsService = useAnalyticsService(); useTrackPage(PageTrackingCodes.ONBOARDING); const navigate = useNavigate(); - useEffectOnce(() => { - analyticsService.page("Onboarding Page"); - }); - const { finishOnboarding } = useWorkspace(); const { hasConnections, hasDestinations, hasSources } = useCurrentWorkspaceState(); diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx index ea1c86b820b7..0a5c338124ce 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/WelcomeStep.tsx @@ -66,6 +66,7 @@ const WelcomeStep: React.FC = ({ userName, onNextStep }) => { link={links.demoLink} /> + diff --git a/airbyte-webapp/src/pages/OnboardingPage/types.ts b/airbyte-webapp/src/pages/OnboardingPage/types.ts index 37974abbe879..f6acb1aaa800 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/types.ts +++ b/airbyte-webapp/src/pages/OnboardingPage/types.ts @@ -5,3 +5,8 @@ export enum StepType { SET_UP_CONNECTION = "set-up-connection", FINAL = "final", } + +// exp-speedy-connection +export interface ILocationState extends Omit { + state: Type; +} diff --git a/airbyte-webapp/src/pages/OnboardingPage/useStepsConfig.tsx b/airbyte-webapp/src/pages/OnboardingPage/useStepsConfig.tsx index c95d5cb84b7b..71cb7b26098d 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/useStepsConfig.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/useStepsConfig.tsx @@ -1,7 +1,8 @@ import { useMemo, useState, useCallback } from "react"; import { FormattedMessage } from "react-intl"; +import { useLocation } from "react-router-dom"; -import { StepType } from "./types"; +import { ILocationState, StepType } from "./types"; const useStepsConfig = ( hasSources: boolean, @@ -13,7 +14,14 @@ const useStepsConfig = ( setCurrentStep: (step: StepType) => void; steps: Array<{ name: JSX.Element; id: StepType }>; } => { + // exp-speedy-connection + const location = useLocation() as unknown as ILocationState<{ step: StepType }>; + const getInitialStep = () => { + // exp-speedy-connection + if (location.state?.step) { + return location.state.step; + } if (hasSources) { if (hasDestinations) { if (hasConnections) { From e62c3c3cb70d607aca5ef48db9a8ec0312865644 Mon Sep 17 00:00:00 2001 From: andriikorotkov <88329385+andriikorotkov@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:26:15 +0300 Subject: [PATCH 222/498] Skip CHECKs steps if previous sync success (#17999) * Disabled CHECKs for source and destination, when previous sync or previous attempt is failure * added tests and added minor changes to the isLastJobOrAttemptFailure method * updated remarks * updated code style * updated code style * fixed remarks * fixed remarks --- .../ConnectionManagerWorkflowImpl.java | 17 ++++- .../JobCreationAndStatusUpdateActivity.java | 14 ++++ ...obCreationAndStatusUpdateActivityImpl.java | 56 ++++++++++++++++ .../ConnectionManagerWorkflowTest.java | 66 ++++++++++++++++++- ...obCreationAndStatusUpdateActivityTest.java | 65 ++++++++++++++++++ 5 files changed, 213 insertions(+), 5 deletions(-) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java index 17ff160c5ee6..a6e662a205fb 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java @@ -60,6 +60,7 @@ import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.EnsureCleanJobStateInput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.JobCancelledInput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.JobCancelledInputWithAttemptNumber; +import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.JobCheckFailureInput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.JobCreationInput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.JobCreationOutput; import io.airbyte.workers.temporal.scheduling.activities.JobCreationAndStatusUpdateActivity.JobFailureInput; @@ -102,6 +103,9 @@ public class ConnectionManagerWorkflowImpl implements ConnectionManagerWorkflow private static final String RENAME_ATTEMPT_ID_TO_NUMBER_TAG = "rename_attempt_id_to_number"; private static final int RENAME_ATTEMPT_ID_TO_NUMBER_CURRENT_VERSION = 1; + private static final String CHECK_PREVIOUS_JOB_OR_ATTEMPT_TAG = "check_previous_job_or_attempt"; + private static final int CHECK_PREVIOUS_JOB_OR_ATTEMPT_TAG_CURRENT_VERSION = 1; + private static final String ENSURE_CLEAN_JOB_STATE = "ensure_clean_job_state"; private static final int ENSURE_CLEAN_JOB_STATE_CURRENT_VERSION = 1; @@ -407,7 +411,16 @@ private SyncCheckConnectionFailure checkConnections(final GenerateInputActivity. final StandardCheckConnectionInput sourceConfiguration = new StandardCheckConnectionInput().withConnectionConfiguration(sourceConfig); final CheckConnectionInput checkSourceInput = new CheckConnectionInput(jobRunConfig, sourceLauncherConfig, sourceConfiguration); - if (isResetJob(sourceLauncherConfig) || checkFailure.isFailed()) { + final int checkJobOutputVersion = + Workflow.getVersion(CHECK_PREVIOUS_JOB_OR_ATTEMPT_TAG, Workflow.DEFAULT_VERSION, CHECK_PREVIOUS_JOB_OR_ATTEMPT_TAG_CURRENT_VERSION); + boolean isLastJobOrAttemptFailure = true; + + if (checkJobOutputVersion >= CHECK_PREVIOUS_JOB_OR_ATTEMPT_TAG_CURRENT_VERSION) { + final JobCheckFailureInput jobStateInput = + new JobCheckFailureInput(Long.parseLong(jobRunConfig.getJobId()), jobRunConfig.getAttemptId().intValue(), connectionId); + isLastJobOrAttemptFailure = runMandatoryActivityWithOutput(jobCreationAndStatusUpdateActivity::isLastJobOrAttemptFailure, jobStateInput); + } + if (isResetJob(sourceLauncherConfig) || checkFailure.isFailed() || !isLastJobOrAttemptFailure) { // reset jobs don't need to connect to any external source, so check connection is unnecessary log.info("SOURCE CHECK: Skipped"); } else { @@ -425,7 +438,7 @@ private SyncCheckConnectionFailure checkConnections(final GenerateInputActivity. final StandardCheckConnectionInput destinationConfiguration = new StandardCheckConnectionInput().withConnectionConfiguration(destinationConfig); final CheckConnectionInput checkDestinationInput = new CheckConnectionInput(jobRunConfig, destinationLauncherConfig, destinationConfiguration); - if (checkFailure.isFailed()) { + if (checkFailure.isFailed() || !isLastJobOrAttemptFailure) { log.info("DESTINATION CHECK: Skipped"); } else { log.info("DESTINATION CHECK: Starting"); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java index 6d394b2dc5e5..b992d38065ba 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivity.java @@ -242,4 +242,18 @@ class EnsureCleanJobStateInput { @ActivityMethod void ensureCleanJobState(EnsureCleanJobStateInput input); + @Data + @NoArgsConstructor + @AllArgsConstructor + class JobCheckFailureInput { + + private long jobId; + private int attemptId; + private UUID connectionId; + + } + + @ActivityMethod + boolean isLastJobOrAttemptFailure(JobCheckFailureInput input); + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java index 76fedf7870a4..0dd4cffe1c27 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java @@ -4,6 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.config.JobConfig.ConfigType.SYNC; +import static io.airbyte.persistence.job.models.AttemptStatus.FAILED; import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; @@ -18,6 +20,7 @@ import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.DestinationConnection; import io.airbyte.config.FailureReason; +import io.airbyte.config.JobConfig; import io.airbyte.config.JobOutput; import io.airbyte.config.JobSyncConfig; import io.airbyte.config.NormalizationSummary; @@ -56,9 +59,13 @@ import jakarta.inject.Singleton; import java.io.IOException; import java.nio.file.Path; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.OptionalLong; +import java.util.Set; import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -372,6 +379,55 @@ public void ensureCleanJobState(final EnsureCleanJobStateInput input) { failNonTerminalJobs(input.getConnectionId()); } + @Override + public boolean isLastJobOrAttemptFailure(JobCheckFailureInput input) { + final int limit = 2; + boolean lastAttemptCheck = false; + boolean lastJobCheck = false; + + Set configTypes = new HashSet<>(); + configTypes.add(SYNC); + + try { + List jobList = jobPersistence.listJobsIncludingId(configTypes, input.getConnectionId().toString(), input.getJobId(), limit); + Optional optionalActiveJob = jobList.stream().filter(job -> job.getId() == input.getJobId()).findFirst(); + if (optionalActiveJob.isPresent()) { + lastAttemptCheck = checkActiveJobPreviousAttempt(optionalActiveJob.get(), input.getAttemptId()); + } + + OptionalLong previousJobId = getPreviousJobId(input.getJobId(), jobList.stream().map(Job::getId).toList()); + if (previousJobId.isPresent()) { + Optional optionalPreviousJob = jobList.stream().filter(job -> job.getId() == previousJobId.getAsLong()).findFirst(); + if (optionalPreviousJob.isPresent()) { + lastJobCheck = optionalPreviousJob.get().getStatus().equals(io.airbyte.persistence.job.models.JobStatus.FAILED); + } + } + + return lastJobCheck || lastAttemptCheck; + } catch (final IOException e) { + throw new RetryableException(e); + } + } + + private OptionalLong getPreviousJobId(Long activeJobId, List jobIdsList) { + return jobIdsList.stream() + .filter(jobId -> !Objects.equals(jobId, activeJobId)) + .mapToLong(jobId -> jobId).max(); + } + + private boolean checkActiveJobPreviousAttempt(Job activeJob, int attemptId) { + final int minAttemptSize = 1; + boolean result = false; + + if (activeJob.getAttempts().size() > minAttemptSize) { + Optional optionalAttempt = activeJob.getAttempts().stream() + .filter(attempt -> attempt.getId() == (attemptId - 1)).findFirst(); + result = optionalAttempt.isPresent() && optionalAttempt.get().getStatus().equals(FAILED); + } + + return result; + } + private void failNonTerminalJobs(final UUID connectionId) { try { final List jobs = jobPersistence.listJobsForConnectionWithStatuses(connectionId, Job.REPLICATION_TYPES, diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java index a75e9275459d..21faa89f00eb 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowTest.java @@ -214,17 +214,33 @@ void setUp() { temporalProxyHelper = new TemporalProxyHelper(List.of(activityOptionsBeanRegistration)); } + private void returnTrueForLastJobOrAttemptFailure() { + when(mJobCreationAndStatusUpdateActivity.isLastJobOrAttemptFailure(Mockito.any())) + .thenReturn(true); + + JobRunConfig jobRunConfig = new JobRunConfig(); + jobRunConfig.setJobId(Long.toString(JOB_ID)); + jobRunConfig.setAttemptId((long) ATTEMPT_ID); + when(mGenerateInputActivityImpl.getSyncWorkflowInputWithAttemptNumber(Mockito.any(SyncInputWithAttemptNumber.class))) + .thenReturn( + new GeneratedJobInput( + jobRunConfig, + new IntegrationLauncherConfig().withDockerImage("some_source"), + new IntegrationLauncherConfig(), + new StandardSyncInput())); + } + @AfterEach void tearDown() { testEnv.shutdown(); TestStateListener.reset(); } - private void mockResetJobInput() { + private void mockResetJobInput(JobRunConfig jobRunConfig) { when(mGenerateInputActivityImpl.getSyncWorkflowInputWithAttemptNumber(Mockito.any(SyncInputWithAttemptNumber.class))) .thenReturn( new GeneratedJobInput( - new JobRunConfig(), + jobRunConfig, new IntegrationLauncherConfig().withDockerImage(WorkerConstants.RESET_JOB_SOURCE_DOCKER_IMAGE_STUB), new IntegrationLauncherConfig(), new StandardSyncInput())); @@ -244,6 +260,7 @@ void setup() { unit = TimeUnit.SECONDS) @DisplayName("Test that a successful workflow retries and waits") void runSuccess() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mConfigFetchActivity.getTimeToWait(Mockito.any())) .thenReturn(new ScheduleRetrieverOutput(SCHEDULE_WAIT)); @@ -287,6 +304,7 @@ void runSuccess() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test workflow does not wait to run after a failure") void retryAfterFail() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mConfigFetchActivity.getTimeToWait(Mockito.any())) .thenReturn(new ScheduleRetrieverOutput(SCHEDULE_WAIT)); @@ -329,6 +347,7 @@ void retryAfterFail() throws InterruptedException { @DisplayName("Test workflow which receives a manual run signal stops waiting") void manualRun() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -377,6 +396,7 @@ void manualRun() throws InterruptedException { @DisplayName("Test workflow which receives an update signal stops waiting, doesn't run, and doesn't update the job status") void updatedSignalReceived() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -425,6 +445,7 @@ void updatedSignalReceived() throws InterruptedException { @DisplayName("Test that cancelling a non-running workflow doesn't do anything") void cancelNonRunning() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -468,6 +489,7 @@ void cancelNonRunning() throws InterruptedException { @DisplayName("Test that the sync is properly deleted") void deleteSync() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -516,6 +538,8 @@ void deleteSync() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that fresh workflow cleans the job state") void testStartFromCleanJobState() throws InterruptedException { + + returnTrueForLastJobOrAttemptFailure(); final ConnectionUpdaterInput input = ConnectionUpdaterInput.builder() .connectionId(UUID.randomUUID()) .jobId(null) @@ -547,6 +571,7 @@ void setup() { unit = TimeUnit.SECONDS) @DisplayName("Test workflow which receives a manual sync while running a scheduled sync does nothing") void manualRun() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mConfigFetchActivity.getTimeToWait(Mockito.any())) .thenReturn(new ScheduleRetrieverOutput(SCHEDULE_WAIT)); @@ -588,6 +613,7 @@ void manualRun() throws InterruptedException { @DisplayName("Test that cancelling a running workflow cancels the sync") void cancelRunning() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -637,6 +663,7 @@ void cancelRunning() throws InterruptedException { @DisplayName("Test that deleting a running workflow cancels the sync") void deleteRunning() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -691,6 +718,7 @@ void deleteRunning() throws InterruptedException { @DisplayName("Test that resetting a non-running workflow starts a reset job") void resetStart() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -723,6 +751,7 @@ void resetStart() throws InterruptedException { @DisplayName("Test that resetting a non-running workflow starts a reset job") void resetAndContinue() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -758,6 +787,7 @@ void resetAndContinue() throws InterruptedException { @DisplayName("Test that resetting a running workflow cancels the running workflow") void resetCancelRunningWorkflow() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -800,6 +830,7 @@ void resetCancelRunningWorkflow() throws InterruptedException { @DisplayName("Test that running workflow which receives an update signal waits for the current run and reports the job status") void updatedSignalReceivedWhileRunning() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final UUID testId = UUID.randomUUID(); final TestStateListener testStateListener = new TestStateListener(); final WorkflowState workflowState = new WorkflowState(testId, testStateListener); @@ -881,6 +912,7 @@ void setup() { unit = TimeUnit.SECONDS) @DisplayName("Test that auto disable activity is touched during failure") void testAutoDisableOnFailure() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(SourceAndDestinationFailureSyncWorkflow.class); @@ -918,6 +950,7 @@ void testAutoDisableOnFailure() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that auto disable activity is not touched during job success") void testNoAutoDisableOnSuccess() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(EmptySyncWorkflow.class); @@ -977,6 +1010,7 @@ void setup() { unit = TimeUnit.SECONDS) @DisplayName("Test that Source CHECK failures are recorded") void testSourceCheckFailuresRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mJobCreationAndStatusUpdateActivity.createNewJob(Mockito.any())) .thenReturn(new JobCreationOutput(JOB_ID)); when(mJobCreationAndStatusUpdateActivity.createNewAttemptNumber(Mockito.any())) @@ -1016,6 +1050,7 @@ void testSourceCheckFailuresRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that Source CHECK failure reasons are recorded") void testSourceCheckFailureReasonsRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mJobCreationAndStatusUpdateActivity.createNewJob(Mockito.any())) .thenReturn(new JobCreationOutput(JOB_ID)); when(mJobCreationAndStatusUpdateActivity.createNewAttemptNumber(Mockito.any())) @@ -1055,6 +1090,7 @@ void testSourceCheckFailureReasonsRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that Destination CHECK failures are recorded") void testDestinationCheckFailuresRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mJobCreationAndStatusUpdateActivity.createNewJob(Mockito.any())) .thenReturn(new JobCreationOutput(JOB_ID)); when(mJobCreationAndStatusUpdateActivity.createNewAttemptNumber(Mockito.any())) @@ -1099,6 +1135,7 @@ void testDestinationCheckFailuresRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that Destination CHECK failure reasons are recorded") void testDestinationCheckFailureReasonsRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mJobCreationAndStatusUpdateActivity.createNewJob(Mockito.any())) .thenReturn(new JobCreationOutput(JOB_ID)); when(mJobCreationAndStatusUpdateActivity.createNewAttemptNumber(Mockito.any())) @@ -1143,11 +1180,26 @@ void testDestinationCheckFailureReasonsRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that reset workflows do not CHECK the source") void testSourceCheckSkippedWhenReset() throws InterruptedException { + + when(mJobCreationAndStatusUpdateActivity.isLastJobOrAttemptFailure(Mockito.any())) + .thenReturn(true); + + JobRunConfig jobRunConfig = new JobRunConfig(); + jobRunConfig.setJobId(Long.toString(JOB_ID)); + jobRunConfig.setAttemptId((long) ATTEMPT_ID); + when(mGenerateInputActivityImpl.getSyncWorkflowInputWithAttemptNumber(Mockito.any(SyncInputWithAttemptNumber.class))) + .thenReturn( + new GeneratedJobInput( + jobRunConfig, + new IntegrationLauncherConfig().withDockerImage("some_source"), + new IntegrationLauncherConfig(), + new StandardSyncInput())); + when(mJobCreationAndStatusUpdateActivity.createNewJob(Mockito.any())) .thenReturn(new JobCreationOutput(JOB_ID)); when(mJobCreationAndStatusUpdateActivity.createNewAttemptNumber(Mockito.any())) .thenReturn(new AttemptNumberCreationOutput(ATTEMPT_ID)); - mockResetJobInput(); + mockResetJobInput(jobRunConfig); when(mCheckConnectionActivity.runWithJobOutput(Mockito.any())) // first call, but should fail destination because source check is skipped .thenReturn(new ConnectorJobOutput().withOutputType(OutputType.CHECK_CONNECTION) @@ -1184,6 +1236,7 @@ void testSourceCheckSkippedWhenReset() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that source and destination failures are recorded") void testSourceAndDestinationFailuresRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(SourceAndDestinationFailureSyncWorkflow.class); @@ -1220,6 +1273,7 @@ void testSourceAndDestinationFailuresRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that normalization failure is recorded") void testNormalizationFailure() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(NormalizationFailureSyncWorkflow.class); @@ -1254,6 +1308,7 @@ void testNormalizationFailure() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that normalization trace failure is recorded") void testNormalizationTraceFailure() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(NormalizationTraceFailureSyncWorkflow.class); @@ -1288,6 +1343,7 @@ void testNormalizationTraceFailure() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that dbt failure is recorded") void testDbtFailureRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(DbtFailureSyncWorkflow.class); @@ -1322,6 +1378,7 @@ void testDbtFailureRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that persistence failure is recorded") void testPersistenceFailureRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(PersistFailureSyncWorkflow.class); @@ -1356,6 +1413,7 @@ void testPersistenceFailureRecorded() throws InterruptedException { unit = TimeUnit.SECONDS) @DisplayName("Test that replication worker failure is recorded") void testReplicationFailureRecorded() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); final Worker syncWorker = testEnv.newWorker(TemporalJobType.SYNC.name()); syncWorker.registerWorkflowImplementationTypes(ReplicateFailureSyncWorkflow.class); @@ -1412,6 +1470,7 @@ static Stream getSetupFailingActivity() { @ParameterizedTest @MethodSource("getSetupFailingActivity") void testWorkflowRestartedAfterFailedActivity(final Thread mockSetup) throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); mockSetup.run(); when(mConfigFetchActivity.getTimeToWait(Mockito.any())).thenReturn(new ScheduleRetrieverOutput( Duration.ZERO)); @@ -1447,6 +1506,7 @@ void testWorkflowRestartedAfterFailedActivity(final Thread mockSetup) throws Int @Test void testCanRetryFailedActivity() throws InterruptedException { + returnTrueForLastJobOrAttemptFailure(); when(mJobCreationAndStatusUpdateActivity.createNewJob(Mockito.any())) .thenThrow(ApplicationFailure.newNonRetryableFailure("", "")) .thenReturn(new JobCreationOutput(1l)); diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java index 57d856a111d3..a01269d3c53e 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityTest.java @@ -4,6 +4,7 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.config.JobConfig.ConfigType.SYNC; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; @@ -59,8 +60,10 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; @@ -121,6 +124,7 @@ class JobCreationAndStatusUpdateActivityTest { private static final String DOCKER_IMAGE_TAG = "0.0.1"; private static final String DOCKER_IMAGE_NAME = DockerUtils.getTaggedImageName(DOCKER_REPOSITORY, DOCKER_IMAGE_TAG); private static final long JOB_ID = 123L; + private static final long PREVIOUS_JOB_ID = 120L; private static final int ATTEMPT_ID = 0; private static final int ATTEMPT_NUMBER = 1; private static final StreamDescriptor STREAM_DESCRIPTOR1 = new StreamDescriptor().withName("stream 1").withNamespace("namespace 1"); @@ -215,6 +219,67 @@ void createAttempt() throws IOException { } } + @Test + void isLastJobOrAttemptFailureTrueTest() throws Exception { + final int activeAttemptNumber = 0; + final Attempt activeAttempt = new Attempt(activeAttemptNumber, 1, Path.of(""), null, AttemptStatus.RUNNING, null, 4L, 5L, null); + + final Job previousJob = new Job(PREVIOUS_JOB_ID, ConfigType.SYNC, CONNECTION_ID.toString(), + new JobConfig(), List.of(), JobStatus.SUCCEEDED, 4L, 4L, 5L); + final Job activeJob = new Job(JOB_ID, ConfigType.SYNC, CONNECTION_ID.toString(), new JobConfig(), List.of(activeAttempt), + JobStatus.RUNNING, 2L, 2L, 3L); + + Set configTypes = new HashSet<>(); + configTypes.add(SYNC); + + Mockito.when(mJobPersistence.listJobsIncludingId(configTypes, CONNECTION_ID.toString(), JOB_ID, 2)) + .thenReturn(List.of(activeJob, previousJob)); + boolean result = jobCreationAndStatusUpdateActivity + .isLastJobOrAttemptFailure(new JobCreationAndStatusUpdateActivity.JobCheckFailureInput(JOB_ID, 0, CONNECTION_ID)); + Assertions.assertThat(result).isEqualTo(false); + } + + @Test + void isLastJobOrAttemptFailureFalseTest() throws Exception { + final int activeAttemptNumber = 0; + final Attempt activeAttempt = new Attempt(activeAttemptNumber, 1, Path.of(""), null, AttemptStatus.RUNNING, null, 4L, 5L, null); + + final Job previousJob = new Job(PREVIOUS_JOB_ID, ConfigType.SYNC, CONNECTION_ID.toString(), + new JobConfig(), List.of(), JobStatus.FAILED, 4L, 4L, 5L); + final Job activeJob = new Job(JOB_ID, ConfigType.SYNC, CONNECTION_ID.toString(), new JobConfig(), List.of(activeAttempt), + JobStatus.RUNNING, 2L, 2L, 3L); + + Set configTypes = new HashSet<>(); + configTypes.add(SYNC); + + Mockito.when(mJobPersistence.listJobsIncludingId(configTypes, CONNECTION_ID.toString(), JOB_ID, 2)) + .thenReturn(List.of(activeJob, previousJob)); + boolean result = jobCreationAndStatusUpdateActivity + .isLastJobOrAttemptFailure(new JobCreationAndStatusUpdateActivity.JobCheckFailureInput(JOB_ID, 0, CONNECTION_ID)); + Assertions.assertThat(result).isEqualTo(true); + } + + @Test + void isLastJobOrAttemptFailurePreviousAttemptFailureTest() throws Exception { + final Attempt previousAttempt = new Attempt(0, 1, Path.of(""), null, AttemptStatus.FAILED, null, 2L, 3L, 3L); + final int activeAttemptNumber = 1; + final Attempt activeAttempt = new Attempt(activeAttemptNumber, 1, Path.of(""), null, AttemptStatus.RUNNING, null, 4L, 5L, null); + + final Job previousJob = new Job(PREVIOUS_JOB_ID, ConfigType.SYNC, CONNECTION_ID.toString(), new JobConfig(), List.of(), + JobStatus.SUCCEEDED, 4L, 4L, 5L); + final Job activeJob = new Job(JOB_ID, ConfigType.SYNC, CONNECTION_ID.toString(), new JobConfig(), List.of(activeAttempt, previousAttempt), + JobStatus.RUNNING, 2L, 2L, 3L); + + Set configTypes = new HashSet<>(); + configTypes.add(SYNC); + + Mockito.when(mJobPersistence.listJobsIncludingId(configTypes, CONNECTION_ID.toString(), JOB_ID, 2)) + .thenReturn(List.of(activeJob, previousJob)); + boolean result = jobCreationAndStatusUpdateActivity + .isLastJobOrAttemptFailure(new JobCreationAndStatusUpdateActivity.JobCheckFailureInput(JOB_ID, 1, CONNECTION_ID)); + Assertions.assertThat(result).isEqualTo(true); + } + @Test @DisplayName("Test exception errors are properly wrapped") void createAttemptThrowException() throws IOException { From 2e1209e78ff90a076be197606756e547e16c1825 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Thu, 20 Oct 2022 15:16:58 +0300 Subject: [PATCH 223/498] Source Github: switch on airbyte-cdk==0.2.0 (#18213) Signed-off-by: Sergey Chvalyuk --- .../main/resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-github/Dockerfile | 2 +- .../connectors/source-github/setup.py | 2 +- .../source-github/source_github/streams.py | 16 ++++++++++++++-- .../source-github/unit_tests/test_stream.py | 15 ++++++++++++--- docs/integrations/sources/github.md | 1 + 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index a383b443942d..b983d9d5f3e7 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -361,7 +361,7 @@ - name: GitHub sourceDefinitionId: ef69ef6e-aa7f-4af1-a01d-ef775033524e dockerRepository: airbyte/source-github - dockerImageTag: 0.3.6 + dockerImageTag: 0.3.7 documentationUrl: https://docs.airbyte.com/integrations/sources/github icon: github.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 758fdad057fe..8194fd7eb100 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3520,7 +3520,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-github:0.3.6" +- dockerImage: "airbyte/source-github:0.3.7" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/github" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-github/Dockerfile b/airbyte-integrations/connectors/source-github/Dockerfile index f1ecd0d3b013..3a625159c848 100644 --- a/airbyte-integrations/connectors/source-github/Dockerfile +++ b/airbyte-integrations/connectors/source-github/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.3.6 +LABEL io.airbyte.version=0.3.7 LABEL io.airbyte.name=airbyte/source-github diff --git a/airbyte-integrations/connectors/source-github/setup.py b/airbyte-integrations/connectors/source-github/setup.py index 52380693ac32..f51356bc392d 100644 --- a/airbyte-integrations/connectors/source-github/setup.py +++ b/airbyte-integrations/connectors/source-github/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1.33", "vcrpy==4.1.1", "pendulum~=2.1.2", "sgqlc"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2.0", "pendulum~=2.1.2", "sgqlc"] TEST_REQUIREMENTS = ["pytest~=6.1", "source-acceptance-test", "responses~=0.19.0"] diff --git a/airbyte-integrations/connectors/source-github/source_github/streams.py b/airbyte-integrations/connectors/source-github/source_github/streams.py index 15d5c4c54666..4ad809cf8f42 100644 --- a/airbyte-integrations/connectors/source-github/source_github/streams.py +++ b/airbyte-integrations/connectors/source-github/source_github/streams.py @@ -75,14 +75,26 @@ def should_retry(self, response: requests.Response) -> bool: (response.headers.get("X-RateLimit-Resource") == "graphql" and self.check_graphql_rate_limited(response.json())) # Rate limit HTTP headers # https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limit-http-headers - or response.headers.get("X-RateLimit-Remaining") == "0" + or (response.status_code != 200 and response.headers.get("X-RateLimit-Remaining") == "0") # Secondary rate limits # https://docs.github.com/en/rest/overview/resources-in-the-rest-api#secondary-rate-limits or "Retry-After" in response.headers ) if retry_flag: + headers = [ + "X-RateLimit-Resource", + "X-RateLimit-Remaining", + "X-RateLimit-Reset", + "X-RateLimit-Limit", + "X-RateLimit-Used", + "Retry-After", + ] + headers = ", ".join([f"{h}: {response.headers[h]}" for h in headers if h in response.headers]) + if headers: + headers = f"HTTP headers: {headers}," + self.logger.info( - f"Rate limit handling for stream `{self.name}` for the response with {response.status_code} status code with message: {response.text}" + f"Rate limit handling for stream `{self.name}` for the response with {response.status_code} status code, {headers} with message: {response.text}" ) return retry_flag diff --git a/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py b/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py index 8e879a4c4bd2..699ec27047d9 100644 --- a/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py +++ b/airbyte-integrations/connectors/source-github/unit_tests/test_stream.py @@ -95,7 +95,7 @@ def test_backoff_time(time_mock, http_status, response_headers, expected_backoff ("http_status", "response_headers", "text"), [ (HTTPStatus.OK, {"X-RateLimit-Resource": "graphql"}, '{"errors": [{"type": "RATE_LIMITED"}]}'), - (HTTPStatus.OK, {"X-RateLimit-Remaining": "0"}, ""), + (HTTPStatus.FORBIDDEN, {"X-RateLimit-Remaining": "0"}, ""), (HTTPStatus.FORBIDDEN, {"Retry-After": "0"}, ""), (HTTPStatus.FORBIDDEN, {"Retry-After": "60"}, ""), (HTTPStatus.INTERNAL_SERVER_ERROR, {}, ""), @@ -495,7 +495,8 @@ def test_stream_project_columns(): ProjectsResponsesAPI.register(data) - stream = ProjectColumns(Projects(**repository_args_with_start_date), **repository_args_with_start_date) + projects_stream = Projects(**repository_args_with_start_date) + stream = ProjectColumns(projects_stream, **repository_args_with_start_date) stream_state = {} @@ -537,6 +538,8 @@ def test_stream_project_columns(): ProjectsResponsesAPI.register(data) + projects_stream._session.cache.clear() + stream._session.cache.clear() records = read_incremental(stream, stream_state=stream_state) assert records == [ {"id": 24, "name": "column_24", "project_id": 2, "repository": "organization/repository", "updated_at": "2022-04-01T10:00:00Z"}, @@ -607,6 +610,9 @@ def test_stream_project_cards(): ProjectsResponsesAPI.register(data) stream_state = {} + + projects_stream._session.cache.clear() + project_columns_stream._session.cache.clear() records = read_incremental(stream, stream_state=stream_state) assert records == [ @@ -887,7 +893,9 @@ def test_stream_team_members_full_refresh(): responses.add("GET", "https://api.github.com/orgs/org1/teams/team2/members", json=[{"login": "login2"}]) responses.add("GET", "https://api.github.com/orgs/org1/teams/team2/memberships/login2", json={"username": "login2"}) - stream = TeamMembers(parent=Teams(**organization_args), **repository_args) + teams_stream = Teams(**organization_args) + stream = TeamMembers(parent=teams_stream, **repository_args) + teams_stream._session.cache.clear() records = list(read_full_refresh(stream)) assert records == [ @@ -977,6 +985,7 @@ def test_stream_commit_comment_reactions_incremental_read(): json=[{"id": 154935433, "created_at": "2022-02-01T17:00:00Z"}], ) + stream._parent_stream._session.cache.clear() records = read_incremental(stream, stream_state) assert records == [ diff --git a/docs/integrations/sources/github.md b/docs/integrations/sources/github.md index 8f5027b95719..14342ef5ae2a 100644 --- a/docs/integrations/sources/github.md +++ b/docs/integrations/sources/github.md @@ -147,6 +147,7 @@ The GitHub connector should not run into GitHub API limitations under normal usa | Version | Date | Pull Request | Subject | | :------ | :--------- | :---------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 0.3.7 | 2022-10-20 | [18213](https://github.com/airbytehq/airbyte/pull/18213) | Skip retry on HTTP 200 | | 0.3.6 | 2022-10-11 | [17852](https://github.com/airbytehq/airbyte/pull/17852) | Use default behaviour, retry on 429 and all 5XX errors | | 0.3.5 | 2022-10-07 | [17715](https://github.com/airbytehq/airbyte/pull/17715) | Improve 502 handling for `comments` stream | | 0.3.4 | 2022-10-04 | [17555](https://github.com/airbytehq/airbyte/pull/17555) | Skip repository if got HTTP 500 for WorkflowRuns stream | From 762c4bdda45b98332f43bc712755e0120d20e5d3 Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Thu, 20 Oct 2022 10:19:24 -0400 Subject: [PATCH 224/498] Move CatalogTree from views to components/connection (#18185) --- .../connection/CatalogTree}/Arrow.tsx | 0 .../connection/CatalogTree}/BulkHeader.module.scss | 0 .../connection/CatalogTree}/BulkHeader.tsx | 6 +++--- .../CatalogTree/CatalogSection.module.scss | 2 +- .../connection}/CatalogTree/CatalogSection.tsx | 12 ++++++------ .../connection}/CatalogTree/CatalogTree.tsx | 2 +- .../CatalogTree/CatalogTreeBody.module.scss | 0 .../connection}/CatalogTree/CatalogTreeBody.tsx | 0 .../CatalogTree/CatalogTreeHeader.module.scss | 0 .../connection}/CatalogTree/CatalogTreeHeader.tsx | 0 .../connection}/CatalogTree/CatalogTreeSearch.tsx | 0 .../CatalogTree/CatalogTreeSubheader.module.scss | 0 .../connection}/CatalogTree/CatalogTreeSubheader.tsx | 0 .../connection/CatalogTree}/DataTypeCell.tsx | 0 .../connection}/CatalogTree/FieldHeader.tsx | 0 .../connection}/CatalogTree/FieldRow.tsx | 4 ++-- .../connection/CatalogTree}/PathPopout.module.scss | 0 .../connection/CatalogTree}/PathPopout.tsx | 0 .../CatalogTree}/PathPopoutButton.module.scss | 4 ++-- .../connection/CatalogTree}/PathPopoutButton.tsx | 0 .../connection}/CatalogTree/StreamFieldTable.tsx | 6 +++--- .../connection}/CatalogTree/StreamHeader.module.scss | 4 ++-- .../connection}/CatalogTree/StreamHeader.tsx | 6 +++--- .../connection/CatalogTree}/SyncSettingsDropdown.tsx | 0 .../connection/CatalogTree}/TreeRowWrapper.tsx | 0 .../connection}/CatalogTree/index.tsx | 0 .../connection}/CatalogTree/styles.tsx | 0 .../connection}/CatalogTree/utils.ts | 2 +- .../ConnectionForm/components/SyncCatalogField.tsx | 2 +- 29 files changed, 25 insertions(+), 25 deletions(-) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/Arrow.tsx (100%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/BulkHeader.module.scss (100%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/BulkHeader.tsx (96%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogSection.module.scss (90%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogSection.tsx (99%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTree.tsx (97%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeBody.module.scss (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeBody.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeHeader.module.scss (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeHeader.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeSearch.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeSubheader.module.scss (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/CatalogTreeSubheader.tsx (100%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/DataTypeCell.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/FieldHeader.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/FieldRow.tsx (94%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/PathPopout.module.scss (100%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/PathPopout.tsx (100%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/PathPopoutButton.module.scss (87%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/PathPopoutButton.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/StreamFieldTable.tsx (87%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/StreamHeader.module.scss (94%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/StreamHeader.tsx (96%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/SyncSettingsDropdown.tsx (100%) rename airbyte-webapp/src/{views/Connection/CatalogTree/components => components/connection/CatalogTree}/TreeRowWrapper.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/index.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/styles.tsx (100%) rename airbyte-webapp/src/{views/Connection => components/connection}/CatalogTree/utils.ts (89%) diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/Arrow.tsx b/airbyte-webapp/src/components/connection/CatalogTree/Arrow.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/Arrow.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/Arrow.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/BulkHeader.module.scss similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/BulkHeader.module.scss diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/BulkHeader.tsx similarity index 96% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/BulkHeader.tsx index fabf20d4b6e3..9788a0da07f8 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/components/BulkHeader.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/BulkHeader.tsx @@ -11,13 +11,13 @@ import { SyncSchemaField, SyncSchemaFieldObject, SyncSchemaStream, traverseSchem import { DestinationSyncMode, SyncMode } from "core/request/AirbyteClient"; import { useBulkEditService } from "hooks/services/BulkEdit/BulkEditService"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig"; -import { SUPPORTED_MODES } from "../../ConnectionForm/formConfig"; -import { ArrowCell, CheckboxCell, HeaderCell } from "../styles"; -import { flatten, getPathType } from "../utils"; import styles from "./BulkHeader.module.scss"; import { pathDisplayName, PathPopout } from "./PathPopout"; +import { ArrowCell, CheckboxCell, HeaderCell } from "./styles"; import { SyncSettingsDropdown } from "./SyncSettingsDropdown"; +import { flatten, getPathType } from "./utils"; const ActionCell = styled.div` display: flex; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.module.scss similarity index 90% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.module.scss index 1ccdb8880366..ab6bdcf4c2b0 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.module.scss +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.module.scss @@ -1,4 +1,4 @@ -@use "../../../scss/colors"; +@use "scss/colors"; .streamFieldTableContainer { margin-left: 85px; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx similarity index 99% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx index c408151a8426..2e1c03fdeba5 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogSection.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx @@ -6,17 +6,17 @@ import { DropDownOptionDataItem } from "components/ui/DropDown"; import { SyncSchemaField, SyncSchemaFieldObject, SyncSchemaStream } from "core/domain/catalog"; import { traverseSchemaToField } from "core/domain/catalog/fieldUtil"; -import { useDestinationNamespace } from "hooks/connection/useDestinationNamespace"; -import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; -import { equal, naturalComparatorBy } from "utils/objects"; -import { ConnectionFormValues, SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig"; - import { AirbyteStreamConfiguration, DestinationSyncMode, NamespaceDefinitionType, SyncMode, -} from "../../../core/request/AirbyteClient"; +} from "core/request/AirbyteClient"; +import { useDestinationNamespace } from "hooks/connection/useDestinationNamespace"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { equal, naturalComparatorBy } from "utils/objects"; +import { ConnectionFormValues, SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig"; + import styles from "./CatalogSection.module.scss"; import { StreamFieldTable } from "./StreamFieldTable"; import { StreamHeader } from "./StreamHeader"; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTree.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx similarity index 97% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTree.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx index 20c99b7565a6..b00a71c0cf9e 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTree.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx @@ -7,11 +7,11 @@ import { BulkEditServiceProvider } from "hooks/services/BulkEdit/BulkEditService import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { naturalComparatorBy } from "utils/objects"; +import { BulkHeader } from "./BulkHeader"; import { CatalogTreeBody } from "./CatalogTreeBody"; import { CatalogTreeHeader } from "./CatalogTreeHeader"; import { CatalogTreeSearch } from "./CatalogTreeSearch"; import { CatalogTreeSubheader } from "./CatalogTreeSubheader"; -import { BulkHeader } from "./components/BulkHeader"; interface CatalogTreeProps { streams: SyncSchemaStream[]; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.module.scss similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.module.scss diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeBody.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeHeader.module.scss similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeHeader.module.scss diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeHeader.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeHeader.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeHeader.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSearch.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSearch.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSearch.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSearch.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSubheader.module.scss similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSubheader.module.scss diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSubheader.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/CatalogTreeSubheader.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSubheader.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/DataTypeCell.tsx b/airbyte-webapp/src/components/connection/CatalogTree/DataTypeCell.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/DataTypeCell.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/DataTypeCell.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/FieldHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/FieldHeader.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/FieldHeader.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/FieldHeader.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/FieldRow.tsx b/airbyte-webapp/src/components/connection/CatalogTree/FieldRow.tsx similarity index 94% rename from airbyte-webapp/src/views/Connection/CatalogTree/FieldRow.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/FieldRow.tsx index 125833bfe2a7..c87a61578f7a 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/FieldRow.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/FieldRow.tsx @@ -10,8 +10,8 @@ import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; import { equal } from "utils/objects"; import { useTranslateDataType } from "utils/useTranslateDataType"; -import DataTypeCell from "./components/DataTypeCell"; -import { pathDisplayName } from "./components/PathPopout"; +import DataTypeCell from "./DataTypeCell"; +import { pathDisplayName } from "./PathPopout"; import { NameContainer } from "./styles"; interface FieldRowProps { diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopout.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/PathPopout.module.scss similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopout.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/PathPopout.module.scss diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopout.tsx b/airbyte-webapp/src/components/connection/CatalogTree/PathPopout.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopout.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/PathPopout.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopoutButton.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/PathPopoutButton.module.scss similarity index 87% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopoutButton.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/PathPopoutButton.module.scss index 2d27778881ed..0b1615bbf20e 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopoutButton.module.scss +++ b/airbyte-webapp/src/components/connection/CatalogTree/PathPopoutButton.module.scss @@ -1,5 +1,5 @@ -@use "../../../../scss/colors"; -@use "../../../../scss/variables"; +@use "scss/colors"; +@use "scss/variables"; .arrow { color: colors.$grey-300; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopoutButton.tsx b/airbyte-webapp/src/components/connection/CatalogTree/PathPopoutButton.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/PathPopoutButton.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/PathPopoutButton.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/StreamFieldTable.tsx b/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx similarity index 87% rename from airbyte-webapp/src/views/Connection/CatalogTree/StreamFieldTable.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx index bf1a1dd2c756..538e1b5b8210 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/StreamFieldTable.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx @@ -2,12 +2,12 @@ import React from "react"; import styled from "styled-components"; import { SyncSchemaField, SyncSchemaFieldObject } from "core/domain/catalog"; +import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; -import { AirbyteStreamConfiguration } from "../../../core/request/AirbyteClient"; -import { pathDisplayName } from "./components/PathPopout"; -import { TreeRowWrapper } from "./components/TreeRowWrapper"; import { FieldHeader } from "./FieldHeader"; import { FieldRow } from "./FieldRow"; +import { pathDisplayName } from "./PathPopout"; +import { TreeRowWrapper } from "./TreeRowWrapper"; const RowsContainer = styled.div` background: ${({ theme }) => theme.whiteColor}; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss similarity index 94% rename from airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss rename to airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss index 576fef3a3806..2bd4fa8812e8 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.module.scss +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss @@ -1,5 +1,5 @@ -@use "../../../scss/colors"; -@use "../../../scss/variables"; +@use "scss/colors"; +@use "scss/variables"; @forward "./CatalogTreeBody.module.scss"; .icon { diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx similarity index 96% rename from airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx index 8ce7252da1d5..eacb3024d886 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/StreamHeader.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx @@ -14,11 +14,11 @@ import { Path, SyncSchemaField, SyncSchemaStream } from "core/domain/catalog"; import { DestinationSyncMode, SyncMode } from "core/request/AirbyteClient"; import { useBulkEditSelect } from "hooks/services/BulkEdit/BulkEditService"; -import { Arrow as ArrowBlock } from "./components/Arrow"; -import { IndexerType, PathPopout } from "./components/PathPopout"; -import { SyncSettingsDropdown } from "./components/SyncSettingsDropdown"; +import { Arrow as ArrowBlock } from "./Arrow"; +import { IndexerType, PathPopout } from "./PathPopout"; import styles from "./StreamHeader.module.scss"; import { ArrowCell, HeaderCell } from "./styles"; +import { SyncSettingsDropdown } from "./SyncSettingsDropdown"; const EmptyField = styled.span` color: ${({ theme }) => theme.greyColor40}; diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/SyncSettingsDropdown.tsx b/airbyte-webapp/src/components/connection/CatalogTree/SyncSettingsDropdown.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/SyncSettingsDropdown.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/SyncSettingsDropdown.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/components/TreeRowWrapper.tsx b/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/components/TreeRowWrapper.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/index.tsx b/airbyte-webapp/src/components/connection/CatalogTree/index.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/index.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/index.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/styles.tsx b/airbyte-webapp/src/components/connection/CatalogTree/styles.tsx similarity index 100% rename from airbyte-webapp/src/views/Connection/CatalogTree/styles.tsx rename to airbyte-webapp/src/components/connection/CatalogTree/styles.tsx diff --git a/airbyte-webapp/src/views/Connection/CatalogTree/utils.ts b/airbyte-webapp/src/components/connection/CatalogTree/utils.ts similarity index 89% rename from airbyte-webapp/src/views/Connection/CatalogTree/utils.ts rename to airbyte-webapp/src/components/connection/CatalogTree/utils.ts index 2209a1f9732c..ee4b7add856b 100644 --- a/airbyte-webapp/src/views/Connection/CatalogTree/utils.ts +++ b/airbyte-webapp/src/components/connection/CatalogTree/utils.ts @@ -1,6 +1,6 @@ import { SyncSchemaField } from "core/domain/catalog"; -import { IndexerType } from "./components/PathPopout"; +import { IndexerType } from "./PathPopout"; export const flatten = (fArr: SyncSchemaField[], arr: SyncSchemaField[] = []): SyncSchemaField[] => fArr.reduce((acc, f) => { diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx index c8d5603809d0..7218199752ab 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/SyncCatalogField.tsx @@ -2,12 +2,12 @@ import { FieldProps } from "formik"; import React, { useCallback } from "react"; import { FormattedMessage } from "react-intl"; +import { CatalogTree } from "components/connection/CatalogTree"; import { Text } from "components/ui/Text"; import { SyncSchemaStream } from "core/domain/catalog"; import { DestinationSyncMode } from "core/request/AirbyteClient"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; -import { CatalogTree } from "views/Connection/CatalogTree"; import styles from "./SyncCatalogField.module.scss"; From 1e9ad1c6f8e0aeb0594c1501133fdc56a7292fac Mon Sep 17 00:00:00 2001 From: cncc Date: Thu, 20 Oct 2022 23:43:26 +0900 Subject: [PATCH 225/498] Update tidb.md (#18210) add CREATE, INSERT, SELECT, DROP to Permissions for TiDB destination --- docs/integrations/destinations/tidb.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/destinations/tidb.md b/docs/integrations/destinations/tidb.md index 2435dc5b9bef..80bfe9007d50 100644 --- a/docs/integrations/destinations/tidb.md +++ b/docs/integrations/destinations/tidb.md @@ -47,7 +47,7 @@ CREATE USER 'airbyte'@'%' IDENTIFIED BY 'your_password_here'; Then give it access to the relevant database: ```sql -GRANT SELECT ON .* TO 'airbyte'@'%'; +GRANT CREATE, INSERT, SELECT, DROP ON .* TO 'airbyte'@'%'; ``` #### Target Database From dc80d6854ee0e4e107590bdc21d1a2c12992917e Mon Sep 17 00:00:00 2001 From: Digambar Tupurwadi Date: Thu, 20 Oct 2022 20:18:40 +0530 Subject: [PATCH 226/498] corrected the documentation for FreshDesk Connector (#18225) --- docs/integrations/sources/freshdesk.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/integrations/sources/freshdesk.md b/docs/integrations/sources/freshdesk.md index 84ca62895866..c1e7176c1e4e 100644 --- a/docs/integrations/sources/freshdesk.md +++ b/docs/integrations/sources/freshdesk.md @@ -12,7 +12,7 @@ This page guides you through the process of setting up the Freshdesk source conn ### Get Domain URL -You can find your domain URL by loggin into your account and check the URL in your browser, the domain url should look like: `https://myaccount.freshdesk.com/...`, where `myaccount.freshdesk.com` - is your domain URL. +You can find your domain URL by logging into your account and check the URL in your browser, the domain url should look like: `https://myaccount.freshdesk.com/...`, where `myaccount.freshdesk.com` - is your domain URL. ### Get Freshdesk API Key @@ -23,8 +23,8 @@ Follow the link to read more about [how to find your API key](https://support.fr **For Airbyte Cloud** -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+ new source**. +1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. +2. Click **Sources** and then click **+ New source**. 3. On the source setup page, select **Freshdesk** from the Source type dropdown and enter a name for this connector. 4. Enter your `Domain URL`. 5. Enter your `Freshdesk API Key`. @@ -34,7 +34,7 @@ Follow the link to read more about [how to find your API key](https://support.fr **For Airbyte Open Source:** 1. Go to local Airbyte page. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+ new source**. +2. Click **Sources** and then click **+ New source**. 3. On the source setup page, select **Freshdesk** from the Source type dropdown and enter a name for this connector. 4. Enter your `Domain URL`. 5. Enter your `Freshdesk API Key`. @@ -73,7 +73,7 @@ Several output streams are available from this source: If there are more endpoints you'd like Airbyte to support, please [create an issue.](https://github.com/airbytehq/airbyte/issues/new/choose) -### Performance considerations +## Performance considerations The Freshdesk connector should not run into Freshdesk API limitations under normal usage. Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. @@ -85,12 +85,12 @@ The Freshdesk connector should not run into Freshdesk API limitations under norm | 0.3.6 | 2022-09-29 | [17410](https://github.com/airbytehq/airbyte/pull/17410) | Migrate to per-stream states. | | 0.3.5 | 2022-09-27 | [17249](https://github.com/airbytehq/airbyte/pull/17249) | Added nullable to all stream schemas, added transformation into declared schema types | | 0.3.4 | 2022-09-27 | [17243](https://github.com/airbytehq/airbyte/pull/17243) | Fixed the issue, when selected stream is not available due to Subscription Plan | -| 0.3.3 | 2022-08-06 | [15378](https://github.com/airbytehq/airbyte/pull/15378) | Allow backward campatibility for input configuration | +| 0.3.3 | 2022-08-06 | [15378](https://github.com/airbytehq/airbyte/pull/15378) | Allow backward compatibility for input configuration | | 0.3.2 | 2022-06-23 | [14049](https://github.com/airbytehq/airbyte/pull/14049) | Update parsing of start_date | | 0.3.1 | 2022-06-03 | [13332](https://github.com/airbytehq/airbyte/pull/13332) | Add new streams | | 0.3.0 | 2022-05-30 | [12334](https://github.com/airbytehq/airbyte/pull/12334) | Implement with latest CDK | | 0.2.11 | 2021-12-14 | [8682](https://github.com/airbytehq/airbyte/pull/8682) | Migrate to the CDK | | 0.2.10 | 2021-12-06 | [8524](https://github.com/airbytehq/airbyte/pull/8524) | Update connector fields title/description | -| 0.2.9 | 2021-11-16 | [8017](https://github.com/airbytehq/airbyte/pull/8017) | Bugfix an issue that caused the connector not to sync more than 50000 contacts | +| 0.2.9 | 2021-11-16 | [8017](https://github.com/airbytehq/airbyte/pull/8017) | Bugfix an issue that caused the connector to not sync more than 50000 contacts | | 0.2.8 | 2021-10-28 | [7486](https://github.com/airbytehq/airbyte/pull/7486) | Include "requester" and "stats" fields in "tickets" stream | | 0.2.7 | 2021-10-13 | [6442](https://github.com/airbytehq/airbyte/pull/6442) | Add start_date parameter to specification from which to start pulling data. | From 8fba3ac95bd00f8c3fc774d3000620d416283640 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 20 Oct 2022 09:08:50 -0700 Subject: [PATCH 227/498] Remove `airbyte-cli` from build scrips (#18251) --- tools/bin/publish_docker.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/bin/publish_docker.sh b/tools/bin/publish_docker.sh index ed7dbace80f7..eda7ceb7bb59 100755 --- a/tools/bin/publish_docker.sh +++ b/tools/bin/publish_docker.sh @@ -4,7 +4,6 @@ set -e # List of directories without "airbyte-" prefix. projectDir=( "bootloader" - "cli" "config/init" "container-orchestrator" "cron" From 672260a3634d043c5091f542c4c8827883ceb12e Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Thu, 20 Oct 2022 19:17:03 +0300 Subject: [PATCH 228/498] Source intercom: switch on `airbyte-cdk=0.2.0` (#18216) Signed-off-by: Sergey Chvalyuk --- .../src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-intercom/Dockerfile | 2 +- .../connectors/source-intercom/setup.py | 2 +- .../source-intercom/source_intercom/source.py | 11 ----------- docs/integrations/sources/intercom.md | 1 + 6 files changed, 5 insertions(+), 15 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index b983d9d5f3e7..5960faafd7bd 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -510,7 +510,7 @@ - name: Intercom sourceDefinitionId: d8313939-3782-41b0-be29-b3ca20d8dd3a dockerRepository: airbyte/source-intercom - dockerImageTag: 0.1.27 + dockerImageTag: 0.1.28 documentationUrl: https://docs.airbyte.com/integrations/sources/intercom icon: intercom.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 8194fd7eb100..f1a4ef305424 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -5030,7 +5030,7 @@ oauthFlowInitParameters: [] oauthFlowOutputParameters: - - "access_token" -- dockerImage: "airbyte/source-intercom:0.1.27" +- dockerImage: "airbyte/source-intercom:0.1.28" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/intercom" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-intercom/Dockerfile b/airbyte-integrations/connectors/source-intercom/Dockerfile index 8d46e345b891..72eea895dca2 100644 --- a/airbyte-integrations/connectors/source-intercom/Dockerfile +++ b/airbyte-integrations/connectors/source-intercom/Dockerfile @@ -35,5 +35,5 @@ COPY source_intercom ./source_intercom ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.27 +LABEL io.airbyte.version=0.1.28 LABEL io.airbyte.name=airbyte/source-intercom diff --git a/airbyte-integrations/connectors/source-intercom/setup.py b/airbyte-integrations/connectors/source-intercom/setup.py index 592a7adca0ad..cba74a441da8 100644 --- a/airbyte-integrations/connectors/source-intercom/setup.py +++ b/airbyte-integrations/connectors/source-intercom/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1", + "airbyte-cdk~=0.2.0", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-intercom/source_intercom/source.py b/airbyte-integrations/connectors/source-intercom/source_intercom/source.py index 483c46f3eee2..161c89ff3bfd 100755 --- a/airbyte-integrations/connectors/source-intercom/source_intercom/source.py +++ b/airbyte-integrations/connectors/source-intercom/source_intercom/source.py @@ -9,8 +9,6 @@ from urllib.parse import parse_qsl, urljoin, urlparse import requests -import vcr -import vcr.cassette as Cassette from airbyte_cdk.logger import AirbyteLogger from airbyte_cdk.sources import AbstractSource from airbyte_cdk.sources.streams import Stream @@ -136,15 +134,6 @@ class IncrementalIntercomSearchStream(IncrementalIntercomStream): sort_order = "ascending" use_cache = True - def request_cache(self) -> Cassette: - """ - Override the default `request_cache` method, due to `match_on` is different for POST requests. - We should check additional criteria like ['query', 'body'] instead of default ['uri', 'method'] - """ - match_on = ["uri", "query", "method", "body"] - cassette = vcr.use_cassette(self.cache_filename, record_mode="new_episodes", serializer="yaml", match_on=match_on) - return cassette - @stream_state_cache.cache_stream_state def request_params(self, **kwargs) -> MutableMapping[str, Any]: """ diff --git a/docs/integrations/sources/intercom.md b/docs/integrations/sources/intercom.md index 96e70483c350..2c27147d1d2b 100644 --- a/docs/integrations/sources/intercom.md +++ b/docs/integrations/sources/intercom.md @@ -49,6 +49,7 @@ The Intercom connector should not run into Intercom API limitations under normal | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | +| 0.1.28 | 2022-10-20 | [18216](https://github.com/airbytehq/airbyte/pull/18216) | Use airbyte-cdk~=0.2.0 with SQLite caching | | 0.1.27 | 2022-08-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states. | | 0.1.26 | 2022-08-18 | [16540](https://github.com/airbytehq/airbyte/pull/16540) | Fix JSON schema | | 0.1.25 | 2022-08-18 | [15681](https://github.com/airbytehq/airbyte/pull/15681) | Update Intercom API to v 2.5 | From 2d32e9d3a6a5d66554ed4cc81bbf7004cda6acc9 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Thu, 20 Oct 2022 12:23:24 -0400 Subject: [PATCH 229/498] Improve secret replacement matching reg ex (#18234) * Improve secret replacement matching reg ex * Remove System.out.println --- .../java/io/airbyte/commons/logging/MaskedDataInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-commons/src/main/java/io/airbyte/commons/logging/MaskedDataInterceptor.java b/airbyte-commons/src/main/java/io/airbyte/commons/logging/MaskedDataInterceptor.java index ef9092c168bf..8fd10f7bd7e1 100644 --- a/airbyte-commons/src/main/java/io/airbyte/commons/logging/MaskedDataInterceptor.java +++ b/airbyte-commons/src/main/java/io/airbyte/commons/logging/MaskedDataInterceptor.java @@ -127,7 +127,7 @@ private String generatePattern(final Set properties) { builder.append("(?i)"); // case insensitive builder.append("\"("); builder.append(properties.stream().collect(Collectors.joining("|"))); - builder.append(")\"\\s*:\\s*\"?((\\\\\"|[^\",}])*)\"?"); + builder.append(")\"\\s*:\\s*(\"(?:[^\"\\\\]|\\\\.)*\"|\\[[^]\\[]*]|\\d+)"); return builder.toString(); } From 0d9b4e5dce1ba9e4cce219ab03e88ea7b47e414c Mon Sep 17 00:00:00 2001 From: Sage Watterworth <83830720+sage-watterworth@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:50:55 -0400 Subject: [PATCH 230/498] =?UTF-8?q?=F0=9F=8E=89=20Source=20Amazon=20Ads:?= =?UTF-8?q?=20filters=20for=20state=20on=20brand,=20product=20and=20displa?= =?UTF-8?q?y=20campaigns=20(#17475)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * amazon ad status filter * update test report stream with parametrized campaigns * remove config files related to previous acceptance test. revert to master acceptance test set up * amazon ad status filter * update test report stream with parametrized campaigns * remove config files related to previous acceptance test. revert to master acceptance test set up * oct 17 edits * oct 17 edits: 2 * bump dockerfile to 1.24 * fix: match the cdk version to new one * auto-bump connector version Co-authored-by: sajarin Co-authored-by: Harshith Mullapudi Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 16 +++++- .../connectors/source-amazon-ads/Dockerfile | 3 +- .../acceptance-test-config.yml | 2 +- .../integration_tests/spec.json | 11 ++++ .../connectors/source-amazon-ads/setup.py | 2 +- .../source_amazon_ads/spec.yaml | 12 ++++ .../streams/sponsored_brands.py | 11 ++++ .../streams/sponsored_display.py | 19 +++++-- .../streams/sponsored_products.py | 11 ++++ .../unit_tests/test_report_streams.py | 56 +++++++++++++++++++ docs/integrations/sources/amazon-ads.md | 1 + 12 files changed, 137 insertions(+), 9 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 5960faafd7bd..6e73e42baae8 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -33,7 +33,7 @@ - name: Amazon Ads sourceDefinitionId: c6b0a29e-1da9-4512-9002-7bfd0cba2246 dockerRepository: airbyte/source-amazon-ads - dockerImageTag: 0.1.23 + dockerImageTag: 0.1.24 documentationUrl: https://docs.airbyte.com/integrations/sources/amazon-ads icon: amazonads.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index f1a4ef305424..1a08b6b37450 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -647,7 +647,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-amazon-ads:0.1.23" +- dockerImage: "airbyte/source-amazon-ads:0.1.24" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/amazon-ads" connectionSpecification: @@ -729,6 +729,20 @@ type: "array" items: type: "integer" + state_filter: + title: "State Filter" + description: "Reflects the state of the Display, Product, and Brand Campaign\ + \ streams as enabled, paused, or archived. If you do not populate this\ + \ field, it will be ignored completely." + items: + type: "string" + enum: + - "enabled" + - "paused" + - "archived" + type: "array" + uniqueItems: true + order: 9 required: - "client_id" - "client_secret" diff --git a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile index 69f76987802c..68fcde896d8f 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-amazon-ads/Dockerfile @@ -12,5 +12,6 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.23 + +LABEL io.airbyte.version=0.1.24 LABEL io.airbyte.name=airbyte/source-amazon-ads diff --git a/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml b/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml index a19c5c48b498..a1e7adb95e7a 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml @@ -46,4 +46,4 @@ tests: configured_catalog_path: "integration_tests/configured_catalog_report.json" ignored_fields: "sponsored_products_report_stream": ["updatedAt"] - timeout_seconds: 3600 + timeout_seconds: 3600 \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-amazon-ads/integration_tests/spec.json b/airbyte-integrations/connectors/source-amazon-ads/integration_tests/spec.json index ab16585e81bd..d1ba1851a4b5 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-amazon-ads/integration_tests/spec.json @@ -69,6 +69,17 @@ "items": { "type": "integer" } + }, + "state_filter": { + "title": "State Filter", + "description": "Reflects the state of the Display, Product, and Brand Campaign streams as enabled, paused, or archived. If you do not populate this field, it will be ignored completely.", + "items": { + "type": "string", + "enum": ["enabled", "paused", "archived"] + }, + "type": "array", + "uniqueItems": true, + "order": 9 } }, "required": ["client_id", "client_secret", "refresh_token"], diff --git a/airbyte-integrations/connectors/source-amazon-ads/setup.py b/airbyte-integrations/connectors/source-amazon-ads/setup.py index 7f4200df6c1b..b74dd73a61ed 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/setup.py +++ b/airbyte-integrations/connectors/source-amazon-ads/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1.68", "requests_oauthlib~=1.3.1", "pendulum~=2.1.2"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2.0", "requests_oauthlib~=1.3.1", "pendulum~=2.1.2"] TEST_REQUIREMENTS = [ "pytest~=6.1", diff --git a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.yaml b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.yaml index b45824964137..5523b774e86f 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.yaml +++ b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/spec.yaml @@ -86,6 +86,18 @@ connectionSpecification: type: array items: type: integer + state_filter: + title: State Filter + description: Reflects the state of the Display, Product, and Brand Campaign streams as enabled, paused, or archived. If you do not populate this field, it will be ignored completely. + items: + type: string + enum: + - enabled + - paused + - archived + type: array + uniqueItems: true + order: 9 required: - client_id - client_secret diff --git a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_brands.py b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_brands.py index d2b69fda42a6..7ab994bca8d3 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_brands.py +++ b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_brands.py @@ -12,12 +12,23 @@ class SponsoredBrandsCampaigns(SubProfilesStream): https://advertising.amazon.com/API/docs/en-us/sponsored-brands/3-0/openapi#/Campaigns """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.state_filter = kwargs.get("config", {}).get("state_filter") + primary_key = "campaignId" + state_filter = None model = BrandsCampaign def path(self, **kvargs) -> str: return "sb/campaigns" + def request_params(self, *args, **kwargs): + params = super().request_params(*args, **kwargs) + if self.state_filter: + params["stateFilter"] = ",".join(self.state_filter) + return params + class SponsoredBrandsAdGroups(SubProfilesStream): """ diff --git a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_display.py b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_display.py index ae3eb1d8ad24..3ce8e264bbcf 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_display.py +++ b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_display.py @@ -12,12 +12,23 @@ class SponsoredDisplayCampaigns(SubProfilesStream): https://advertising.amazon.com/API/docs/en-us/sponsored-display/3-0/openapi#/Campaigns """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.state_filter = kwargs.get("config", {}).get("state_filter") + primary_key = "campaignId" + state_filter = None model = DisplayCampaign - def path(self, **kvargs) -> str: + def path(self, **kwargs) -> str: return "sd/campaigns" + def request_params(self, *args, **kwargs): + params = super().request_params(*args, **kwargs) + if self.state_filter: + params["stateFilter"] = ",".join(self.state_filter) + return params + class SponsoredDisplayAdGroups(SubProfilesStream): """ @@ -28,7 +39,7 @@ class SponsoredDisplayAdGroups(SubProfilesStream): primary_key = "adGroupId" model = DisplayAdGroup - def path(self, **kvargs) -> str: + def path(self, **kwargs) -> str: return "sd/adGroups" @@ -41,7 +52,7 @@ class SponsoredDisplayProductAds(SubProfilesStream): primary_key = "adId" model = DisplayProductAds - def path(self, **kvargs) -> str: + def path(self, **kwargs) -> str: return "sd/productAds" @@ -54,5 +65,5 @@ class SponsoredDisplayTargetings(SubProfilesStream): primary_key = "targetId" model = DisplayTargeting - def path(self, **kvargs) -> str: + def path(self, **kwargs) -> str: return "sd/targets" diff --git a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_products.py b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_products.py index 444b7eb4ff44..b5baa89105c0 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_products.py +++ b/airbyte-integrations/connectors/source-amazon-ads/source_amazon_ads/streams/sponsored_products.py @@ -12,12 +12,23 @@ class SponsoredProductCampaigns(SubProfilesStream): https://advertising.amazon.com/API/docs/en-us/sponsored-display/3-0/openapi#/Campaigns """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.state_filter = kwargs.get("config", {}).get("state_filter") + primary_key = "campaignId" + state_filter = None model = ProductCampaign def path(self, **kvargs) -> str: return "v2/sp/campaigns" + def request_params(self, *args, **kwargs): + params = super().request_params(*args, **kwargs) + if self.state_filter: + params["stateFilter"] = ",".join(self.state_filter) + return params + class SponsoredProductAdGroups(SubProfilesStream): """ diff --git a/airbyte-integrations/connectors/source-amazon-ads/unit_tests/test_report_streams.py b/airbyte-integrations/connectors/source-amazon-ads/unit_tests/test_report_streams.py index 6fc2b3d41951..f53a10777ae0 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/unit_tests/test_report_streams.py +++ b/airbyte-integrations/connectors/source-amazon-ads/unit_tests/test_report_streams.py @@ -19,9 +19,12 @@ from source_amazon_ads.schemas.profile import AccountInfo, Profile from source_amazon_ads.source import CONFIG_DATE_FORMAT from source_amazon_ads.streams import ( + SponsoredBrandsCampaigns, SponsoredBrandsReportStream, SponsoredBrandsVideoReportStream, + SponsoredDisplayCampaigns, SponsoredDisplayReportStream, + SponsoredProductCampaigns, SponsoredProductsReportStream, ) from source_amazon_ads.streams.report_streams.report_streams import ReportGenerationFailure, ReportGenerationInProgress, TooManyRequests @@ -556,3 +559,56 @@ def test_read_incremental_with_records_start_date(config): records = list(read_incremental(stream, state)) assert state == {"1": {"reportDate": "20210104"}} assert {r["reportDate"] for r in records} == {"20210103", "20210104", "20210105", "20210106"} + + +@pytest.mark.parametrize( + "state_filter, stream_class", + [ + ( + ["enabled", "archived", "paused"], + SponsoredBrandsCampaigns, + ), + ( + ["enabled"], + SponsoredBrandsCampaigns, + ), + ( + None, + SponsoredBrandsCampaigns, + ), + ( + ["enabled", "archived", "paused"], + SponsoredProductCampaigns, + ), + ( + ["enabled"], + SponsoredProductCampaigns, + ), + ( + None, + SponsoredProductCampaigns, + ), + ( + ["enabled", "archived", "paused"], + SponsoredDisplayCampaigns, + ), + ( + ["enabled"], + SponsoredDisplayCampaigns, + ), + ( + None, + SponsoredDisplayCampaigns, + ), + ], +) +def test_streams_state_filter(mocker, config, state_filter, stream_class): + profiles = make_profiles() + mocker.patch.object(stream_class, "state_filter", new_callable=mocker.PropertyMock, return_value=state_filter) + + stream = stream_class(config, profiles) + params = stream.request_params(stream_state=None, stream_slice=None, next_page_token=None) + if "stateFilter" in params: + assert params["stateFilter"] == ",".join(state_filter) + else: + assert state_filter is None diff --git a/docs/integrations/sources/amazon-ads.md b/docs/integrations/sources/amazon-ads.md index 00bd74d74e9a..acdb987934c4 100644 --- a/docs/integrations/sources/amazon-ads.md +++ b/docs/integrations/sources/amazon-ads.md @@ -91,6 +91,7 @@ Information about expected report generation waiting time you may find [here](ht | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------| +| 0.1.24 | 2022-10-19 | [17475](https://github.com/airbytehq/airbyte/pull/17475) | Add filters for state on brand, product and display campaigns | | 0.1.23 | 2022-09-06 | [16342](https://github.com/airbytehq/airbyte/pull/16342) | Add attribution reports | | 0.1.22 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream state. | | 0.1.21 | 2022-09-27 | [17202](https://github.com/airbytehq/airbyte/pull/17202) | Improved handling if known reporting errors | From ad29513a43418852b05d077c557a5c0cab4f2757 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Thu, 20 Oct 2022 13:23:06 -0400 Subject: [PATCH 231/498] CDK: Handle config validation errors as config_error and failed check status (#18214) * schema helpers raises a config_error * traced exceptions can be turned into connection status messages * add tests for schema helpers * return a failed status message rather than throwing in check command * remove unused imports * add comment * bump version / update changelog --- airbyte-cdk/python/CHANGELOG.md | 4 +++ .../airbyte_cdk/destinations/destination.py | 10 ++++++- airbyte-cdk/python/airbyte_cdk/entrypoint.py | 10 ++++++- .../sources/utils/schema_helpers.py | 9 +++++-- .../airbyte_cdk/utils/traced_exception.py | 17 +++++++++++- airbyte-cdk/python/setup.py | 2 +- .../sources/utils/test_schema_helpers.py | 16 +++++++----- .../python/unit_tests/test_entrypoint.py | 10 ++++--- .../unit_tests/utils/test_traced_exception.py | 26 ++++++++++++++++++- 9 files changed, 88 insertions(+), 16 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index c31ff880d91a..d884a2f0369b 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.2 +- Report config validation errors as failed connection status during `check`. +- Report config validation errors as `config_error` failure type. + ## 0.2.1 - Low-code: Always convert stream slices output to an iterator diff --git a/airbyte-cdk/python/airbyte_cdk/destinations/destination.py b/airbyte-cdk/python/airbyte_cdk/destinations/destination.py index 97bb381113bd..73411e73c82a 100644 --- a/airbyte-cdk/python/airbyte_cdk/destinations/destination.py +++ b/airbyte-cdk/python/airbyte_cdk/destinations/destination.py @@ -13,6 +13,7 @@ from airbyte_cdk.exception_handler import init_uncaught_exception_handler from airbyte_cdk.models import AirbyteMessage, ConfiguredAirbyteCatalog, Type from airbyte_cdk.sources.utils.schema_helpers import check_config_against_spec_or_exit +from airbyte_cdk.utils.traced_exception import AirbyteTracedException from pydantic import ValidationError logger = logging.getLogger("airbyte") @@ -95,7 +96,14 @@ def run_cmd(self, parsed_args: argparse.Namespace) -> Iterable[AirbyteMessage]: return config = self.read_config(config_path=parsed_args.config) if self.check_config_against_spec or cmd == "check": - check_config_against_spec_or_exit(config, spec) + try: + check_config_against_spec_or_exit(config, spec) + except AirbyteTracedException as traced_exc: + connection_status = traced_exc.as_connection_status_message() + if connection_status and cmd == "check": + yield connection_status.json(exclude_unset=True) + return + raise traced_exc if cmd == "check": yield self._run_check(config=config) diff --git a/airbyte-cdk/python/airbyte_cdk/entrypoint.py b/airbyte-cdk/python/airbyte_cdk/entrypoint.py index 7d57dc7e9e24..b3b853429989 100644 --- a/airbyte-cdk/python/airbyte_cdk/entrypoint.py +++ b/airbyte-cdk/python/airbyte_cdk/entrypoint.py @@ -18,6 +18,7 @@ from airbyte_cdk.sources import Source from airbyte_cdk.sources.utils.schema_helpers import check_config_against_spec_or_exit, split_config from airbyte_cdk.utils.airbyte_secrets_utils import get_secrets, update_secrets +from airbyte_cdk.utils.traced_exception import AirbyteTracedException logger = init_logger("airbyte") @@ -93,7 +94,14 @@ def run(self, parsed_args: argparse.Namespace) -> Iterable[str]: # jsonschema's additionalProperties flag wont fail the validation connector_config, _ = split_config(config) if self.source.check_config_against_spec or cmd == "check": - check_config_against_spec_or_exit(connector_config, source_spec) + try: + check_config_against_spec_or_exit(connector_config, source_spec) + except AirbyteTracedException as traced_exc: + connection_status = traced_exc.as_connection_status_message() + if connection_status and cmd == "check": + yield connection_status.json(exclude_unset=True) + return + raise traced_exc if cmd == "check": check_result = self.source.check(self.logger, config) diff --git a/airbyte-cdk/python/airbyte_cdk/sources/utils/schema_helpers.py b/airbyte-cdk/python/airbyte_cdk/sources/utils/schema_helpers.py index be8e257d600a..aea02ecec950 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/utils/schema_helpers.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/utils/schema_helpers.py @@ -10,7 +10,8 @@ from typing import Any, ClassVar, Dict, List, Mapping, MutableMapping, Optional, Tuple, Union import jsonref -from airbyte_cdk.models import ConnectorSpecification +from airbyte_cdk.models import ConnectorSpecification, FailureType +from airbyte_cdk.utils.traced_exception import AirbyteTracedException from jsonschema import RefResolver, validate from jsonschema.exceptions import ValidationError from pydantic import BaseModel, Field @@ -157,7 +158,11 @@ def check_config_against_spec_or_exit(config: Mapping[str, Any], spec: Connector try: validate(instance=config, schema=spec_schema) except ValidationError as validation_error: - raise Exception("Config validation error: " + validation_error.message) from None + raise AirbyteTracedException( + message="Config validation error: " + validation_error.message, + internal_message=validation_error.message, + failure_type=FailureType.config_error, + ) from None # required to prevent logging config secrets from the ValidationError's stacktrace class InternalConfig(BaseModel): diff --git a/airbyte-cdk/python/airbyte_cdk/utils/traced_exception.py b/airbyte-cdk/python/airbyte_cdk/utils/traced_exception.py index af8bad293ff0..985a91dd7973 100644 --- a/airbyte-cdk/python/airbyte_cdk/utils/traced_exception.py +++ b/airbyte-cdk/python/airbyte_cdk/utils/traced_exception.py @@ -5,7 +5,15 @@ import traceback from datetime import datetime -from airbyte_cdk.models import AirbyteErrorTraceMessage, AirbyteMessage, AirbyteTraceMessage, FailureType, TraceType +from airbyte_cdk.models import ( + AirbyteConnectionStatus, + AirbyteErrorTraceMessage, + AirbyteMessage, + AirbyteTraceMessage, + FailureType, + Status, + TraceType, +) from airbyte_cdk.models import Type as MessageType from airbyte_cdk.utils.airbyte_secrets_utils import filter_secrets @@ -56,6 +64,13 @@ def as_airbyte_message(self) -> AirbyteMessage: return AirbyteMessage(type=MessageType.TRACE, trace=trace_message) + def as_connection_status_message(self) -> AirbyteMessage: + if self.failure_type == FailureType.config_error: + output_message = AirbyteMessage( + type=MessageType.CONNECTION_STATUS, connectionStatus=AirbyteConnectionStatus(status=Status.FAILED, message=self.message) + ) + return output_message + def emit_message(self): """ Prints the exception as an AirbyteTraceMessage. diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index e47495492f9b..36dfdbbe0355 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.2.1", + version="0.2.2", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/utils/test_schema_helpers.py b/airbyte-cdk/python/unit_tests/sources/utils/test_schema_helpers.py index 55328fed0f2a..2274ffb02ae5 100644 --- a/airbyte-cdk/python/unit_tests/sources/utils/test_schema_helpers.py +++ b/airbyte-cdk/python/unit_tests/sources/utils/test_schema_helpers.py @@ -13,8 +13,9 @@ import jsonref from airbyte_cdk.logger import AirbyteLogger -from airbyte_cdk.models.airbyte_protocol import ConnectorSpecification +from airbyte_cdk.models.airbyte_protocol import ConnectorSpecification, FailureType from airbyte_cdk.sources.utils.schema_helpers import ResourceSchemaLoader, check_config_against_spec_or_exit +from airbyte_cdk.utils.traced_exception import AirbyteTracedException from pytest import fixture from pytest import raises as pytest_raises @@ -57,12 +58,15 @@ def spec_object(): def test_check_config_against_spec_or_exit_does_not_print_schema(capsys, spec_object): config = {"super_secret_token": "really_a_secret"} - with pytest_raises(Exception) as ex_info: + + with pytest_raises(AirbyteTracedException) as ex_info: check_config_against_spec_or_exit(config, spec_object) - exc = ex_info.value - traceback.print_exception(type(exc), exc, exc.__traceback__) - out, err = capsys.readouterr() - assert "really_a_secret" not in out + err + + exc = ex_info.value + traceback.print_exception(type(exc), exc, exc.__traceback__) + out, err = capsys.readouterr() + assert "really_a_secret" not in out + err + assert exc.failure_type == FailureType.config_error, "failure_type should be config_error" def test_should_not_fail_validation_for_valid_config(spec_object): diff --git a/airbyte-cdk/python/unit_tests/test_entrypoint.py b/airbyte-cdk/python/unit_tests/test_entrypoint.py index a71cd40eadd4..dd7910721b2b 100644 --- a/airbyte-cdk/python/unit_tests/test_entrypoint.py +++ b/airbyte-cdk/python/unit_tests/test_entrypoint.py @@ -155,12 +155,16 @@ def test_config_validate(entrypoint: AirbyteEntrypoint, mocker, config_mock, sch check_value = AirbyteConnectionStatus(status=Status.SUCCEEDED) mocker.patch.object(MockSource, "check", return_value=check_value) mocker.patch.object(MockSource, "spec", return_value=ConnectorSpecification(connectionSpecification=schema)) + + messages = list(entrypoint.run(parsed_args)) if config_valid: - messages = list(entrypoint.run(parsed_args)) assert [_wrap_message(check_value)] == messages else: - with pytest.raises(Exception, match=r"(?i)Config Validation Error:.*"): - list(entrypoint.run(parsed_args)) + assert len(messages) == 1 + airbyte_message = AirbyteMessage.parse_raw(messages[0]) + assert airbyte_message.type == Type.CONNECTION_STATUS + assert airbyte_message.connectionStatus.status == Status.FAILED + assert airbyte_message.connectionStatus.message.startswith("Config validation error:") def test_run_check(entrypoint: AirbyteEntrypoint, mocker, spec_mock, config_mock): diff --git a/airbyte-cdk/python/unit_tests/utils/test_traced_exception.py b/airbyte-cdk/python/unit_tests/utils/test_traced_exception.py index c95d0bf48dcf..b27b5d69ae9d 100644 --- a/airbyte-cdk/python/unit_tests/utils/test_traced_exception.py +++ b/airbyte-cdk/python/unit_tests/utils/test_traced_exception.py @@ -5,7 +5,14 @@ import json import pytest -from airbyte_cdk.models.airbyte_protocol import AirbyteErrorTraceMessage, AirbyteMessage, AirbyteTraceMessage, FailureType, TraceType +from airbyte_cdk.models.airbyte_protocol import ( + AirbyteErrorTraceMessage, + AirbyteMessage, + AirbyteTraceMessage, + FailureType, + Status, + TraceType, +) from airbyte_cdk.models.airbyte_protocol import Type as MessageType from airbyte_cdk.utils.traced_exception import AirbyteTracedException @@ -55,6 +62,23 @@ def test_existing_exception_as_airbyte_message(raised_exception): ) +def test_config_error_as_connection_status_message(): + traced_exc = AirbyteTracedException("an internal message", message="Config validation error", failure_type=FailureType.config_error) + airbyte_message = traced_exc.as_connection_status_message() + + assert type(airbyte_message) == AirbyteMessage + assert airbyte_message.type == MessageType.CONNECTION_STATUS + assert airbyte_message.connectionStatus.status == Status.FAILED + assert airbyte_message.connectionStatus.message == "Config validation error" + + +def test_other_error_as_connection_status_message(): + traced_exc = AirbyteTracedException("an internal message", failure_type=FailureType.system_error) + airbyte_message = traced_exc.as_connection_status_message() + + assert airbyte_message is None + + def test_emit_message(capsys): traced_exc = AirbyteTracedException( internal_message="internal message", message="user-friendly message", exception=RuntimeError("oh no") From ec304fd3769163e2d11b92e702c8198bf5c145d3 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 20 Oct 2022 10:41:48 -0700 Subject: [PATCH 232/498] Disable basic auth for frontend acceptance tests (#18255) * Disable auth for frontend acceptance tests * Update auth flags --- tools/bin/e2e_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/bin/e2e_test.sh b/tools/bin/e2e_test.sh index 68feb4d56a7a..59d8e4ef3fed 100755 --- a/tools/bin/e2e_test.sh +++ b/tools/bin/e2e_test.sh @@ -15,7 +15,7 @@ echo "Starting app..." mkdir -p /tmp/airbyte_local # Detach so we can run subsequent commands -VERSION=dev TRACKING_STRATEGY=logging docker-compose up -d +VERSION=dev BASIC_AUTH_USERNAME="" BASIC_AUTH_PASSWORD="" TRACKING_STRATEGY=logging docker-compose up -d # Uncomment for debugging. Warning, this is verbose. # trap 'echo "docker-compose logs:" && docker-compose logs -t --tail 1000 && docker-compose down && docker stop airbyte_ci_pg' EXIT From 035f353b438475319236b85fbba56456548031f9 Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 20 Oct 2022 11:25:47 -0700 Subject: [PATCH 233/498] Add basic auth notes to README (#18264) --- README.md | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b25170efa749..970a661cc62a 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ The new open-source standard to sync data from applications, APIs & databases to Airbyte is on a mission to make data integration pipelines a commodity. -* **Maintenance-free connectors you can use in minutes**. Just authenticate your sources and warehouse, and get connectors that adapt to schema and API changes for you. -* **Building new connectors made trivial.** We make it very easy to add new connectors that you need, using the language of your choice, by offering scheduling and orchestration. -* Designed to **cover the long tail of connectors and needs**. Benefit from the community's battle-tested connectors and adapt them to your specific needs. -* **Your data stays in your cloud**. Have full control over your data, and the costs of your data transfers. -* **No more security compliance process** to go through as Airbyte is self-hosted. -* **No more pricing indexed on volume**, as cloud-based solutions offer. +- **Maintenance-free connectors you can use in minutes**. Just authenticate your sources and warehouse, and get connectors that adapt to schema and API changes for you. +- **Building new connectors made trivial.** We make it very easy to add new connectors that you need, using the language of your choice, by offering scheduling and orchestration. +- Designed to **cover the long tail of connectors and needs**. Benefit from the community's battle-tested connectors and adapt them to your specific needs. +- **Your data stays in your cloud**. Have full control over your data, and the costs of your data transfers. +- **No more security compliance process** to go through as Airbyte is self-hosted. +- **No more pricing indexed on volume**, as cloud-based solutions offer. Here's a list of our [connectors with their health status](docs/integrations/). @@ -24,19 +24,19 @@ cd airbyte docker-compose up ``` -Now visit [http://localhost:8000](http://localhost:8000) +Now visit [http://localhost:8000](http://localhost:8000). You will be asked for a username (default: `airbyte`) and password (default: `password`). You should update these values by changing `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` in your local `.env` file. Here is a [step-by-step guide](https://github.com/airbytehq/airbyte/tree/e378d40236b6a34e1c1cb481c8952735ec687d88/docs/quickstart/getting-started.md) showing you how to load data from an API into a file, all on your computer. ## Features -* **Built for extensibility**: Adapt an existing connector to your needs or build a new one with ease. -* **Optional normalized schemas**: Entirely customizable, start with raw data or from some suggestion of normalized data. -* **Full-grade scheduler**: Automate your replications with the frequency you need. -* **Real-time monitoring**: We log all errors in full detail to help you understand. -* **Incremental updates**: Automated replications are based on incremental updates to reduce your data transfer costs. -* **Manual full refresh**: Sometimes, you need to re-sync all your data to start again. -* **Debugging autonomy**: Modify and debug pipelines as you see fit, without waiting. +- **Built for extensibility**: Adapt an existing connector to your needs or build a new one with ease. +- **Optional normalized schemas**: Entirely customizable, start with raw data or from some suggestion of normalized data. +- **Full-grade scheduler**: Automate your replications with the frequency you need. +- **Real-time monitoring**: We log all errors in full detail to help you understand. +- **Incremental updates**: Automated replications are based on incremental updates to reduce your data transfer costs. +- **Manual full refresh**: Sometimes, you need to re-sync all your data to start again. +- **Debugging autonomy**: Modify and debug pipelines as you see fit, without waiting. [See more on our website.](https://airbyte.io/features/) @@ -54,13 +54,14 @@ See our [Contributing guide](docs/contributing-to-airbyte/README.md) on how to g For general help using Airbyte, please refer to the official Airbyte documentation. For additional help, you can use one of these channels to ask a question: -* [Slack](https://slack.airbyte.io) \(For live discussion with the Community and Airbyte team\) -* [Forum](https://discuss.airbyte.io/) \(For deeper conversations about features, connectors, or problems\) -* [GitHub](https://github.com/airbytehq/airbyte) \(Bug reports, Contributions\) -* [Twitter](https://twitter.com/airbytehq) \(Get the news fast\) -* [Weekly office hours](https://airbyte.io/weekly-office-hours/) \(Live informal 30-minute video call sessions with the Airbyte team\) +- [Slack](https://slack.airbyte.io) \(For live discussion with the Community and Airbyte team\) +- [Forum](https://discuss.airbyte.io/) \(For deeper conversations about features, connectors, or problems\) +- [GitHub](https://github.com/airbytehq/airbyte) \(Bug reports, Contributions\) +- [Twitter](https://twitter.com/airbytehq) \(Get the news fast\) +- [Weekly office hours](https://airbyte.io/weekly-office-hours/) \(Live informal 30-minute video call sessions with the Airbyte team\) ## Reporting Vulnerabilities + ⚠️ Please do not file GitHub issues or post on our public forum for security vulnerabilities as they are public! ⚠️ Airbyte takes security issues very seriously. If you have any concerns about Airbyte or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@airbyte.io. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible. From 9d20fa3a30f09f7864653c75533d7b7419c87ca7 Mon Sep 17 00:00:00 2001 From: Jimmy Ma Date: Thu, 20 Oct 2022 11:48:36 -0700 Subject: [PATCH 234/498] Ensure we have protocol version on actor def reads (#18206) * Ensure we have protocol version on actor def reads * Fix null ref --- .../config/persistence/ConfigRepository.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 270aaf4710f2..3d48f93cecaf 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -190,10 +190,16 @@ public void setFeedback(final UUID workflowId) throws JsonValidationException, C public StandardSourceDefinition getStandardSourceDefinition(final UUID sourceDefinitionId) throws JsonValidationException, IOException, ConfigNotFoundException { - return persistence.getConfig( + final StandardSourceDefinition sourceDef = persistence.getConfig( ConfigSchema.STANDARD_SOURCE_DEFINITION, sourceDefinitionId.toString(), StandardSourceDefinition.class); + // Make sure we have a default version of the Protocol. + // This corner case may happen for connectors that haven't been upgraded since we added versioning. + if (sourceDef != null) { + return sourceDef.withProtocolVersion(AirbyteProtocolVersion.getWithDefault(sourceDef.getProtocolVersion()).serialize()); + } + return null; } public StandardSourceDefinition getSourceDefinitionFromSource(final UUID sourceId) { @@ -302,8 +308,15 @@ public void deleteSourceDefinitionAndAssociations(final UUID sourceDefinitionId) public StandardDestinationDefinition getStandardDestinationDefinition(final UUID destinationDefinitionId) throws JsonValidationException, IOException, ConfigNotFoundException { - return persistence.getConfig(ConfigSchema.STANDARD_DESTINATION_DEFINITION, destinationDefinitionId.toString(), - StandardDestinationDefinition.class); + final StandardDestinationDefinition destDef = + persistence.getConfig(ConfigSchema.STANDARD_DESTINATION_DEFINITION, destinationDefinitionId.toString(), + StandardDestinationDefinition.class); + // Make sure we have a default version of the Protocol. + // This corner case may happen for connectors that haven't been upgraded since we added versioning. + if (destDef != null) { + return destDef.withProtocolVersion(AirbyteProtocolVersion.getWithDefault(destDef.getProtocolVersion()).serialize()); + } + return null; } public StandardDestinationDefinition getDestinationDefinitionFromDestination(final UUID destinationId) { From f861a7580c36f1e79b63728e9b688d3494d40640 Mon Sep 17 00:00:00 2001 From: Henri Blancke Date: Thu, 20 Oct 2022 15:17:28 -0400 Subject: [PATCH 235/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20Insightl?= =?UTF-8?q?y=20[python=20cdk]=20(#18164)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ADD] logic for source insightly Signed-off-by: Henri Blancke * [UPD] schema cleanup Signed-off-by: Henri Blancke * [ADD] documentation Signed-off-by: Henri Blancke * [UPD] configured catalog Co-authored-by: Marcos Marx * [RMV] catalog and logs Signed-off-by: Henri Blancke * [UPD] source tests Signed-off-by: Henri Blancke * format files * fix pk * add seed config * auto-bump connector version Signed-off-by: Henri Blancke Co-authored-by: Marcos Marx Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 7 + .../src/main/resources/seed/source_specs.yaml | 33 ++ airbyte-integrations/builds.md | 1 + .../connectors/source-insightly/.dockerignore | 6 + .../connectors/source-insightly/Dockerfile | 38 ++ .../connectors/source-insightly/README.md | 132 ++++++ .../acceptance-test-config.yml | 24 ++ .../acceptance-test-docker.sh | 16 + .../connectors/source-insightly/bootstrap.md | 13 + .../connectors/source-insightly/build.gradle | 9 + .../integration_tests/__init__.py | 3 + .../integration_tests/abnormal_state.json | 13 + .../integration_tests/acceptance.py | 16 + .../integration_tests/configured_catalog.json | 26 ++ .../integration_tests/invalid_config.json | 4 + .../integration_tests/sample_config.json | 4 + .../integration_tests/sample_state.json | 13 + .../connectors/source-insightly/main.py | 13 + .../source-insightly/requirements.txt | 2 + .../connectors/source-insightly/setup.py | 29 ++ .../source_insightly/__init__.py | 8 + .../schemas/activity_sets.json | 122 ++++++ .../source_insightly/schemas/contacts.json | 204 +++++++++ .../source_insightly/schemas/countries.json | 9 + .../source_insightly/schemas/currencies.json | 12 + .../source_insightly/schemas/emails.json | 85 ++++ .../source_insightly/schemas/events.json | 96 +++++ .../schemas/knowledge_article_categories.json | 26 ++ .../schemas/knowledge_article_folders.json | 32 ++ .../schemas/knowledge_articles.json | 99 +++++ .../schemas/lead_sources.json | 18 + .../schemas/lead_statuses.json | 21 + .../source_insightly/schemas/leads.json | 179 ++++++++ .../source_insightly/schemas/milestones.json | 39 ++ .../source_insightly/schemas/notes.json | 61 +++ .../schemas/opportunities.json | 148 +++++++ .../schemas/opportunity_categories.json | 18 + .../schemas/opportunity_products.json | 64 +++ .../schemas/opportunity_state_reasons.json | 15 + .../schemas/organisations.json | 185 ++++++++ .../schemas/pipeline_stages.json | 24 ++ .../source_insightly/schemas/pipelines.json | 21 + .../schemas/pricebook_entries.json | 52 +++ .../source_insightly/schemas/pricebooks.json | 38 ++ .../source_insightly/schemas/products.json | 64 +++ .../schemas/project_categories.json | 18 + .../source_insightly/schemas/projects.json | 124 ++++++ .../source_insightly/schemas/prospects.json | 140 ++++++ .../schemas/quote_products.json | 67 +++ .../source_insightly/schemas/quotes.json | 137 ++++++ .../schemas/relationships.json | 27 ++ .../source_insightly/schemas/tags.json | 9 + .../schemas/task_categories.json | 18 + .../source_insightly/schemas/tasks.json | 153 +++++++ .../schemas/team_members.json | 15 + .../source_insightly/schemas/teams.json | 26 ++ .../source_insightly/schemas/tickets.json | 86 ++++ .../source_insightly/schemas/users.json | 68 +++ .../source_insightly/source.py | 404 ++++++++++++++++++ .../source_insightly/spec.json | 25 ++ .../source-insightly/unit_tests/__init__.py | 3 + .../unit_tests/test_incremental_streams.py | 63 +++ .../unit_tests/test_source.py | 56 +++ .../unit_tests/test_streams.py | 126 ++++++ docs/integrations/README.md | 1 + docs/integrations/sources/insightly.md | 74 ++++ 66 files changed, 3682 insertions(+) create mode 100644 airbyte-integrations/connectors/source-insightly/.dockerignore create mode 100644 airbyte-integrations/connectors/source-insightly/Dockerfile create mode 100644 airbyte-integrations/connectors/source-insightly/README.md create mode 100644 airbyte-integrations/connectors/source-insightly/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-insightly/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-insightly/bootstrap.md create mode 100644 airbyte-integrations/connectors/source-insightly/build.gradle create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/abnormal_state.json create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-insightly/integration_tests/sample_state.json create mode 100644 airbyte-integrations/connectors/source-insightly/main.py create mode 100644 airbyte-integrations/connectors/source-insightly/requirements.txt create mode 100644 airbyte-integrations/connectors/source-insightly/setup.py create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/__init__.py create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/activity_sets.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/contacts.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/countries.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/currencies.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/emails.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/events.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_categories.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_folders.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_articles.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_sources.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_statuses.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/leads.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/milestones.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/notes.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunities.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_categories.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_products.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_state_reasons.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/organisations.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipeline_stages.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipelines.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebook_entries.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebooks.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/products.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/project_categories.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/projects.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/prospects.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quote_products.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quotes.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/relationships.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tags.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/task_categories.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/team_members.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/teams.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tickets.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/schemas/users.json create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/source.py create mode 100644 airbyte-integrations/connectors/source-insightly/source_insightly/spec.json create mode 100644 airbyte-integrations/connectors/source-insightly/unit_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-insightly/unit_tests/test_incremental_streams.py create mode 100644 airbyte-integrations/connectors/source-insightly/unit_tests/test_source.py create mode 100644 airbyte-integrations/connectors/source-insightly/unit_tests/test_streams.py create mode 100644 docs/integrations/sources/insightly.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 6e73e42baae8..4f8608cba5f6 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -499,6 +499,13 @@ icon: db2.svg sourceType: database releaseStage: alpha +- name: Insightly + sourceDefinitionId: 38f84314-fe6a-4257-97be-a8dcd942d693 + dockerRepository: airbyte/source-insightly + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/insightly + sourceType: api + releaseStage: alpha - name: Instagram sourceDefinitionId: 6acf6b55-4f1e-4fca-944e-1a3caef8aba8 dockerRepository: airbyte/source-instagram diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 1a08b6b37450..ea9316dbe684 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -5004,6 +5004,39 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-insightly:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/sources/insightly" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Insightly Spec" + type: "object" + required: + - "token" + - "start_date" + additionalProperties: true + properties: + token: + type: + - "string" + - "null" + title: "API Token" + description: "Your Insightly API token." + airbyte_secret: true + start_date: + type: + - "string" + - "null" + title: "Start Date" + description: "The date from which you'd like to replicate data for Insightly\ + \ in the format YYYY-MM-DDT00:00:00Z. All data generated after this date\ + \ will be replicated. Note that it will be used only for incremental streams." + examples: + - "2021-03-01T00:00:00Z" + pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-instagram:1.0.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/instagram" diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 7af75b786c27..398a014ee215 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -50,6 +50,7 @@ | Greenhouse | [![source-greenhouse](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-greenhouse%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-greenhouse) | | HubSpot | [![source-hubspot](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-hubspot%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-hubspot) | | IBM Db2 | [![source-db2](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-db2%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-db2) | +| Insightly | [![source-insightly](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-insightly%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-insightly) | | Instagram | [![source-instagram](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-instagram%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-instagram) | | Intercom | [![source-intercom](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-intercom-singer%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-intercom) | | Iterable | [![source-iterable](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-iterable%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-iterable) | diff --git a/airbyte-integrations/connectors/source-insightly/.dockerignore b/airbyte-integrations/connectors/source-insightly/.dockerignore new file mode 100644 index 000000000000..83cb1d2730f6 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_insightly +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-insightly/Dockerfile b/airbyte-integrations/connectors/source-insightly/Dockerfile new file mode 100644 index 000000000000..37b76fba0d19 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.13-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_insightly ./source_insightly + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-insightly diff --git a/airbyte-integrations/connectors/source-insightly/README.md b/airbyte-integrations/connectors/source-insightly/README.md new file mode 100644 index 000000000000..bd9f9dc0778f --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/README.md @@ -0,0 +1,132 @@ +# Insightly Source + +This is the repository for the Insightly source connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/insightly). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.9.0` + +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python -m venv .venv +``` + +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +pip install '.[tests]' +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-insightly:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/insightly) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_insightly/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source insightly test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-insightly:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-insightly:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-insightly:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-insightly:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-insightly:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-insightly:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-insightly:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-insightly:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-insightly/acceptance-test-config.yml b/airbyte-integrations/connectors/source-insightly/acceptance-test-config.yml new file mode 100644 index 000000000000..afadd4a05794 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/acceptance-test-config.yml @@ -0,0 +1,24 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-insightly:dev +tests: + spec: + - spec_path: "source_insightly/spec.json" + connection: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + incremental: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + future_state_path: "integration_tests/abnormal_state.json" + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-insightly/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-insightly/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-insightly/bootstrap.md b/airbyte-integrations/connectors/source-insightly/bootstrap.md new file mode 100644 index 000000000000..d52b29577dea --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/bootstrap.md @@ -0,0 +1,13 @@ +# Insightly +OpenWeather is an online service offering an API to retrieve historical, current and forecasted weather data over the globe. + +### Auth +API calls are authenticated through an API key. An API key can be retrieved from Insightly User Settings page in the API section. + +### Rate limits +The API has different rate limits for different account types. Keep that in mind when syncing large amounts of data: +* Free/Gratis - 1,000 requests/day/instance +* Legacy plans - 20,000 requests/day/instance +* Plus - 40,000 requests/day/instance +* Professional - 60,000 requests/day/instance +* Enterprise - 100,000 requests/day/instance diff --git a/airbyte-integrations/connectors/source-insightly/build.gradle b/airbyte-integrations/connectors/source-insightly/build.gradle new file mode 100644 index 000000000000..e3b780080d17 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_insightly' +} diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/__init__.py b/airbyte-integrations/connectors/source-insightly/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-insightly/integration_tests/abnormal_state.json new file mode 100644 index 000000000000..c06e9d0a75c0 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/abnormal_state.json @@ -0,0 +1,13 @@ +[ + { + "type": "STREAM", + "stream": { + "stream_descriptor": { + "name": "users" + }, + "stream_state": { + "DATE_UPDATED_UTC": "2122-10-17T19:10:14+00:00" + } + } + } +] diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-insightly/integration_tests/acceptance.py new file mode 100644 index 000000000000..1302b2f57e10 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/acceptance.py @@ -0,0 +1,16 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + # TODO: setup test dependencies if needed. otherwise remove the TODO comments + yield + # TODO: clean up test dependencies diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..1f4508d73195 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json @@ -0,0 +1,26 @@ +{ + "streams": [ + { + "stream": { + "name": "team_members", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"], + "source_defined_primary_key": [["MEMBER_USER_ID"]] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "users", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["DATE_UPDATED_UTC"], + "source_defined_primary_key": [["USER_ID"]] + }, + "sync_mode": "incremental", + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-insightly/integration_tests/invalid_config.json new file mode 100644 index 000000000000..ac0271de4fbd --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/invalid_config.json @@ -0,0 +1,4 @@ +{ + "token": "bad-token", + "start_date": "2019-01-01T00:00:00Z" +} diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-insightly/integration_tests/sample_config.json new file mode 100644 index 000000000000..2dd9a2f7b016 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/sample_config.json @@ -0,0 +1,4 @@ +{ + "token": "my-insightly-api-token", + "start_date": "2022-10-01T00:00:00Z" +} diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-insightly/integration_tests/sample_state.json new file mode 100644 index 000000000000..3127937e0f0e --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/sample_state.json @@ -0,0 +1,13 @@ +[ + { + "type": "STREAM", + "stream": { + "stream_descriptor": { + "name": "users" + }, + "stream_state": { + "DATE_UPDATED_UTC": "2022-10-17T19:10:14+00:00" + } + } + } +] diff --git a/airbyte-integrations/connectors/source-insightly/main.py b/airbyte-integrations/connectors/source-insightly/main.py new file mode 100644 index 000000000000..4cf5bb05f50f --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_insightly import SourceInsightly + +if __name__ == "__main__": + source = SourceInsightly() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-insightly/requirements.txt b/airbyte-integrations/connectors/source-insightly/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-insightly/setup.py b/airbyte-integrations/connectors/source-insightly/setup.py new file mode 100644 index 000000000000..490ed64ed1c4 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.2", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_insightly", + description="Source implementation for Insightly.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/__init__.py b/airbyte-integrations/connectors/source-insightly/source_insightly/__init__.py new file mode 100644 index 000000000000..12a428b70751 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceInsightly + +__all__ = ["SourceInsightly"] diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/activity_sets.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/activity_sets.json new file mode 100644 index 000000000000..11436dde10ad --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/activity_sets.json @@ -0,0 +1,122 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "ACTIVITYSET_ID": { + "type": "integer" + }, + "NAME": { + "type": ["string", "null"] + }, + "FOR_CONTACTS": { + "type": ["boolean", "null"] + }, + "FOR_ORGANISATIONS": { + "type": ["boolean", "null"] + }, + "FOR_OPPORTUNITIES": { + "type": ["boolean", "null"] + }, + "FOR_PROJECTS": { + "type": ["boolean", "null"] + }, + "FOR_LEADS": { + "type": ["boolean", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "ACTIVITIES": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ACTIVITY_ID": { + "type": ["integer", "null"] + }, + "ACTIVITYSET_ID": { + "type": ["integer", "null"] + }, + "ACTIVITY_NAME": { + "type": ["string", "null"] + }, + "ACTIVITY_DETAILS": { + "type": ["string", "null"] + }, + "ACTIVITY_TYPE": { + "type": ["string", "null"] + }, + "CATEGORY_ID": { + "type": ["integer", "null"] + }, + "REMINDER": { + "type": ["boolean", "null"] + }, + "REMINDER_DAYS_BEFORE_DUE": { + "type": ["integer", "null"] + }, + "REMINDER_TIME": { + "type": ["string", "null"] + }, + "PUBLICLY_VISIBLE": { + "type": ["boolean", "null"] + }, + "OWNER_VISIBLE": { + "type": ["boolean", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "RESPONSIBLE_USER_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_TEAM_ID": { + "type": ["integer", "null"] + }, + "SKIP_SUN": { + "type": ["boolean", "null"] + }, + "SKIP_MON": { + "type": ["boolean", "null"] + }, + "SKIP_TUE": { + "type": ["boolean", "null"] + }, + "SKIP_WED": { + "type": ["boolean", "null"] + }, + "SKIP_THU": { + "type": ["boolean", "null"] + }, + "SKIP_FRI": { + "type": ["boolean", "null"] + }, + "SKIP_SAT": { + "type": ["boolean", "null"] + }, + "DUE_DAYS_AFTER_START": { + "type": ["integer", "null"] + }, + "DUE_DAYS_BEFORE_END": { + "type": ["integer", "null"] + }, + "EVENT_DAYS_AFTER_START": { + "type": ["integer", "null"] + }, + "EVENT_DAYS_BEFORE_END": { + "type": ["integer", "null"] + }, + "EVENT_TIME": { + "type": ["string", "null"] + }, + "ALL_DAY": { + "type": ["boolean", "null"] + }, + "DURATION": { + "type": ["integer", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/contacts.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/contacts.json new file mode 100644 index 000000000000..dd0b5039fe01 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/contacts.json @@ -0,0 +1,204 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "CONTACT_ID": { + "type": "integer" + }, + "SALUTATION": { + "type": ["string", "null"] + }, + "FIRST_NAME": { + "type": ["string", "null"] + }, + "LAST_NAME": { + "type": ["string", "null"] + }, + "IMAGE_URL": { + "type": ["string", "null"] + }, + "BACKGROUND": { + "type": ["string", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "SOCIAL_LINKEDIN": { + "type": ["string", "null"] + }, + "SOCIAL_FACEBOOK": { + "type": ["string", "null"] + }, + "SOCIAL_TWITTER": { + "type": ["string", "null"] + }, + "DATE_OF_BIRTH": { + "type": ["string", "null"] + }, + "PHONE": { + "type": ["string", "null"] + }, + "PHONE_HOME": { + "type": ["string", "null"] + }, + "PHONE_MOBILE": { + "type": ["string", "null"] + }, + "PHONE_OTHER": { + "type": ["string", "null"] + }, + "PHONE_ASSISTANT": { + "type": ["string", "null"] + }, + "PHONE_FAX": { + "type": ["string", "null"] + }, + "EMAIL_ADDRESS": { + "type": ["string", "null"] + }, + "ASSISTANT_NAME": { + "type": ["string", "null"] + }, + "ADDRESS_MAIL_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_MAIL_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_MAIL_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_MAIL_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_MAIL_COUNTRY": { + "type": ["string", "null"] + }, + "ADDRESS_OTHER_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_OTHER_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_OTHER_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_OTHER_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_OTHER_COUNTRY": { + "type": ["string", "null"] + }, + "LAST_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "NEXT_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "ORGANISATION_ID": { + "type": ["integer", "null"] + }, + "TITLE": { + "type": ["string", "null"] + }, + "EMAIL_OPTED_OUT": { + "type": ["boolean", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + }, + "DATES": { + "type": "array", + "items": { + "type": "object", + "properties": { + "DATE_ID": { + "type": ["integer", "null"] + }, + "OCCASION_NAME": { + "type": ["string", "null"] + }, + "OCCASION_DATE": { + "type": ["string", "null"] + }, + "REPEAT_YEARLY": { + "type": ["boolean", "null"] + }, + "CREATE_TASK_YEARLY": { + "type": ["boolean", "null"] + } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/countries.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/countries.json new file mode 100644 index 000000000000..82516175877f --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/countries.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "COUNTRY_NAME": { + "type": "string" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/currencies.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/currencies.json new file mode 100644 index 000000000000..96e34e18b993 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/currencies.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "CURRENCY_CODE": { + "type": "string" + }, + "CURRENCY_SYMBOL": { + "type": ["string", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/emails.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/emails.json new file mode 100644 index 000000000000..a6ab10d8b40a --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/emails.json @@ -0,0 +1,85 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "EMAIL_ID": { + "type": "integer" + }, + "EMAIL_FROM": { + "type": ["string", "null"] + }, + "SUBJECT": { + "type": ["string", "null"] + }, + "EMAIL_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "FORMAT": { + "type": ["string", "null"] + }, + "SIZE": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "QUEUED_SEND_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/events.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/events.json new file mode 100644 index 000000000000..c09f58ca3afb --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/events.json @@ -0,0 +1,96 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "EVENT_ID": { + "type": "integer" + }, + "TITLE": { + "type": ["string", "null"] + }, + "LOCATION": { + "type": ["string", "null"] + }, + "START_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "END_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "ALL_DAY": { + "type": ["boolean", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "REMINDER_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "REMINDER_SENT": { + "type": ["boolean", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_categories.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_categories.json new file mode 100644 index 000000000000..9bdd9488ca21 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_categories.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "CATEGORY_ID": { + "type": "integer" + }, + "CATEGORY_NAME": { + "type": ["string", "null"] + }, + "DESCRIPTION": { + "type": ["string", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_folders.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_folders.json new file mode 100644 index 000000000000..6b3e171a05e1 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_article_folders.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "FOLDER_ID": { + "type": "integer" + }, + "FOLDER_NAME": { + "type": ["string", "null"] + }, + "CATEGORY_ID": { + "type": ["string", "null"] + }, + "VISIBILITY": { + "type": ["string", "null"] + }, + "ORDER_ARTICLES": { + "type": ["string", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_articles.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_articles.json new file mode 100644 index 000000000000..437899bdbfc1 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/knowledge_articles.json @@ -0,0 +1,99 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ARTICLE_ID": { + "type": "integer" + }, + "CATEGORY_ID": { + "type": ["integer", "null"] + }, + "FOLDER_ID": { + "type": ["integer", "null"] + }, + "ARTICLE_NO": { + "type": ["integer", "null"] + }, + "ArticleVersion": { + "type": ["string", "null"] + }, + "Status": { + "type": ["string", "null"] + }, + "Language": { + "type": ["string", "null"] + }, + "Title": { + "type": ["string", "null"] + }, + "Body": { + "type": ["string", "null"] + }, + "URL_SLUG": { + "type": ["string", "null"] + }, + "DOWNVOTE_COUNT": { + "type": ["integer", "null"] + }, + "UPVOTE_COUNT": { + "type": ["integer", "null"] + }, + "PROMOTED": { + "type": ["boolean", "null"] + }, + "FIRST_PUBLISHED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "LAST_PUBLISHED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "ARCHIVED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "ExternalLinkCount": { + "type": ["string", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_sources.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_sources.json new file mode 100644 index 000000000000..e16440465573 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_sources.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "LEAD_SOURCE_ID": { + "type": "integer" + }, + "LEAD_SOURCE": { + "type": ["string", "null"] + }, + "DEFAULT_VALUE": { + "type": ["boolean", "null"] + }, + "FIELD_ORDER": { + "type": ["integer", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_statuses.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_statuses.json new file mode 100644 index 000000000000..c6a889765836 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/lead_statuses.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "LEAD_STATUS_ID": { + "type": "integer" + }, + "LEAD_STATUS": { + "type": ["string", "null"] + }, + "DEFAULT_STATUS": { + "type": ["boolean", "null"] + }, + "STATUS_TYPE": { + "type": ["integer", "null"] + }, + "FIELD_ORDER": { + "type": ["integer", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/leads.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/leads.json new file mode 100644 index 000000000000..bfc7364f4660 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/leads.json @@ -0,0 +1,179 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "LEAD_ID": { + "type": "integer" + }, + "SALUTATION": { + "type": ["string", "null"] + }, + "FIRST_NAME": { + "type": ["string", "null"] + }, + "LAST_NAME": { + "type": ["string", "null"] + }, + "LEAD_SOURCE_ID": { + "type": ["integer", "null"] + }, + "LEAD_STATUS_ID": { + "type": ["integer", "null"] + }, + "TITLE": { + "type": ["string", "null"] + }, + "CONVERTED": { + "type": ["boolean", "null"] + }, + "CONVERTED_CONTACT_ID": { + "type": ["integer", "null"] + }, + "CONVERTED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CONVERTED_OPPORTUNITY_ID": { + "type": ["integer", "null"] + }, + "CONVERTED_ORGANISATION_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "EMAIL": { + "type": ["string", "null"] + }, + "EMPLOYEE_COUNT": { + "type": ["integer", "null"] + }, + "FAX": { + "type": ["string", "null"] + }, + "INDUSTRY": { + "type": ["string", "null"] + }, + "LEAD_DESCRIPTION": { + "type": ["string", "null"] + }, + "LEAD_RATING": { + "type": ["integer", "null"] + }, + "MOBILE": { + "type": ["string", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "PHONE": { + "type": ["string", "null"] + }, + "RESPONSIBLE_USER_ID": { + "type": ["integer", "null"] + }, + "WEBSITE": { + "type": ["string", "null"] + }, + "ADDRESS_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_COUNTRY": { + "type": ["string", "null"] + }, + "LAST_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "NEXT_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "ORGANISATION_NAME": { + "type": ["string", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "IMAGE_URL": { + "type": ["string", "null"] + }, + "EMAIL_OPTED_OUT": { + "type": ["boolean", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/milestones.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/milestones.json new file mode 100644 index 000000000000..374484668926 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/milestones.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "MILESTONE_ID": { + "type": "integer" + }, + "TITLE": { + "type": ["string", "null"] + }, + "COMPLETED": { + "type": ["boolean", "null"] + }, + "DUE_DATE": { + "type": ["string", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "COMPLETED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "PROJECT_ID": { + "type": ["integer", "null"] + }, + "RESPONSIBLE_USER": { + "type": ["integer", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/notes.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/notes.json new file mode 100644 index 000000000000..a0d500c14919 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/notes.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "NOTE_ID": { + "type": "integer" + }, + "TITLE": { + "type": ["string", "null"] + }, + "BODY": { + "type": ["string", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunities.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunities.json new file mode 100644 index 000000000000..e154e26090d2 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunities.json @@ -0,0 +1,148 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "OPPORTUNITY_ID": { + "type": "integer" + }, + "OPPORTUNITY_NAME": { + "type": ["string", "null"] + }, + "OPPORTUNITY_DETAILS": { + "type": ["string", "null"] + }, + "OPPORTUNITY_STATE": { + "type": ["string", "null"] + }, + "RESPONSIBLE_USER_ID": { + "type": ["integer", "null"] + }, + "CATEGORY_ID": { + "type": ["integer", "null"] + }, + "IMAGE_URL": { + "type": ["string", "null"] + }, + "BID_CURRENCY": { + "type": ["string", "null"] + }, + "BID_AMOUNT": { + "type": ["integer", "null"] + }, + "BID_TYPE": { + "type": ["string", "null"] + }, + "BID_DURATION": { + "type": ["integer", "null"] + }, + "ACTUAL_CLOSE_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "OPPORTUNITY_VALUE": { + "type": ["integer", "null"] + }, + "PROBABILITY": { + "type": ["integer", "null"] + }, + "FORECAST_CLOSE_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "LAST_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "NEXT_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "PIPELINE_ID": { + "type": ["integer", "null"] + }, + "STAGE_ID": { + "type": ["integer", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "ORGANISATION_ID": { + "type": ["integer", "null"] + }, + "PRICEBOOK_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + }, + "required": ["TAG_NAME"] + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_categories.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_categories.json new file mode 100644 index 000000000000..bfbb4f93292d --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_categories.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "CATEGORY_ID": { + "type": "integer" + }, + "CATEGORY_NAME": { + "type": ["string", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "BACKGROUND_COLOR": { + "type": ["string", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_products.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_products.json new file mode 100644 index 000000000000..8a684f0671fc --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_products.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "OPPORTUNITY_ITEM_ID": { + "type": "integer" + }, + "OPPORTUNITY_ID": { + "type": ["integer", "null"] + }, + "PRICEBOOK_ENTRY_ID": { + "type": ["integer", "null"] + }, + "CURRENCY_CODE": { + "type": ["string", "null"] + }, + "UNIT_PRICE": { + "type": ["integer", "null"] + }, + "DESCRIPTION": { + "type": ["string", "null"] + }, + "QUANTITY": { + "type": ["integer", "null"] + }, + "SERVICE_DATE": { + "type": ["string", "null"] + }, + "TOTAL_PRICE": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "LIST_PRICE": { + "type": ["integer", "null"] + }, + "SUBTOTAL": { + "type": ["integer", "null"] + }, + "DISCOUNT": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_state_reasons.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_state_reasons.json new file mode 100644 index 000000000000..c7f426c9e0b6 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/opportunity_state_reasons.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "STATE_REASON_ID": { + "type": "integer" + }, + "STATE_REASON": { + "type": ["string", "null"] + }, + "FOR_OPPORTUNITY_STATE": { + "type": ["string", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/organisations.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/organisations.json new file mode 100644 index 000000000000..b2dd05169110 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/organisations.json @@ -0,0 +1,185 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "ORGANISATION_ID": { + "type": "integer" + }, + "ORGANISATION_NAME": { + "type": ["string", "null"] + }, + "BACKGROUND": { + "type": ["string", "null"] + }, + "IMAGE_URL": { + "type": ["string", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "LAST_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "NEXT_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "PHONE": { + "type": ["string", "null"] + }, + "PHONE_FAX": { + "type": ["string", "null"] + }, + "WEBSITE": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_COUNTRY": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_SHIP_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_SHIP_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_SHIP_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_SHIP_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_SHIP_COUNTRY": { + "type": ["string", "null"] + }, + "SOCIAL_LINKEDIN": { + "type": ["string", "null"] + }, + "SOCIAL_FACEBOOK": { + "type": ["string", "null"] + }, + "SOCIAL_TWITTER": { + "type": ["string", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + }, + "DATES": { + "type": "array", + "items": { + "type": "object", + "properties": { + "DATE_ID": { + "type": ["integer", "null"] + }, + "OCCASION_NAME": { + "type": ["string", "null"] + }, + "OCCASION_DATE": { + "type": ["string", "null"] + }, + "REPEAT_YEARLY": { + "type": ["boolean", "null"] + }, + "CREATE_TASK_YEARLY": { + "type": ["boolean", "null"] + } + } + } + }, + "EMAILDOMAINS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "EMAIL_DOMAIN_ID": { + "type": ["integer", "null"] + }, + "EMAIL_DOMAIN": { + "type": ["string", "null"] + } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipeline_stages.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipeline_stages.json new file mode 100644 index 000000000000..1eff2cb51d24 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipeline_stages.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "STAGE_ID": { + "type": "integer" + }, + "PIPELINE_ID": { + "type": ["integer", "null"] + }, + "STAGE_NAME": { + "type": ["string", "null"] + }, + "STAGE_ORDER": { + "type": ["integer", "null"] + }, + "ACTIVITYSET_ID": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipelines.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipelines.json new file mode 100644 index 000000000000..158c1e64e9d7 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pipelines.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "PIPELINE_ID": { + "type": "integer" + }, + "PIPELINE_NAME": { + "type": ["string", "null"] + }, + "FOR_OPPORTUNITIES": { + "type": ["boolean", "null"] + }, + "FOR_PROJECTS": { + "type": ["boolean", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebook_entries.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebook_entries.json new file mode 100644 index 000000000000..1346e9792901 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebook_entries.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "PRICEBOOK_ENTRY_ID": { + "type": "integer" + }, + "PRICEBOOK_ID": { + "type": ["integer", "null"] + }, + "PRODUCT_ID": { + "type": ["integer", "null"] + }, + "CURRENCY_CODE": { + "type": ["string", "null"] + }, + "PRICE": { + "type": ["integer", "null"] + }, + "USE_STANDARD_PRICE": { + "type": ["boolean", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebooks.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebooks.json new file mode 100644 index 000000000000..d8a11459487d --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/pricebooks.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "PRICEBOOK_ID": { + "type": "integer" + }, + "NAME": { + "type": ["string", "null"] + }, + "DESCRIPTION": { + "type": ["string", "null"] + }, + "CURRENCY_CODE": { + "type": ["string", "null"] + }, + "IS_STANDARD": { + "type": ["boolean", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/products.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/products.json new file mode 100644 index 000000000000..19b7d6259dfa --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/products.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "PRODUCT_ID": { + "type": "integer" + }, + "PRODUCT_NAME": { + "type": ["string", "null"] + }, + "PRODUCT_CODE": { + "type": ["string", "null"] + }, + "PRODUCT_SKU": { + "type": ["string", "null"] + }, + "DESCRIPTION": { + "type": ["string", "null"] + }, + "PRODUCT_FAMILY": { + "type": ["string", "null"] + }, + "PRODUCT_IMAGE_URL": { + "type": ["string", "null"] + }, + "CURRENCY_CODE": { + "type": ["string", "null"] + }, + "DEFAULT_PRICE": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/project_categories.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/project_categories.json new file mode 100644 index 000000000000..bfbb4f93292d --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/project_categories.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "CATEGORY_ID": { + "type": "integer" + }, + "CATEGORY_NAME": { + "type": ["string", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "BACKGROUND_COLOR": { + "type": ["string", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/projects.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/projects.json new file mode 100644 index 000000000000..fa91ca651ef4 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/projects.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "PROJECT_ID": { + "type": "integer" + }, + "PROJECT_NAME": { + "type": ["string", "null"] + }, + "STATUS": { + "type": ["string", "null"] + }, + "PROJECT_DETAILS": { + "type": ["string", "null"] + }, + "STARTED_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "COMPLETED_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "OPPORTUNITY_ID": { + "type": ["integer", "null"] + }, + "CATEGORY_ID": { + "type": ["integer", "null"] + }, + "PIPELINE_ID": { + "type": ["integer", "null"] + }, + "STAGE_ID": { + "type": ["integer", "null"] + }, + "IMAGE_URL": { + "type": ["string", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "LAST_ACTIVITY_DATE_UTC": { + "type": ["string", "null"] + }, + "NEXT_ACTIVITY_DATE_UTC": { + "type": ["string", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "RESPONSIBLE_USER_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/prospects.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/prospects.json new file mode 100644 index 000000000000..a4debd505434 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/prospects.json @@ -0,0 +1,140 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "PROSPECT_ID": { + "type": "integer" + }, + "LEAD_ID": { + "type": ["integer", "null"] + }, + "CONTACT_ID": { + "type": ["integer", "null"] + }, + "ORGANISATION_ID": { + "type": ["integer", "null"] + }, + "SALUTATION": { + "type": ["string", "null"] + }, + "FIRST_NAME": { + "type": ["string", "null"] + }, + "LAST_NAME": { + "type": ["string", "null"] + }, + "ORGANISATION_NAME": { + "type": ["string", "null"] + }, + "TITLE": { + "type": ["string", "null"] + }, + "EMAIL_ADDRESS": { + "type": ["string", "null"] + }, + "PHONE": { + "type": ["string", "null"] + }, + "MOBILE": { + "type": ["string", "null"] + }, + "FAX": { + "type": ["string", "null"] + }, + "WEBSITE": { + "type": ["string", "null"] + }, + "ADDRESS_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_COUNTRY": { + "type": ["string", "null"] + }, + "INDUSTRY": { + "type": ["string", "null"] + }, + "EMPLOYEE_COUNT": { + "type": ["integer", "null"] + }, + "SCORE": { + "type": ["integer", "null"] + }, + "GRADE": { + "type": ["string", "null"] + }, + "DESCRIPTION": { + "type": ["string", "null"] + }, + "DO_NOT_EMAIL": { + "type": ["boolean", "null"] + }, + "DO_NOT_CALL": { + "type": ["boolean", "null"] + }, + "OPTED_OUT": { + "type": ["boolean", "null"] + }, + "LAST_ACTIVITY_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DO_NOT_SYNC": { + "type": ["boolean", "null"] + }, + "LEAD_CONVERSION_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "GRADE_PROFILE_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quote_products.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quote_products.json new file mode 100644 index 000000000000..dcff3f256b87 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quote_products.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "QUOTATION_ITEM_ID": { + "type": "integer" + }, + "QUOTE_ID": { + "type": ["integer", "null"] + }, + "OPPORTUNITY_ITEM_ID": { + "type": ["integer", "null"] + }, + "PRICEBOOK_ENTRY_ID": { + "type": ["integer", "null"] + }, + "DESCRIPTION": { + "type": ["string", "null"] + }, + "CURRENCY_CODE": { + "type": ["string", "null"] + }, + "QUANTITY": { + "type": ["integer", "null"] + }, + "LIST_PRICE": { + "type": ["integer", "null"] + }, + "UNIT_PRICE": { + "type": ["integer", "null"] + }, + "SUBTOTAL": { + "type": ["integer", "null"] + }, + "DISCOUNT": { + "type": ["integer", "null"] + }, + "TOTAL_PRICE": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "SORT_ORDER": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quotes.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quotes.json new file mode 100644 index 000000000000..5812e3484c0b --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/quotes.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "QUOTE_ID": { + "type": "integer" + }, + "QUOTATION_NAME": { + "type": ["string", "null"] + }, + "OPPORTUNITY_ID": { + "type": ["integer", "null"] + }, + "CONTACT_ID": { + "type": ["integer", "null"] + }, + "ORGANISATION_ID": { + "type": ["integer", "null"] + }, + "PRICEBOOK_ID": { + "type": ["integer", "null"] + }, + "QUOTATION_NUMBER": { + "type": ["string", "null"] + }, + "QUOTATION_DESCRIPTION": { + "type": ["string", "null"] + }, + "QUOTATION_PHONE": { + "type": ["string", "null"] + }, + "QUOTATION_EMAIL": { + "type": ["string", "null"] + }, + "QUOTATION_FAX": { + "type": ["string", "null"] + }, + "QUOTE_STATUS": { + "type": ["string", "null"] + }, + "QUOTATION_EXPIRATION_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "LINE_ITEM_COUNT": { + "type": ["integer", "null"] + }, + "IS_SYNCING": { + "type": ["boolean", "null"] + }, + "QUOTATION_CURRENCY_CODE": { + "type": ["string", "null"] + }, + "SUBTOTAL": { + "type": ["integer", "null"] + }, + "DISCOUNT": { + "type": ["integer", "null"] + }, + "TOTAL_PRICE": { + "type": ["integer", "null"] + }, + "SHIPPING_HANDLING": { + "type": ["integer", "null"] + }, + "TAX": { + "type": ["integer", "null"] + }, + "GRAND_TOTAL": { + "type": ["integer", "null"] + }, + "ADDRESS_BILLING_NAME": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_BILLING_COUNTRY": { + "type": ["string", "null"] + }, + "ADDRESS_SHIPPING_NAME": { + "type": ["string", "null"] + }, + "ADDRESS_SHIPPING_STREET": { + "type": ["string", "null"] + }, + "ADDRESS_SHIPPING_CITY": { + "type": ["string", "null"] + }, + "ADDRESS_SHIPPING_STATE": { + "type": ["string", "null"] + }, + "ADDRESS_SHIPPING_POSTCODE": { + "type": ["string", "null"] + }, + "ADDRESS_SHIPPING_COUNTRY": { + "type": ["string", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/relationships.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/relationships.json new file mode 100644 index 000000000000..a8a07e5f45e8 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/relationships.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "RELATIONSHIP_ID": { + "type": "integer" + }, + "FORWARD_TITLE": { + "type": ["string", "null"] + }, + "FORWARD": { + "type": ["string", "null"] + }, + "REVERSE_TITLE": { + "type": ["string", "null"] + }, + "REVERSE": { + "type": ["string", "null"] + }, + "FOR_CONTACTS": { + "type": ["boolean", "null"] + }, + "FOR_ORGANISATIONS": { + "type": ["boolean", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tags.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tags.json new file mode 100644 index 000000000000..7c22e0955ed1 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tags.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "TAG_NAME": { + "type": "string" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/task_categories.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/task_categories.json new file mode 100644 index 000000000000..bfbb4f93292d --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/task_categories.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "CATEGORY_ID": { + "type": "integer" + }, + "CATEGORY_NAME": { + "type": ["string", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "BACKGROUND_COLOR": { + "type": ["string", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json new file mode 100644 index 000000000000..448b0d9c5644 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json @@ -0,0 +1,153 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "TASK_ID": { + "type": "integer" + }, + "TITLE": { + "type": ["string", "null"] + }, + "CATEGORY_ID": { + "type": ["integer", "null"] + }, + "DUE_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "COMPLETED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "COMPLETED": { + "type": ["boolean", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "STATUS": { + "type": ["string", "null"] + }, + "PRIORITY": { + "type": ["integer", "null"] + }, + "PERCENT_COMPLETE": { + "type": ["integer", "null"] + }, + "START_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "MILESTONE_ID": { + "type": ["integer", "null"] + }, + "RESPONSIBLE_USER_ID": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "EMAIL_ID": { + "type": ["integer", "null"] + }, + "PROJECT_ID": { + "type": ["integer", "null"] + }, + "REMINDER_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "REMINDER_SENT": { + "type": ["boolean", "null"] + }, + "OWNER_VISIBLE": { + "type": ["boolean", "null"] + }, + "STAGE_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_BY_USER_ID": { + "type": ["integer", "null"] + }, + "PARENT_TASK_ID": { + "type": ["integer", "null"] + }, + "RECURRENCE": { + "type": ["string", "null"] + }, + "OPPORTUNITY_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_TEAM_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": + { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + + }, + "LINKS": { + "type": "array", + "items": + { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] + } + } + } + + } + } + } \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/team_members.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/team_members.json new file mode 100644 index 000000000000..63029d0e86b5 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/team_members.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "PERMISSION_ID": { + "type": ["integer", "null"] + }, + "TEAM_ID": { + "type": ["integer", "null"] + }, + "MEMBER_USER_ID": { + "type": "integer" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/teams.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/teams.json new file mode 100644 index 000000000000..b8454b8b393e --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/teams.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "TEAM_ID": { + "type": "integer" + }, + "TEAM_NAME": { + "type": ["string", "null"] + }, + "ANONYMOUS_TEAM": { + "type": ["boolean", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "TEAMMEMBERS": { + "type": "object" + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tickets.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tickets.json new file mode 100644 index 000000000000..ac863a02219d --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tickets.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "TICKET_ID": { + "type": "integer" + }, + "ORGANISATION_ID": { + "type": ["integer", "null"] + }, + "TICKET_TYPE": { + "type": ["string", "null"] + }, + "SUBJECT": { + "type": ["string", "null"] + }, + "TICKET_STATUS": { + "type": ["string", "null"] + }, + "PRIORITY": { + "type": ["string", "null"] + }, + "TO_EMAIL_ADDRESS": { + "type": ["string", "null"] + }, + "CONTACT_ID": { + "type": ["integer", "null"] + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "TICKET_NUMBER": { + "type": ["string", "null"] + }, + "SOURCE": { + "type": ["string", "null"] + }, + "DATE_SOLVED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_CLOSED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "TicketCommentBodyHtml": { + "type": ["string", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" + } + } + } + }, + "TAGS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "TAG_NAME": { + "type": ["string", "null"] + } + } + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/users.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/users.json new file mode 100644 index 000000000000..b7383bb2e99f --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/users.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "USER_ID": { + "type": "integer" + }, + "CONTACT_ID": { + "type": ["integer", "null"] + }, + "FIRST_NAME": { + "type": ["string", "null"] + }, + "LAST_NAME": { + "type": ["string", "null"] + }, + "TIMEZONE_ID": { + "type": ["string", "null"] + }, + "EMAIL_ADDRESS": { + "type": ["string", "null"] + }, + "EMAIL_DROPBOX_IDENTIFIER": { + "type": ["string", "null"] + }, + "EMAIL_DROPBOX_ADDRESS": { + "type": ["string", "null"] + }, + "ADMINISTRATOR": { + "type": ["boolean", "null"] + }, + "ACCOUNT_OWNER": { + "type": ["boolean", "null"] + }, + "ACTIVE": { + "type": ["boolean", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "USER_CURRENCY": { + "type": ["string", "null"] + }, + "CONTACT_DISPLAY": { + "type": ["string", "null"] + }, + "CONTACT_ORDER": { + "type": ["string", "null"] + }, + "TASK_WEEK_START": { + "type": ["integer", "null"] + }, + "INSTANCE_ID": { + "type": ["integer", "null"] + }, + "PROFILE_ID": { + "type": ["integer", "null"] + }, + "ROLE_ID": { + "type": ["integer", "null"] + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/source.py b/airbyte-integrations/connectors/source-insightly/source_insightly/source.py new file mode 100644 index 000000000000..fe3906c7d958 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/source.py @@ -0,0 +1,404 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from abc import ABC +from datetime import datetime, timedelta +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Tuple +from urllib.parse import parse_qs, urlparse + +import pendulum +import requests +from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.streams.http import HttpStream +from airbyte_cdk.sources.streams.http.auth import BasicHttpAuthenticator +from requests.auth import AuthBase + +PAGE_SIZE = 500 +BASE_URL = "https://api.insightly.com/v3.1/" + + +# Basic full refresh stream +class InsightlyStream(HttpStream, ABC): + total_count: int = 0 + page_size: Optional[int] = PAGE_SIZE + + url_base = BASE_URL + + def __init__(self, authenticator: AuthBase, start_date: str = None, **kwargs): + self.start_date = start_date + super().__init__(authenticator=authenticator) + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + parsed = urlparse(response.request.url) + previous_skip = parse_qs(parsed.query)["skip"][0] + new_skip = int(previous_skip) + self.page_size + return new_skip if new_skip <= self.total_count else None + + def request_params( + self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, any] = None, next_page_token: Mapping[str, Any] = None + ) -> MutableMapping[str, Any]: + return { + "count_total": True, + "top": self.page_size, + "skip": next_page_token or 0, + } + + def request_headers(self, **kwargs) -> Mapping[str, Any]: + return {"Accept": "application/json"} + + def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: + self.total_count = int(response.headers.get("X-Total-Count", 0)) + results = response.json() + yield from results + + +class ActivitySets(InsightlyStream): + primary_key = "ACTIVITYSET_ID" + + def path(self, **kwargs) -> str: + return "ActivitySets" + + +class Countries(InsightlyStream): + primary_key = "COUNTRY_NAME" + + def path(self, **kwargs) -> str: + return "Countries" + + +class Currencies(InsightlyStream): + primary_key = "CURRENCY_CODE" + + def path(self, **kwargs) -> str: + return "Currencies" + + +class Emails(InsightlyStream): + primary_key = "EMAIL_ID" + + def path(self, **kwargs) -> str: + return "Emails" + + +class LeadSources(InsightlyStream): + primary_key = "LEAD_SOURCE_ID" + + def path(self, **kwargs) -> str: + return "LeadSources" + + +class LeadStatuses(InsightlyStream): + primary_key = "LEAD_STATUS_ID" + + def path(self, **kwargs) -> str: + return "LeadStatuses" + + +class OpportunityCategories(InsightlyStream): + primary_key = "CATEGORY_ID" + + def path(self, **kwargs) -> str: + return "OpportunityCategories" + + +class OpportunityStateReasons(InsightlyStream): + primary_key = "STATE_REASON_ID" + + def path(self, **kwargs) -> str: + return "OpportunityStateReasons" + + +class Pipelines(InsightlyStream): + primary_key = "PIPELINE_ID" + + def path(self, **kwargs) -> str: + return "Pipelines" + + +class PipelineStages(InsightlyStream): + primary_key = "STAGE_ID" + + def path(self, **kwargs) -> str: + return "PipelineStages" + + +class ProjectCategories(InsightlyStream): + primary_key = "CATEGORY_ID" + + def path(self, **kwargs) -> str: + return "ProjectCategories" + + +class Relationships(InsightlyStream): + primary_key = "RELATIONSHIP_ID" + + def path(self, **kwargs) -> str: + return "Relationships" + + +class Tags(InsightlyStream): + primary_key = "TAG_NAME" + + def path(self, **kwargs) -> str: + return "Tags" + + +class TaskCategories(InsightlyStream): + primary_key = "CATEGORY_ID" + + def path(self, **kwargs) -> str: + return "TaskCategories" + + +class TeamMembers(InsightlyStream): + primary_key = "MEMBER_USER_ID" + + def path(self, **kwargs) -> str: + return "TeamMembers" + + +class Teams(InsightlyStream): + primary_key = "TEAM_ID" + + def path(self, **kwargs) -> str: + return "Teams" + + +class IncrementalInsightlyStream(InsightlyStream, ABC): + """Insighlty incremental stream using `updated_after_utc` filter""" + + cursor_field = "DATE_UPDATED_UTC" + + def request_params( + self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None, **kwargs + ) -> MutableMapping[str, Any]: + params = super().request_params(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token) + + start_datetime = pendulum.parse(self.start_date) + if stream_state.get(self.cursor_field): + start_datetime = pendulum.parse(stream_state[self.cursor_field]) + + # Add one second to avoid duplicate records and ensure greater than + params.update({"updated_after_utc": (start_datetime + timedelta(seconds=1)).strftime("%Y-%m-%dT%H:%M:%SZ")}) + return params + + def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]: + record_time = pendulum.parse(latest_record[self.cursor_field]) + current_state = current_stream_state.get(self.cursor_field) + if current_state: + current_state = current_state if isinstance(current_state, datetime) else pendulum.parse(current_state) + + current_stream_state[self.cursor_field] = max(record_time, current_state) if current_state else record_time + return current_stream_state + + +class Contacts(IncrementalInsightlyStream): + primary_key = "CONTACT_ID" + + def path(self, **kwargs) -> str: + return "Contacts/Search" + + +class Events(IncrementalInsightlyStream): + primary_key = "EVENT_ID" + + def path(self, **kwargs) -> str: + return "Events/Search" + + +class KnowledgeArticleCategories(IncrementalInsightlyStream): + primary_key = "CATEGORY_ID" + + def path(self, **kwargs) -> str: + return "KnowledgeArticleCategory/Search" + + +class KnowledgeArticleFolders(IncrementalInsightlyStream): + primary_key = "FOLDER_ID" + + def path(self, **kwargs) -> str: + return "KnowledgeArticleFolder/Search" + + +class KnowledgeArticles(IncrementalInsightlyStream): + primary_key = "ARTICLE_ID" + + def path(self, **kwargs) -> str: + return "KnowledgeArticle/Search" + + +class Leads(IncrementalInsightlyStream): + primary_key = "LEAD_ID" + + def path(self, **kwargs) -> str: + return "Leads/Search" + + +class Milestones(IncrementalInsightlyStream): + primary_key = "MILESTONE_ID" + + def path(self, **kwargs) -> str: + return "Milestones/Search" + + +class Notes(IncrementalInsightlyStream): + primary_key = "NOTE_ID" + + def path(self, **kwargs) -> str: + return "Notes/Search" + + +class Opportunities(IncrementalInsightlyStream): + primary_key = "OPPORTUNITY_ID" + + def path(self, **kwargs) -> str: + return "Opportunities/Search" + + +class OpportunityProducts(IncrementalInsightlyStream): + primary_key = "OPPORTUNITY_ITEM_ID" + + def path(self, **kwargs) -> str: + return "OpportunityLineItem/Search" + + +class Organisations(IncrementalInsightlyStream): + primary_key = "ORGANISATION_ID" + + def path(self, **kwargs) -> str: + return "Organisations/Search" + + +class PricebookEntries(IncrementalInsightlyStream): + primary_key = "PRICEBOOK_ENTRY_ID" + + def path(self, **kwargs) -> str: + return "PricebookEntry/Search" + + +class Pricebooks(IncrementalInsightlyStream): + primary_key = "PRICEBOOK_ID" + + def path(self, **kwargs) -> str: + return "Pricebook/Search" + + +class Products(IncrementalInsightlyStream): + primary_key = "PRODUCT_ID" + + def path(self, **kwargs) -> str: + return "Product/Search" + + +class Projects(IncrementalInsightlyStream): + primary_key = "PROJECT_ID" + + def path(self, **kwargs) -> str: + return "Projects/Search" + + +class Prospects(IncrementalInsightlyStream): + primary_key = "PROSPECT_ID" + + def path(self, **kwargs) -> str: + return "Prospect/Search" + + +class QuoteProducts(IncrementalInsightlyStream): + primary_key = "QUOTATION_ITEM_ID" + + def path(self, **kwargs) -> str: + return "QuotationLineItem/Search" + + +class Quotes(IncrementalInsightlyStream): + primary_key = "QUOTE_ID" + + def path(self, **kwargs) -> str: + return "Quotation/Search" + + +class Tasks(IncrementalInsightlyStream): + primary_key = "TASK_ID" + + def path(self, **kwargs) -> str: + return "Tasks/Search" + + +class Tickets(IncrementalInsightlyStream): + primary_key = "TICKET_ID" + + def path(self, **kwargs) -> str: + return "Ticket/Search" + + +class Users(IncrementalInsightlyStream): + primary_key = "USER_ID" + + def path(self, **kwargs) -> str: + return "Users/Search" + + +# Source +class SourceInsightly(AbstractSource): + def check_connection(self, logger, config) -> Tuple[bool, any]: + try: + token = config.get("token") + response = requests.get(f"{BASE_URL}Instance", auth=(token, "")) + response.raise_for_status() + + result = response.json() + logger.info(result) + + return True, None + except Exception as e: + return False, e + + def streams(self, config: Mapping[str, Any]) -> List[Stream]: + """ + :param config: A Mapping of the user input configuration as defined in the connector spec. + """ + + auth = BasicHttpAuthenticator(username=config.get("token"), password="") + return [ + ActivitySets(authenticator=auth, **config), + Contacts(authenticator=auth, **config), + Countries(authenticator=auth, **config), + Currencies(authenticator=auth, **config), + Emails(authenticator=auth, **config), + Events(authenticator=auth, **config), + KnowledgeArticleCategories(authenticator=auth, **config), + KnowledgeArticleFolders(authenticator=auth, **config), + KnowledgeArticles(authenticator=auth, **config), + LeadSources(authenticator=auth, **config), + LeadStatuses(authenticator=auth, **config), + Leads(authenticator=auth, **config), + Milestones(authenticator=auth, **config), + Notes(authenticator=auth, **config), + Opportunities(authenticator=auth, **config), + OpportunityCategories(authenticator=auth, **config), + OpportunityProducts(authenticator=auth, **config), + OpportunityStateReasons(authenticator=auth, **config), + Organisations(authenticator=auth, **config), + PipelineStages(authenticator=auth, **config), + Pipelines(authenticator=auth, **config), + PricebookEntries(authenticator=auth, **config), + Pricebooks(authenticator=auth, **config), + Products(authenticator=auth, **config), + ProjectCategories(authenticator=auth, **config), + Projects(authenticator=auth, **config), + Prospects(authenticator=auth, **config), + QuoteProducts(authenticator=auth, **config), + Quotes(authenticator=auth, **config), + Relationships(authenticator=auth, **config), + Tags(authenticator=auth, **config), + TaskCategories(authenticator=auth, **config), + Tasks(authenticator=auth, **config), + TeamMembers(authenticator=auth, **config), + Teams(authenticator=auth, **config), + Tickets(authenticator=auth, **config), + Users(authenticator=auth, **config), + ] diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/spec.json b/airbyte-integrations/connectors/source-insightly/source_insightly/spec.json new file mode 100644 index 000000000000..a21504f3e553 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/spec.json @@ -0,0 +1,25 @@ +{ + "documentationUrl": "https://docs.airbyte.com/integrations/sources/insightly", + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Insightly Spec", + "type": "object", + "required": ["token", "start_date"], + "additionalProperties": true, + "properties": { + "token": { + "type": ["string", "null"], + "title": "API Token", + "description": "Your Insightly API token.", + "airbyte_secret": true + }, + "start_date": { + "type": ["string", "null"], + "title": "Start Date", + "description": "The date from which you'd like to replicate data for Insightly in the format YYYY-MM-DDT00:00:00Z. All data generated after this date will be replicated. Note that it will be used only for incremental streams.", + "examples": ["2021-03-01T00:00:00Z"], + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + } + } + } +} diff --git a/airbyte-integrations/connectors/source-insightly/unit_tests/__init__.py b/airbyte-integrations/connectors/source-insightly/unit_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/unit_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-insightly/unit_tests/test_incremental_streams.py b/airbyte-integrations/connectors/source-insightly/unit_tests/test_incremental_streams.py new file mode 100644 index 000000000000..88e3206148bd --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/unit_tests/test_incremental_streams.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from datetime import datetime, timezone + +from airbyte_cdk.sources.streams.http.auth import BasicHttpAuthenticator +from pytest import fixture +from source_insightly.source import IncrementalInsightlyStream + +start_date = "2021-01-01T00:00:00Z" +authenticator = BasicHttpAuthenticator(username="test", password="") + + +@fixture +def patch_incremental_base_class(mocker): + # Mock abstract methods to enable instantiating abstract class + mocker.patch.object(IncrementalInsightlyStream, "path", "v0/example_endpoint") + mocker.patch.object(IncrementalInsightlyStream, "primary_key", "test_primary_key") + mocker.patch.object(IncrementalInsightlyStream, "__abstractmethods__", set()) + + +def test_cursor_field(patch_incremental_base_class): + stream = IncrementalInsightlyStream(authenticator=authenticator, start_date=start_date) + # TODO: replace this with your expected cursor field + expected_cursor_field = "DATE_UPDATED_UTC" + assert stream.cursor_field == expected_cursor_field + + +def test_get_updated_state(patch_incremental_base_class): + stream = IncrementalInsightlyStream(authenticator=authenticator, start_date=start_date) + inputs = { + "current_stream_state": {"DATE_UPDATED_UTC": "2021-01-01T00:00:00Z"}, + "latest_record": {"DATE_UPDATED_UTC": "2021-02-01T00:00:00Z"}, + } + expected_state = {"DATE_UPDATED_UTC": datetime(2021, 2, 1, 0, 0, 0, tzinfo=timezone.utc)} + assert stream.get_updated_state(**inputs) == expected_state + + +def test_get_updated_state_no_current_state(patch_incremental_base_class): + stream = IncrementalInsightlyStream(authenticator=authenticator, start_date=start_date) + inputs = {"current_stream_state": {}, "latest_record": {"DATE_UPDATED_UTC": "2021-01-01T00:00:00Z"}} + expected_state = {"DATE_UPDATED_UTC": datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc)} + assert stream.get_updated_state(**inputs) == expected_state + + +def test_supports_incremental(patch_incremental_base_class, mocker): + mocker.patch.object(IncrementalInsightlyStream, "cursor_field", "dummy_field") + stream = IncrementalInsightlyStream(authenticator=authenticator, start_date=start_date) + assert stream.supports_incremental + + +def test_source_defined_cursor(patch_incremental_base_class): + stream = IncrementalInsightlyStream(authenticator=authenticator, start_date=start_date) + assert stream.source_defined_cursor + + +def test_stream_checkpoint_interval(patch_incremental_base_class): + stream = IncrementalInsightlyStream(authenticator=authenticator, start_date=start_date) + # TODO: replace this with your expected checkpoint interval + expected_checkpoint_interval = None + assert stream.state_checkpoint_interval == expected_checkpoint_interval diff --git a/airbyte-integrations/connectors/source-insightly/unit_tests/test_source.py b/airbyte-integrations/connectors/source-insightly/unit_tests/test_source.py new file mode 100644 index 000000000000..b03ef20d6136 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/unit_tests/test_source.py @@ -0,0 +1,56 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from unittest.mock import MagicMock, patch + +from source_insightly.source import SourceInsightly + + +class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + def raise_for_status(self): + if self.status_code != 200: + raise Exception("Bad things happened") + + +def mocked_requests_get(fail=False): + def wrapper(*args, **kwargs): + if fail: + return MockResponse(None, 404) + + return MockResponse( + {"INSTANCE_NAME": "bossco", "INSTANCE_SUBDOMAIN": None, "PLAN_NAME": "Gratis", "NEW_USER_EXPERIENCE_ENABLED": True}, 200 + ) + + return wrapper + + +@patch("requests.get", side_effect=mocked_requests_get()) +def test_check_connection(mocker): + source = SourceInsightly() + logger_mock, config_mock = MagicMock(), MagicMock() + assert source.check_connection(logger_mock, config_mock) == (True, None) + + +@patch("requests.get", side_effect=mocked_requests_get(fail=True)) +def test_check_connection_fail(mocker): + source = SourceInsightly() + logger_mock, config_mock = MagicMock(), MagicMock() + assert source.check_connection(logger_mock, config_mock)[0] is False + assert source.check_connection(logger_mock, config_mock)[1] is not None + + +def test_streams(mocker): + source = SourceInsightly() + config_mock = MagicMock() + streams = source.streams(config_mock) + + expected_streams_number = 37 + assert len(streams) == expected_streams_number diff --git a/airbyte-integrations/connectors/source-insightly/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-insightly/unit_tests/test_streams.py new file mode 100644 index 000000000000..4eae18c0e479 --- /dev/null +++ b/airbyte-integrations/connectors/source-insightly/unit_tests/test_streams.py @@ -0,0 +1,126 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from unittest.mock import MagicMock + +import pytest +from airbyte_cdk.sources.streams.http.auth import BasicHttpAuthenticator +from source_insightly.source import InsightlyStream + +authenticator = BasicHttpAuthenticator(username="test", password="") + + +@pytest.fixture +def patch_base_class(mocker): + # Mock abstract methods to enable instantiating abstract class + mocker.patch.object(InsightlyStream, "path", "v0/example_endpoint") + mocker.patch.object(InsightlyStream, "primary_key", "test_primary_key") + mocker.patch.object(InsightlyStream, "__abstractmethods__", set()) + + +def test_request_params(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_params = {"count_total": True, "skip": 0, "top": 500} + assert stream.request_params(**inputs) == expected_params + + +def test_request_param_with_next_page_token(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": 1000} + expected_params = {"count_total": True, "skip": 1000, "top": 500} + assert stream.request_params(**inputs) == expected_params + + +def test_next_page_token(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + stream.total_count = 10000 + + request = MagicMock() + request.url = "https://api.insight.ly/v0/example_endpoint?count_total=True&skip=0&top=500" + response = MagicMock() + response.status_code = HTTPStatus.OK + response.request = request + + inputs = {"response": response} + expected_token = 500 + assert stream.next_page_token(**inputs) == expected_token + + +def test_next_page_token_last_records(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + stream.total_count = 2100 + + request = MagicMock() + request.url = "https://api.insight.ly/v0/example_endpoint?count_total=True&skip=1500&top=500" + response = MagicMock() + response.status_code = HTTPStatus.OK + response.request = request + + inputs = {"response": response} + expected_token = 2000 + assert stream.next_page_token(**inputs) == expected_token + + +def test_next_page_token_no_more_records(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + stream.total_count = 1000 + + request = MagicMock() + request.url = "https://api.insight.ly/v0/example_endpoint?count_total=True&skip=1000&top=500" + response = MagicMock() + response.status_code = HTTPStatus.OK + response.request = request + + inputs = {"response": response} + expected_token = None + assert stream.next_page_token(**inputs) == expected_token + + +def test_parse_response(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + + response = MagicMock() + response.json = MagicMock(return_value=[{"data_field": [{"keys": ["keys"]}]}]) + + inputs = {"stream_state": "test_stream_state", "response": response} + expected_parsed_object = response.json()[0] + assert next(stream.parse_response(**inputs)) == expected_parsed_object + + +def test_request_headers(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_headers = {"Accept": "application/json"} + assert stream.request_headers(**inputs) == expected_headers + + +def test_http_method(patch_base_class): + stream = InsightlyStream(authenticator=authenticator) + expected_method = "GET" + assert stream.http_method == expected_method + + +@pytest.mark.parametrize( + ("http_status", "should_retry"), + [ + (HTTPStatus.OK, False), + (HTTPStatus.BAD_REQUEST, False), + (HTTPStatus.TOO_MANY_REQUESTS, True), + (HTTPStatus.INTERNAL_SERVER_ERROR, True), + ], +) +def test_should_retry(patch_base_class, http_status, should_retry): + response_mock = MagicMock() + response_mock.status_code = http_status + stream = InsightlyStream(authenticator=authenticator) + assert stream.should_retry(response_mock) == should_retry + + +def test_backoff_time(patch_base_class): + response_mock = MagicMock() + stream = InsightlyStream(authenticator=authenticator) + expected_backoff_time = None + assert stream.backoff_time(response_mock) == expected_backoff_time diff --git a/docs/integrations/README.md b/docs/integrations/README.md index c8c1d3f21fe6..070288bacc6d 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -80,6 +80,7 @@ For more information about the grading system, see [Product Release Stages](http | [Harvest](sources/harvest.md) | Generally Available | Yes | | [http-request](sources/http-request.md) | Alpha | No | | [HubSpot](sources/hubspot.md) | Generally Available | Yes | +| [Insightly](sources/insightly.md) | Alpha | Yes | | [Instagram](sources/instagram.md) | Generally Available | Yes | | [Intercom](sources/intercom.md) | Generally Available | Yes | | [Iterable](sources/iterable.md) | Generally Available | Yes | diff --git a/docs/integrations/sources/insightly.md b/docs/integrations/sources/insightly.md new file mode 100644 index 000000000000..21f8549ca9c8 --- /dev/null +++ b/docs/integrations/sources/insightly.md @@ -0,0 +1,74 @@ +# Insightly + +This page guides you through the process of setting up the Insightly source connector. + +## Set up the Insightly connector + +1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) or Airbyte Open Source account. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Insightly** from the Source type dropdown. +4. Enter a name for your source. +5. For **API token**, enter the API token for your Insightly account. You can find your API token in your Insightly Account > Click on your avatar > User Settings > API. +6. For **Start date**, enter the date in YYYY-MM-DDTHH:mm:ssZ format. The data added on and after this date will be replicated. +7. Click **Set up source**. + +## Supported sync modes + +The Insightly source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): + + - Full Refresh + - Incremental + +## Supported Streams + +The Insightly source connector supports the following streams, some of them may need elevated permissions: + +* [Activity Sets](https://api.na1.insightly.com/v3.1/#!/ActivitySets/GetActivitySets) \(Full table\) +* [Contacts](https://api.na1.insightly.com/v3.1/#!/Contacts/GetEntities) \(Incremental\) +* [Countries](https://api.na1.insightly.com/v3.1/#!/Countries/GetCountries) \(Full table\) +* [Currencies](https://api.na1.insightly.com/v3.1/#!/Currencies/GetCurrencies) \(Full table\) +* [Emails](https://api.na1.insightly.com/v3.1/#!/Emails/GetEntities) \(Full table\) +* [Events](https://api.na1.insightly.com/v3.1/#!/Events/GetEntities) \(Incremental\) +* [Knowledge Article Categories](https://api.na1.insightly.com/v3.1/#!/KnowledgeArticleCategories/GetEntities) \(Incremental\) +* [Knowledge Article Folders](https://api.na1.insightly.com/v3.1/#!/KnowledgeArticleFolders/GetEntities) \(Incremental\) +* [Knowledge Articles](https://api.na1.insightly.com/v3.1/#!/KnowledgeArticles/GetEntities) \(Incremental\) +* [Leads](https://api.na1.insightly.com/v3.1/#!/Leads/GetEntities) \(Incremental\) +* [Lead Sources](https://api.na1.insightly.com/v3.1/#!/LeadSources/GetLeadSources) \(Full table\) +* [Lead Statuses](https://api.na1.insightly.com/v3.1/#!/LeadStatuses/GetLeadStatuses) \(Full table\) +* [Milestones](https://api.na1.insightly.com/v3.1/#!/Milestones/GetEntities) \(Incremental\) +* [Notes](https://api.na1.insightly.com/v3.1/#!/Notes/GetEntities) \(Incremental\) +* [Opportunities](https://api.na1.insightly.com/v3.1/#!/Opportunities/GetEntities) \(Incremental\) +* [Opportunity Categories](https://api.na1.insightly.com/v3.1/#!/OpportunityCategories/GetOpportunityCategories) \(Full table\) +* [Opportunity Products](https://api.na1.insightly.com/v3.1/#!/OpportunityProducts/GetEntities) \(Incremental\) +* [Opportunity State Reasons](https://api.na1.insightly.com/v3.1/#!/OpportunityStateReasons/GetOpportunityStateReasons) \(Full table\) +* [Organisations](https://api.na1.insightly.com/v3.1/#!/Organisations/GetEntities) \(Incremental\) +* [Pipelines](https://api.na1.insightly.com/v3.1/#!/Pipelines/GetPipelines) \(Full table\) +* [Pipeline Stages](https://api.na1.insightly.com/v3.1/#!/PipelineStages/GetPipelineStages) \(Full table\) +* [Price Book Entries](https://api.na1.insightly.com/v3.1/#!/PriceBookEntries/GetEntities) \(Incremental\) +* [Price Books](https://api.na1.insightly.com/v3.1/#!/PriceBooks/GetEntities) \(Incremental\) +* [Products](https://api.na1.insightly.com/v3.1/#!/Products/GetEntities) \(Incremental\) +* [Project Categories](https://api.na1.insightly.com/v3.1/#!/ProjectCategories/GetProjectCategories) \(Full table\) +* [Projects](https://api.na1.insightly.com/v3.1/#!/Projects/GetEntities) \(Incremental\) +* [Prospects](https://api.na1.insightly.com/v3.1/#!/Prospects/GetEntities) \(Incremental\) +* [Quote Products](https://api.na1.insightly.com/v3.1/#!/QuoteProducts/GetEntities) \(Incremental\) +* [Quotes](https://api.na1.insightly.com/v3.1/#!/Quotes/GetEntities) \(Incremental\) +* [Relationships](https://api.na1.insightly.com/v3.1/#!/Relationships/GetRelationships) \(Full table\) +* [Tags](https://api.na1.insightly.com/v3.1/#!/Tags/GetTags) \(Full table\) +* [Task Categories](https://api.na1.insightly.com/v3.1/#!/TaskCategories/GetTaskCategories) \(Full table\) +* [Tasks](https://api.na1.insightly.com/v3.1/#!/Tasks/GetEntities) \(Incremental\) +* [Team Members](https://api.na1.insightly.com/v3.1/#!/TeamMembers/GetTeamMembers) \(Full table\) +* [Teams](https://api.na1.insightly.com/v3.1/#!/Teams/GetTeams) \(Full table\) +* [Tickets](https://api.na1.insightly.com/v3.1/#!/Tickets/GetEntities) \(Incremental\) +* [Users](https://api.na1.insightly.com/v3.1/#!/Users/GetUsers) \(Incremental\) + + +## Performance considerations + +The connector is restricted by Insightly [requests limitation](https://api.na1.insightly.com/v3.1/#!/Overview/Introduction). + + +## Changelog + +| Version | Date | Pull Request | Subject | +| :------ | :--------- | :------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | +| 0.1.0 | 2021-07-19 | | Release Insightly CDK Connector | From 48a6c56a2afbc3eb0656838e6583adff3684417e Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Thu, 20 Oct 2022 15:38:57 -0400 Subject: [PATCH 236/498] Add APM tracing to Container Orchestrator (#18252) * Add env vars to support APM * Formatting * Fix property name * Add custom tracing to orchestrator * Use correct constant * PR feedback * Formatting * Add custom trace to dbt job orchestrator --- .../workers/ContainerOrchestratorConfig.java | 2 + .../process/AsyncOrchestratorPodProcess.java | 17 +++----- .../airbyte/workers/sync/LauncherWorker.java | 5 +-- airbyte-container-orchestrator/build.gradle | 2 +- .../DbtJobOrchestrator.java | 11 +++++ .../NormalizationJobOrchestrator.java | 11 +++++ .../ReplicationJobOrchestrator.java | 12 ++++++ .../TraceConstants.java | 41 +++++++++++++++++++ ...ontainerOrchestratorConfigBeanFactory.java | 38 +++++++++++++++++ .../src/main/resources/application.yml | 9 ++++ ...OrchestratorPodProcessIntegrationTest.java | 3 +- 11 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java index 17d8c2569b49..a8bd084cf908 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/ContainerOrchestratorConfig.java @@ -7,10 +7,12 @@ import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.workers.storage.DocumentStoreClient; import io.fabric8.kubernetes.client.KubernetesClient; +import java.util.Map; public record ContainerOrchestratorConfig( String namespace, DocumentStoreClient documentStoreClient, + Map environmentVariables, KubernetesClient kubernetesClient, String secretName, String secretMountPath, diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java index 2bb0ccf3a454..ce593c0b2d54 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AsyncOrchestratorPodProcess.java @@ -4,10 +4,8 @@ package io.airbyte.workers.process; -import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.io.IOs; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.EnvConfigs; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.helpers.LogClientSingleton; import io.airbyte.workers.storage.DocumentStoreClient; @@ -61,7 +59,7 @@ public class AsyncOrchestratorPodProcess implements KubePod { private final String secretMountPath; private final String googleApplicationCredentials; private final AtomicReference> cachedExitValue; - private final boolean useStreamCapableState; + private final Map environmentVariables; private final Integer serverPort; public AsyncOrchestratorPodProcess( @@ -71,7 +69,7 @@ public AsyncOrchestratorPodProcess( final String secretName, final String secretMountPath, final String googleApplicationCredentials, - final boolean useStreamCapableState, + final Map environmentVariables, final Integer serverPort) { this.kubePodInfo = kubePodInfo; this.documentStoreClient = documentStoreClient; @@ -80,7 +78,7 @@ public AsyncOrchestratorPodProcess( this.secretMountPath = secretMountPath; this.googleApplicationCredentials = googleApplicationCredentials; this.cachedExitValue = new AtomicReference<>(Optional.empty()); - this.useStreamCapableState = useStreamCapableState; + this.environmentVariables = environmentVariables; this.serverPort = serverPort; } @@ -294,12 +292,9 @@ public void create(final Map allLabels, } - final EnvConfigs envConfigs = new EnvConfigs(); - envVars.add(new EnvVar(EnvConfigs.METRIC_CLIENT, envConfigs.getMetricClient(), null)); - envVars.add(new EnvVar(EnvConfigs.DD_AGENT_HOST, envConfigs.getDDAgentHost(), null)); - envVars.add(new EnvVar(EnvConfigs.DD_DOGSTATSD_PORT, envConfigs.getDDDogStatsDPort(), null)); - envVars.add(new EnvVar(EnvConfigs.PUBLISH_METRICS, Boolean.toString(envConfigs.getPublishMetrics()), null)); - envVars.add(new EnvVar(EnvVariableFeatureFlags.USE_STREAM_CAPABLE_STATE, Boolean.toString(useStreamCapableState), null)); + // Copy all additionally provided environment variables + envVars.addAll(environmentVariables.entrySet().stream().map(e -> new EnvVar(e.getKey(), e.getValue(), null)).toList()); + final List containerPorts = KubePodProcess.createContainerPortList(portMap); containerPorts.add(new ContainerPort(serverPort, null, null, null, null)); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java index a02552917074..03c9bbdefc71 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/sync/LauncherWorker.java @@ -5,7 +5,6 @@ package io.airbyte.workers.sync; import com.google.common.base.Stopwatch; -import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; import io.airbyte.commons.temporal.TemporalUtils; @@ -38,7 +37,6 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -import lombok.val; /** * Coordinates configuring and managing the state of an async process. This is tied to the (job_id, @@ -133,7 +131,6 @@ public OUTPUT run(final INPUT input, final Path jobRoot) throws WorkerException final var kubePodInfo = new KubePodInfo(containerOrchestratorConfig.namespace(), podName, mainContainerInfo); - val featureFlag = new EnvVariableFeatureFlags(); // Use the configuration to create the process. process = new AsyncOrchestratorPodProcess( @@ -143,7 +140,7 @@ public OUTPUT run(final INPUT input, final Path jobRoot) throws WorkerException containerOrchestratorConfig.secretName(), containerOrchestratorConfig.secretMountPath(), containerOrchestratorConfig.googleApplicationCredentials(), - featureFlag.useStreamCapableState(), + containerOrchestratorConfig.environmentVariables(), serverPort); // Define what to do on cancellation. diff --git a/airbyte-container-orchestrator/build.gradle b/airbyte-container-orchestrator/build.gradle index e22f5217a038..87c827963965 100644 --- a/airbyte-container-orchestrator/build.gradle +++ b/airbyte-container-orchestrator/build.gradle @@ -8,7 +8,7 @@ dependencies { implementation 'org.apache.commons:commons-text:1.9' implementation 'org.eclipse.jetty:jetty-server:9.4.31.v20200723' implementation 'org.eclipse.jetty:jetty-servlet:9.4.31.v20200723' - + implementation libs.bundles.datadog implementation project(':airbyte-api') implementation project(':airbyte-config:config-models') diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java index 589bbb23d5ff..7911a0545dc7 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java @@ -4,9 +4,15 @@ package io.airbyte.container_orchestrator; +import static io.airbyte.container_orchestrator.TraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.Configs; import io.airbyte.config.OperatorDbtInput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.WorkerConfigs; @@ -17,6 +23,7 @@ import io.airbyte.workers.process.ProcessFactory; import io.airbyte.workers.sync.ReplicationLauncherWorker; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -43,6 +50,7 @@ public Class getInputClass() { return OperatorDbtInput.class; } + @Trace(operationName = JOB_ORCHESTRATOR_OPERATION_NAME) @Override public Optional runJob() throws Exception { final JobRunConfig jobRunConfig = JobOrchestrator.readJobRunConfig(); @@ -52,6 +60,9 @@ public Optional runJob() throws Exception { Path.of(KubePodProcess.CONFIG_DIR, ReplicationLauncherWorker.INIT_FILE_DESTINATION_LAUNCHER_CONFIG), IntegrationLauncherConfig.class); + ApmTraceUtils + .addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage())); + log.info("Setting up dbt worker..."); final DbtTransformationWorker worker = new DbtTransformationWorker( jobRunConfig.getJobId(), diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java index 34116b594601..dbb4ada1f4c8 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java @@ -4,11 +4,17 @@ package io.airbyte.container_orchestrator; +import static io.airbyte.container_orchestrator.TraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.JOB_ID_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.Configs; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.persistence.job.models.IntegrationLauncherConfig; import io.airbyte.persistence.job.models.JobRunConfig; import io.airbyte.workers.general.DefaultNormalizationWorker; @@ -18,6 +24,7 @@ import io.airbyte.workers.process.ProcessFactory; import io.airbyte.workers.sync.ReplicationLauncherWorker; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -42,6 +49,7 @@ public Class getInputClass() { return NormalizationInput.class; } + @Trace(operationName = JOB_ORCHESTRATOR_OPERATION_NAME) @Override public Optional runJob() throws Exception { final JobRunConfig jobRunConfig = JobOrchestrator.readJobRunConfig(); @@ -51,6 +59,9 @@ public Optional runJob() throws Exception { Path.of(KubePodProcess.CONFIG_DIR, ReplicationLauncherWorker.INIT_FILE_DESTINATION_LAUNCHER_CONFIG), IntegrationLauncherConfig.class); + ApmTraceUtils + .addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage())); + log.info("Setting up normalization worker..."); final NormalizationWorker normalizationWorker = new DefaultNormalizationWorker( jobRunConfig.getJobId(), diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java index 4c71979210ee..b7f093434a68 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java @@ -4,12 +4,19 @@ package io.airbyte.container_orchestrator; +import static io.airbyte.container_orchestrator.TraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.container_orchestrator.TraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; + +import datadog.trace.api.Trace; import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.TemporalUtils; import io.airbyte.config.Configs; import io.airbyte.config.ReplicationOutput; import io.airbyte.config.StandardSyncInput; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.metrics.lib.MetricClient; import io.airbyte.metrics.lib.MetricClientFactory; import io.airbyte.metrics.lib.MetricEmittingApps; @@ -33,6 +40,7 @@ import io.airbyte.workers.process.ProcessFactory; import io.airbyte.workers.sync.ReplicationLauncherWorker; import java.nio.file.Path; +import java.util.Map; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -61,6 +69,7 @@ public Class getInputClass() { return StandardSyncInput.class; } + @Trace(operationName = JOB_ORCHESTRATOR_OPERATION_NAME) @Override public Optional runJob() throws Exception { final JobRunConfig jobRunConfig = JobOrchestrator.readJobRunConfig(); @@ -74,6 +83,9 @@ public Optional runJob() throws Exception { Path.of(KubePodProcess.CONFIG_DIR, ReplicationLauncherWorker.INIT_FILE_DESTINATION_LAUNCHER_CONFIG), IntegrationLauncherConfig.class); + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobRunConfig.getJobId(), DESTINATION_DOCKER_IMAGE_KEY, destinationLauncherConfig.getDockerImage(), + SOURCE_DOCKER_IMAGE_KEY, sourceLauncherConfig.getDockerImage())); + log.info("Setting up source launcher..."); final IntegrationLauncher sourceLauncher = new AirbyteIntegrationLauncher( sourceLauncherConfig.getJobId(), diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java new file mode 100644 index 000000000000..00c5263c171d --- /dev/null +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.container_orchestrator; + +/** + * Collection of constants for APM tracing. + */ +public class TraceConstants { + + public static final String JOB_ORCHESTRATOR_OPERATION_NAME = "job.orchestrator"; + + private TraceConstants() {} + + /** + * Trace tag constants. + */ + public static final class Tags { + + /** + * Name of the APM trace tag that holds the destination Docker image value associated with the + * trace. + */ + public static final String DESTINATION_DOCKER_IMAGE_KEY = "destination.docker_image"; + + /** + * Name of the APM trace tag that holds the job ID value associated with the trace. + */ + public static final String JOB_ID_KEY = "job_id"; + + /** + * Name of the APM trace tag that holds the source Docker image value associated with the trace. + */ + public static final String SOURCE_DOCKER_IMAGE_KEY = "source.docker_image"; + + private Tags() {} + + } + +} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java index 23e3fa1c6a9d..79fe622aeeb3 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/ContainerOrchestratorConfigBeanFactory.java @@ -4,6 +4,8 @@ package io.airbyte.workers.config; +import io.airbyte.commons.features.EnvVariableFeatureFlags; +import io.airbyte.commons.features.FeatureFlags; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.storage.CloudStorageConfigs; import io.airbyte.workers.ContainerOrchestratorConfig; @@ -17,6 +19,8 @@ import jakarta.inject.Named; import jakarta.inject.Singleton; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; /** @@ -25,6 +29,15 @@ @Factory public class ContainerOrchestratorConfigBeanFactory { + private static final String METRIC_CLIENT_ENV_VAR = "METRIC_CLIENT"; + private static final String DD_AGENT_HOST_ENV_VAR = "DD_AGENT_HOST"; + private static final String DD_DOGSTATSD_PORT_ENV_VAR = "DD_DOGSTATSD_PORT"; + private static final String DD_ENV_ENV_VAR = "DD_ENV"; + private static final String DD_SERVICE_ENV_VAR = "DD_SERVICE"; + private static final String DD_VERSION_ENV_VAR = "DD_VERSION"; + private static final String JAVA_OPTS_ENV_VAR = "JAVA_OPTS"; + private static final String PUBLISH_METRICS_ENV_VAR = "PUBLISH_METRICS"; + // IMPORTANT: Changing the storage location will orphan already existing kube pods when the new // version is deployed! private static final Path STATE_STORAGE_PREFIX = Path.of("/state"); @@ -42,6 +55,12 @@ public ContainerOrchestratorConfig kubernetesContainerOrchestratorConfig( @Value("${airbyte.container.orchestrator.secret-name}") final String containerOrchestratorSecretName, @Value("${google.application.credentials}") final String googleApplicationCredentials, @Value("${airbyte.worker.job.kube.namespace}") final String namespace, + @Value("${airbyte.metric.client}") final String metricClient, + @Value("${datadog.agent.host}") final String dataDogAgentHost, + @Value("${datadog.agent.port}") final String dataDogStatsdPort, + @Value("${airbyte.metric.should-publish}") final String shouldPublishMetrics, + final FeatureFlags featureFlags, + @Value("${airbyte.container.orchestrator.java-opts}") final String containerOrchestratorJavaOpts, final WorkerEnvironment workerEnvironment) { final var kubernetesClient = new DefaultKubernetesClient(); @@ -49,9 +68,28 @@ public ContainerOrchestratorConfig kubernetesContainerOrchestratorConfig( cloudStateStorageConfiguration.orElse(null), STATE_STORAGE_PREFIX); + // Build the map of additional environment variables to be passed to the container orchestrator + final Map environmentVariables = new HashMap<>(); + environmentVariables.put(METRIC_CLIENT_ENV_VAR, metricClient); + environmentVariables.put(DD_AGENT_HOST_ENV_VAR, dataDogAgentHost); + environmentVariables.put(DD_SERVICE_ENV_VAR, "airbyte-container-orchestrator"); + environmentVariables.put(DD_DOGSTATSD_PORT_ENV_VAR, dataDogStatsdPort); + environmentVariables.put(PUBLISH_METRICS_ENV_VAR, shouldPublishMetrics); + environmentVariables.put(EnvVariableFeatureFlags.USE_STREAM_CAPABLE_STATE, Boolean.toString(featureFlags.useStreamCapableState())); + environmentVariables.put(JAVA_OPTS_ENV_VAR, containerOrchestratorJavaOpts); + + if (System.getenv(DD_ENV_ENV_VAR) != null) { + environmentVariables.put(DD_ENV_ENV_VAR, System.getenv(DD_ENV_ENV_VAR)); + } + + if (System.getenv(DD_VERSION_ENV_VAR) != null) { + environmentVariables.put(DD_VERSION_ENV_VAR, System.getenv(DD_VERSION_ENV_VAR)); + } + return new ContainerOrchestratorConfig( namespace, documentStoreClient, + environmentVariables, kubernetesClient, containerOrchestratorSecretName, containerOrchestratorSecretMountPath, diff --git a/airbyte-workers/src/main/resources/application.yml b/airbyte-workers/src/main/resources/application.yml index 15a5c0248001..2c5cfce97404 100644 --- a/airbyte-workers/src/main/resources/application.yml +++ b/airbyte-workers/src/main/resources/application.yml @@ -54,6 +54,7 @@ airbyte: orchestrator: enabled: ${CONTAINER_ORCHESTRATOR_ENABLED:false} image: ${CONTAINER_ORCHESTRATOR_IMAGE:} + java-opts: ${CONTAINER_ORCHESTRATOR_JAVA_OPTS:} secret-mount-path: ${CONTAINER_ORCHESTRATOR_SECRET_MOUNT_PATH:} secret-name: ${CONTAINER_ORCHESTRATOR_SECRET_NAME:} control: @@ -84,6 +85,9 @@ airbyte: local: docker-mount: ${LOCAL_DOCKER_MOUNT:} root: ${LOCAL_ROOT} + metric: + client: ${METRIC_CLIENT:} + should-publish: ${PUBLISH_METRICS:false} worker: check: enabled: ${SHOULD_RUN_CHECK_CONNECTION_WORKFLOWS:true} @@ -192,6 +196,11 @@ airbyte: docker-mount: ${WORKSPACE_DOCKER_MOUNT:} root: ${WORKSPACE_ROOT} +datadog: + agent: + host: ${DD_AGENT_HOST:} + port: ${DD_DOGSTATSD_PORT:} + docker: network: ${DOCKER_NETWORK:host} diff --git a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java index 0e0b2b87e639..b088ee1f6fcf 100644 --- a/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java +++ b/airbyte-workers/src/test-integration/java/io/airbyte/workers/process/AsyncOrchestratorPodProcessIntegrationTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.temporal.sync.OrchestratorConstants; import io.airbyte.config.EnvConfigs; @@ -113,7 +114,7 @@ public void testAsyncOrchestratorPodProcess(final String pullPolicy) throws Inte null, null, null, - true, + Map.of(EnvVariableFeatureFlags.USE_STREAM_CAPABLE_STATE, "true"), serverPort); final Map portMap = Map.of( From 35218025451e47610fcec4e67531bbd9a2611afa Mon Sep 17 00:00:00 2001 From: Brian Lai <51336873+brianjlai@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:06:02 -0400 Subject: [PATCH 237/498] Update Python Source connectors to use the latest airbyte-cdk minor version (#18261) * update all connectors currently only adhering to patch versions to pull latest minor version * also add greenhouse * fix scaffolds --- airbyte-integrations/bases/source-acceptance-test/setup.py | 2 +- .../connector-templates/source-python-http-api/setup.py.hbs | 2 +- .../connector-templates/source-python/setup.py.hbs | 2 +- airbyte-integrations/connectors/source-adjust/setup.py | 2 +- airbyte-integrations/connectors/source-amazon-ads/setup.py | 2 +- airbyte-integrations/connectors/source-appfollow/setup.py | 2 +- airbyte-integrations/connectors/source-appsflyer/setup.py | 2 +- airbyte-integrations/connectors/source-cart/setup.py | 2 +- airbyte-integrations/connectors/source-courier/setup.py | 2 +- .../connectors/source-facebook-marketing/setup.py | 2 +- airbyte-integrations/connectors/source-fauna/setup.py | 2 +- airbyte-integrations/connectors/source-file/setup.py | 2 +- airbyte-integrations/connectors/source-freshservice/setup.py | 2 +- airbyte-integrations/connectors/source-github/setup.py | 2 +- airbyte-integrations/connectors/source-glassfrog/setup.py | 2 +- airbyte-integrations/connectors/source-google-sheets/setup.py | 2 +- airbyte-integrations/connectors/source-greenhouse/setup.py | 2 +- airbyte-integrations/connectors/source-hubspot/setup.py | 2 +- airbyte-integrations/connectors/source-instagram/setup.py | 2 +- airbyte-integrations/connectors/source-mailchimp/setup.py | 2 +- airbyte-integrations/connectors/source-netsuite/setup.py | 2 +- airbyte-integrations/connectors/source-orbit/setup.py | 2 +- airbyte-integrations/connectors/source-primetric/setup.py | 2 +- airbyte-integrations/connectors/source-salesforce/setup.py | 2 +- airbyte-integrations/connectors/source-salesloft/setup.py | 2 +- .../connectors/source-scaffold-source-http/setup.py | 2 +- .../connectors/source-scaffold-source-python/setup.py | 2 +- airbyte-integrations/connectors/source-shortio/setup.py | 2 +- airbyte-integrations/connectors/source-wrike/setup.py | 2 +- airbyte-integrations/connectors/source-yandex-metrica/setup.py | 2 +- airbyte-integrations/connectors/source-zendesk-support/setup.py | 2 +- 31 files changed, 31 insertions(+), 31 deletions(-) diff --git a/airbyte-integrations/bases/source-acceptance-test/setup.py b/airbyte-integrations/bases/source-acceptance-test/setup.py index 1d3af8414e1d..9b9d7ffe8fa9 100644 --- a/airbyte-integrations/bases/source-acceptance-test/setup.py +++ b/airbyte-integrations/bases/source-acceptance-test/setup.py @@ -6,7 +6,7 @@ import setuptools MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.2.0", + "airbyte-cdk~=0.2", "docker~=5.0.3", "PyYAML~=5.4", "icdiff~=1.9", diff --git a/airbyte-integrations/connector-templates/source-python-http-api/setup.py.hbs b/airbyte-integrations/connector-templates/source-python-http-api/setup.py.hbs index 7c93e7c474bc..cc7cbf02ba6c 100644 --- a/airbyte-integrations/connector-templates/source-python-http-api/setup.py.hbs +++ b/airbyte-integrations/connector-templates/source-python-http-api/setup.py.hbs @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.2", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connector-templates/source-python/setup.py.hbs b/airbyte-integrations/connector-templates/source-python/setup.py.hbs index 7e8ace3b9be1..2c78aeb2ae2d 100644 --- a/airbyte-integrations/connector-templates/source-python/setup.py.hbs +++ b/airbyte-integrations/connector-templates/source-python/setup.py.hbs @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.2", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-adjust/setup.py b/airbyte-integrations/connectors/source-adjust/setup.py index 4103a9501e61..b8318da346c3 100644 --- a/airbyte-integrations/connectors/source-adjust/setup.py +++ b/airbyte-integrations/connectors/source-adjust/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-amazon-ads/setup.py b/airbyte-integrations/connectors/source-amazon-ads/setup.py index b74dd73a61ed..37143594bdb9 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/setup.py +++ b/airbyte-integrations/connectors/source-amazon-ads/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2.0", "requests_oauthlib~=1.3.1", "pendulum~=2.1.2"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2", "requests_oauthlib~=1.3.1", "pendulum~=2.1.2"] TEST_REQUIREMENTS = [ "pytest~=6.1", diff --git a/airbyte-integrations/connectors/source-appfollow/setup.py b/airbyte-integrations/connectors/source-appfollow/setup.py index 2e4a21ee3d08..c833bbdd2a5c 100644 --- a/airbyte-integrations/connectors/source-appfollow/setup.py +++ b/airbyte-integrations/connectors/source-appfollow/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = ["pytest~=6.1", "pytest-mock~=3.6.1", "source-acceptance-test", "requests_mock~=1.9"] diff --git a/airbyte-integrations/connectors/source-appsflyer/setup.py b/airbyte-integrations/connectors/source-appsflyer/setup.py index 4f8c6c9cc829..bc31f8d0c458 100644 --- a/airbyte-integrations/connectors/source-appsflyer/setup.py +++ b/airbyte-integrations/connectors/source-appsflyer/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1.25", "pendulum~=2.1.2"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "pendulum~=2.1.2"] TEST_REQUIREMENTS = [ "pytest~=6.1", diff --git a/airbyte-integrations/connectors/source-cart/setup.py b/airbyte-integrations/connectors/source-cart/setup.py index 4b843619ac54..30507cc35519 100644 --- a/airbyte-integrations/connectors/source-cart/setup.py +++ b/airbyte-integrations/connectors/source-cart/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.7", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-courier/setup.py b/airbyte-integrations/connectors/source-courier/setup.py index 89a69bf5ec7d..8a7e73bd373c 100644 --- a/airbyte-integrations/connectors/source-courier/setup.py +++ b/airbyte-integrations/connectors/source-courier/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.99", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-facebook-marketing/setup.py b/airbyte-integrations/connectors/source-facebook-marketing/setup.py index 81d02041c125..fc9fc73c0a0b 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/setup.py +++ b/airbyte-integrations/connectors/source-facebook-marketing/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", "cached_property==1.5.2", "facebook_business==14.0.0", "pendulum>=2,<3", diff --git a/airbyte-integrations/connectors/source-fauna/setup.py b/airbyte-integrations/connectors/source-fauna/setup.py index 1353ad7c429d..52c78dcf55ea 100644 --- a/airbyte-integrations/connectors/source-fauna/setup.py +++ b/airbyte-integrations/connectors/source-fauna/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", "faunadb~=4.2", ] diff --git a/airbyte-integrations/connectors/source-file/setup.py b/airbyte-integrations/connectors/source-file/setup.py index 9203aba8120d..4d12358e6cce 100644 --- a/airbyte-integrations/connectors/source-file/setup.py +++ b/airbyte-integrations/connectors/source-file/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.2.0", + "airbyte-cdk~=0.2", "gcsfs==2022.7.1", "genson==1.2.2", "google-cloud-storage==2.5.0", diff --git a/airbyte-integrations/connectors/source-freshservice/setup.py b/airbyte-integrations/connectors/source-freshservice/setup.py index 023788265154..edbef19478fa 100644 --- a/airbyte-integrations/connectors/source-freshservice/setup.py +++ b/airbyte-integrations/connectors/source-freshservice/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.25", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-github/setup.py b/airbyte-integrations/connectors/source-github/setup.py index f51356bc392d..476271bbbf77 100644 --- a/airbyte-integrations/connectors/source-github/setup.py +++ b/airbyte-integrations/connectors/source-github/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2.0", "pendulum~=2.1.2", "sgqlc"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2", "pendulum~=2.1.2", "sgqlc"] TEST_REQUIREMENTS = ["pytest~=6.1", "source-acceptance-test", "responses~=0.19.0"] diff --git a/airbyte-integrations/connectors/source-glassfrog/setup.py b/airbyte-integrations/connectors/source-glassfrog/setup.py index bb4bfddfa4db..314c1a0d751c 100644 --- a/airbyte-integrations/connectors/source-glassfrog/setup.py +++ b/airbyte-integrations/connectors/source-glassfrog/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-google-sheets/setup.py b/airbyte-integrations/connectors/source-google-sheets/setup.py index 6a24a1956721..6e49f9b86f27 100644 --- a/airbyte-integrations/connectors/source-google-sheets/setup.py +++ b/airbyte-integrations/connectors/source-google-sheets/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", "backoff", "requests", "google-auth-httplib2", diff --git a/airbyte-integrations/connectors/source-greenhouse/setup.py b/airbyte-integrations/connectors/source-greenhouse/setup.py index 17489c2260b3..ed5e097474ea 100644 --- a/airbyte-integrations/connectors/source-greenhouse/setup.py +++ b/airbyte-integrations/connectors/source-greenhouse/setup.py @@ -16,7 +16,7 @@ author="Airbyte", author_email="contact@airbyte.io", packages=find_packages(), - install_requires=["airbyte-cdk>=0.1.91", "dataclasses-jsonschema==2.15.1"], + install_requires=["airbyte-cdk~=0.1", "dataclasses-jsonschema==2.15.1"], package_data={"": ["*.json", "*.yaml", "schemas/*.json"]}, extras_require={ "tests": TEST_REQUIREMENTS, diff --git a/airbyte-integrations/connectors/source-hubspot/setup.py b/airbyte-integrations/connectors/source-hubspot/setup.py index dd570f8a1475..0ebfed398b9c 100644 --- a/airbyte-integrations/connectors/source-hubspot/setup.py +++ b/airbyte-integrations/connectors/source-hubspot/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.55", + "airbyte-cdk~=0.1", "backoff==1.11.1", "pendulum==2.1.2", "requests==2.26.0", diff --git a/airbyte-integrations/connectors/source-instagram/setup.py b/airbyte-integrations/connectors/source-instagram/setup.py index 47c27dce37be..2564950e9980 100644 --- a/airbyte-integrations/connectors/source-instagram/setup.py +++ b/airbyte-integrations/connectors/source-instagram/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.81", + "airbyte-cdk~=0.1", "cached_property~=1.5", "facebook_business~=11.0", "pendulum>=2,<3", diff --git a/airbyte-integrations/connectors/source-mailchimp/setup.py b/airbyte-integrations/connectors/source-mailchimp/setup.py index e1e3d33c0399..442372dde2ec 100644 --- a/airbyte-integrations/connectors/source-mailchimp/setup.py +++ b/airbyte-integrations/connectors/source-mailchimp/setup.py @@ -15,7 +15,7 @@ author_email="contact@airbyte.io", packages=find_packages(), install_requires=[ - "airbyte-cdk~=0.1.35", + "airbyte-cdk~=0.1", "pytest~=6.1", ], package_data={"": ["*.json", "schemas/*.json", "schemas/shared/*.json"]}, diff --git a/airbyte-integrations/connectors/source-netsuite/setup.py b/airbyte-integrations/connectors/source-netsuite/setup.py index ad74909eb66b..572013714c36 100644 --- a/airbyte-integrations/connectors/source-netsuite/setup.py +++ b/airbyte-integrations/connectors/source-netsuite/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", "requests-oauthlib~=1.3", ] diff --git a/airbyte-integrations/connectors/source-orbit/setup.py b/airbyte-integrations/connectors/source-orbit/setup.py index 00a30d789955..7598440195a9 100644 --- a/airbyte-integrations/connectors/source-orbit/setup.py +++ b/airbyte-integrations/connectors/source-orbit/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-primetric/setup.py b/airbyte-integrations/connectors/source-primetric/setup.py index b23b47d37c22..dbc4c52e4af9 100644 --- a/airbyte-integrations/connectors/source-primetric/setup.py +++ b/airbyte-integrations/connectors/source-primetric/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-salesforce/setup.py b/airbyte-integrations/connectors/source-salesforce/setup.py index f463d87e3551..beabe0dd8bf3 100644 --- a/airbyte-integrations/connectors/source-salesforce/setup.py +++ b/airbyte-integrations/connectors/source-salesforce/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1.56", "vcrpy==4.1.1", "pandas"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "vcrpy==4.1.1", "pandas"] TEST_REQUIREMENTS = ["pytest~=6.1", "pytest-mock~=3.6", "requests_mock", "source-acceptance-test", "pytest-timeout"] diff --git a/airbyte-integrations/connectors/source-salesloft/setup.py b/airbyte-integrations/connectors/source-salesloft/setup.py index f3b8c5474ec4..aade7c4c8505 100644 --- a/airbyte-integrations/connectors/source-salesloft/setup.py +++ b/airbyte-integrations/connectors/source-salesloft/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.22", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-scaffold-source-http/setup.py b/airbyte-integrations/connectors/source-scaffold-source-http/setup.py index a357e3f6d055..d4d1f9591eb2 100644 --- a/airbyte-integrations/connectors/source-scaffold-source-http/setup.py +++ b/airbyte-integrations/connectors/source-scaffold-source-http/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.2", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-scaffold-source-python/setup.py b/airbyte-integrations/connectors/source-scaffold-source-python/setup.py index 06e4569ab586..3031ea896510 100644 --- a/airbyte-integrations/connectors/source-scaffold-source-python/setup.py +++ b/airbyte-integrations/connectors/source-scaffold-source-python/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.2", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-shortio/setup.py b/airbyte-integrations/connectors/source-shortio/setup.py index afd147bfd877..1b33fcd30411 100644 --- a/airbyte-integrations/connectors/source-shortio/setup.py +++ b/airbyte-integrations/connectors/source-shortio/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1.56"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1"] TEST_REQUIREMENTS = [ "pytest~=6.2.5", diff --git a/airbyte-integrations/connectors/source-wrike/setup.py b/airbyte-integrations/connectors/source-wrike/setup.py index 39c707d08a5a..48e2d73d0e16 100644 --- a/airbyte-integrations/connectors/source-wrike/setup.py +++ b/airbyte-integrations/connectors/source-wrike/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-yandex-metrica/setup.py b/airbyte-integrations/connectors/source-yandex-metrica/setup.py index 1df1e88b2e22..9e13d977fd04 100644 --- a/airbyte-integrations/connectors/source-yandex-metrica/setup.py +++ b/airbyte-integrations/connectors/source-yandex-metrica/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1.56", + "airbyte-cdk~=0.1", "pytest~=6.1", ] diff --git a/airbyte-integrations/connectors/source-zendesk-support/setup.py b/airbyte-integrations/connectors/source-zendesk-support/setup.py index 659f2f0a4625..2322cc04de1a 100644 --- a/airbyte-integrations/connectors/source-zendesk-support/setup.py +++ b/airbyte-integrations/connectors/source-zendesk-support/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1.60", "pytz", "requests-futures~=1.0.0", "pendulum~=2.1.2"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "pytz", "requests-futures~=1.0.0", "pendulum~=2.1.2"] TEST_REQUIREMENTS = ["pytest~=6.1", "pytest-mock~=3.6", "source-acceptance-test", "requests-mock==1.9.3"] From ce0bbf3f758a473635b13a9133dee4db9c3ac735 Mon Sep 17 00:00:00 2001 From: Octavia Squidington III <90398440+octavia-squidington-iii@users.noreply.github.com> Date: Thu, 20 Oct 2022 15:29:54 -0500 Subject: [PATCH 238/498] Bump Airbyte version from 0.40.15 to 0.40.16 (#18268) Co-authored-by: evantahler --- .bumpversion.cfg | 4 ++-- .env | 2 +- airbyte-bootloader/Dockerfile | 2 +- airbyte-container-orchestrator/Dockerfile | 2 +- airbyte-cron/Dockerfile | 2 +- airbyte-metrics/reporter/Dockerfile | 2 +- airbyte-proxy/Dockerfile | 2 +- airbyte-server/Dockerfile | 2 +- airbyte-webapp/package-lock.json | 4 ++-- airbyte-webapp/package.json | 2 +- airbyte-workers/Dockerfile | 2 +- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-server/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.yaml | 2 +- charts/airbyte/README.md | 4 ++-- docs/operator-guides/upgrading-airbyte.md | 2 +- kube/overlays/stable-with-resource-limits/.env | 2 +- .../stable-with-resource-limits/kustomization.yaml | 12 ++++++------ kube/overlays/stable/.env | 2 +- kube/overlays/stable/kustomization.yaml | 12 ++++++------ octavia-cli/Dockerfile | 2 +- octavia-cli/README.md | 4 ++-- octavia-cli/install.sh | 2 +- octavia-cli/setup.py | 2 +- 27 files changed, 41 insertions(+), 41 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 28e83fc2c91a..05dbd9ce8ba5 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,9 +1,9 @@ [bumpversion] -current_version = 0.40.15 +current_version = 0.40.16 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-[a-z]+)? -serialize = +serialize = {major}.{minor}.{patch} [bumpversion:file:.bumpversion.cfg] diff --git a/.env b/.env index e2b27523a2c8..dbe04920d885 100644 --- a/.env +++ b/.env @@ -10,7 +10,7 @@ ### SHARED ### -VERSION=0.40.15 +VERSION=0.40.16 # When using the airbyte-db via default docker image CONFIG_ROOT=/data diff --git a/airbyte-bootloader/Dockerfile b/airbyte-bootloader/Dockerfile index a063f7ea0ebf..bb2d03ee3924 100644 --- a/airbyte-bootloader/Dockerfile +++ b/airbyte-bootloader/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-bootloader ENV VERSION ${VERSION} diff --git a/airbyte-container-orchestrator/Dockerfile b/airbyte-container-orchestrator/Dockerfile index 86e187079e6a..6f82f3871cb9 100644 --- a/airbyte-container-orchestrator/Dockerfile +++ b/airbyte-container-orchestrator/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-container-orchestrator ENV VERSION=${VERSION} diff --git a/airbyte-cron/Dockerfile b/airbyte-cron/Dockerfile index afd1eb0a144d..88cd040dfb3d 100644 --- a/airbyte-cron/Dockerfile +++ b/airbyte-cron/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS cron -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-cron ENV VERSION ${VERSION} diff --git a/airbyte-metrics/reporter/Dockerfile b/airbyte-metrics/reporter/Dockerfile index dcd697f66baf..9bce1e5d6d16 100644 --- a/airbyte-metrics/reporter/Dockerfile +++ b/airbyte-metrics/reporter/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS metrics-reporter -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-metrics-reporter ENV VERSION ${VERSION} diff --git a/airbyte-proxy/Dockerfile b/airbyte-proxy/Dockerfile index 619b4eddf074..4c7c6b0c9681 100644 --- a/airbyte-proxy/Dockerfile +++ b/airbyte-proxy/Dockerfile @@ -2,7 +2,7 @@ FROM nginx:latest -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-proxy ENV VERSION ${VERSION} diff --git a/airbyte-server/Dockerfile b/airbyte-server/Dockerfile index ca65443b10ea..bdcb33c35968 100644 --- a/airbyte-server/Dockerfile +++ b/airbyte-server/Dockerfile @@ -3,7 +3,7 @@ FROM ${JDK_IMAGE} AS server EXPOSE 8000 -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-server ENV VERSION ${VERSION} diff --git a/airbyte-webapp/package-lock.json b/airbyte-webapp/package-lock.json index e301769fc9dc..02695734a588 100644 --- a/airbyte-webapp/package-lock.json +++ b/airbyte-webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "airbyte-webapp", - "version": "0.40.15", + "version": "0.40.16", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "airbyte-webapp", - "version": "0.40.15", + "version": "0.40.16", "dependencies": { "@datadog/browser-rum": "^4.21.2", "@floating-ui/react-dom": "^1.0.0", diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index 35c62a58cb74..4add14e9465e 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -1,6 +1,6 @@ { "name": "airbyte-webapp", - "version": "0.40.15", + "version": "0.40.16", "private": true, "engines": { "node": ">=16.0.0" diff --git a/airbyte-workers/Dockerfile b/airbyte-workers/Dockerfile index 090dee9fcac9..f601ffd2be13 100644 --- a/airbyte-workers/Dockerfile +++ b/airbyte-workers/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.15 +ARG VERSION=0.40.16 ENV APPLICATION airbyte-workers ENV VERSION ${VERSION} diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 097ac5c7b8f8..42c59cd39f57 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.15" +appVersion: "0.40.16" dependencies: - name: common diff --git a/charts/airbyte-server/Chart.yaml b/charts/airbyte-server/Chart.yaml index a590b6431ab3..65930ba8894b 100644 --- a/charts/airbyte-server/Chart.yaml +++ b/charts/airbyte-server/Chart.yaml @@ -21,7 +21,7 @@ version: 0.45.3 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.15" +appVersion: "0.40.16" dependencies: - name: common diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index 487a391ccfb6..b5a8ce4030d6 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.15" +appVersion: "0.40.16" dependencies: - name: common diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index abe1441b92a9..aae4365420af 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.15" +appVersion: "0.40.16" dependencies: - name: common diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 683e7eea40b5..7ab6fe96553f 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.15" +appVersion: "0.40.16" dependencies: - name: common diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 47f4e6e59d0e..8b4abb63d7ce 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -22,7 +22,7 @@ version: 0.40.27 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.15" +appVersion: "0.40.16" dependencies: - name: common diff --git a/charts/airbyte/README.md b/charts/airbyte/README.md index 08b3cedfca28..cc841057bea0 100644 --- a/charts/airbyte/README.md +++ b/charts/airbyte/README.md @@ -1,6 +1,6 @@ # airbyte -![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.15](https://img.shields.io/badge/AppVersion-0.40.15-informational?style=flat-square) +![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.16](https://img.shields.io/badge/AppVersion-0.40.16-informational?style=flat-square) Helm chart to deploy airbyte @@ -248,7 +248,7 @@ Helm chart to deploy airbyte | worker.hpa.enabled | bool | `false` | | | worker.image.pullPolicy | string | `"IfNotPresent"` | | | worker.image.repository | string | `"airbyte/worker"` | | -| worker.image.tag | string | `"0.40.15"` | | +| worker.image.tag | string | `"0.40.16"` | | | worker.livenessProbe.enabled | bool | `true` | | | worker.livenessProbe.failureThreshold | int | `3` | | | worker.livenessProbe.initialDelaySeconds | int | `30` | | diff --git a/docs/operator-guides/upgrading-airbyte.md b/docs/operator-guides/upgrading-airbyte.md index 4c7bfd2a88db..ac394608a0cc 100644 --- a/docs/operator-guides/upgrading-airbyte.md +++ b/docs/operator-guides/upgrading-airbyte.md @@ -103,7 +103,7 @@ If you are upgrading from (i.e. your current version of Airbyte is) Airbyte vers Here's an example of what it might look like with the values filled in. It assumes that the downloaded `airbyte_archive.tar.gz` is in `/tmp`. ```bash - docker run --rm -v /tmp:/config airbyte/migration:0.40.15 --\ + docker run --rm -v /tmp:/config airbyte/migration:0.40.16 --\ --input /config/airbyte_archive.tar.gz\ --output /config/airbyte_archive_migrated.tar.gz ``` diff --git a/kube/overlays/stable-with-resource-limits/.env b/kube/overlays/stable-with-resource-limits/.env index 0503f6ba669b..b18d147d78f3 100644 --- a/kube/overlays/stable-with-resource-limits/.env +++ b/kube/overlays/stable-with-resource-limits/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.15 +AIRBYTE_VERSION=0.40.16 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable-with-resource-limits/kustomization.yaml b/kube/overlays/stable-with-resource-limits/kustomization.yaml index 5110d70a151e..b38172a1a64b 100644 --- a/kube/overlays/stable-with-resource-limits/kustomization.yaml +++ b/kube/overlays/stable-with-resource-limits/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/bootloader - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/server - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/webapp - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/worker - newTag: 0.40.15 + newTag: 0.40.16 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.15 + newTag: 0.40.16 configMapGenerator: - name: airbyte-env diff --git a/kube/overlays/stable/.env b/kube/overlays/stable/.env index 3931170e9451..b2643a499a36 100644 --- a/kube/overlays/stable/.env +++ b/kube/overlays/stable/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.15 +AIRBYTE_VERSION=0.40.16 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable/kustomization.yaml b/kube/overlays/stable/kustomization.yaml index ccdd46b6a73d..2ebbb39c4509 100644 --- a/kube/overlays/stable/kustomization.yaml +++ b/kube/overlays/stable/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/bootloader - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/server - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/webapp - newTag: 0.40.15 + newTag: 0.40.16 - name: airbyte/worker - newTag: 0.40.15 + newTag: 0.40.16 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.15 + newTag: 0.40.16 configMapGenerator: - name: airbyte-env diff --git a/octavia-cli/Dockerfile b/octavia-cli/Dockerfile index dfff65c6f22c..3fc1f9802744 100644 --- a/octavia-cli/Dockerfile +++ b/octavia-cli/Dockerfile @@ -14,5 +14,5 @@ USER octavia-cli WORKDIR /home/octavia-project ENTRYPOINT ["octavia"] -LABEL io.airbyte.version=0.40.15 +LABEL io.airbyte.version=0.40.16 LABEL io.airbyte.name=airbyte/octavia-cli diff --git a/octavia-cli/README.md b/octavia-cli/README.md index 52a9489fc63d..79c8ce0c26de 100644 --- a/octavia-cli/README.md +++ b/octavia-cli/README.md @@ -104,7 +104,7 @@ This script: ```bash touch ~/.octavia # Create a file to store env variables that will be mapped the octavia-cli container mkdir my_octavia_project_directory # Create your octavia project directory where YAML configurations will be stored. -docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.15 +docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.16 ``` ### Using `docker-compose` @@ -712,7 +712,7 @@ You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment | Version | Date | Description | PR | | ------- | ---------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | 0.41.0 | 2022-10-13 | Use Basic Authentication for making API requests | [#17982](https://github.com/airbytehq/airbyte/pull/17982) | -| 0.40.15 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | +| 0.40.16 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | | 0.39.33 | 2022-07-05 | Add `octavia import all` command | [#14374](https://github.com/airbytehq/airbyte/pull/14374) | | 0.39.32 | 2022-06-30 | Create import command to import and manage existing Airbyte resource from octavia-cli | [#14137](https://github.com/airbytehq/airbyte/pull/14137) | | 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | diff --git a/octavia-cli/install.sh b/octavia-cli/install.sh index ea7c8f245a0f..e5e9597c32fa 100755 --- a/octavia-cli/install.sh +++ b/octavia-cli/install.sh @@ -3,7 +3,7 @@ # This install scripts currently only works for ZSH and Bash profiles. # It creates an octavia alias in your profile bound to a docker run command and your current user. -VERSION=0.40.15 +VERSION=0.40.16 OCTAVIA_ENV_FILE=${HOME}/.octavia detect_profile() { diff --git a/octavia-cli/setup.py b/octavia-cli/setup.py index 158f2bb34b30..ce00e1004812 100644 --- a/octavia-cli/setup.py +++ b/octavia-cli/setup.py @@ -15,7 +15,7 @@ setup( name="octavia-cli", - version="0.40.15", + version="0.40.16", description="A command line interface to manage Airbyte configurations", long_description=README, author="Airbyte", From e232ffa9fc0faae9db044acbb24625a5f0044a0b Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 20 Oct 2022 14:10:24 -0700 Subject: [PATCH 239/498] Skip basic auth for Octavia integration tests (#18270) --- tools/bin/integration_tests_octavia.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/bin/integration_tests_octavia.sh b/tools/bin/integration_tests_octavia.sh index 57acbe2a40d5..5719a03a2ca8 100755 --- a/tools/bin/integration_tests_octavia.sh +++ b/tools/bin/integration_tests_octavia.sh @@ -9,7 +9,7 @@ assert_root echo "Starting app..." # Detach so we can run subsequent commands -VERSION=dev TRACKING_STRATEGY=logging docker-compose up -d +VERSION=dev TRACKING_STRATEGY=logging BASIC_AUTH_USERNAME="" BASIC_AUTH_PASSWORD="" docker-compose up -d # Sometimes source/dest containers using airbyte volumes survive shutdown, which need to be killed in order to shut down properly. shutdown_cmd="docker-compose down -v || docker kill \$(docker ps -a -f volume=airbyte_workspace -f volume=airbyte_data -f volume=airbyte_db -q) && docker-compose down -v" From 4e236b5b42213572b759f07541891cb593e69d68 Mon Sep 17 00:00:00 2001 From: Parker Mossman Date: Thu, 20 Oct 2022 14:48:27 -0700 Subject: [PATCH 240/498] Add Geography support to RouterService (#17902) * router service uses geography in database instead of env var move geography map to a helper that can be overridden with a separate implementation in cloud format pmd fix import move geography mapper interface to airbyte-commons-temporal * add DefaultGeographyMapper back in * remove all args constructor and extranneous import * rename GeographyMapper to TaskQueueMapper --- .../temporal/scheduling/TaskQueueMapper.java | 17 ++++++ .../main/java/io/airbyte/config/Configs.java | 10 ---- .../java/io/airbyte/config/EnvConfigs.java | 12 ---- .../config/persistence/ConfigRepository.java | 9 +++ .../ConfigRepositoryE2EReadWriteTest.java | 10 ++++ .../scheduling/DefaultTaskQueueMapper.java | 37 ++++++++++++ .../temporal/scheduling/RouterService.java | 40 +++++++++++++ .../RouteToSyncTaskQueueActivityImpl.java | 15 ++++- .../workers/temporal/sync/RouterService.java | 48 --------------- .../src/main/resources/application.yml | 1 - .../DefaultTaskQueueMapperTest.java | 41 +++++++++++++ .../scheduling/RouterServiceTest.java | 60 +++++++++++++++++++ .../temporal/sync/RouterServiceTest.java | 60 ------------------- 13 files changed, 226 insertions(+), 134 deletions(-) create mode 100644 airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/TaskQueueMapper.java create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapper.java create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/RouterService.java delete mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RouterService.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapperTest.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/RouterServiceTest.java delete mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/RouterServiceTest.java diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/TaskQueueMapper.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/TaskQueueMapper.java new file mode 100644 index 000000000000..c7003ef23b1b --- /dev/null +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/scheduling/TaskQueueMapper.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.commons.temporal.scheduling; + +import io.airbyte.config.Geography; + +/** + * Maps a {@link Geography} to a Temporal Task Queue that should be used to run syncs for the given + * Geography. + */ +public interface TaskQueueMapper { + + String getTaskQueue(Geography geography); + +} diff --git a/airbyte-config/config-models/src/main/java/io/airbyte/config/Configs.java b/airbyte-config/config-models/src/main/java/io/airbyte/config/Configs.java index 6e05ed9e3cfb..d7d718dba28f 100644 --- a/airbyte-config/config-models/src/main/java/io/airbyte/config/Configs.java +++ b/airbyte-config/config-models/src/main/java/io/airbyte/config/Configs.java @@ -579,16 +579,6 @@ public interface Configs { */ boolean shouldRunConnectionManagerWorkflows(); - // Worker - Control Plane configs - - /** - * TEMPORARY: Define a set of connection IDs that should run in Airbyte's MVP Data Plane. - This - * should only be set on Control-plane workers, since those workers decide which Data Plane task - * queue to use based on connectionId. - Will be removed in favor of the Routing Service in the - * future. Internal-use only. - */ - Set connectionIdsForMvpDataPlane(); - // Worker - Data Plane configs /** diff --git a/airbyte-config/config-models/src/main/java/io/airbyte/config/EnvConfigs.java b/airbyte-config/config-models/src/main/java/io/airbyte/config/EnvConfigs.java index f92f69abec8a..7dd72dc36f10 100644 --- a/airbyte-config/config-models/src/main/java/io/airbyte/config/EnvConfigs.java +++ b/airbyte-config/config-models/src/main/java/io/airbyte/config/EnvConfigs.java @@ -138,7 +138,6 @@ public class EnvConfigs implements Configs { // Worker - Control plane configs private static final String DEFAULT_DATA_SYNC_TASK_QUEUES = "SYNC"; // should match TemporalJobType.SYNC.name() - private static final String CONNECTION_IDS_FOR_MVP_DATA_PLANE = "CONNECTION_IDS_FOR_MVP_DATA_PLANE"; // Worker - Data Plane configs private static final String DATA_SYNC_TASK_QUEUES = "DATA_SYNC_TASK_QUEUES"; @@ -947,17 +946,6 @@ public boolean shouldRunConnectionManagerWorkflows() { return getEnvOrDefault(SHOULD_RUN_CONNECTION_MANAGER_WORKFLOWS, true); } - // Worker - Control plane - - @Override - public Set connectionIdsForMvpDataPlane() { - final var connectionIds = getEnvOrDefault(CONNECTION_IDS_FOR_MVP_DATA_PLANE, ""); - if (connectionIds.isEmpty()) { - return new HashSet<>(); - } - return Arrays.stream(connectionIds.split(",")).collect(Collectors.toSet()); - } - // Worker - Data plane @Override diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 3d48f93cecaf..771e902b7492 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -33,6 +33,7 @@ import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.DestinationOAuthParameter; +import io.airbyte.config.Geography; import io.airbyte.config.SourceConnection; import io.airbyte.config.SourceOAuthParameter; import io.airbyte.config.StandardDestinationDefinition; @@ -1122,4 +1123,12 @@ public ConfiguredAirbyteCatalog getConfiguredCatalogForConnection(final UUID con return standardSync.getCatalog(); } + public Geography getGeographyForConnection(final UUID connectionId) throws IOException { + return database.query(ctx -> ctx.select(CONNECTION.GEOGRAPHY) + .from(CONNECTION) + .where(CONNECTION.ID.eq(connectionId)) + .limit(1)) + .fetchOneInto(Geography.class); + } + } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index 3b3a626ed4e2..9cf255a3b27c 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -18,6 +18,7 @@ import io.airbyte.config.ActorCatalog; import io.airbyte.config.DestinationConnection; import io.airbyte.config.DestinationOAuthParameter; +import io.airbyte.config.Geography; import io.airbyte.config.SourceConnection; import io.airbyte.config.SourceOAuthParameter; import io.airbyte.config.StandardDestinationDefinition; @@ -491,4 +492,13 @@ void testGetDestinationAndDefinitionsFromDestinationIds() throws IOException { assertThat(actual).hasSameElementsAs(expected); } + @Test + void testGetGeographyForConnection() throws IOException { + final StandardSync sync = MockData.standardSyncs().get(0); + final Geography expected = sync.getGeography(); + final Geography actual = configRepository.getGeographyForConnection(sync.getConnectionId()); + + assertEquals(expected, actual); + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapper.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapper.java new file mode 100644 index 000000000000..fadd2b1e4b0b --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.scheduling; + +import com.google.common.annotations.VisibleForTesting; +import io.airbyte.commons.temporal.TemporalJobType; +import io.airbyte.commons.temporal.scheduling.TaskQueueMapper; +import io.airbyte.config.Geography; +import jakarta.inject.Singleton; +import java.util.Map; + +@Singleton +public class DefaultTaskQueueMapper implements TaskQueueMapper { + + @VisibleForTesting + static final String DEFAULT_SYNC_TASK_QUEUE = TemporalJobType.SYNC.name(); + + // By default, map every Geography value to the default task queue. + // To override this behavior, define a new TaskQueueMapper bean with the @Primary annotation. + @VisibleForTesting + static final Map GEOGRAPHY_TASK_QUEUE_MAP = Map.of( + Geography.AUTO, DEFAULT_SYNC_TASK_QUEUE, + Geography.US, DEFAULT_SYNC_TASK_QUEUE, + Geography.EU, DEFAULT_SYNC_TASK_QUEUE); + + @Override + public String getTaskQueue(final Geography geography) { + if (GEOGRAPHY_TASK_QUEUE_MAP.containsKey(geography)) { + return GEOGRAPHY_TASK_QUEUE_MAP.get(geography); + } + + throw new IllegalArgumentException(String.format("Unexpected geography %s", geography)); + } + +} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/RouterService.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/RouterService.java new file mode 100644 index 000000000000..14c911aecb9f --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/RouterService.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.scheduling; + +import io.airbyte.commons.temporal.scheduling.TaskQueueMapper; +import io.airbyte.config.Geography; +import io.airbyte.config.persistence.ConfigRepository; +import jakarta.inject.Singleton; +import java.io.IOException; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; + +/** + * Decides which Task Queue should be used for a given connection's sync operations, based on the + * configured {@link Geography} + */ +@Singleton +@Slf4j +public class RouterService { + + private final ConfigRepository configRepository; + private final TaskQueueMapper taskQueueMapper; + + public RouterService(final ConfigRepository configRepository, final TaskQueueMapper taskQueueMapper) { + this.configRepository = configRepository; + this.taskQueueMapper = taskQueueMapper; + } + + /** + * Given a connectionId, look up the connection's configured {@link Geography} in the config DB and + * use it to determine which Task Queue should be used for this connection's sync. + */ + public String getTaskQueue(final UUID connectionId) throws IOException { + final Geography geography = configRepository.getGeographyForConnection(connectionId); + return taskQueueMapper.getTaskQueue(geography); + } + +} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java index 3bf7a4967afb..95dd69e976b6 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java @@ -8,11 +8,15 @@ import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; import datadog.trace.api.Trace; +import io.airbyte.commons.temporal.exception.RetryableException; import io.airbyte.metrics.lib.ApmTraceUtils; -import io.airbyte.workers.temporal.sync.RouterService; +import io.airbyte.workers.temporal.scheduling.RouterService; import jakarta.inject.Singleton; +import java.io.IOException; import java.util.Map; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Singleton public class RouteToSyncTaskQueueActivityImpl implements RouteToSyncTaskQueueActivity { @@ -27,9 +31,14 @@ public RouteToSyncTaskQueueActivityImpl(final RouterService routerService) { public RouteToSyncTaskQueueOutput route(final RouteToSyncTaskQueueInput input) { ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, input.getConnectionId())); - final String taskQueueForConnectionId = routerService.getTaskQueue(input.getConnectionId()); + try { + final String taskQueueForConnectionId = routerService.getTaskQueue(input.getConnectionId()); - return new RouteToSyncTaskQueueOutput(taskQueueForConnectionId); + return new RouteToSyncTaskQueueOutput(taskQueueForConnectionId); + } catch (final IOException e) { + log.warn("Encountered an error while attempting to route connection {} to a task queue: \n{}", input.getConnectionId(), e); + throw new RetryableException(e); + } } } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RouterService.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RouterService.java deleted file mode 100644 index 12626e497b4d..000000000000 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RouterService.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.workers.temporal.sync; - -import com.google.common.annotations.VisibleForTesting; -import io.airbyte.commons.temporal.TemporalJobType; -import io.micronaut.context.annotation.Value; -import io.micronaut.core.util.StringUtils; -import jakarta.inject.Singleton; -import java.util.Arrays; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -@Singleton -public class RouterService { - - static final String MVP_DATA_PLANE_TASK_QUEUE = "MVP_DATA_PLANE"; - - @Value("${airbyte.data.plane.connection-ids-mvp}") - private String connectionIdsForMvpDataPlane; - - /** - * For now, returns a Task Queue by checking to see if the connectionId is on the env var list for - * usage in the MVP Data Plane. This will be replaced by a proper Router Service in the future. - */ - public String getTaskQueue(final UUID connectionId) { - if (connectionId != null) { - if (getConnectionIdsForMvpDataPlane().contains(connectionId.toString())) { - return MVP_DATA_PLANE_TASK_QUEUE; - } - } - return TemporalJobType.SYNC.name(); - } - - private Set getConnectionIdsForMvpDataPlane() { - return StringUtils.isNotEmpty(connectionIdsForMvpDataPlane) ? Arrays.stream(connectionIdsForMvpDataPlane.split(",")).collect(Collectors.toSet()) - : Set.of(); - } - - @VisibleForTesting - void setConnectionIdsForMvpDataPlane(final String connectionIdsForMvpDataPlane) { - this.connectionIdsForMvpDataPlane = connectionIdsForMvpDataPlane; - } - -} diff --git a/airbyte-workers/src/main/resources/application.yml b/airbyte-workers/src/main/resources/application.yml index 2c5cfce97404..b2fd4e6f9bdc 100644 --- a/airbyte-workers/src/main/resources/application.yml +++ b/airbyte-workers/src/main/resources/application.yml @@ -64,7 +64,6 @@ airbyte: sync: task-queue: ${DATA_SYNC_TASK_QUEUES:SYNC} plane: - connection-ids-mvp: ${CONNECTION_IDS_FOR_MVP_DATA_PLANE:} service-account: credentials-path: ${DATA_PLANE_SERVICE_ACCOUNT_CREDENTIALS_PATH:} email: ${DATA_PLANE_SERVICE_ACCOUNT_EMAIL:} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapperTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapperTest.java new file mode 100644 index 000000000000..81560faaca6e --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/DefaultTaskQueueMapperTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.airbyte.commons.temporal.scheduling.TaskQueueMapper; +import io.airbyte.config.Geography; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +class DefaultTaskQueueMapperTest { + + @Test + void testGetTaskQueue() { + // By default, every Geography should map to the default SYNC task queue + final TaskQueueMapper mapper = new DefaultTaskQueueMapper(); + + assertEquals(DefaultTaskQueueMapper.DEFAULT_SYNC_TASK_QUEUE, mapper.getTaskQueue(Geography.AUTO)); + assertEquals(DefaultTaskQueueMapper.DEFAULT_SYNC_TASK_QUEUE, mapper.getTaskQueue(Geography.US)); + assertEquals(DefaultTaskQueueMapper.DEFAULT_SYNC_TASK_QUEUE, mapper.getTaskQueue(Geography.EU)); + } + + /** + * If this test fails, it likely means that a new value was added to the {@link Geography} enum. A + * new entry must be added to {@link DefaultTaskQueueMapper#GEOGRAPHY_TASK_QUEUE_MAP} to get this + * test to pass. + */ + @Test + void testAllGeographiesHaveAMapping() { + final Set allGeographies = Arrays.stream(Geography.values()).collect(Collectors.toSet()); + final Set mappedGeographies = DefaultTaskQueueMapper.GEOGRAPHY_TASK_QUEUE_MAP.keySet(); + + assertEquals(allGeographies, mappedGeographies); + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/RouterServiceTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/RouterServiceTest.java new file mode 100644 index 000000000000..bf5a0c718f4f --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/RouterServiceTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.scheduling; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.airbyte.commons.temporal.scheduling.TaskQueueMapper; +import io.airbyte.config.Geography; +import io.airbyte.config.persistence.ConfigRepository; +import java.io.IOException; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +/** + * Test suite for the {@link RouterService} class. + */ +@ExtendWith(MockitoExtension.class) +class RouterServiceTest { + + private static final UUID CONNECTION_ID = UUID.randomUUID(); + private static final String US_TASK_QUEUE = "US_TASK_QUEUE"; + private static final String EU_TASK_QUEUE = "EU_TASK_QUEUE"; + + @Mock + private ConfigRepository mConfigRepository; + + @Mock + private TaskQueueMapper mTaskQueueMapper; + + private RouterService routerService; + + @BeforeEach + void init() { + routerService = new RouterService(mConfigRepository, mTaskQueueMapper); + + Mockito.when(mTaskQueueMapper.getTaskQueue(Geography.AUTO)).thenReturn(US_TASK_QUEUE); + Mockito.when(mTaskQueueMapper.getTaskQueue(Geography.US)).thenReturn(US_TASK_QUEUE); + Mockito.when(mTaskQueueMapper.getTaskQueue(Geography.EU)).thenReturn(EU_TASK_QUEUE); + } + + @Test + void testGetTaskQueue() throws IOException { + Mockito.when(mConfigRepository.getGeographyForConnection(CONNECTION_ID)).thenReturn(Geography.AUTO); + assertEquals(US_TASK_QUEUE, routerService.getTaskQueue(CONNECTION_ID)); + + Mockito.when(mConfigRepository.getGeographyForConnection(CONNECTION_ID)).thenReturn(Geography.US); + assertEquals(US_TASK_QUEUE, routerService.getTaskQueue(CONNECTION_ID)); + + Mockito.when(mConfigRepository.getGeographyForConnection(CONNECTION_ID)).thenReturn(Geography.EU); + assertEquals(EU_TASK_QUEUE, routerService.getTaskQueue(CONNECTION_ID)); + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/RouterServiceTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/RouterServiceTest.java deleted file mode 100644 index 0f9165e99b15..000000000000 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/RouterServiceTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.workers.temporal.sync; - -import static io.airbyte.workers.temporal.sync.RouterService.MVP_DATA_PLANE_TASK_QUEUE; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import io.airbyte.commons.temporal.TemporalJobType; -import java.util.UUID; -import org.junit.jupiter.api.Test; - -/** - * Test suite for the {@link RouterService} class. - */ -class RouterServiceTest { - - @Test - void testSelectionOfTaskQueueForDataPlane() { - final UUID connectionId = UUID.randomUUID(); - final String connectionIdsForMvpDataPlane = connectionId.toString(); - final RouterService routerService = new RouterService(); - routerService.setConnectionIdsForMvpDataPlane(connectionIdsForMvpDataPlane); - - final String taskQueue = routerService.getTaskQueue(connectionId); - assertEquals(MVP_DATA_PLANE_TASK_QUEUE, taskQueue); - } - - @Test - void testSelectionOfTaskQueueForNonMatchingConnectionId() { - final UUID connectionId = UUID.randomUUID(); - final String connectionIdsForMvpDataPlane = "1,2,3,4,5"; - final RouterService routerService = new RouterService(); - routerService.setConnectionIdsForMvpDataPlane(connectionIdsForMvpDataPlane); - - final String taskQueue = routerService.getTaskQueue(connectionId); - assertEquals(TemporalJobType.SYNC.name(), taskQueue); - } - - @Test - void testSelectionOfTaskQueueForNullConnectionId() { - final String connectionIdsForMvpDataPlane = "1,2,3,4,5"; - final RouterService routerService = new RouterService(); - routerService.setConnectionIdsForMvpDataPlane(connectionIdsForMvpDataPlane); - - final String taskQueue = routerService.getTaskQueue(null); - assertEquals(TemporalJobType.SYNC.name(), taskQueue); - } - - @Test - void testSelectionOfTaskQueueForBlankConnectionIdSet() { - final UUID connectionId = UUID.randomUUID(); - final RouterService routerService = new RouterService(); - - final String taskQueue = routerService.getTaskQueue(connectionId); - assertEquals(TemporalJobType.SYNC.name(), taskQueue); - } - -} From c5336ce1dbd27add24c644a8db7b400a1cc7122b Mon Sep 17 00:00:00 2001 From: Parker Mossman Date: Thu, 20 Oct 2022 15:05:59 -0700 Subject: [PATCH 241/498] use basic auth username and password from .env (#18273) --- tools/bin/load_test/cleanup_load_test.sh | 3 +++ tools/bin/load_test/load_test_airbyte.sh | 3 +++ tools/bin/load_test/load_test_utils.sh | 1 + 3 files changed, 7 insertions(+) diff --git a/tools/bin/load_test/cleanup_load_test.sh b/tools/bin/load_test/cleanup_load_test.sh index 03e60bc2558b..49e339944bea 100755 --- a/tools/bin/load_test/cleanup_load_test.sh +++ b/tools/bin/load_test/cleanup_load_test.sh @@ -7,6 +7,9 @@ This script cleans up an earlier load test. It reads from cleanup files that the in order to determine which IDs to delete. comment +echo "Sourcing environment variables from .env" +source .env + cd "$(dirname "$0")" source load_test_utils.sh diff --git a/tools/bin/load_test/load_test_airbyte.sh b/tools/bin/load_test/load_test_airbyte.sh index dc7f893b651e..a2c36b5f8ebd 100755 --- a/tools/bin/load_test/load_test_airbyte.sh +++ b/tools/bin/load_test/load_test_airbyte.sh @@ -9,6 +9,9 @@ that requires port-forwarding. It stores connector and connection IDs that it cr which means the script will delete every connector and connection ID that it created and stored in that file. comment +echo "Sourcing environment variables from .env" +source .env + cd "$(dirname "$0")" source load_test_utils.sh diff --git a/tools/bin/load_test/load_test_utils.sh b/tools/bin/load_test/load_test_utils.sh index 1d70b506590c..fcd6ab106922 100644 --- a/tools/bin/load_test/load_test_utils.sh +++ b/tools/bin/load_test/load_test_utils.sh @@ -22,6 +22,7 @@ function callApi { --show-error \ --header 'Content-Type: application/json' \ --header "X-Endpoint-API-UserInfo: ${x_endpoint_header}" \ + --user "${BASIC_AUTH_USERNAME}:${BASIC_AUTH_PASSWORD}" \ --data "${payload}" \ "${hostname}:${api_port}/api/v1/${endpoint}" } From ec52a637b9848fbce53628ed81f137a7f2bc7a2c Mon Sep 17 00:00:00 2001 From: pmossman Date: Thu, 20 Oct 2022 15:09:33 -0700 Subject: [PATCH 242/498] remove file that should be gitignored --- .../d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tools/bin/load_test/cleanup/d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt diff --git a/tools/bin/load_test/cleanup/d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt b/tools/bin/load_test/cleanup/d67e6a27-cd77-4979-af48-eaa545e5a9ff_destination_ids.txt deleted file mode 100644 index e69de29bb2d1..000000000000 From 94dfe73e7010d6f0691a2d5c510f641a69b1ff2e Mon Sep 17 00:00:00 2001 From: Dhroov Makwana Date: Fri, 21 Oct 2022 04:23:55 +0530 Subject: [PATCH 243/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20Gutendex?= =?UTF-8?q?=20API=20[low-code=20CDK]=20(#18075)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial commit for source-gutendex * Clean up unnecessary code * solve conflict * Fix schema, Rename results_stream to books_stream * Add parameters in gutendex.md, change doc url to official Airbyte url * Add pagination, read each and every record instead of just 32 * Add bootstrap.md, add badge in builds.md * add source definition to airbyte-config * auto-bump connector version Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 7 ++ .../src/main/resources/seed/source_specs.yaml | 78 ++++++++++++ airbyte-integrations/builds.md | 1 + .../connectors/source-gutendex/.dockerignore | 6 + .../connectors/source-gutendex/Dockerfile | 38 ++++++ .../connectors/source-gutendex/README.md | 79 ++++++++++++ .../connectors/source-gutendex/__init__.py | 3 + .../acceptance-test-config.yml | 20 +++ .../source-gutendex/acceptance-test-docker.sh | 16 +++ .../connectors/source-gutendex/bootstrap.md | 51 ++++++++ .../connectors/source-gutendex/build.gradle | 9 ++ .../integration_tests/__init__.py | 3 + .../integration_tests/acceptance.py | 14 +++ .../integration_tests/configured_catalog.json | 13 ++ .../integration_tests/invalid_config.json | 5 + .../integration_tests/sample_config.json | 6 + .../integration_tests/sample_state.json | 1 + .../connectors/source-gutendex/main.py | 13 ++ .../source-gutendex/requirements.txt | 2 + .../connectors/source-gutendex/setup.py | 29 +++++ .../source_gutendex/__init__.py | 8 ++ .../source_gutendex/gutendex.yaml | 51 ++++++++ .../source_gutendex/schemas/TODO.md | 16 +++ .../source_gutendex/schemas/books.json | 114 ++++++++++++++++++ .../source-gutendex/source_gutendex/source.py | 18 +++ .../source-gutendex/source_gutendex/spec.yaml | 59 +++++++++ docs/integrations/README.md | 3 +- docs/integrations/sources/gutendex.md | 85 +++++++++++++ 28 files changed, 747 insertions(+), 1 deletion(-) create mode 100644 airbyte-integrations/connectors/source-gutendex/.dockerignore create mode 100644 airbyte-integrations/connectors/source-gutendex/Dockerfile create mode 100644 airbyte-integrations/connectors/source-gutendex/README.md create mode 100644 airbyte-integrations/connectors/source-gutendex/__init__.py create mode 100644 airbyte-integrations/connectors/source-gutendex/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-gutendex/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-gutendex/bootstrap.md create mode 100644 airbyte-integrations/connectors/source-gutendex/build.gradle create mode 100644 airbyte-integrations/connectors/source-gutendex/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-gutendex/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-gutendex/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-gutendex/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-gutendex/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-gutendex/integration_tests/sample_state.json create mode 100644 airbyte-integrations/connectors/source-gutendex/main.py create mode 100644 airbyte-integrations/connectors/source-gutendex/requirements.txt create mode 100644 airbyte-integrations/connectors/source-gutendex/setup.py create mode 100644 airbyte-integrations/connectors/source-gutendex/source_gutendex/__init__.py create mode 100644 airbyte-integrations/connectors/source-gutendex/source_gutendex/gutendex.yaml create mode 100644 airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/TODO.md create mode 100644 airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/books.json create mode 100644 airbyte-integrations/connectors/source-gutendex/source_gutendex/source.py create mode 100644 airbyte-integrations/connectors/source-gutendex/source_gutendex/spec.yaml create mode 100644 docs/integrations/sources/gutendex.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 4f8608cba5f6..0b22cd4f3b65 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -453,6 +453,13 @@ icon: greenhouse.svg sourceType: api releaseStage: generally_available +- name: Gutendex + sourceDefinitionId: bff9a277-e01d-420d-81ee-80f28a307318 + dockerRepository: airbyte/source-gutendex + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/gutendex + sourceType: api + releaseStage: alpha - name: Harness sourceDefinitionId: 6fe89830-d04d-401b-aad6-6552ffa5c4af dockerRepository: farosai/airbyte-harness-source diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index ea9316dbe684..0c4db83c5e79 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -4575,6 +4575,84 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-gutendex:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/sources/gutendex" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Gutendex Spec" + type: "object" + additionalProperties: true + properties: + author_year_start: + type: "string" + description: "(Optional) Defines the minimum birth year of the authors.\ + \ Books by authors born prior to the start year will not be returned.\ + \ Supports both positive (CE) or negative (BCE) integer values" + pattern: "^[-]?[0-9]{1,4}$" + examples: + - 2002 + - 500 + - -500 + - 2020 + author_year_end: + type: "string" + description: "(Optional) Defines the maximum birth year of the authors.\ + \ Books by authors born after the end year will not be returned. Supports\ + \ both positive (CE) or negative (BCE) integer values" + pattern: "^[-]?[0-9]{1,4}$" + examples: + - 2002 + - 500 + - -500 + - 2020 + copyright: + type: "string" + description: "(Optional) Use this to find books with a certain copyright\ + \ status - true for books with existing copyrights, false for books in\ + \ the public domain in the USA, or null for books with no available copyright\ + \ information." + pattern: "^(true|false|null)$" + examples: + - true + - false + - null + languages: + type: "string" + description: "(Optional) Use this to find books in any of a list of languages.\ + \ They must be comma-separated, two-character language codes." + examples: + - "en" + - "en,fr,fi" + search: + type: "string" + description: "(Optional) Use this to search author names and book titles\ + \ with given words. They must be separated by a space (i.e. %20 in URL-encoded\ + \ format) and are case-insensitive." + examples: + - "dickens%20great%20expect" + - "dickens" + sort: + type: "string" + description: "(Optional) Use this to sort books - ascending for Project\ + \ Gutenberg ID numbers from lowest to highest, descending for IDs highest\ + \ to lowest, or popular (the default) for most popular to least popular\ + \ by number of downloads." + pattern: "^(ascending|descending|popular)$" + examples: + - "ascending" + - "descending" + - "popular" + topic: + type: "string" + description: "(Optional) Use this to search for a case-insensitive key-phrase\ + \ in books' bookshelves or subjects." + examples: + - "children" + - "fantasy" + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "farosai/airbyte-harness-source:0.1.23" spec: documentationUrl: "https://docs.faros.ai" diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 398a014ee215..0444a363c60b 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -48,6 +48,7 @@ | Google Directory API | [![source-google-directory](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-google-directory%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-google-directory) | | Google Workspace Admin | [![source-google-workspace-admin-reports](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-google-workspace-admin-reports%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-google-workspace-admin-reports) | | Greenhouse | [![source-greenhouse](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-greenhouse%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-greenhouse) | +| Gutendex | [![source-gutendex](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-gutendex%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-gutendex) | | HubSpot | [![source-hubspot](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-hubspot%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-hubspot) | | IBM Db2 | [![source-db2](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-db2%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-db2) | | Insightly | [![source-insightly](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-insightly%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-insightly) | diff --git a/airbyte-integrations/connectors/source-gutendex/.dockerignore b/airbyte-integrations/connectors/source-gutendex/.dockerignore new file mode 100644 index 000000000000..55b5f4c1e88c --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_gutendex +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-gutendex/Dockerfile b/airbyte-integrations/connectors/source-gutendex/Dockerfile new file mode 100644 index 000000000000..9326be96bcde --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.11-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_gutendex ./source_gutendex + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-gutendex diff --git a/airbyte-integrations/connectors/source-gutendex/README.md b/airbyte-integrations/connectors/source-gutendex/README.md new file mode 100644 index 000000000000..99d0de8d9bf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/README.md @@ -0,0 +1,79 @@ +# Gutendex Source + +This is the repository for the Gutendex configuration based source connector. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/gutendex). + +## Local development + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-gutendex:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/gutendex) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_gutendex/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source gutendex test creds` +and place them into `secrets/config.json`. + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-gutendex:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-gutendex:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-gutendex:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-gutendex:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-gutendex:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-gutendex:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing + +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. + +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-gutendex:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-gutendex:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-gutendex/__init__.py b/airbyte-integrations/connectors/source-gutendex/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-gutendex/acceptance-test-config.yml b/airbyte-integrations/connectors/source-gutendex/acceptance-test-config.yml new file mode 100644 index 000000000000..dfaeaba5938a --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/acceptance-test-config.yml @@ -0,0 +1,20 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-gutendex:dev +tests: + spec: + - spec_path: "source_gutendex/spec.yaml" + connection: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-gutendex/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-gutendex/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-gutendex/bootstrap.md b/airbyte-integrations/connectors/source-gutendex/bootstrap.md new file mode 100644 index 000000000000..961a8e20b7a9 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/bootstrap.md @@ -0,0 +1,51 @@ +# Gutendex + +## Overview + +Project Gutenberg is a volunteer effort to digitize and archive cultural works, as well as to "encourage the creation and distribution of eBooks." It was founded in 1971 by American writer Michael S. Hart and is the oldest digital library. It has over 60,000 books + +[Gutendex](https://gutendex.com/) is a JSON web API for Project Gutenberg eBook metadata. The Gutendex Connector is implemented with the [Airbyte Low-Code CDK](https://docs.airbyte.com/connector-development/config-based/low-code-cdk-overview). + +## Output Format + +#### Each Book has the following structure + +```yaml +{ + "id": , + "title": , + "authors": , + "translators": , + "subjects": , + "bookshelves": , + "languages": , + "copyright": , + "media_type": , + "formats": , + "download_count": , +} +``` + +#### Each Person has the following structure + +```yaml +{ + "birth_year": , + "death_year": , + "name": , +} +``` + +## Core Streams + +Connector supports the `books` stream that provides information and metadata about books matching the query. + +## Rate Limiting + +No published rate limit. + +## Authentication and Permissions + +No authentication. + +See [this](https://docs.airbyte.io/integrations/sources/gutendex) link for the connector docs. \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-gutendex/build.gradle b/airbyte-integrations/connectors/source-gutendex/build.gradle new file mode 100644 index 000000000000..ac8a19190f5c --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_gutendex' +} diff --git a/airbyte-integrations/connectors/source-gutendex/integration_tests/__init__.py b/airbyte-integrations/connectors/source-gutendex/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-gutendex/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-gutendex/integration_tests/acceptance.py new file mode 100644 index 000000000000..950b53b59d41 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/integration_tests/acceptance.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + yield diff --git a/airbyte-integrations/connectors/source-gutendex/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-gutendex/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..19be72fdac18 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/integration_tests/configured_catalog.json @@ -0,0 +1,13 @@ +{ + "streams": [ + { + "stream": { + "name": "books", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-gutendex/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-gutendex/integration_tests/invalid_config.json new file mode 100644 index 000000000000..25a5d3e24e15 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/integration_tests/invalid_config.json @@ -0,0 +1,5 @@ +{ + "author_year_start": "3000", + "languages": "en,fr", + "topic": "young" +} diff --git a/airbyte-integrations/connectors/source-gutendex/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-gutendex/integration_tests/sample_config.json new file mode 100644 index 000000000000..323e5056d589 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/integration_tests/sample_config.json @@ -0,0 +1,6 @@ +{ + "author_year_start": "1900", + "languages": "en,fr", + "topic": "young", + "sort": "popular" +} diff --git a/airbyte-integrations/connectors/source-gutendex/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-gutendex/integration_tests/sample_state.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/integration_tests/sample_state.json @@ -0,0 +1 @@ +{} diff --git a/airbyte-integrations/connectors/source-gutendex/main.py b/airbyte-integrations/connectors/source-gutendex/main.py new file mode 100644 index 000000000000..cc68e623a4c9 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_gutendex import SourceGutendex + +if __name__ == "__main__": + source = SourceGutendex() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-gutendex/requirements.txt b/airbyte-integrations/connectors/source-gutendex/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-gutendex/setup.py b/airbyte-integrations/connectors/source-gutendex/setup.py new file mode 100644 index 000000000000..75f9ae066236 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.1", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_gutendex", + description="Source implementation for Gutendex.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-gutendex/source_gutendex/__init__.py b/airbyte-integrations/connectors/source-gutendex/source_gutendex/__init__.py new file mode 100644 index 000000000000..96976f4f8912 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/source_gutendex/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceGutendex + +__all__ = ["SourceGutendex"] diff --git a/airbyte-integrations/connectors/source-gutendex/source_gutendex/gutendex.yaml b/airbyte-integrations/connectors/source-gutendex/source_gutendex/gutendex.yaml new file mode 100644 index 000000000000..e596131eee4f --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/source_gutendex/gutendex.yaml @@ -0,0 +1,51 @@ +version: "0.1.0" + +definitions: + selector: + extractor: + field_pointer: + - results + requester: + url_base: "https://gutendex.com/" + http_method: "GET" + request_options_provider: + request_parameters: + author_year_start: "{{ config['author_year_start'] }}" + author_year_end: "{{ config['author_year_end'] }}" + copyright: "{{ config['copyright'] }}" + languages: "{{ config['languages'] }}" + search: "{{ config['search'] }}" + sort: "{{ config['sort'] }}" + topic: "{{ config['topic'] }}" + retriever: + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + type: "DefaultPaginator" + url_base: "*ref(definitions.requester.url_base)" + pagination_strategy: + type: "PageIncrement" + page_size: 32 + page_token_option: + inject_into: "request_parameter" + field_name: "page" + page_size_option: + inject_into: "body_data" + field_name: "page_size" + requester: + $ref: "*ref(definitions.requester)" + base_stream: + retriever: + $ref: "*ref(definitions.retriever)" + books_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "books" + path: "/books" + +streams: + - "*ref(definitions.books_stream)" + +check: + stream_names: + - "books" diff --git a/airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/TODO.md b/airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/TODO.md new file mode 100644 index 000000000000..0e1dfe18bb86 --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/TODO.md @@ -0,0 +1,16 @@ +# TODO: Define your stream schemas +Your connector must describe the schema of each stream it can output using [JSONSchema](https://json-schema.org). + +You can describe the schema of your streams using one `.json` file per stream. + +## Static schemas +From the `gutendex.yaml` configuration file, you read the `.json` files in the `schemas/` directory. You can refer to a schema in your configuration file using the `schema_loader` component's `file_path` field. For example: +``` +schema_loader: + type: JsonSchema + file_path: "./source_gutendex/schemas/customers.json" +``` +Every stream specified in the configuration file should have a corresponding `.json` schema file. + +Delete this file once you're done. Or don't. Up to you :) + diff --git a/airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/books.json b/airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/books.json new file mode 100644 index 000000000000..715c849065bd --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/source_gutendex/schemas/books.json @@ -0,0 +1,114 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "authors": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "birth_year": { + "type": ["integer", "null"] + }, + "death_year": { + "type": ["integer", "null"] + } + }, + "required": ["name", "birth_year", "death_year"] + } + }, + "translators": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "birth_year": { + "type": ["integer", "null"] + }, + "death_year": { + "type": ["integer", "null"] + } + }, + "required": ["name", "birth_year", "death_year"] + } + }, + "subjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "bookshelves": { + "type": "array", + "items": { + "type": "string" + } + }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, + "copyright": { + "type": ["boolean", "null"] + }, + "media_type": { + "type": "string" + }, + "formats": { + "type": "object", + "properties": { + "image/jpeg": { + "type": "string" + }, + "application/x-mobipocket-ebook": { + "type": "string" + }, + "application/rdf+xml": { + "type": "string" + }, + "text/html": { + "type": "string" + }, + "application/epub+zip": { + "type": "string" + }, + "text/plain; charset=us-ascii": { + "type": "string" + }, + "application/octet-stream": { + "type": "string" + } + } + }, + "download_count": { + "type": "integer" + } + }, + "required": [ + "id", + "title", + "authors", + "translators", + "subjects", + "bookshelves", + "languages", + "copyright", + "media_type", + "formats", + "download_count" + ] +} diff --git a/airbyte-integrations/connectors/source-gutendex/source_gutendex/source.py b/airbyte-integrations/connectors/source-gutendex/source_gutendex/source.py new file mode 100644 index 000000000000..f7a5c4092cff --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/source_gutendex/source.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource + +""" +This file provides the necessary constructs to interpret a provided declarative YAML configuration file into +source connector. + +WARNING: Do not modify this file. +""" + + +# Declarative Source +class SourceGutendex(YamlDeclarativeSource): + def __init__(self): + super().__init__(**{"path_to_yaml": "gutendex.yaml"}) diff --git a/airbyte-integrations/connectors/source-gutendex/source_gutendex/spec.yaml b/airbyte-integrations/connectors/source-gutendex/source_gutendex/spec.yaml new file mode 100644 index 000000000000..f22b1645b25e --- /dev/null +++ b/airbyte-integrations/connectors/source-gutendex/source_gutendex/spec.yaml @@ -0,0 +1,59 @@ +documentationUrl: https://docs.airbyte.com/integrations/sources/gutendex +connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Gutendex Spec + type: object + additionalProperties: true + properties: + author_year_start: + type: string + description: (Optional) Defines the minimum birth year of the authors. Books by authors born prior to the start year will not be returned. Supports both positive (CE) or negative (BCE) integer values + pattern: ^[-]?[0-9]{1,4}$ + examples: + - 2002 + - 500 + - -500 + - 2020 + author_year_end: + type: string + description: (Optional) Defines the maximum birth year of the authors. Books by authors born after the end year will not be returned. Supports both positive (CE) or negative (BCE) integer values + pattern: ^[-]?[0-9]{1,4}$ + examples: + - 2002 + - 500 + - -500 + - 2020 + copyright: + type: string + description: (Optional) Use this to find books with a certain copyright status - true for books with existing copyrights, false for books in the public domain in the USA, or null for books with no available copyright information. + pattern: ^(true|false|null)$ + examples: + - true + - false + - null + languages: + type: string + description: (Optional) Use this to find books in any of a list of languages. They must be comma-separated, two-character language codes. + examples: + - en + - en,fr,fi + search: + type: string + description: (Optional) Use this to search author names and book titles with given words. They must be separated by a space (i.e. %20 in URL-encoded format) and are case-insensitive. + examples: + - dickens%20great%20expect + - dickens + sort: + type: string + description: (Optional) Use this to sort books - ascending for Project Gutenberg ID numbers from lowest to highest, descending for IDs highest to lowest, or popular (the default) for most popular to least popular by number of downloads. + pattern: ^(ascending|descending|popular)$ + examples: + - ascending + - descending + - popular + topic: + type: string + description: (Optional) Use this to search for a case-insensitive key-phrase in books' bookshelves or subjects. + examples: + - children + - fantasy diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 070288bacc6d..95659449e483 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -75,7 +75,8 @@ For more information about the grading system, see [Product Release Stages](http | [Google Search Console](sources/google-search-console.md) | Generally Available | Yes | | [Google Sheets](sources/google-sheets.md) | Generally Available | Yes | | [Google Workspace Admin Reports](sources/google-workspace-admin-reports.md) | Alpha | Yes | -| [Greenhouse](sources/greenhouse.md) | Generally Available | Yes | +| [Greenhouse](sources/greenhouse.md) | Beta | Yes | +| [Gutendex](sources/gutendex.md) | Alpha | No | | [Harness](sources/harness.md) | Alpha | No | | [Harvest](sources/harvest.md) | Generally Available | Yes | | [http-request](sources/http-request.md) | Alpha | No | diff --git a/docs/integrations/sources/gutendex.md b/docs/integrations/sources/gutendex.md new file mode 100644 index 000000000000..ada413dfbce8 --- /dev/null +++ b/docs/integrations/sources/gutendex.md @@ -0,0 +1,85 @@ +# Gutendex + +## Overview + +The Gutendex source can sync data from the [Gutendex API](https://gutendex.com/) + +## Requirements + +Gutendex requires no access token/API key to make requests. +The following (optional) parameters can be provided to the connector :- +___ +##### `author_year_start` and `author_year_end` +Use these to find books with at least one author alive in a given range of years. They must have positive (CE) or negative (BCE) integer values. + +For example, `/books?author_year_start=1800&author_year_end=1899` gives books with authors alive in the 19th Century. +___ +##### `copyright` +Use this to find books with a certain copyright status: true for books with existing copyrights, false for books in the public domain in the USA, or null for books with no available copyright information. +___ +##### `languages` +Use this to find books in any of a list of languages. They must be comma-separated, two-character language codes. For example, `/books?languages=en` gives books in English, and `/books?languages=fr,fi` gives books in either French or Finnish or both. +___ +##### `search` +Use this to search author names and book titles with given words. They must be separated by a space (i.e. %20 in URL-encoded format) and are case-insensitive. For example, `/books?search=dickens%20great` includes Great Expectations by Charles Dickens. +___ +##### `sort` +Use this to sort books: ascending for Project Gutenberg ID numbers from lowest to highest, descending for IDs highest to lowest, or popular (the default) for most popular to least popular by number of downloads. +___ +##### `topic` +Use this to search for a case-insensitive key-phrase in books' bookshelves or subjects. For example, `/books?topic=children` gives books on the "Children's Literature" bookshelf, with the subject "Sick children -- Fiction", and so on. +___ + +## Output schema + +Lists of book information in the Project Gutenberg database are queried using the API at /books (e.g. gutendex.com/books). Book data will be returned in the format:- + + { + "count": , + "next": , + "previous": , + "results": + } + +where `results` is an array of 0-32 book objects, next and previous are URLs to the next and previous pages of results, and count in the total number of books for the query on all pages combined. + +By default, books are ordered by popularity, determined by their numbers of downloads from Project Gutenberg. + +The source is capable of syncing the results stream. + +## Setup guide + +## Step 1: Set up the Gutendex connector in Airbyte + +### For Airbyte Cloud: + +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. +2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. +3. On the Set up the source page, select **Gutendex** from the Source type dropdown. +4. Click **Set up source**. + +### For Airbyte OSS: + +1. Navigate to the Airbyte Open Source dashboard. +2. Set the name for your source (Gutendex). +3. Click **Set up source**. + +## Supported sync modes + +The Gutendex source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): + +| Feature | Supported? | +| :---------------- | :--------- | +| Full Refresh Sync | Yes | +| Incremental Sync | No | +| Namespaces | No | + +## Performance considerations + +There is no published rate limit. However, since this data updates infrequently, it is recommended to set the update cadence to 24hr or higher. + +## Changelog + +| Version | Date | Pull Request | Subject | +| :------ | :--------- | :-------------------------------------------------------- | :----------------------------------------- | +| 0.1.0 | 2022-10-17 | [#18075](https://github.com/airbytehq/airbyte/pull/18075) | 🎉 New Source: Gutendex API [low-code CDK] | From f5de64df3b7de0d384621e65e2c73d5ae8911a38 Mon Sep 17 00:00:00 2001 From: "Pedro S. Lopez" Date: Thu, 20 Oct 2022 18:57:29 -0400 Subject: [PATCH 244/498] fix issues running connector dependency report on PRs from forks (#18269) * fix issues running connector dependency report on PRs from forks * dont need to prevent running on forks * fix typo * fix typo --- .github/workflows/report-connectors-dependency.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/report-connectors-dependency.yml b/.github/workflows/report-connectors-dependency.yml index 8e60d2c94b49..24aaf39b77ec 100644 --- a/.github/workflows/report-connectors-dependency.yml +++ b/.github/workflows/report-connectors-dependency.yml @@ -12,13 +12,9 @@ jobs: with: fetch-depth: 0 # OR "2" -> To retrieve the preceding commit. - - name: Extract current branch name - shell: bash - run: echo "##[set-output name=branch;]$(echo ${{ github.head_ref }})" - id: extract_branch - - name: Extract git-dif change file + - name: Extract git-diff changed files run: | - git diff --name-only remotes/origin/master...origin/${{ steps.extract_branch.outputs.branch }} -- airbyte-integrations/ > ./changed_files.txt + git diff --name-only remotes/origin/master...HEAD -- airbyte-integrations/ > ./changed_files.txt id: extract_changed_files - name: Install dependencies run: | From 7fcadf433c9dcecfbc86b69ca9ccc77ad84b32ab Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Thu, 20 Oct 2022 16:02:31 -0700 Subject: [PATCH 245/498] Remove `airbyte-queue` (#18250) --- airbyte-queue/LICENSE | 21 ---- airbyte-queue/build.gradle | 7 -- airbyte-queue/readme.md | 6 - .../java/io/airbyte/queue/OnDiskQueue.java | 111 ------------------ .../io/airbyte/queue/OnDiskQueueTest.java | 98 ---------------- settings.gradle | 1 - 6 files changed, 244 deletions(-) delete mode 100644 airbyte-queue/LICENSE delete mode 100644 airbyte-queue/build.gradle delete mode 100644 airbyte-queue/readme.md delete mode 100644 airbyte-queue/src/main/java/io/airbyte/queue/OnDiskQueue.java delete mode 100644 airbyte-queue/src/test/java/io/airbyte/queue/OnDiskQueueTest.java diff --git a/airbyte-queue/LICENSE b/airbyte-queue/LICENSE deleted file mode 100644 index ec45d182fcb9..000000000000 --- a/airbyte-queue/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Airbyte, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/airbyte-queue/build.gradle b/airbyte-queue/build.gradle deleted file mode 100644 index 390d217e5e6e..000000000000 --- a/airbyte-queue/build.gradle +++ /dev/null @@ -1,7 +0,0 @@ -plugins { - id 'java-library' -} - -dependencies { - implementation 'com.baidu:leansoft-bigqueue:0.7.3' -} diff --git a/airbyte-queue/readme.md b/airbyte-queue/readme.md deleted file mode 100644 index 16678592af58..000000000000 --- a/airbyte-queue/readme.md +++ /dev/null @@ -1,6 +0,0 @@ -# airbyte-queue - -Wraps an external tool that provides an on-disk queue. - -## Entrypoint -* `OnDiskQueue.java` diff --git a/airbyte-queue/src/main/java/io/airbyte/queue/OnDiskQueue.java b/airbyte-queue/src/main/java/io/airbyte/queue/OnDiskQueue.java deleted file mode 100644 index 1fd65c968829..000000000000 --- a/airbyte-queue/src/main/java/io/airbyte/queue/OnDiskQueue.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.queue; - -import com.google.common.base.Preconditions; -import com.leansoft.bigqueue.BigQueueImpl; -import com.leansoft.bigqueue.IBigQueue; -import io.airbyte.commons.lang.CloseableQueue; -import java.io.IOException; -import java.nio.file.Path; -import java.util.AbstractQueue; -import java.util.Iterator; -import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.commons.io.FileUtils; - -/** - * This Queue should be used when it is possible for the contents of the queue to be greater than - * the size of memory. It is meant for use by a single process. Closing this queue deletes the data - * on disk. It is NOT meant to be a long-lived, persistent queue. - * - * Wraps BigQueueImpl behind Airbyte persistent queue interface. BigQueueImpl is threadsafe. - * - */ -public class OnDiskQueue extends AbstractQueue implements CloseableQueue { - - private final IBigQueue queue; - private final AtomicBoolean closed = new AtomicBoolean(false); - private final Path persistencePath; - - public OnDiskQueue(final Path persistencePath, final String queueName) throws IOException { - this.persistencePath = persistencePath; - queue = new BigQueueImpl(persistencePath.toString(), queueName); - } - - @Override - public boolean offer(final byte[] bytes) { - Preconditions.checkState(!closed.get()); - try { - queue.enqueue(bytes); - return true; - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public byte[] poll() { - Preconditions.checkState(!closed.get()); - try { - return queue.dequeue(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public byte[] peek() { - Preconditions.checkState(!closed.get()); - try { - return queue.peek(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public int size() { - Preconditions.checkState(!closed.get()); - return Math.toIntExact(queue.size()); - } - - /** - * Logging frameworks call this method when printing out this class. Throw an disable this for now - * since iterating the contents of a queue is tricky and we want to avoid this for now. - */ - @Override - public Iterator iterator() { - // TODO(davin): Implement this properly. - throw new UnsupportedOperationException("This queue does not support iteration"); - } - - @Override - public void close() throws Exception { - closed.set(true); - try { - // todo (cgardens) - this barfs out a huge warning. known issue with the lib: - // https://github.com/bulldog2011/bigqueue/issues/35. - // deallocates memory used by bigqueue - queue.close(); - } finally { - // deletes all data files. - FileUtils.deleteQuietly(persistencePath.toFile()); - } - } - - /** - * Print size instead of queue contents to avoid any sort of logging complication. Note this does - * not hold any read locks for simplicity, and queue size cannot be used as a source of truth. - */ - @Override - public String toString() { - return "OnDiskQueue{" + - "queue=" + queue.hashCode() + - ", size=" + queue.size() + - ", closed=" + closed + - '}'; - } - -} diff --git a/airbyte-queue/src/test/java/io/airbyte/queue/OnDiskQueueTest.java b/airbyte-queue/src/test/java/io/airbyte/queue/OnDiskQueueTest.java deleted file mode 100644 index be9d1e5c7798..000000000000 --- a/airbyte-queue/src/test/java/io/airbyte/queue/OnDiskQueueTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.queue; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.google.common.base.Charsets; -import io.airbyte.commons.lang.CloseableQueue; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class OnDiskQueueTest { - - private static final Path TEST_ROOT = Path.of("/tmp/airbyte_tests"); - private static final String HELLO = "hello"; - private CloseableQueue queue; - private Path queueRoot; - - @BeforeEach - void setup() throws IOException { - queueRoot = Files.createTempDirectory(Files.createDirectories(TEST_ROOT), "test"); - queue = new OnDiskQueue(queueRoot, "test"); - } - - @AfterEach - void teardown() throws Exception { - queue.close(); - } - - @Test - void testPoll() { - queue.offer(toBytes(HELLO)); - assertEquals(HELLO, new String(Objects.requireNonNull(queue.poll()), Charsets.UTF_8)); - } - - @Test - void testPeek() { - queue.offer(toBytes(HELLO)); - assertEquals(HELLO, new String(Objects.requireNonNull(queue.peek()), Charsets.UTF_8)); - assertEquals(HELLO, new String(Objects.requireNonNull(queue.peek()), Charsets.UTF_8)); - assertEquals(HELLO, new String(Objects.requireNonNull(queue.poll()), Charsets.UTF_8)); - } - - @Test - void testSize() { - assertEquals(0, queue.size()); - queue.offer(toBytes(HELLO)); - assertEquals(1, queue.size()); - queue.offer(toBytes(HELLO)); - assertEquals(2, queue.size()); - } - - @Test - void testClosed() throws Exception { - queue.close(); - assertDoesNotThrow(() -> queue.close()); - assertThrows(IllegalStateException.class, () -> queue.offer(toBytes(HELLO))); - assertThrows(IllegalStateException.class, () -> queue.poll()); - } - - @Test - void testCleanupOnEmpty() throws Exception { - assertTrue(Files.exists(queueRoot)); - - queue.offer(toBytes(HELLO)); - queue.poll(); - queue.close(); - - assertFalse(Files.exists(queueRoot)); - } - - @Test - void testCleanupOnNotEmpty() throws Exception { - assertTrue(Files.exists(queueRoot)); - - queue.offer(toBytes(HELLO)); - queue.close(); - - assertFalse(Files.exists(queueRoot)); - } - - @SuppressWarnings("SameParameterValue") - private static byte[] toBytes(final String string) { - return string.getBytes(Charsets.UTF_8); - } - -} diff --git a/settings.gradle b/settings.gradle index 073a53c4b0e1..2dc801ee0554 100644 --- a/settings.gradle +++ b/settings.gradle @@ -72,7 +72,6 @@ include ':airbyte-json-validation' include ':airbyte-metrics:metrics-lib' include ':airbyte-oauth' include ':airbyte-protocol:protocol-models' -include ':airbyte-queue' include ':airbyte-test-utils' // airbyte-workers has a lot of dependencies. From 067e36dc722d4181e01fe8e3c9ac36319159d174 Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Fri, 21 Oct 2022 14:51:11 +0300 Subject: [PATCH 246/498] Source Stripe: update stream schema for `payment_intents` stream (#18228) * #709 source stripe: upd stream schema for payment_intents stream * #709 source stripe: upd changelog * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 6 ++-- .../connectors/source-stripe/Dockerfile | 2 +- .../schemas/payment_intents.json | 28 ++++++++++++++----- docs/integrations/sources/stripe.md | 3 +- 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 0b22cd4f3b65..eb41f515d4b0 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1047,7 +1047,7 @@ - name: Stripe sourceDefinitionId: e094cb9a-26de-4645-8761-65c0c425d1de dockerRepository: airbyte/source-stripe - dockerImageTag: 0.1.39 + dockerImageTag: 0.1.40 documentationUrl: https://docs.airbyte.com/integrations/sources/stripe icon: stripe.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 0c4db83c5e79..a1d450de0f6a 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11029,7 +11029,7 @@ type: "string" path_in_connector_config: - "client_secret" -- dockerImage: "airbyte/source-stripe:0.1.39" +- dockerImage: "airbyte/source-stripe:0.1.40" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/stripe" connectionSpecification: @@ -11086,9 +11086,9 @@ - 180 - 360 description: "The time increment used by the connector when requesting data\ - \ from the Stripe API. The bigger the value is, the less requests will\ + \ from the Stripe API. The bigger the value is, the less requests will\ \ be made and faster the sync will be. On the other hand, the more seldom\ - \ the state is persisted." + \ the state is persisted." order: 4 supportsNormalization: false supportsDBT: false diff --git a/airbyte-integrations/connectors/source-stripe/Dockerfile b/airbyte-integrations/connectors/source-stripe/Dockerfile index b18ea514d130..9578c23dcd7b 100644 --- a/airbyte-integrations/connectors/source-stripe/Dockerfile +++ b/airbyte-integrations/connectors/source-stripe/Dockerfile @@ -12,5 +12,5 @@ COPY main.py ./ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.39 +LABEL io.airbyte.version=0.1.40 LABEL io.airbyte.name=airbyte/source-stripe diff --git a/airbyte-integrations/connectors/source-stripe/source_stripe/schemas/payment_intents.json b/airbyte-integrations/connectors/source-stripe/source_stripe/schemas/payment_intents.json index 6228fbbd1a34..8f0b97caf851 100644 --- a/airbyte-integrations/connectors/source-stripe/source_stripe/schemas/payment_intents.json +++ b/airbyte-integrations/connectors/source-stripe/source_stripe/schemas/payment_intents.json @@ -124,8 +124,12 @@ } } }, - "afterpay_clearpay": {}, - "alipay": {}, + "afterpay_clearpay": { + "type": ["null", "string"] + }, + "alipay": { + "type": ["null", "string"] + }, "au_becs_debit": { "type": ["null", "object"], "properties": { @@ -154,7 +158,9 @@ } } }, - "bancontact": {}, + "bancontact": { + "type": ["null", "string"] + }, "billing_details": { "type": ["null", "object"], "properties": { @@ -372,12 +378,18 @@ "wallet": { "type": ["null", "object"], "properties": { - "amex_express_checkout": {}, - "apple_pay": {}, + "amex_express_checkout": { + "type": ["null", "string"] + }, + "apple_pay": { + "type": ["null", "string"] + }, "dynamic_last4": { "type": ["null", "string"] }, - "google_pay": {}, + "google_pay": { + "type": ["null", "string"] + }, "masterpass": { "type": ["null", "object"], "properties": { @@ -435,7 +447,9 @@ } } }, - "samsung_pay": {}, + "samsung_pay": { + "type": ["null", "string"] + }, "type": { "type": ["null", "string"] }, diff --git a/docs/integrations/sources/stripe.md b/docs/integrations/sources/stripe.md index c3aa3bb0526b..fd55a58fb55f 100644 --- a/docs/integrations/sources/stripe.md +++ b/docs/integrations/sources/stripe.md @@ -75,7 +75,8 @@ The Stripe connector should not run into Stripe API limitations under normal usa ## Changelog | Version | Date | Pull Request | Subject | -| :------ | :--------- | :------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | +|:--------|:-----------|:---------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.1.40 | 2022-10-20 | [18228](https://github.com/airbytehq/airbyte/pull/18228) | Update the `Payment Intents` stream schema | | 0.1.39 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Migrate to per-stream states. | | 0.1.38 | 2022-09-09 | [16537](https://github.com/airbytehq/airbyte/pull/16537) | Fix `redeem_by` field type for `customers` stream | | 0.1.37 | 2022-08-16 | [15686](https://github.com/airbytehq/airbyte/pull/15686) | Fix the bug when the stream couldn't be fetched due to limited permission set, if so - it should be skipped | From e3ff75f3d6c4ce4b418dac8cb5e97f5ae1941fb3 Mon Sep 17 00:00:00 2001 From: Jared Rhizor Date: Fri, 21 Oct 2022 05:25:40 -0700 Subject: [PATCH 247/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20Whisky?= =?UTF-8?q?=20Hunter=20API=20[low-code=20CDK]=20(#17918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial commit for source-whisky-hunter * better logs * finish up connector * fix catalog * fix bug in connector_runner.py that doesn't allow falsey configs * add bootstrap.md * add docs * fix typo * fix source-accept test * auto-bump connector version Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 7 + .../src/main/resources/seed/source_specs.yaml | 12 ++ .../source_acceptance_test/tests/test_core.py | 5 +- .../utils/connector_runner.py | 5 +- .../unit_tests/test_core.py | 2 +- airbyte-integrations/builds.md | 1 + .../source-whisky-hunter/.dockerignore | 6 + .../source-whisky-hunter/Dockerfile | 38 +++++ .../connectors/source-whisky-hunter/README.md | 79 +++++++++++ .../source-whisky-hunter/__init__.py | 3 + .../acceptance-test-config.yml | 18 +++ .../acceptance-test-docker.sh | 16 +++ .../source-whisky-hunter/bootstrap.md | 20 +++ .../source-whisky-hunter/build.gradle | 9 ++ .../integration_tests/__init__.py | 3 + .../integration_tests/acceptance.py | 14 ++ .../integration_tests/catalog.json | 112 +++++++++++++++ .../integration_tests/configured_catalog.json | 130 ++++++++++++++++++ .../integration_tests/sample_config.json | 1 + .../integration_tests/sample_state.json | 1 + .../connectors/source-whisky-hunter/main.py | 13 ++ .../source-whisky-hunter/requirements.txt | 2 + .../connectors/source-whisky-hunter/setup.py | 29 ++++ .../source_whisky_hunter/__init__.py | 8 ++ .../schemas/auctions_data.json | 33 +++++ .../schemas/auctions_info.json | 30 ++++ .../schemas/distilleries_info.json | 24 ++++ .../source_whisky_hunter/source.py | 18 +++ .../source_whisky_hunter/spec.yaml | 7 + .../source_whisky_hunter/whisky_hunter.yaml | 46 +++++++ docs/integrations/README.md | 1 + docs/integrations/sources/whisky-hunter.md | 37 +++++ 32 files changed, 726 insertions(+), 4 deletions(-) create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/.dockerignore create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/Dockerfile create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/README.md create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/__init__.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/bootstrap.md create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/build.gradle create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_state.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/main.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/requirements.txt create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/setup.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/__init__.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_data.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_info.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/distilleries_info.json create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/source.py create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/spec.yaml create mode 100644 airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml create mode 100644 docs/integrations/sources/whisky-hunter.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index eb41f515d4b0..75aadbd817d0 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1156,6 +1156,13 @@ icon: webflow.svg sourceType: api releaseStage: alpha +- name: Whisky Hunter + sourceDefinitionId: e65f84c0-7598-458a-bfac-f770c381ff5d + dockerRepository: airbyte/source-whisky-hunter + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/whisky-hunter + sourceType: api + releaseStage: alpha - name: WooCommerce sourceDefinitionId: 2a2552ca-9f78-4c1c-9eb7-4d0dc66d72df dockerRepository: airbyte/source-woocommerce diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index a1d450de0f6a..71503972f7cc 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11844,6 +11844,18 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-whisky-hunter:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.io/integrations/sources/whisky-hunter" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Whisky Hunter Spec" + type: "object" + additionalProperties: true + properties: {} + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-woocommerce:0.1.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/woocommerce" diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py index d53594855323..3cdfc2987d7f 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py @@ -360,7 +360,10 @@ def _validate_records_structure(records: List[AirbyteRecordMessage], configured_ continue record_fields = set(get_object_structure(record.data)) common_fields = set.intersection(record_fields, schema_pathes) - assert common_fields, f" Record from {record.stream} stream should have some fields mentioned by json schema, {schema_pathes}" + + assert ( + common_fields + ), f" Record {record} from {record.stream} stream with fields {record_fields} should have some fields mentioned by json schema: {schema_pathes}" @staticmethod def _validate_schema(records: List[AirbyteRecordMessage], configured_catalog: ConfiguredAirbyteCatalog): diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py index 533eba42eeb4..0d7aa48045a3 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/connector_runner.py @@ -39,7 +39,8 @@ def _prepare_volumes(self, config: Optional[Mapping], state: Optional[Mapping], self.input_folder.mkdir(parents=True) self.output_folder.mkdir(parents=True) - if config: + # using "is not None" to allow falsey config objects like {} to still write + if config is not None: with open(str(self.input_folder / "tap_config.json"), "w") as outfile: json.dump(dict(config), outfile) @@ -149,7 +150,7 @@ def read(cls, container: Container, command: str = None, with_ext: bool = True) raise if exit_status["StatusCode"]: error = exit_status["Error"] or exception or line - logging.error(f"Docker container was failed, " f'code {exit_status["StatusCode"]}, error:\n{error}') + logging.error(f"Docker container failed, " f'code {exit_status["StatusCode"]}, error:\n{error}') if with_ext: raise ContainerError( container=container, diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py index 6db6125eb4c8..eec175467915 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py @@ -268,7 +268,7 @@ def test_read(schema, record, should_fail): ] t = _TestBasicRead() if should_fail: - with pytest.raises(AssertionError, match="stream should have some fields mentioned by json schema"): + with pytest.raises(AssertionError, match="should have some fields mentioned by json schema"): t.test_read(None, catalog, input_config, [], docker_runner_mock, MagicMock()) else: t.test_read(None, catalog, input_config, [], docker_runner_mock, MagicMock()) diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 0444a363c60b..1385df6ad8fb 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -118,6 +118,7 @@ | Twilio | [![source-twilio](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-twilio%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-twilio) | | Typeform | [![source-typeform](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-typeform%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-typeform) | | US Census | [![source-us-census](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-us-census%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-us-census) | +| Whisky Hunter | [![source-whisky-hunter](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-whisky-hunter%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-whisky-hunter) | | Wrike | [![source-wrike](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-wrike%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-wrike) | | YouTube Analytics | [![source-youtube-analytics](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-youtube-analytics%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-youtube-analytics) | | Zendesk Chat | [![source-zendesk-chat](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-zendesk-chat%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-zendesk-chat) | diff --git a/airbyte-integrations/connectors/source-whisky-hunter/.dockerignore b/airbyte-integrations/connectors/source-whisky-hunter/.dockerignore new file mode 100644 index 000000000000..26206a22c8cc --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_whisky_hunter +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-whisky-hunter/Dockerfile b/airbyte-integrations/connectors/source-whisky-hunter/Dockerfile new file mode 100644 index 000000000000..51864abfbf85 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.11-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_whisky_hunter ./source_whisky_hunter + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-whisky-hunter diff --git a/airbyte-integrations/connectors/source-whisky-hunter/README.md b/airbyte-integrations/connectors/source-whisky-hunter/README.md new file mode 100644 index 000000000000..232fe8b3abe8 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/README.md @@ -0,0 +1,79 @@ +# Whisky Hunter Source + +This is the repository for the Whisky Hunter configuration based source connector. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/whisky-hunter). + +## Local development + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-whisky-hunter:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/whisky-hunter) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_whisky_hunter/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source whisky-hunter test creds` +and place them into `secrets/config.json`. + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-whisky-hunter:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-whisky-hunter:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-whisky-hunter:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-whisky-hunter:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-whisky-hunter:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-whisky-hunter:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing + +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. + +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-whisky-hunter:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-whisky-hunter:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-whisky-hunter/__init__.py b/airbyte-integrations/connectors/source-whisky-hunter/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-config.yml b/airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-config.yml new file mode 100644 index 000000000000..e4537545ec47 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-config.yml @@ -0,0 +1,18 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-whisky-hunter:dev +tests: + spec: + - spec_path: "source_whisky_hunter/spec.yaml" + connection: + - config_path: "secrets/config.json" + status: "succeed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-whisky-hunter/bootstrap.md b/airbyte-integrations/connectors/source-whisky-hunter/bootstrap.md new file mode 100644 index 000000000000..472c1c2709ef --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/bootstrap.md @@ -0,0 +1,20 @@ +## Core streams + +[Whisky Hunter](https://whiskyhunter.net/api/) is an API. Connector is implemented with the [Airbyte Low-Code CDK](https://docs.airbyte.com/connector-development/config-based/low-code-cdk-overview). + +Connector supports the following three streams: +* `auctions_data` + * Provides stats about specific auctions. +* `auctions_info` + * Provides information and metadata about recurring and one-off auctions. +* `distilleries_info` + * Provides information about distilleries. + +Rate Limiting: +* No published rate limit. + +Authentication and Permissions: +* No authentication. + + +See [this](https://docs.airbyte.io/integrations/sources/whisky-hunter) link for the connector docs. diff --git a/airbyte-integrations/connectors/source-whisky-hunter/build.gradle b/airbyte-integrations/connectors/source-whisky-hunter/build.gradle new file mode 100644 index 000000000000..6fb1398539bf --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_whisky_hunter' +} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/__init__.py b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/acceptance.py new file mode 100644 index 000000000000..950b53b59d41 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/acceptance.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + yield diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json new file mode 100644 index 000000000000..b5acb2a9ab76 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json @@ -0,0 +1,112 @@ +{ + "type": "CATALOG", + "catalog": { + "streams": [ + { + "name": "auctions_data", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "dt": { + "type": "string" + }, + "all_auctions_lots_count": { + "type": "integer" + }, + "auction_slug": { + "type": "string" + }, + "auction_lots_count": { + "type": "integer" + }, + "auction_name": { + "type": "string" + }, + "winning_bid_min": { + "type": "number" + }, + "auction_trading_volume": { + "type": "number" + }, + "winning_bid_max": { + "type": "number" + }, + "winning_bid_mean": { + "type": "number" + } + } + }, + "supported_sync_modes": [ + "full_refresh" + ] + }, + { + "name": "auctions_info", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "base_currency": { + "type": "string" + }, + "buyers_fee": { + "type": "number" + }, + "reserve_fee": { + "type": "number" + }, + "listing_fee": { + "type": "number" + }, + "name": { + "type": "string" + }, + "sellers_fee": { + "type": "number" + }, + "slug": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "supported_sync_modes": [ + "full_refresh" + ] + }, + { + "name": "distilleries_info", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "whiskybase_votes": { + "type": "string" + }, + "country": { + "type": "string" + }, + "whiskybase_whiskies": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "whiskybase_rating": { + "type": "string" + } + } + }, + "supported_sync_modes": [ + "full_refresh" + ] + } + ] + } +} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..74fdfedae11c --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json @@ -0,0 +1,130 @@ +{ + "streams": [ + { + "stream": { + "name": "auctions_data", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "dt": { + "type": "string" + }, + "all_auctions_lots_count": { + "type": "integer" + }, + "auction_slug": { + "type": "string" + }, + "auction_lots_count": { + "type": "integer" + }, + "auction_name": { + "type": "string" + }, + "winning_bid_min": { + "type": "number" + }, + "auction_trading_volume": { + "type": "number" + }, + "winning_bid_max": { + "type": "number" + }, + "winning_bid_mean": { + "type": "number" + } + } + } + }, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "auctions_info", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "base_currency": { + "type": "string" + }, + "buyers_fee": { + "type": "number" + }, + "reserve_fee": { + "type": "number" + }, + "listing_fee": { + "type": "number" + }, + "name": { + "type": "string" + }, + "sellers_fee": { + "type": "number" + }, + "slug": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + }, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "distilleries_info", + "json_schema": { + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "array", + "items": { + "type": "object", + "properties": { + "whiskybase_votes": { + "type": "string" + }, + "country": { + "type": "string" + }, + "whiskybase_whiskies": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "whiskybase_rating": { + "type": "string" + } + } + } + }, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_config.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_config.json @@ -0,0 +1 @@ +{} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_state.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/sample_state.json @@ -0,0 +1 @@ +{} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/main.py b/airbyte-integrations/connectors/source-whisky-hunter/main.py new file mode 100644 index 000000000000..1f7a4dba114a --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_whisky_hunter import SourceWhiskyHunter + +if __name__ == "__main__": + source = SourceWhiskyHunter() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-whisky-hunter/requirements.txt b/airbyte-integrations/connectors/source-whisky-hunter/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-whisky-hunter/setup.py b/airbyte-integrations/connectors/source-whisky-hunter/setup.py new file mode 100644 index 000000000000..fbeae664a051 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.2", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_whisky_hunter", + description="Source implementation for Whisky Hunter.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/__init__.py b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/__init__.py new file mode 100644 index 000000000000..b6a52f86e1cf --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceWhiskyHunter + +__all__ = ["SourceWhiskyHunter"] diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_data.json b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_data.json new file mode 100644 index 000000000000..afd2e3be8301 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_data.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "dt": { + "type": "string" + }, + "all_auctions_lots_count": { + "type": "integer" + }, + "auction_slug": { + "type": "string" + }, + "auction_lots_count": { + "type": "integer" + }, + "auction_name": { + "type": "string" + }, + "winning_bid_min": { + "type": "number" + }, + "auction_trading_volume": { + "type": "number" + }, + "winning_bid_max": { + "type": "number" + }, + "winning_bid_mean": { + "type": "number" + } + } +} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_info.json b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_info.json new file mode 100644 index 000000000000..8a8bd673b8e2 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/auctions_info.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "base_currency": { + "type": "string" + }, + "buyers_fee": { + "type": "number" + }, + "reserve_fee": { + "type": "number" + }, + "listing_fee": { + "type": "number" + }, + "name": { + "type": "string" + }, + "sellers_fee": { + "type": "number" + }, + "slug": { + "type": "string" + }, + "url": { + "type": "string" + } + } +} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/distilleries_info.json b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/distilleries_info.json new file mode 100644 index 000000000000..d94228e12ce5 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/schemas/distilleries_info.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "whiskybase_votes": { + "type": "string" + }, + "country": { + "type": "string" + }, + "whiskybase_whiskies": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "whiskybase_rating": { + "type": "string" + } + } +} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/source.py b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/source.py new file mode 100644 index 000000000000..c0d22e785848 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/source.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource + +""" +This file provides the necessary constructs to interpret a provided declarative YAML configuration file into +source connector. + +WARNING: Do not modify this file. +""" + + +# Declarative Source +class SourceWhiskyHunter(YamlDeclarativeSource): + def __init__(self): + super().__init__(**{"path_to_yaml": "whisky_hunter.yaml"}) diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/spec.yaml b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/spec.yaml new file mode 100644 index 000000000000..37450e46c38a --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/spec.yaml @@ -0,0 +1,7 @@ +documentationUrl: https://docs.airbyte.io/integrations/sources/whisky-hunter +connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Whisky Hunter Spec + type: object + additionalProperties: true + properties: {} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml new file mode 100644 index 000000000000..26c9ee9a6060 --- /dev/null +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml @@ -0,0 +1,46 @@ +version: "0.1.0" + +definitions: + selector: + extractor: + field_pointer: [] + requester: + url_base: "https://whiskyhunter.net/api" + http_method: "GET" + retriever: + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + type: NoPagination + requester: + $ref: "*ref(definitions.requester)" + base_stream: + retriever: + $ref: "*ref(definitions.retriever)" + auctions_data_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "auctions_data" + path: "/auctions_data/?format=json" + auctions_info_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "auctions_info" + path: "/auctions_info?format=json" + distilleries_info_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "distilleries_info" + path: "/distilleries_info/?format=json" + + +streams: + - "*ref(definitions.auctions_data_stream)" + - "*ref(definitions.auctions_info_stream)" + - "*ref(definitions.distilleries_info_stream)" + +check: + stream_names: + - "auctions_data" + - "auctions_info" + - "distilleries_info" diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 95659449e483..f21e8a5b2946 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -170,6 +170,7 @@ For more information about the grading system, see [Product Release Stages](http | [US Census](sources/us-census.md) | Alpha | Yes | | [VictorOps](sources/victorops.md) | Alpha | No | | [Webflow](sources/webflow.md ) | Alpha | Yes | +| [Whisky Hunter](sources/whisky-hunter.md ) | Alpha | No | | [WooCommerce](sources/woocommerce.md) | Alpha | No | | [Wordpress](sources/wordpress.md) | Alpha | No | | [Wrike](sources/wrike.md) | Alpha | No | diff --git a/docs/integrations/sources/whisky-hunter.md b/docs/integrations/sources/whisky-hunter.md new file mode 100644 index 000000000000..cc999a841560 --- /dev/null +++ b/docs/integrations/sources/whisky-hunter.md @@ -0,0 +1,37 @@ +# Whisky Hunter + +## Overview + +The Whisky Hunter source can sync data from the [Whisky Hunter API](https://whiskyhunter.net/api/) + +#### Output schema + +This source is capable of syncing the following streams: +* `auctions_data` + * Provides stats about specific auctions. +* `auctions_info` + * Provides information and metadata about recurring and one-off auctions. +* `distilleries_info` + * Provides information about distilleries. + +#### Features + +| Feature | Supported? | +| :--- | :--- | +| Full Refresh Sync | Yes | +| Incremental - Append Sync | No | +| Namespaces | No | + +### Requirements / Setup Guide + +No config is required. + +## Performance considerations + +There is no published rate limit. However, since this data updates infrequently, it is recommended to set the update cadence to 24hr or higher. + +## Changelog + +| Version | Date | Pull Request | Subject | +| :--- | :--- | :--- | :--- | +| 0.1.0 | 2022-10-12 | [17918](https://github.com/airbytehq/airbyte/pull/17918) | Initial release supporting the Whisky Hunter API | From 39c551279ebe98bcddc71280cfee66cee4c1087b Mon Sep 17 00:00:00 2001 From: Joey Marshment-Howell Date: Fri, 21 Oct 2022 14:45:02 +0200 Subject: [PATCH 248/498] Update docs for local webapp development with basic auth (#18288) * add note to disable basic auth when developing the UI locally --- docs/contributing-to-airbyte/developing-locally.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/contributing-to-airbyte/developing-locally.md b/docs/contributing-to-airbyte/developing-locally.md index 951a5d2d826b..1e327cc0dfda 100644 --- a/docs/contributing-to-airbyte/developing-locally.md +++ b/docs/contributing-to-airbyte/developing-locally.md @@ -166,12 +166,13 @@ Note: If you are contributing a Python file without imports or function definiti ### Develop on `airbyte-webapp` - Spin up Airbyte locally so the UI can make requests against the local API. -- Stop the `webapp`. ```bash -docker-compose stop webapp +BASIC_AUTH_USERNAME="" BASIC_AUTH_PASSWORD="" docker-compose up ``` +Note: [basic auth](https://docs.airbyte.com/operator-guides/security#network-security) must be disabled by setting `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` to empty values, otherwise requests from the development server will fail against the local API. + - Start up the react app. ```bash From 2e0fae47fc5f77aece8548fd412b223e70fe1541 Mon Sep 17 00:00:00 2001 From: Nataly Merezhuk <65251165+natalyjazzviolin@users.noreply.github.com> Date: Fri, 21 Oct 2022 09:39:15 -0400 Subject: [PATCH 249/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=A7=B9=20Splits=20?= =?UTF-8?q?the=20imageBlock=20component=20in=20two.=20(#17479)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Splits the imageBlock component. * Adds and corrects some styles. * Finishes cleaning up styles and properties. * Cleanup. * Requested changes 1/2. * Requestion changes 2/x, margin not working in className. * Corrects assing className as prop. * Requested changes. --- .../components/ConnectEntitiesCell.tsx | 20 +++++---- .../ui/ImageBlock/ImageBlock.module.scss | 42 ------------------- .../ui/ImageBlock/ImageBlock.stories.tsx | 1 - .../components/ui/ImageBlock/ImageBlock.tsx | 24 +++-------- .../ui/NumberBadge/NumberBadge.module.scss | 39 +++++++++++++++++ .../ui/NumberBadge/NumberBadge.stories.tsx | 16 +++++++ .../components/ui/NumberBadge/NumberBadge.tsx | 26 ++++++++++++ .../src/components/ui/NumberBadge/index.tsx | 1 + .../components/DiffIconBlock.tsx | 23 +++++----- 9 files changed, 110 insertions(+), 82 deletions(-) create mode 100644 airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.module.scss create mode 100644 airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.stories.tsx create mode 100644 airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.tsx create mode 100644 airbyte-webapp/src/components/ui/NumberBadge/index.tsx diff --git a/airbyte-webapp/src/components/EntityTable/components/ConnectEntitiesCell.tsx b/airbyte-webapp/src/components/EntityTable/components/ConnectEntitiesCell.tsx index cdf9048488ab..dbff24eeb6e2 100644 --- a/airbyte-webapp/src/components/EntityTable/components/ConnectEntitiesCell.tsx +++ b/airbyte-webapp/src/components/EntityTable/components/ConnectEntitiesCell.tsx @@ -2,7 +2,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import styled from "styled-components"; -import { ImageBlock } from "components/ui/ImageBlock"; +import { NumberBadge } from "components/ui/NumberBadge"; interface IProps { values: Array<{ @@ -13,16 +13,16 @@ interface IProps { entity: "source" | "destination"; } +const Number = styled(NumberBadge)` + margin-right: 6px; +`; + const Content = styled.div<{ enabled?: boolean }>` display: flex; align-items: center; color: ${({ theme, enabled }) => (!enabled ? theme.greyColor40 : "inheret")}; `; -const Image = styled(ImageBlock)` - margin-right: 6px; -`; - const Connector = styled.div` font-weight: normal; font-size: 12px; @@ -34,7 +34,7 @@ const ConnectEntitiesCell: React.FC = ({ values, enabled, entity }) => { if (values.length === 1) { return ( - +
    {values[0].name} {values[0].connector} @@ -44,12 +44,16 @@ const ConnectEntitiesCell: React.FC = ({ values, enabled, entity }) => { } if (!values.length) { - return null; + return ( + + + + ); } return ( - +
    {`${values[0].connector}, ${values[1].connector}${values.length > 2 ? ",..." : ""}`} diff --git a/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.module.scss b/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.module.scss index 1b7963a9cd05..5d80661bfaf9 100644 --- a/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.module.scss +++ b/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.module.scss @@ -9,35 +9,6 @@ margin-right: 6px; } -.circle { - height: 20px; - width: 20px; - min-width: 20px; - border-radius: 50%; - text-align: center; - margin-right: 6px; - overflow: hidden; - - &.darkBlue { - background: colors.$dark-blue-100; - } - - &.green { - background: colors.$green; - color: colors.$white; - } - - &.red { - background: colors.$red; - color: colors.$white; - } - - &.blue { - background: colors.$blue; - color: colors.$white; - } -} - .small { border-radius: 0%; } @@ -46,16 +17,3 @@ max-height: 100%; max-width: 100%; } - -.number { - font-family: fonts.$primary; - font-style: normal; - font-weight: 600; - font-size: 10px; - color: colors.$white; - padding: 3px 0; - - &.light { - font-weight: 500; - } -} diff --git a/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.stories.tsx b/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.stories.tsx index 5c10509544b3..f8cf9d105ad7 100644 --- a/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.stories.tsx +++ b/airbyte-webapp/src/components/ui/ImageBlock/ImageBlock.stories.tsx @@ -13,6 +13,5 @@ const Template: ComponentStory = (args) => = ({ img, num, small, color, light, ariaLabel }) => { - const imageCircleClassnames = classnames({ - [styles.circle]: num, - [styles.iconContainer]: !num || num === undefined, - [styles.small]: small && !num, - [styles.darkBlue]: !small && num && !color, - [styles.green]: color === "green", - [styles.red]: color === "red", - [styles.blue]: color === "blue", - [styles.light]: light, +export const ImageBlock: React.FC = ({ img, small, "aria-label": ariaLabel }) => { + const imageCircleClassnames = classnames(styles.iconContainer, { + [styles.small]: small, }); - const numberStyles = classnames(styles.number, { [styles.light]: light }); - return (
    - {num ?
    {num}
    :
    {getIcon(img)}
    } +
    {getIcon(img)}
    ); }; diff --git a/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.module.scss b/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.module.scss new file mode 100644 index 000000000000..e0a6b4a5d309 --- /dev/null +++ b/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.module.scss @@ -0,0 +1,39 @@ +@use "../../../scss/colors"; +@use "../../../scss/fonts"; + +.circle { + height: 20px; + width: fit-content; + min-width: 20px; + border-radius: 10px; + padding: 0 4px; + text-align: center; + overflow: hidden; + display: flex; + justify-content: center; + align-items: center; + line-height: normal; + font-family: fonts.$primary; + font-style: normal; + font-weight: 500; + font-size: 10px; + + &.default { + background: colors.$dark-blue-100; + } + + &.green { + background: colors.$green; + color: colors.$black; + } + + &.red { + background: colors.$red; + color: colors.$black; + } + + &.blue { + background: colors.$blue; + color: colors.$white; + } +} diff --git a/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.stories.tsx b/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.stories.tsx new file mode 100644 index 000000000000..e356afd008f8 --- /dev/null +++ b/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.stories.tsx @@ -0,0 +1,16 @@ +import { ComponentStory, ComponentMeta } from "@storybook/react"; + +import { NumberBadge } from "./NumberBadge"; + +export default { + title: "UI/NumberBadge", + component: NumberBadge, + argTypes: {}, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Primary = Template.bind({}); +Primary.args = { + value: 10, +}; diff --git a/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.tsx b/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.tsx new file mode 100644 index 000000000000..d38716afd5ce --- /dev/null +++ b/airbyte-webapp/src/components/ui/NumberBadge/NumberBadge.tsx @@ -0,0 +1,26 @@ +import classnames from "classnames"; +import React from "react"; + +import styles from "./NumberBadge.module.scss"; + +interface NumberBadgeProps { + value: number; + color?: "green" | "red" | "blue" | "default"; + className?: string; + "aria-label"?: string; +} + +export const NumberBadge: React.FC = ({ value, color, className, "aria-label": ariaLabel }) => { + const numberBadgeClassnames = classnames(styles.circle, className, { + [styles.default]: !color || color === "default", + [styles.green]: color === "green", + [styles.red]: color === "red", + [styles.blue]: color === "blue", + }); + + return ( +
    +
    {value}
    +
    + ); +}; diff --git a/airbyte-webapp/src/components/ui/NumberBadge/index.tsx b/airbyte-webapp/src/components/ui/NumberBadge/index.tsx new file mode 100644 index 000000000000..57fea8264b27 --- /dev/null +++ b/airbyte-webapp/src/components/ui/NumberBadge/index.tsx @@ -0,0 +1 @@ +export * from "./NumberBadge"; diff --git a/airbyte-webapp/src/views/Connection/CatalogDiffModal/components/DiffIconBlock.tsx b/airbyte-webapp/src/views/Connection/CatalogDiffModal/components/DiffIconBlock.tsx index a9c3467fd2ca..b74e011a35ea 100644 --- a/airbyte-webapp/src/views/Connection/CatalogDiffModal/components/DiffIconBlock.tsx +++ b/airbyte-webapp/src/views/Connection/CatalogDiffModal/components/DiffIconBlock.tsx @@ -1,6 +1,6 @@ import { useIntl } from "react-intl"; -import { ImageBlock } from "components/ui/ImageBlock"; +import { NumberBadge } from "components/ui/NumberBadge"; import styles from "./DiffIconBlock.module.scss"; @@ -15,11 +15,10 @@ export const DiffIconBlock: React.FC = ({ newCount, removedC return (
    {removedCount > 0 && ( - = ({ newCount, removedC /> )} {newCount > 0 && ( - = ({ newCount, removedC /> )} {changedCount > 0 && ( - Date: Fri, 21 Oct 2022 16:27:18 +0200 Subject: [PATCH 250/498] Fix broken link to config in .env comment (#18287) --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index dbe04920d885..d58019616892 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ # Variables with defaults have been omitted to avoid duplication of defaults. # The only exception to the non-default rule are env vars related to scaling. # -# See https://github.com/airbytehq/airbyte/blob/master/airbyte-config/models/src/main/java/io/airbyte/config/Configs.java +# See https://github.com/airbytehq/airbyte/blob/master/airbyte-config/config-models/src/main/java/io/airbyte/config/Configs.java # for the latest environment variables. # # # Contributors - please organise this env file according to the above linked file. From cf28f7a01f55d13c64515e2ea4ee7a9a4300b78b Mon Sep 17 00:00:00 2001 From: Jimmy Ma Date: Fri, 21 Oct 2022 09:32:42 -0700 Subject: [PATCH 251/498] Add message migration to discover schema (#18205) --- .../commons/temporal/TemporalClient.java | 3 +- .../types/JobDiscoverCatalogConfig.yaml | 3 ++ .../server/handlers/SchedulerHandler.java | 6 ++-- .../DefaultSynchronousSchedulerClient.java | 6 +++- .../scheduler/SynchronousSchedulerClient.java | 3 +- .../server/handlers/SchedulerHandlerTest.java | 30 +++++++++++-------- ...DefaultSynchronousSchedulerClientTest.java | 3 +- .../catalog/DiscoverCatalogActivityImpl.java | 15 ++++++++-- 8 files changed, 48 insertions(+), 21 deletions(-) diff --git a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java index 433a22bdb902..ab18c9b1a6c4 100644 --- a/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java +++ b/airbyte-commons-temporal/src/main/java/io/airbyte/commons/temporal/TemporalClient.java @@ -355,7 +355,8 @@ public TemporalResponse submitDiscoverSchema(final UUID jobI final IntegrationLauncherConfig launcherConfig = new IntegrationLauncherConfig() .withJobId(jobId.toString()) .withAttemptId((long) attempt) - .withDockerImage(config.getDockerImage()); + .withDockerImage(config.getDockerImage()) + .withProtocolVersion(config.getProtocolVersion()); final StandardDiscoverCatalogInput input = new StandardDiscoverCatalogInput().withConnectionConfiguration(config.getConnectionConfiguration()) .withSourceId(config.getSourceId()).withConnectorVersion(config.getConnectorVersion()).withConfigHash(config.getConfigHash()); diff --git a/airbyte-config/config-models/src/main/resources/types/JobDiscoverCatalogConfig.yaml b/airbyte-config/config-models/src/main/resources/types/JobDiscoverCatalogConfig.yaml index 174e935bf997..a9f189714b69 100644 --- a/airbyte-config/config-models/src/main/resources/types/JobDiscoverCatalogConfig.yaml +++ b/airbyte-config/config-models/src/main/resources/types/JobDiscoverCatalogConfig.yaml @@ -30,3 +30,6 @@ properties: configHash: description: Hash of the source configuration -- see `configuration` field in SourceConnection.yaml -- so we can persist this alongside the discovered catalog. type: string + protocolVersion: + type: object + existingJavaType: io.airbyte.commons.version.Version diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java index c936e5768625..8ec0e99fedd3 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SchedulerHandler.java @@ -228,7 +228,8 @@ public SourceDiscoverSchemaRead discoverSchemaForSourceFromSourceId(final Source configRepository.getActorCatalog(discoverSchemaRequestBody.getSourceId(), connectorVersion, configHash); final boolean bustActorCatalogCache = discoverSchemaRequestBody.getDisableCache() != null && discoverSchemaRequestBody.getDisableCache(); if (currentCatalog.isEmpty() || bustActorCatalogCache) { - final SynchronousResponse persistedCatalogId = synchronousSchedulerClient.createDiscoverSchemaJob(source, imageName, connectorVersion); + final SynchronousResponse persistedCatalogId = + synchronousSchedulerClient.createDiscoverSchemaJob(source, imageName, connectorVersion, new Version(sourceDef.getProtocolVersion())); return retrieveDiscoveredSchema(persistedCatalogId); } final AirbyteCatalog airbyteCatalog = Jsons.object(currentCatalog.get().getCatalog(), AirbyteCatalog.class); @@ -260,7 +261,8 @@ public SourceDiscoverSchemaRead discoverSchemaForSourceFromSourceCreate(final So .withSourceDefinitionId(sourceCreate.getSourceDefinitionId()) .withConfiguration(partialConfig) .withWorkspaceId(sourceCreate.getWorkspaceId()); - final SynchronousResponse response = synchronousSchedulerClient.createDiscoverSchemaJob(source, imageName, sourceDef.getDockerImageTag()); + final SynchronousResponse response = synchronousSchedulerClient.createDiscoverSchemaJob(source, imageName, sourceDef.getDockerImageTag(), + new Version(sourceDef.getProtocolVersion())); return retrieveDiscoveredSchema(response); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java index 8ad8e23dda80..d96acb0dd5d2 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClient.java @@ -112,7 +112,10 @@ public SynchronousResponse createDestinationCheck } @Override - public SynchronousResponse createDiscoverSchemaJob(final SourceConnection source, final String dockerImage, final String connectorVersion) + public SynchronousResponse createDiscoverSchemaJob(final SourceConnection source, + final String dockerImage, + final String connectorVersion, + final Version protocolVersion) throws IOException { final JsonNode sourceConfiguration = oAuthConfigSupplier.injectSourceOAuthParameters( source.getSourceDefinitionId(), @@ -121,6 +124,7 @@ public SynchronousResponse createDiscoverSchemaJob(final SourceConnection final JobDiscoverCatalogConfig jobDiscoverCatalogConfig = new JobDiscoverCatalogConfig() .withConnectionConfiguration(sourceConfiguration) .withDockerImage(dockerImage) + .withProtocolVersion(protocolVersion) .withSourceId(source.getSourceId().toString()) .withConfigHash(HASH_FUNCTION.hashBytes(Jsons.serialize(source.getConfiguration()).getBytes( Charsets.UTF_8)).toString()) diff --git a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java index 02f9a3d7995f..f472d9da7ced 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java +++ b/airbyte-server/src/main/java/io/airbyte/server/scheduler/SynchronousSchedulerClient.java @@ -28,7 +28,8 @@ SynchronousResponse createDestinationCheckConnect Version protocolVersion) throws IOException; - SynchronousResponse createDiscoverSchemaJob(SourceConnection source, String dockerImage, String connectorVersion) throws IOException; + SynchronousResponse createDiscoverSchemaJob(SourceConnection source, String dockerImage, String connectorVersion, Version protocolVersion) + throws IOException; SynchronousResponse createGetSpecJob(String dockerImage) throws IOException; diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java index 5e2953190af0..a8429a52e44f 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SchedulerHandlerTest.java @@ -395,10 +395,11 @@ void testDiscoverSchemaForSourceFromSourceId() throws IOException, JsonValidatio .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); when(configRepository.getSourceConnection(source.getSourceId())).thenReturn(source); when(configRepository.getActorCatalog(any(), any(), any())).thenReturn(Optional.empty()); - when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) + when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION))) .thenReturn(discoverResponse); final SourceDiscoverSchemaRead actual = schedulerHandler.discoverSchemaForSourceFromSourceId(request); @@ -409,7 +410,7 @@ void testDiscoverSchemaForSourceFromSourceId() throws IOException, JsonValidatio assertTrue(actual.getJobInfo().getSucceeded()); verify(configRepository).getSourceConnection(source.getSourceId()); verify(configRepository).getActorCatalog(eq(request.getSourceId()), eq(SOURCE_DOCKER_TAG), any()); - verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); + verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION)); } @Test @@ -436,7 +437,7 @@ void testDiscoverSchemaForSourceFromSourceIdCachedCatalog() throws IOException, .withCatalogHash("") .withId(thisCatalogId); when(configRepository.getActorCatalog(any(), any(), any())).thenReturn(Optional.of(actorCatalog)); - when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) + when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION))) .thenReturn(discoverResponse); final SourceDiscoverSchemaRead actual = schedulerHandler.discoverSchemaForSourceFromSourceId(request); @@ -448,7 +449,8 @@ void testDiscoverSchemaForSourceFromSourceIdCachedCatalog() throws IOException, verify(configRepository).getSourceConnection(source.getSourceId()); verify(configRepository).getActorCatalog(eq(request.getSourceId()), any(), any()); verify(configRepository, never()).writeActorCatalogFetchEvent(any(), any(), any(), any()); - verify(synchronousSchedulerClient, never()).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); + verify(synchronousSchedulerClient, never()).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, + new Version(SOURCE_PROTOCOL_VERSION)); } @Test @@ -468,6 +470,7 @@ void testDiscoverSchemaForSourceFromSourceIdDisableCache() throws IOException, J .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); when(configRepository.getSourceConnection(source.getSourceId())).thenReturn(source); final ActorCatalog actorCatalog = new ActorCatalog() @@ -475,7 +478,7 @@ void testDiscoverSchemaForSourceFromSourceIdDisableCache() throws IOException, J .withCatalogHash("") .withId(discoveredCatalogId); when(configRepository.getActorCatalogById(discoveredCatalogId)).thenReturn(actorCatalog); - when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) + when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION))) .thenReturn(discoverResponse); final SourceDiscoverSchemaRead actual = schedulerHandler.discoverSchemaForSourceFromSourceId(request); @@ -485,7 +488,7 @@ void testDiscoverSchemaForSourceFromSourceIdDisableCache() throws IOException, J assertTrue(actual.getJobInfo().getSucceeded()); verify(configRepository).getSourceConnection(source.getSourceId()); verify(configRepository).getActorCatalog(eq(request.getSourceId()), any(), any()); - verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); + verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION)); } @Test @@ -497,9 +500,10 @@ void testDiscoverSchemaForSourceFromSourceIdFailed() throws IOException, JsonVal .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); when(configRepository.getSourceConnection(source.getSourceId())).thenReturn(source); - when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) + when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION))) .thenReturn((SynchronousResponse) jobResponse); when(completedJob.getSuccessOutput()).thenReturn(Optional.empty()); when(completedJob.getStatus()).thenReturn(JobStatus.FAILED); @@ -510,7 +514,7 @@ void testDiscoverSchemaForSourceFromSourceIdFailed() throws IOException, JsonVal assertNotNull(actual.getJobInfo()); assertFalse(actual.getJobInfo().getSucceeded()); verify(configRepository).getSourceConnection(source.getSourceId()); - verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); + verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION)); } @Test @@ -539,8 +543,9 @@ void testDiscoverSchemaForSourceFromSourceCreate() throws JsonValidationExceptio .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); - when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) + when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION))) .thenReturn(discoverResponse); when(secretsRepositoryWriter.statefulSplitEphemeralSecrets( eq(source.getConfiguration()), @@ -552,7 +557,7 @@ void testDiscoverSchemaForSourceFromSourceCreate() throws JsonValidationExceptio assertNotNull(actual.getJobInfo()); assertEquals(actual.getCatalogId(), discoverResponse.getOutput()); assertTrue(actual.getJobInfo().getSucceeded()); - verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); + verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION)); } @Test @@ -570,8 +575,9 @@ void testDiscoverSchemaForSourceFromSourceCreateFailed() throws JsonValidationEx .thenReturn(new StandardSourceDefinition() .withDockerRepository(SOURCE_DOCKER_REPO) .withDockerImageTag(SOURCE_DOCKER_TAG) + .withProtocolVersion(SOURCE_PROTOCOL_VERSION) .withSourceDefinitionId(source.getSourceDefinitionId())); - when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG)) + when(synchronousSchedulerClient.createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION))) .thenReturn((SynchronousResponse) jobResponse); when(secretsRepositoryWriter.statefulSplitEphemeralSecrets( eq(source.getConfiguration()), @@ -584,7 +590,7 @@ void testDiscoverSchemaForSourceFromSourceCreateFailed() throws JsonValidationEx assertNull(actual.getCatalog()); assertNotNull(actual.getJobInfo()); assertFalse(actual.getJobInfo().getSucceeded()); - verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG); + verify(synchronousSchedulerClient).createDiscoverSchemaJob(source, SOURCE_DOCKER_IMAGE, SOURCE_DOCKER_TAG, new Version(SOURCE_PROTOCOL_VERSION)); } @Test diff --git a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java index 8cf86db022c0..e9af5118545d 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/scheduler/DefaultSynchronousSchedulerClientTest.java @@ -237,7 +237,8 @@ void testCreateDiscoverSchemaJob() throws IOException { final ConnectorJobOutput jobOutput = new ConnectorJobOutput().withDiscoverCatalogId(expectedCatalogId); when(temporalClient.submitDiscoverSchema(any(UUID.class), eq(0), any(JobDiscoverCatalogConfig.class))) .thenReturn(new TemporalResponse<>(jobOutput, createMetadata(true))); - final SynchronousResponse response = schedulerClient.createDiscoverSchemaJob(SOURCE_CONNECTION, DOCKER_IMAGE, DOCKER_IMAGE_TAG); + final SynchronousResponse response = + schedulerClient.createDiscoverSchemaJob(SOURCE_CONNECTION, DOCKER_IMAGE, DOCKER_IMAGE_TAG, PROTOCOL_VERSION); assertEquals(expectedCatalogId, response.getOutput()); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java index 7c384eabf65e..94da4bc7e348 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java @@ -12,6 +12,8 @@ import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; +import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; +import io.airbyte.commons.protocol.AirbyteMessageVersionedMigratorFactory; import io.airbyte.commons.temporal.CancellationHandler; import io.airbyte.commons.temporal.config.WorkerMode; import io.airbyte.config.Configs.WorkerEnvironment; @@ -27,7 +29,7 @@ import io.airbyte.workers.WorkerConfigs; import io.airbyte.workers.general.DefaultDiscoverCatalogWorker; import io.airbyte.workers.internal.AirbyteStreamFactory; -import io.airbyte.workers.internal.DefaultAirbyteStreamFactory; +import io.airbyte.workers.internal.VersionedAirbyteStreamFactory; import io.airbyte.workers.process.AirbyteIntegrationLauncher; import io.airbyte.workers.process.IntegrationLauncher; import io.airbyte.workers.process.ProcessFactory; @@ -57,6 +59,8 @@ public class DiscoverCatalogActivityImpl implements DiscoverCatalogActivity { private final String airbyteVersion; private final ConfigRepository configRepository; + private final AirbyteMessageSerDeProvider serDeProvider; + private final AirbyteMessageVersionedMigratorFactory migratorFactory; public DiscoverCatalogActivityImpl(@Named("discoverWorkerConfigs") final WorkerConfigs workerConfigs, @Named("discoverProcessFactory") final ProcessFactory processFactory, @@ -66,7 +70,9 @@ public DiscoverCatalogActivityImpl(@Named("discoverWorkerConfigs") final WorkerC final WorkerEnvironment workerEnvironment, final LogConfigs logConfigs, final AirbyteApiClient airbyteApiClient, - @Value("${airbyte.version}") final String airbyteVersion) { + @Value("${airbyte.version}") final String airbyteVersion, + final AirbyteMessageSerDeProvider serDeProvider, + final AirbyteMessageVersionedMigratorFactory migratorFactory) { this.configRepository = configRepository; this.workerConfigs = workerConfigs; this.processFactory = processFactory; @@ -76,6 +82,8 @@ public DiscoverCatalogActivityImpl(@Named("discoverWorkerConfigs") final WorkerC this.logConfigs = logConfigs; this.airbyteApiClient = airbyteApiClient; this.airbyteVersion = airbyteVersion; + this.serDeProvider = serDeProvider; + this.migratorFactory = migratorFactory; } @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @@ -115,7 +123,8 @@ private CheckedSupplier final IntegrationLauncher integrationLauncher = new AirbyteIntegrationLauncher(launcherConfig.getJobId(), launcherConfig.getAttemptId().intValue(), launcherConfig.getDockerImage(), processFactory, workerConfigs.getResourceRequirements()); - final AirbyteStreamFactory streamFactory = new DefaultAirbyteStreamFactory(); + final AirbyteStreamFactory streamFactory = + new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, launcherConfig.getProtocolVersion()); return new DefaultDiscoverCatalogWorker(configRepository, integrationLauncher, streamFactory); }; } From b347829b856c37531c6c110fce3813cea88c8b37 Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Fri, 21 Oct 2022 09:42:34 -0700 Subject: [PATCH 252/498] upgrade commons-text to 1.10.0 (#18275) * test * fix * fix other modules * version bumps + changelog * auto-bump connector version Co-authored-by: Octavia Squidington III --- airbyte-commons-worker/build.gradle | 2 +- .../seed/destination_definitions.yaml | 2 +- .../resources/seed/destination_specs.yaml | 2 +- airbyte-container-orchestrator/build.gradle | 2 +- .../Dockerfile | 2 +- .../connectors/destination-mssql/Dockerfile | 2 +- .../destination-redshift/build.gradle | 2 +- .../connectors/source-redshift/build.gradle | 2 +- airbyte-workers/build.gradle | 2 +- docs/integrations/destinations/mssql.md | 61 ++++++++++--------- 10 files changed, 41 insertions(+), 38 deletions(-) diff --git a/airbyte-commons-worker/build.gradle b/airbyte-commons-worker/build.gradle index 0cf067e0e1c4..1421de6da4f9 100644 --- a/airbyte-commons-worker/build.gradle +++ b/airbyte-commons-worker/build.gradle @@ -12,7 +12,7 @@ dependencies { implementation 'io.fabric8:kubernetes-client:5.12.2' implementation 'io.temporal:temporal-sdk:1.8.1' implementation 'org.apache.ant:ant:1.10.10' - implementation 'org.apache.commons:commons-text:1.9' + implementation 'org.apache.commons:commons-text:1.10.0' implementation project(':airbyte-api') implementation project(':airbyte-commons-protocol') diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 101091aacce3..6daff601bdb5 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -175,7 +175,7 @@ - name: MS SQL Server destinationDefinitionId: d4353156-9217-4cad-8dd7-c108fd4f74cf dockerRepository: airbyte/destination-mssql - dockerImageTag: 0.1.20 + dockerImageTag: 0.1.22 documentationUrl: https://docs.airbyte.com/integrations/destinations/mssql icon: mssql.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 523616dedcd5..81d2e83655eb 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -2939,7 +2939,7 @@ supportsDBT: false supported_destination_sync_modes: - "append" -- dockerImage: "airbyte/destination-mssql:0.1.20" +- dockerImage: "airbyte/destination-mssql:0.1.22" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/mssql" connectionSpecification: diff --git a/airbyte-container-orchestrator/build.gradle b/airbyte-container-orchestrator/build.gradle index 87c827963965..3a96f895137d 100644 --- a/airbyte-container-orchestrator/build.gradle +++ b/airbyte-container-orchestrator/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { implementation 'io.fabric8:kubernetes-client:5.12.2' implementation 'org.apache.commons:commons-lang3:3.11' - implementation 'org.apache.commons:commons-text:1.9' + implementation 'org.apache.commons:commons-text:1.10.0' implementation 'org.eclipse.jetty:jetty-server:9.4.31.v20200723' implementation 'org.eclipse.jetty:jetty-servlet:9.4.31.v20200723' implementation libs.bundles.datadog diff --git a/airbyte-integrations/connectors/destination-mssql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/destination-mssql-strict-encrypt/Dockerfile index 64d56b9e981e..542f6ab8cdab 100644 --- a/airbyte-integrations/connectors/destination-mssql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/destination-mssql-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-mssql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.21 +LABEL io.airbyte.version=0.1.22 LABEL io.airbyte.name=airbyte/destination-mssql-strict-encrypt diff --git a/airbyte-integrations/connectors/destination-mssql/Dockerfile b/airbyte-integrations/connectors/destination-mssql/Dockerfile index aa07ad06dba5..8141e99ddcea 100644 --- a/airbyte-integrations/connectors/destination-mssql/Dockerfile +++ b/airbyte-integrations/connectors/destination-mssql/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-mssql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.20 +LABEL io.airbyte.version=0.1.22 LABEL io.airbyte.name=airbyte/destination-mssql diff --git a/airbyte-integrations/connectors/destination-redshift/build.gradle b/airbyte-integrations/connectors/destination-redshift/build.gradle index 8c45899a0b5f..ca61023831dc 100644 --- a/airbyte-integrations/connectors/destination-redshift/build.gradle +++ b/airbyte-integrations/connectors/destination-redshift/build.gradle @@ -30,7 +30,7 @@ dependencies { testImplementation project(':airbyte-test-utils') - testImplementation 'org.apache.commons:commons-text:1.9' + testImplementation 'org.apache.commons:commons-text:1.10.0' testImplementation 'org.apache.commons:commons-lang3:3.11' testImplementation 'org.apache.commons:commons-dbcp2:2.7.0' testImplementation "org.mockito:mockito-inline:4.1.0" diff --git a/airbyte-integrations/connectors/source-redshift/build.gradle b/airbyte-integrations/connectors/source-redshift/build.gradle index 6b60c985136e..5e8a4af94133 100644 --- a/airbyte-integrations/connectors/source-redshift/build.gradle +++ b/airbyte-integrations/connectors/source-redshift/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation 'com.amazon.redshift:redshift-jdbc42:1.2.43.1067' - testImplementation 'org.apache.commons:commons-text:1.9' + testImplementation 'org.apache.commons:commons-text:1.10.0' testImplementation 'org.apache.commons:commons-lang3:3.11' testImplementation 'org.apache.commons:commons-dbcp2:2.7.0' testImplementation testFixtures(project(':airbyte-integrations:connectors:source-jdbc')) diff --git a/airbyte-workers/build.gradle b/airbyte-workers/build.gradle index e544ee4f2a69..5b318cfcfceb 100644 --- a/airbyte-workers/build.gradle +++ b/airbyte-workers/build.gradle @@ -29,7 +29,7 @@ dependencies { implementation 'io.temporal:temporal-sdk:1.8.1' implementation 'org.apache.ant:ant:1.10.10' implementation 'org.apache.commons:commons-lang3:3.11' - implementation 'org.apache.commons:commons-text:1.9' + implementation 'org.apache.commons:commons-text:1.10.0' implementation 'org.quartz-scheduler:quartz:2.3.2' implementation libs.micrometer.statsd implementation libs.bundles.datadog diff --git a/docs/integrations/destinations/mssql.md b/docs/integrations/destinations/mssql.md index 4751cf12a9cd..4ec292bba809 100644 --- a/docs/integrations/destinations/mssql.md +++ b/docs/integrations/destinations/mssql.md @@ -115,37 +115,40 @@ Using this feature requires additional configuration, when creating the source. | Version | Date | Pull Request | Subject | |:--------| :--- |:---------------------------------------------------------|:----------------------------------------------------------------------------------------------------| -| 0.1.20 | 2022-07-14 | [\#14618](https://github.com/airbytehq/airbyte/pull/14618) | Removed additionalProperties: false from JDBC destination connectors | -| 0.1.19 | 2022-05-25 | [13054](https://github.com/airbytehq/airbyte/pull/13054) | Destination MSSQL: added custom JDBC parameters support. | -| 0.1.18 | 2022-05-17 | [12820](https://github.com/airbytehq/airbyte/pull/12820) | Improved 'check' operation performance | -| 0.1.17 | 2022-04-05 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.1.15 | 2022-02-25 | [10421](https://github.com/airbytehq/airbyte/pull/10421) | Refactor JDBC parameters handling | -| 0.1.14 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | -| 0.1.13 | 2021-12-28 | [\#9158](https://github.com/airbytehq/airbyte/pull/9158) | Update connector fields title/description | -| 0.1.12 | 2021-12-01 | [\#8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.1.11 | 2021-11-08 | [#7719](https://github.com/airbytehq/airbyte/pull/7719) | Improve handling of wide rows by buffering records based on their byte size rather than their count | -| 0.1.10 | 2021-10-11 | [\#6877](https://github.com/airbytehq/airbyte/pull/6877) | Add `normalization` capability, add `append+deduplication` sync mode | -| 0.1.9 | 2021-09-29 | [\#5970](https://github.com/airbytehq/airbyte/pull/5970) | Add support & test cases for MSSQL Destination via SSH tunnels | -| 0.1.8 | 2021-08-07 | [\#5272](https://github.com/airbytehq/airbyte/pull/5272) | Add batch method to insert records | -| 0.1.7 | 2021-07-30 | [\#5125](https://github.com/airbytehq/airbyte/pull/5125) | Enable `additionalPropertities` in spec.json | -| 0.1.6 | 2021-06-21 | [\#3555](https://github.com/airbytehq/airbyte/pull/3555) | Partial Success in BufferedStreamConsumer | -| 0.1.5 | 2021-07-20 | [\#4874](https://github.com/airbytehq/airbyte/pull/4874) | declare object types correctly in spec | -| 0.1.4 | 2021-06-17 | [\#3744](https://github.com/airbytehq/airbyte/pull/3744) | Fix doc/params in specification file | -| 0.1.3 | 2021-05-28 | [\#3728](https://github.com/airbytehq/airbyte/pull/3973) | Change dockerfile entrypoint | -| 0.1.2 | 2021-05-13 | [\#3367](https://github.com/airbytehq/airbyte/pull/3671) | Fix handle symbols unicode | -| 0.1.1 | 2021-05-11 | [\#3566](https://github.com/airbytehq/airbyte/pull/3195) | MS SQL Server Destination Release! | +| 0.1.22 | 2022-10-21 | [18275](https://github.com/airbytehq/airbyte/pull/18275) | Upgrade commons-text for CVE 2022-42889 | +| 0.1.20 | 2022-07-14 | [\#14618](https://github.com/airbytehq/airbyte/pull/14618) | Removed additionalProperties: false from JDBC destination connectors | +| 0.1.19 | 2022-05-25 | [13054](https://github.com/airbytehq/airbyte/pull/13054) | Destination MSSQL: added custom JDBC parameters support. | +| 0.1.18 | 2022-05-17 | [12820](https://github.com/airbytehq/airbyte/pull/12820) | Improved 'check' operation performance | +| 0.1.17 | 2022-04-05 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.1.15 | 2022-02-25 | [10421](https://github.com/airbytehq/airbyte/pull/10421) | Refactor JDBC parameters handling | +| 0.1.14 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | +| 0.1.13 | 2021-12-28 | [\#9158](https://github.com/airbytehq/airbyte/pull/9158) | Update connector fields title/description | +| 0.1.12 | 2021-12-01 | [\#8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.1.11 | 2021-11-08 | [#7719](https://github.com/airbytehq/airbyte/pull/7719) | Improve handling of wide rows by buffering records based on their byte size rather than their count | +| 0.1.10 | 2021-10-11 | [\#6877](https://github.com/airbytehq/airbyte/pull/6877) | Add `normalization` capability, add `append+deduplication` sync mode | +| 0.1.9 | 2021-09-29 | [\#5970](https://github.com/airbytehq/airbyte/pull/5970) | Add support & test cases for MSSQL Destination via SSH tunnels | +| 0.1.8 | 2021-08-07 | [\#5272](https://github.com/airbytehq/airbyte/pull/5272) | Add batch method to insert records | +| 0.1.7 | 2021-07-30 | [\#5125](https://github.com/airbytehq/airbyte/pull/5125) | Enable `additionalPropertities` in spec.json | +| 0.1.6 | 2021-06-21 | [\#3555](https://github.com/airbytehq/airbyte/pull/3555) | Partial Success in BufferedStreamConsumer | +| 0.1.5 | 2021-07-20 | [\#4874](https://github.com/airbytehq/airbyte/pull/4874) | declare object types correctly in spec | +| 0.1.4 | 2021-06-17 | [\#3744](https://github.com/airbytehq/airbyte/pull/3744) | Fix doc/params in specification file | +| 0.1.3 | 2021-05-28 | [\#3728](https://github.com/airbytehq/airbyte/pull/3973) | Change dockerfile entrypoint | +| 0.1.2 | 2021-05-13 | [\#3367](https://github.com/airbytehq/airbyte/pull/3671) | Fix handle symbols unicode | +| 0.1.1 | 2021-05-11 | [\#3566](https://github.com/airbytehq/airbyte/pull/3195) | MS SQL Server Destination Release! | ### Changelog (Strict Encrypt) | Version | Date | Pull Request | Subject | |:--------| :--- | :--- |:----------------------------------------------------------------------------------------------------| -| 0.1.20 | 2022-07-14 | [\#15260](https://github.com/airbytehq/airbyte/pull/15260) | Align version of strict encrypt connector with regular connector | -| 0.1.10 | 2022-07-14 | [\#14618](https://github.com/airbytehq/airbyte/pull/14618) | Removed additionalProperties: false from JDBC destination connectors | -| 0.1.9 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 0.1.8 | 2022-05-25 | [13054](https://github.com/airbytehq/airbyte/pull/13054) | Destination MSSQL: added custom JDBC parameters support. | -| 0.1.6 | 2022-05-17 | [12820](https://github.com/airbytehq/airbyte/pull/12820) | Improved 'check' operation performance | -| 0.1.5 | 2022-02-25 | [10421](https://github.com/airbytehq/airbyte/pull/10421) | Refactor JDBC parameters handling | -| 0.1.4 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | -| 0.1.3 | 2021-12-28 | [\#9158](https://github.com/airbytehq/airbyte/pull/9158) | Update connector fields title/description | -| 0.1.2 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.1.1 | 2021-11-08 | [#7719](https://github.com/airbytehq/airbyte/pull/7719) | Improve handling of wide rows by buffering records based on their byte size rather than their count | +| 0.1.22 | 2022-10-21 | [18275](https://github.com/airbytehq/airbyte/pull/18275) | Upgrade commons-text for CVE 2022-42889 | +| 0.1.21 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.1.20 | 2022-07-14 | [\#15260](https://github.com/airbytehq/airbyte/pull/15260) | Align version of strict encrypt connector with regular connector | +| 0.1.10 | 2022-07-14 | [\#14618](https://github.com/airbytehq/airbyte/pull/14618) | Removed additionalProperties: false from JDBC destination connectors | +| 0.1.9 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 0.1.8 | 2022-05-25 | [13054](https://github.com/airbytehq/airbyte/pull/13054) | Destination MSSQL: added custom JDBC parameters support. | +| 0.1.6 | 2022-05-17 | [12820](https://github.com/airbytehq/airbyte/pull/12820) | Improved 'check' operation performance | +| 0.1.5 | 2022-02-25 | [10421](https://github.com/airbytehq/airbyte/pull/10421) | Refactor JDBC parameters handling | +| 0.1.4 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | +| 0.1.3 | 2021-12-28 | [\#9158](https://github.com/airbytehq/airbyte/pull/9158) | Update connector fields title/description | +| 0.1.2 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.1.1 | 2021-11-08 | [#7719](https://github.com/airbytehq/airbyte/pull/7719) | Improve handling of wide rows by buffering records based on their byte size rather than their count | From 76acfb8192a832ede218fe0486ea79627a41ebfa Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 21 Oct 2022 10:16:25 -0700 Subject: [PATCH 253/498] [low-code] Propagate options to InterpolatedRequestInputProvider (#18050) * properly propagate options * cleanup * turn into dataclass * rename * no need for deepcopy * fix test * bump * cleaner --- airbyte-cdk/python/CHANGELOG.md | 3 ++ .../interpolated_request_input_provider.py | 30 +++++++++---------- .../interpolated_request_options_provider.py | 24 ++++++++++----- airbyte-cdk/python/setup.py | 2 +- ...est_interpolated_request_input_provider.py | 11 +++++-- .../sources/declarative/test_factory.py | 4 +-- 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index d884a2f0369b..edd080f4400a 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.3 +- Propagate options to InterpolatedRequestInputProvider + ## 0.2.2 - Report config validation errors as failed connection status during `check`. - Report config validation errors as `config_error` failure type. diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py index e3bf9e0f0e19..370e5d8caeac 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_input_provider.py @@ -2,6 +2,7 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from dataclasses import InitVar, dataclass, field from typing import Any, Mapping, Optional, Union from airbyte_cdk.sources.declarative.interpolation.interpolated_mapping import InterpolatedMapping @@ -9,30 +10,27 @@ from airbyte_cdk.sources.declarative.types import Config, StreamSlice, StreamState +@dataclass class InterpolatedRequestInputProvider: """ Helper class that generically performs string interpolation on the provided dictionary or string input """ - def __init__( - self, *, config: Config, request_inputs: Optional[Union[str, Mapping[str, str]]] = None, **options: Optional[Mapping[str, Any]] - ): - """ - :param config: The user-provided configuration as specified by the source's spec - :param request_inputs: The dictionary to interpolate - :param options: Additional runtime parameters to be used for string interpolation - """ + options: InitVar[Mapping[str, Any]] + request_inputs: Optional[Union[str, Mapping[str, str]]] = field(default=None) + config: Config = field(default_factory=dict) + _interpolator: Union[InterpolatedString, InterpolatedMapping] = field(init=False, repr=False, default=None) + _request_inputs: Union[str, Mapping[str, str]] = field(init=False, repr=False, default=None) - self._config = config + def __post_init__(self, options: Mapping[str, Any]): - if request_inputs is None: - request_inputs = {} - if isinstance(request_inputs, str): - self._interpolator = InterpolatedString(request_inputs, default="", options=options) + self._request_inputs = self.request_inputs or {} + if isinstance(self.request_inputs, str): + self._interpolator = InterpolatedString(self.request_inputs, default="", options=options) else: - self._interpolator = InterpolatedMapping(request_inputs, options=options) + self._interpolator = InterpolatedMapping(self._request_inputs, options=options) - def request_inputs( + def eval_request_inputs( self, stream_state: StreamState, stream_slice: Optional[StreamSlice] = None, next_page_token: Mapping[str, Any] = None ) -> Mapping[str, Any]: """ @@ -44,7 +42,7 @@ def request_inputs( :return: The request inputs to set on an outgoing HTTP request """ kwargs = {"stream_state": stream_state, "stream_slice": stream_slice, "next_page_token": next_page_token} - interpolated_value = self._interpolator.eval(self._config, **kwargs) + interpolated_value = self._interpolator.eval(self.config, **kwargs) if isinstance(interpolated_value, dict): non_null_tokens = {k: v for k, v in interpolated_value.items() if v} diff --git a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py index 6348ccc35884..5dd5dbda9b35 100644 --- a/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py +++ b/airbyte-cdk/python/airbyte_cdk/sources/declarative/requesters/request_options/interpolated_request_options_provider.py @@ -46,10 +46,18 @@ def __post_init__(self, options: Mapping[str, Any]): if self.request_body_json and self.request_body_data: raise ValueError("RequestOptionsProvider should only contain either 'request_body_data' or 'request_body_json' not both") - self._parameter_interpolator = InterpolatedRequestInputProvider(config=self.config, request_inputs=self.request_parameters) - self._headers_interpolator = InterpolatedRequestInputProvider(config=self.config, request_inputs=self.request_headers) - self._body_data_interpolator = InterpolatedRequestInputProvider(config=self.config, request_inputs=self.request_body_data) - self._body_json_interpolator = InterpolatedRequestInputProvider(config=self.config, request_inputs=self.request_body_json) + self._parameter_interpolator = InterpolatedRequestInputProvider( + config=self.config, request_inputs=self.request_parameters, options=options + ) + self._headers_interpolator = InterpolatedRequestInputProvider( + config=self.config, request_inputs=self.request_headers, options=options + ) + self._body_data_interpolator = InterpolatedRequestInputProvider( + config=self.config, request_inputs=self.request_body_data, options=options + ) + self._body_json_interpolator = InterpolatedRequestInputProvider( + config=self.config, request_inputs=self.request_body_json, options=options + ) def get_request_params( self, @@ -58,7 +66,7 @@ def get_request_params( stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, ) -> MutableMapping[str, Any]: - interpolated_value = self._parameter_interpolator.request_inputs(stream_state, stream_slice, next_page_token) + interpolated_value = self._parameter_interpolator.eval_request_inputs(stream_state, stream_slice, next_page_token) if isinstance(interpolated_value, dict): return interpolated_value return {} @@ -70,7 +78,7 @@ def get_request_headers( stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, ) -> Mapping[str, Any]: - return self._headers_interpolator.request_inputs(stream_state, stream_slice, next_page_token) + return self._headers_interpolator.eval_request_inputs(stream_state, stream_slice, next_page_token) def get_request_body_data( self, @@ -79,7 +87,7 @@ def get_request_body_data( stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, ) -> Optional[Union[Mapping, str]]: - return self._body_data_interpolator.request_inputs(stream_state, stream_slice, next_page_token) + return self._body_data_interpolator.eval_request_inputs(stream_state, stream_slice, next_page_token) def get_request_body_json( self, @@ -88,4 +96,4 @@ def get_request_body_json( stream_slice: Optional[StreamSlice] = None, next_page_token: Optional[Mapping[str, Any]] = None, ) -> Optional[Mapping]: - return self._body_json_interpolator.request_inputs(stream_state, stream_slice, next_page_token) + return self._body_json_interpolator.eval_request_inputs(stream_state, stream_slice, next_page_token) diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index 36dfdbbe0355..34416a842a39 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.2.2", + version="0.2.3", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py index 74ee47267c35..026848854c92 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/requesters/test_interpolated_request_input_provider.py @@ -13,15 +13,20 @@ ("test_static_map_data", {"a_static_request_param": "a_static_value"}, {"a_static_request_param": "a_static_value"}), ("test_map_depends_on_stream_slice", {"read_from_slice": "{{ stream_slice['slice_key'] }}"}, {"read_from_slice": "slice_value"}), ("test_map_depends_on_config", {"read_from_config": "{{ config['config_key'] }}"}, {"read_from_config": "value_of_config"}), + ( + "test_map_depends_on_options", + {"read_from_options": "{{ options['read_from_options'] }}"}, + {"read_from_options": "value_of_options"}, + ), ("test_defaults_to_empty_dictionary", None, {}), ], ) def test_initialize_interpolated_mapping_request_input_provider(test_name, input_request_data, expected_request_data): config = {"config_key": "value_of_config"} stream_slice = {"slice_key": "slice_value"} - - provider = InterpolatedRequestInputProvider(config=config, request_inputs=input_request_data) - actual_request_data = provider.request_inputs(stream_state={}, stream_slice=stream_slice) + options = {"read_from_options": "value_of_options"} + provider = InterpolatedRequestInputProvider(request_inputs=input_request_data, config=config, options=options) + actual_request_data = provider.eval_request_inputs(stream_state={}, stream_slice=stream_slice) assert isinstance(provider._interpolator, InterpolatedMapping) assert actual_request_data == expected_request_data diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py b/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py index 4adf401c481a..684a68c01561 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/test_factory.py @@ -69,9 +69,9 @@ def test_factory(): request_options_provider = factory.create_component(config["request_options"], input_config)() assert type(request_options_provider) == InterpolatedRequestOptionsProvider - assert request_options_provider._parameter_interpolator._config == input_config + assert request_options_provider._parameter_interpolator.config == input_config assert request_options_provider._parameter_interpolator._interpolator.mapping["offset"] == "{{ next_page_token['offset'] }}" - assert request_options_provider._body_json_interpolator._config == input_config + assert request_options_provider._body_json_interpolator.config == input_config assert request_options_provider._body_json_interpolator._interpolator.mapping["body_offset"] == "{{ next_page_token['offset'] }}" From f71fe5ac1572b1fab0d24f325ccdf4cc41f67647 Mon Sep 17 00:00:00 2001 From: Jimmy Ma Date: Fri, 21 Oct 2022 10:27:34 -0700 Subject: [PATCH 254/498] Add message translation to GetSpec (#18130) * Update SpecActivityImpl to build a VersionedStreamFactory * Enable Protocol Version detection from Stream for SPEC * Print log before the action for better debugging * Fix buffer size for protocol detection * Improve detectVersion error handling * extract constan * Rename attribute for clarity --- .../protocol/AirbyteMessageMigrator.java | 14 ++- ...irbyteMessageVersionedMigratorFactory.java | 4 + .../VersionedAirbyteStreamFactory.java | 109 ++++++++++++++++- .../VersionedAirbyteStreamFactoryTest.java | 115 ++++++++++++++++++ .../version-detection/logs-with-version.jsonl | 5 + .../logs-without-spec-message.jsonl | 18 +++ .../logs-without-version.jsonl | 5 + .../test/acceptance/BasicAcceptanceTests.java | 2 +- .../temporal/spec/SpecActivityImpl.java | 23 +++- 9 files changed, 284 insertions(+), 11 deletions(-) create mode 100644 airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactoryTest.java create mode 100644 airbyte-commons-worker/src/test/resources/version-detection/logs-with-version.jsonl create mode 100644 airbyte-commons-worker/src/test/resources/version-detection/logs-without-spec-message.jsonl create mode 100644 airbyte-commons-worker/src/test/resources/version-detection/logs-without-version.jsonl diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageMigrator.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageMigrator.java index 316bb7df71db..1906e143c33a 100644 --- a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageMigrator.java +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageMigrator.java @@ -27,7 +27,7 @@ public class AirbyteMessageMigrator { private final List> migrationsToRegister; private final SortedMap> migrations = new TreeMap<>(); - private String mostRecentVersion = ""; + private String mostRecentMajorVersion = ""; public AirbyteMessageMigrator(List> migrations) { migrationsToRegister = migrations; @@ -47,7 +47,7 @@ public void initialize() { * required migrations */ public PreviousVersion downgrade(final CurrentVersion message, final Version target) { - if (target.getMajorVersion().equals(mostRecentVersion)) { + if (target.getMajorVersion().equals(mostRecentMajorVersion)) { return (PreviousVersion) message; } @@ -64,7 +64,7 @@ public PreviousVersion downgrade(final Current * migrations */ public CurrentVersion upgrade(final PreviousVersion message, final Version source) { - if (source.getMajorVersion().equals(mostRecentVersion)) { + if (source.getMajorVersion().equals(mostRecentMajorVersion)) { return (CurrentVersion) message; } @@ -75,6 +75,10 @@ public CurrentVersion upgrade(final PreviousVe return (CurrentVersion) result; } + public Version getMostRecentVersion() { + return new Version(mostRecentMajorVersion, "0", "0"); + } + private Collection> selectMigrations(final Version version) { final Collection> results = migrations.tailMap(version.getMajorVersion()).values(); if (results.isEmpty()) { @@ -107,8 +111,8 @@ void registerMigration(final AirbyteMessageMigration migration) { final String key = migration.getPreviousVersion().getMajorVersion(); if (!migrations.containsKey(key)) { migrations.put(key, migration); - if (migration.getCurrentVersion().getMajorVersion().compareTo(mostRecentVersion) > 0) { - mostRecentVersion = migration.getCurrentVersion().getMajorVersion(); + if (migration.getCurrentVersion().getMajorVersion().compareTo(mostRecentMajorVersion) > 0) { + mostRecentMajorVersion = migration.getCurrentVersion().getMajorVersion(); } } else { throw new RuntimeException("Trying to register a duplicated migration " + migration.getClass().getName()); diff --git a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java index afce6264089f..dec45297880e 100644 --- a/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java +++ b/airbyte-commons-protocol/src/main/java/io/airbyte/commons/protocol/AirbyteMessageVersionedMigratorFactory.java @@ -23,4 +23,8 @@ public AirbyteMessageVersionedMigrator getVersionedMigrator(final Version return new AirbyteMessageVersionedMigrator<>(this.migrator, version); } + public Version getMostRecentVersion() { + return migrator.getMostRecentVersion(); + } + } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java index 88410da0c55d..b49190dc8658 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java @@ -5,6 +5,7 @@ package io.airbyte.workers.internal; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Preconditions; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.logging.MdcScope; import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; @@ -13,7 +14,12 @@ import io.airbyte.commons.protocol.serde.AirbyteMessageDeserializer; import io.airbyte.commons.version.Version; import io.airbyte.protocol.models.AirbyteMessage; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Stream; +import lombok.SneakyThrows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,10 +32,23 @@ public class VersionedAirbyteStreamFactory extends DefaultAirbyteStreamFactory { private static final Logger LOGGER = LoggerFactory.getLogger(VersionedAirbyteStreamFactory.class); + private static final Version fallbackVersion = new Version("0.2.0"); - private final AirbyteMessageDeserializer deserializer; - private final AirbyteMessageVersionedMigrator migrator; - private final Version protocolVersion; + // Buffer size to use when detecting the protocol version. + // Given that BufferedReader::reset fails if we try to reset if we go past its buffer size, this + // buffer has to be big enough to contain our longest spec and whatever messages get emitted before + // the SPEC. + private static final int BUFFER_READ_AHEAD_LIMIT = 32000; + private static final int MESSAGES_LOOK_AHEAD_FOR_DETECTION = 10; + private static final String TYPE_FIELD_NAME = "type"; + + private final AirbyteMessageSerDeProvider serDeProvider; + private final AirbyteMessageVersionedMigratorFactory migratorFactory; + private AirbyteMessageDeserializer deserializer; + private AirbyteMessageVersionedMigrator migrator; + private Version protocolVersion; + + private boolean shouldDetectVersion = false; public VersionedAirbyteStreamFactory(final AirbyteMessageSerDeProvider serDeProvider, final AirbyteMessageVersionedMigratorFactory migratorFactory, @@ -43,6 +62,90 @@ public VersionedAirbyteStreamFactory(final AirbyteMessageSerDeProvider serDeProv final MdcScope.Builder containerLogMdcBuilder) { // TODO AirbyteProtocolPredicate needs to be updated to be protocol version aware super(new AirbyteProtocolPredicate(), LOGGER, containerLogMdcBuilder); + Preconditions.checkNotNull(protocolVersion); + this.serDeProvider = serDeProvider; + this.migratorFactory = migratorFactory; + this.initializeForProtocolVersion(protocolVersion); + } + + /** + * Create the AirbyteMessage stream. + * + * If detectVersion is set to true, it will decide which protocol version to use from the content of + * the stream rather than the one passed from the constructor. + */ + @SneakyThrows + @Override + public Stream create(final BufferedReader bufferedReader) { + if (shouldDetectVersion) { + final Optional versionMaybe = detectVersion(bufferedReader); + if (versionMaybe.isPresent()) { + logger.info("Detected Protocol Version {}", versionMaybe.get().serialize()); + initializeForProtocolVersion(versionMaybe.get()); + } else { + // No version found, use the default as a fallback + logger.info("Unable to detect Protocol Version, assuming protocol version {}", fallbackVersion.serialize()); + initializeForProtocolVersion(fallbackVersion); + } + } + return super.create(bufferedReader); + } + + /** + * Attempt to detect the version by scanning the stream + * + * Using the BufferedReader reset/mark feature to get a look-ahead. We will attempt to find the + * first SPEC message and decide on a protocol version from this message. + * + * @param bufferedReader the stream to read + * @return The Version if found + * @throws IOException + */ + private Optional detectVersion(final BufferedReader bufferedReader) throws IOException { + // Buffersize needs to be big enough to containing everything we need for the detection. Otherwise, + // the reset will fail. + bufferedReader.mark(BUFFER_READ_AHEAD_LIMIT); + try { + // Cap detection to the first 10 messages. When doing the protocol detection, we expect the SPEC + // message to show up early in the stream. Ideally it should be first message however we do not + // enforce this constraint currently so connectors may send LOG messages before. + for (int i = 0; i < MESSAGES_LOOK_AHEAD_FOR_DETECTION; ++i) { + final String line = bufferedReader.readLine(); + final Optional jsonOpt = Jsons.tryDeserialize(line); + if (jsonOpt.isPresent()) { + final JsonNode json = jsonOpt.get(); + if (isSpecMessage(json)) { + final JsonNode protocolVersionNode = json.at("/spec/protocol_version"); + bufferedReader.reset(); + return Optional.ofNullable(protocolVersionNode).filter(Predicate.not(JsonNode::isMissingNode)).map(node -> new Version(node.asText())); + } + } + } + bufferedReader.reset(); + return Optional.empty(); + } catch (IOException e) { + logger.warn( + "Protocol version detection failed, it is likely than the connector sent more than {}B without an complete SPEC message." + + " A SPEC message that is too long could be the root cause here.", + BUFFER_READ_AHEAD_LIMIT); + throw e; + } + } + + private boolean isSpecMessage(final JsonNode json) { + return json.has(TYPE_FIELD_NAME) && "spec".equalsIgnoreCase(json.get(TYPE_FIELD_NAME).asText()); + } + + public boolean setDetectVersion(final boolean detectVersion) { + return this.shouldDetectVersion = detectVersion; + } + + public VersionedAirbyteStreamFactory withDetectVersion(final boolean detectVersion) { + setDetectVersion(detectVersion); + return this; + } + + final protected void initializeForProtocolVersion(final Version protocolVersion) { this.deserializer = (AirbyteMessageDeserializer) serDeProvider.getDeserializer(protocolVersion).orElseThrow(); this.migrator = migratorFactory.getVersionedMigrator(protocolVersion); this.protocolVersion = protocolVersion; diff --git a/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactoryTest.java b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactoryTest.java new file mode 100644 index 000000000000..f6ac35de3a54 --- /dev/null +++ b/airbyte-commons-worker/src/test/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactoryTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.internal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import io.airbyte.commons.protocol.AirbyteMessageMigrator; +import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; +import io.airbyte.commons.protocol.AirbyteMessageVersionedMigratorFactory; +import io.airbyte.commons.protocol.migrations.AirbyteMessageMigrationV0; +import io.airbyte.commons.protocol.serde.AirbyteMessageV0Deserializer; +import io.airbyte.commons.protocol.serde.AirbyteMessageV0Serializer; +import io.airbyte.commons.version.Version; +import io.airbyte.protocol.models.AirbyteMessage; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.ClassLoaderUtils; + +class VersionedAirbyteStreamFactoryTest { + + AirbyteMessageSerDeProvider serDeProvider; + AirbyteMessageVersionedMigratorFactory migratorFactory; + + final static Version defaultVersion = new Version("0.2.0"); + + @BeforeEach + void beforeEach() { + serDeProvider = spy(new AirbyteMessageSerDeProvider( + List.of(new AirbyteMessageV0Deserializer()), + List.of(new AirbyteMessageV0Serializer()))); + serDeProvider.initialize(); + final AirbyteMessageMigrator migrator = new AirbyteMessageMigrator( + List.of(new AirbyteMessageMigrationV0())); + migrator.initialize(); + migratorFactory = spy(new AirbyteMessageVersionedMigratorFactory(migrator)); + } + + @Test + void testCreate() { + final Version initialVersion = new Version("0.1.2"); + final VersionedAirbyteStreamFactory streamFactory = new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, initialVersion); + + final BufferedReader bufferedReader = new BufferedReader(new StringReader("")); + streamFactory.create(bufferedReader); + + verify(serDeProvider).getDeserializer(initialVersion); + verify(migratorFactory).getVersionedMigrator(initialVersion); + } + + @Test + void testCreateWithVersionDetection() { + final Version initialVersion = new Version("0.0.0"); + final VersionedAirbyteStreamFactory streamFactory = new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, initialVersion) + .withDetectVersion(true); + + final BufferedReader bufferedReader = + getBuffereredReader("version-detection/logs-with-version.jsonl"); + final Stream stream = streamFactory.create(bufferedReader); + + long messageCount = stream.toList().size(); + verify(serDeProvider).getDeserializer(initialVersion); + verify(serDeProvider).getDeserializer(new Version("0.5.9")); + assertEquals(1, messageCount); + } + + @Test + void testCreateWithVersionDetectionFallback() { + final Version initialVersion = new Version("0.0.6"); + final VersionedAirbyteStreamFactory streamFactory = new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, initialVersion) + .withDetectVersion(true); + + final BufferedReader bufferedReader = + getBuffereredReader("version-detection/logs-without-version.jsonl"); + final Stream stream = streamFactory.create(bufferedReader); + + final long messageCount = stream.toList().size(); + verify(serDeProvider).getDeserializer(initialVersion); + verify(serDeProvider).getDeserializer(defaultVersion); + assertEquals(1, messageCount); + } + + @Test + void testCreateWithVersionDetectionWithoutSpecMessage() { + final Version initialVersion = new Version("0.0.1"); + final VersionedAirbyteStreamFactory streamFactory = new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, initialVersion) + .withDetectVersion(true); + + final BufferedReader bufferedReader = + getBuffereredReader("version-detection/logs-without-spec-message.jsonl"); + final Stream stream = streamFactory.create(bufferedReader); + + final long messageCount = stream.toList().size(); + verify(serDeProvider).getDeserializer(initialVersion); + verify(serDeProvider).getDeserializer(defaultVersion); + assertEquals(2, messageCount); + } + + BufferedReader getBuffereredReader(final String resourceFile) { + return new BufferedReader( + new InputStreamReader( + ClassLoaderUtils.getDefaultClassLoader().getResourceAsStream(resourceFile), + Charset.defaultCharset())); + } + +} diff --git a/airbyte-commons-worker/src/test/resources/version-detection/logs-with-version.jsonl b/airbyte-commons-worker/src/test/resources/version-detection/logs-with-version.jsonl new file mode 100644 index 000000000000..9fab56f890af --- /dev/null +++ b/airbyte-commons-worker/src/test/resources/version-detection/logs-with-version.jsonl @@ -0,0 +1,5 @@ +{"type":"LOG","log":{"level":"INFO","message":"integration args: {spec=null}"}} +{"type":"LOG","log":{"level":"INFO","message":"Running integration: io.airbyte.integrations.destination.bigquery.BigQueryDestination"}} +{"type":"LOG","log":{"level":"INFO","message":"Command: SPEC"}} +{"type":"LOG","log":{"level":"INFO","message":"Integration config: IntegrationConfig{command=SPEC, configPath='null', catalogPath='null', statePath='null'}"}} +{"type":"SPEC","spec":{"protocol_version":"0.5.9","documentationUrl":"https://docs.airbyte.io/integrations/destinations/bigquery","connectionSpecification":{"$schema":"http://json-schema.org/draft-07/schema#","title":"BigQuery Destination Spec","type":"object","required":["project_id","dataset_location","dataset_id"],"additionalProperties":true,"properties":{"project_id":{"type":"string","description":"The GCP project ID for the project containing the target BigQuery dataset. Read more here.","title":"Project ID","order":0},"dataset_location":{"type":"string","description":"The location of the dataset. Warning: Changes made after creation will not be applied. Read more here.","title":"Dataset Location","order":1,"enum":["US","EU","asia-east1","asia-east2","asia-northeast1","asia-northeast2","asia-northeast3","asia-south1","asia-south2","asia-southeast1","asia-southeast2","australia-southeast1","australia-southeast2","europe-central2","europe-north1","europe-west1","europe-west2","europe-west3","europe-west4","europe-west6","northamerica-northeast1","northamerica-northeast2","southamerica-east1","southamerica-west1","us-central1","us-east1","us-east4","us-west1","us-west2","us-west3","us-west4"]},"dataset_id":{"type":"string","description":"The default BigQuery Dataset ID that tables are replicated to if the source does not specify a namespace. Read more here.","title":"Default Dataset ID","order":2},"loading_method":{"type":"object","title":"Loading Method","description":"Loading method used to send select the way data will be uploaded to BigQuery.
    Standard Inserts - Direct uploading using SQL INSERT statements. This method is extremely inefficient and provided only for quick testing. In almost all cases, you should use staging.
    GCS Staging - Writes large batches of records to a file, uploads the file to GCS, then uses COPY INTO table to upload the file. Recommended for most workloads for better speed and scalability. Read more about GCS Staging here.","order":3,"oneOf":[{"title":"Standard Inserts","required":["method"],"properties":{"method":{"type":"string","const":"Standard"}}},{"title":"GCS Staging","required":["method","gcs_bucket_name","gcs_bucket_path","credential"],"properties":{"method":{"type":"string","const":"GCS Staging","order":0},"credential":{"title":"Credential","description":"An HMAC key is a type of credential and can be associated with a service account or a user account in Cloud Storage. Read more here.","type":"object","order":1,"oneOf":[{"title":"HMAC key","required":["credential_type","hmac_key_access_id","hmac_key_secret"],"properties":{"credential_type":{"type":"string","const":"HMAC_KEY","order":0},"hmac_key_access_id":"**********","type":"string","description":"HMAC key access ID. When linked to a service account, this ID is 61 characters long; when linked to a user account, it is 24 characters long.","title":"HMAC Key Access ID","airbyte_secret":true,"examples":["1234567890abcdefghij1234"],"order":1},"hmac_key_secret":"**********","type":"string","description":"The corresponding secret for the access ID. It is a 40-character base-64 encoded string.","title":"HMAC Key Secret","airbyte_secret":true,"examples":["1234567890abcdefghij1234567890ABCDEFGHIJ"],"order":2}]},"gcs_bucket_name":{"title":"GCS Bucket Name","type":"string","description":"The name of the GCS bucket. Read more here.","examples":["airbyte_sync"],"order":2},"gcs_bucket_path":{"title":"GCS Bucket Path","description":"Directory under the GCS bucket where data will be written.","type":"string","examples":["data_sync/test"],"order":3},"keep_files_in_gcs-bucket":{"type":"string","description":"This upload method is supposed to temporary store records in GCS bucket. By this select you can chose if these records should be removed from GCS when migration has finished. The default \"Delete all tmp files from GCS\" value is used if not set explicitly.","title":"GCS Tmp Files Afterward Processing (Optional)","default":"Delete all tmp files from GCS","enum":["Delete all tmp files from GCS","Keep all tmp files in GCS"],"order":4}}}]},"credentials_json":"**********","type":"string","description":"The contents of the JSON service account key. Check out the docs if you need help generating this key. Default credentials will be used if this field is left empty.","title":"Service Account Key JSON (Required for cloud, optional for open-source)","airbyte_secret":true,"order":4},"transformation_priority":{"type":"string","description":"Interactive run type means that the query is executed as soon as possible, and these queries count towards concurrent rate limit and daily limit. Read more about interactive run type here. Batch queries are queued and started as soon as idle resources are available in the BigQuery shared resource pool, which usually occurs within a few minutes. Batch queries don’t count towards your concurrent rate limit. Read more about batch queries here. The default \"interactive\" value is used if not set explicitly.","title":"Transformation Query Run Type (Optional)","default":"interactive","enum":["interactive","batch"],"order":5},"big_query_client_buffer_size_mb":{"title":"Google BigQuery Client Chunk Size (Optional)","description":"Google BigQuery client's chunk (buffer) size (MIN=1, MAX = 15) for each table. The size that will be written by a single RPC. Written data will be buffered and only flushed upon reaching this size or closing the channel. The default 15MB value is used if not set explicitly. Read more here.","type":"integer","minimum":1,"maximum":15,"default":15,"examples":["15"],"order":6}}},"supportsIncremental":true,"supportsNormalization":true,"supportsDBT":true,"supported_destination_sync_modes":["overwrite","append","append_dedup"]} diff --git a/airbyte-commons-worker/src/test/resources/version-detection/logs-without-spec-message.jsonl b/airbyte-commons-worker/src/test/resources/version-detection/logs-without-spec-message.jsonl new file mode 100644 index 000000000000..267d60190925 --- /dev/null +++ b/airbyte-commons-worker/src/test/resources/version-detection/logs-without-spec-message.jsonl @@ -0,0 +1,18 @@ +{"type":"LOG","log":{"level":"INFO","message":"integration args: {spec=null}"}} +{"type":"LOG","log":{"level":"INFO","message":"Running integration: io.airbyte.integrations.destination.bigquery.BigQueryDestination"}} +{"type":"LOG","log":{"level":"INFO","message":"Command: SPEC"}} +{"type":"LOG","log":{"level":"INFO","message":"Integration config: IntegrationConfig{command=SPEC, configPath='null', catalogPath='null', statePath='null'}"}} +{"type":"RECORD","stream":"s","emitted_at":5,"data":{"protocol_version":"0.5.9","documentationUrl":"https://docs.airbyte.io/integrations/destinations/bigquery","connectionSpecification":{"$schema":"http://json-schema.org/draft-07/schema#","title":"BigQuery Destination Spec","type":"object","required":["project_id","dataset_location","dataset_id"],"additionalProperties":true,"properties":{"project_id":{"type":"string","description":"The GCP project ID for the project containing the target BigQuery dataset. Read more here.","title":"Project ID","order":0},"dataset_location":{"type":"string","description":"The location of the dataset. Warning: Changes made after creation will not be applied. Read more here.","title":"Dataset Location","order":1,"enum":["US","EU","asia-east1","asia-east2","asia-northeast1","asia-northeast2","asia-northeast3","asia-south1","asia-south2","asia-southeast1","asia-southeast2","australia-southeast1","australia-southeast2","europe-central2","europe-north1","europe-west1","europe-west2","europe-west3","europe-west4","europe-west6","northamerica-northeast1","northamerica-northeast2","southamerica-east1","southamerica-west1","us-central1","us-east1","us-east4","us-west1","us-west2","us-west3","us-west4"]},"dataset_id":{"type":"string","description":"The default BigQuery Dataset ID that tables are replicated to if the source does not specify a namespace. Read more here.","title":"Default Dataset ID","order":2},"loading_method":{"type":"object","title":"Loading Method","description":"Loading method used to send select the way data will be uploaded to BigQuery.
    Standard Inserts - Direct uploading using SQL INSERT statements. This method is extremely inefficient and provided only for quick testing. In almost all cases, you should use staging.
    GCS Staging - Writes large batches of records to a file, uploads the file to GCS, then uses COPY INTO table to upload the file. Recommended for most workloads for better speed and scalability. Read more about GCS Staging here.","order":3,"oneOf":[{"title":"Standard Inserts","required":["method"],"properties":{"method":{"type":"string","const":"Standard"}}},{"title":"GCS Staging","required":["method","gcs_bucket_name","gcs_bucket_path","credential"],"properties":{"method":{"type":"string","const":"GCS Staging","order":0},"credential":{"title":"Credential","description":"An HMAC key is a type of credential and can be associated with a service account or a user account in Cloud Storage. Read more here.","type":"object","order":1,"oneOf":[{"title":"HMAC key","required":["credential_type","hmac_key_access_id","hmac_key_secret"],"properties":{"credential_type":{"type":"string","const":"HMAC_KEY","order":0},"hmac_key_access_id":"**********","type":"string","description":"HMAC key access ID. When linked to a service account, this ID is 61 characters long; when linked to a user account, it is 24 characters long.","title":"HMAC Key Access ID","airbyte_secret":true,"examples":["1234567890abcdefghij1234"],"order":1},"hmac_key_secret":"**********","type":"string","description":"The corresponding secret for the access ID. It is a 40-character base-64 encoded string.","title":"HMAC Key Secret","airbyte_secret":true,"examples":["1234567890abcdefghij1234567890ABCDEFGHIJ"],"order":2}]},"gcs_bucket_name":{"title":"GCS Bucket Name","type":"string","description":"The name of the GCS bucket. Read more here.","examples":["airbyte_sync"],"order":2},"gcs_bucket_path":{"title":"GCS Bucket Path","description":"Directory under the GCS bucket where data will be written.","type":"string","examples":["data_sync/test"],"order":3},"keep_files_in_gcs-bucket":{"type":"string","description":"This upload method is supposed to temporary store records in GCS bucket. By this select you can chose if these records should be removed from GCS when migration has finished. The default \"Delete all tmp files from GCS\" value is used if not set explicitly.","title":"GCS Tmp Files Afterward Processing (Optional)","default":"Delete all tmp files from GCS","enum":["Delete all tmp files from GCS","Keep all tmp files in GCS"],"order":4}}}]},"credentials_json":"**********","type":"string","description":"The contents of the JSON service account key. Check out the docs if you need help generating this key. Default credentials will be used if this field is left empty.","title":"Service Account Key JSON (Required for cloud, optional for open-source)","airbyte_secret":true,"order":4},"transformation_priority":{"type":"string","description":"Interactive run type means that the query is executed as soon as possible, and these queries count towards concurrent rate limit and daily limit. Read more about interactive run type here. Batch queries are queued and started as soon as idle resources are available in the BigQuery shared resource pool, which usually occurs within a few minutes. Batch queries don’t count towards your concurrent rate limit. Read more about batch queries here. The default \"interactive\" value is used if not set explicitly.","title":"Transformation Query Run Type (Optional)","default":"interactive","enum":["interactive","batch"],"order":5},"big_query_client_buffer_size_mb":{"title":"Google BigQuery Client Chunk Size (Optional)","description":"Google BigQuery client's chunk (buffer) size (MIN=1, MAX = 15) for each table. The size that will be written by a single RPC. Written data will be buffered and only flushed upon reaching this size or closing the channel. The default 15MB value is used if not set explicitly. Read more here.","type":"integer","minimum":1,"maximum":15,"default":15,"examples":["15"],"order":6}}},"supportsIncremental":true,"supportsNormalization":true,"supportsDBT":true,"supported_destination_sync_modes":["overwrite","append","append_dedup"]} +{"type":"LOG","log":{"level":"INFO","message":"integration args: {spec=null}"}} +{"type":"LOG","log":{"level":"INFO","message":"Running integration: io.airbyte.integrations.destination.bigquery.BigQueryDestination"}} +{"type":"LOG","log":{"level":"INFO","message":"Command: SPEC"}} +{"type":"LOG","log":{"level":"INFO","message":"Integration config: IntegrationConfig{command=SPEC, configPath='null', catalogPath='null', statePath='null'}"}} +{"type":"LOG","log":{"level":"INFO","message":"integration args: {spec=null}"}} +{"type":"LOG","log":{"level":"INFO","message":"Running integration: io.airbyte.integrations.destination.bigquery.BigQueryDestination"}} +{"type":"LOG","log":{"level":"INFO","message":"Command: SPEC"}} +{"type":"LOG","log":{"level":"INFO","message":"Integration config: IntegrationConfig{command=SPEC, configPath='null', catalogPath='null', statePath='null'}"}} +{"type":"LOG","log":{"level":"INFO","message":"integration args: {spec=null}"}} +{"type":"LOG","log":{"level":"INFO","message":"Running integration: io.airbyte.integrations.destination.bigquery.BigQueryDestination"}} +{"type":"LOG","log":{"level":"INFO","message":"Command: SPEC"}} +{"type":"LOG","log":{"level":"INFO","message":"Integration config: IntegrationConfig{command=SPEC, configPath='null', catalogPath='null', statePath='null'}"}} +{"type":"RECORD","stream":"s","emitted_at":6,"data":{"protocol_version":"0.5.9","documentationUrl":"https://docs.airbyte.io/integrations/destinations/bigquery","connectionSpecification":{"$schema":"http://json-schema.org/draft-07/schema#","title":"BigQuery Destination Spec","type":"object","required":["project_id","dataset_location","dataset_id"],"additionalProperties":true,"properties":{"project_id":{"type":"string","description":"The GCP project ID for the project containing the target BigQuery dataset. Read more here.","title":"Project ID","order":0},"dataset_location":{"type":"string","description":"The location of the dataset. Warning: Changes made after creation will not be applied. Read more here.","title":"Dataset Location","order":1,"enum":["US","EU","asia-east1","asia-east2","asia-northeast1","asia-northeast2","asia-northeast3","asia-south1","asia-south2","asia-southeast1","asia-southeast2","australia-southeast1","australia-southeast2","europe-central2","europe-north1","europe-west1","europe-west2","europe-west3","europe-west4","europe-west6","northamerica-northeast1","northamerica-northeast2","southamerica-east1","southamerica-west1","us-central1","us-east1","us-east4","us-west1","us-west2","us-west3","us-west4"]},"dataset_id":{"type":"string","description":"The default BigQuery Dataset ID that tables are replicated to if the source does not specify a namespace. Read more here.","title":"Default Dataset ID","order":2},"loading_method":{"type":"object","title":"Loading Method","description":"Loading method used to send select the way data will be uploaded to BigQuery.
    Standard Inserts - Direct uploading using SQL INSERT statements. This method is extremely inefficient and provided only for quick testing. In almost all cases, you should use staging.
    GCS Staging - Writes large batches of records to a file, uploads the file to GCS, then uses COPY INTO table to upload the file. Recommended for most workloads for better speed and scalability. Read more about GCS Staging here.","order":3,"oneOf":[{"title":"Standard Inserts","required":["method"],"properties":{"method":{"type":"string","const":"Standard"}}},{"title":"GCS Staging","required":["method","gcs_bucket_name","gcs_bucket_path","credential"],"properties":{"method":{"type":"string","const":"GCS Staging","order":0},"credential":{"title":"Credential","description":"An HMAC key is a type of credential and can be associated with a service account or a user account in Cloud Storage. Read more here.","type":"object","order":1,"oneOf":[{"title":"HMAC key","required":["credential_type","hmac_key_access_id","hmac_key_secret"],"properties":{"credential_type":{"type":"string","const":"HMAC_KEY","order":0},"hmac_key_access_id":"**********","type":"string","description":"HMAC key access ID. When linked to a service account, this ID is 61 characters long; when linked to a user account, it is 24 characters long.","title":"HMAC Key Access ID","airbyte_secret":true,"examples":["1234567890abcdefghij1234"],"order":1},"hmac_key_secret":"**********","type":"string","description":"The corresponding secret for the access ID. It is a 40-character base-64 encoded string.","title":"HMAC Key Secret","airbyte_secret":true,"examples":["1234567890abcdefghij1234567890ABCDEFGHIJ"],"order":2}]},"gcs_bucket_name":{"title":"GCS Bucket Name","type":"string","description":"The name of the GCS bucket. Read more here.","examples":["airbyte_sync"],"order":2},"gcs_bucket_path":{"title":"GCS Bucket Path","description":"Directory under the GCS bucket where data will be written.","type":"string","examples":["data_sync/test"],"order":3},"keep_files_in_gcs-bucket":{"type":"string","description":"This upload method is supposed to temporary store records in GCS bucket. By this select you can chose if these records should be removed from GCS when migration has finished. The default \"Delete all tmp files from GCS\" value is used if not set explicitly.","title":"GCS Tmp Files Afterward Processing (Optional)","default":"Delete all tmp files from GCS","enum":["Delete all tmp files from GCS","Keep all tmp files in GCS"],"order":4}}}]},"credentials_json":"**********","type":"string","description":"The contents of the JSON service account key. Check out the docs if you need help generating this key. Default credentials will be used if this field is left empty.","title":"Service Account Key JSON (Required for cloud, optional for open-source)","airbyte_secret":true,"order":4},"transformation_priority":{"type":"string","description":"Interactive run type means that the query is executed as soon as possible, and these queries count towards concurrent rate limit and daily limit. Read more about interactive run type here. Batch queries are queued and started as soon as idle resources are available in the BigQuery shared resource pool, which usually occurs within a few minutes. Batch queries don’t count towards your concurrent rate limit. Read more about batch queries here. The default \"interactive\" value is used if not set explicitly.","title":"Transformation Query Run Type (Optional)","default":"interactive","enum":["interactive","batch"],"order":5},"big_query_client_buffer_size_mb":{"title":"Google BigQuery Client Chunk Size (Optional)","description":"Google BigQuery client's chunk (buffer) size (MIN=1, MAX = 15) for each table. The size that will be written by a single RPC. Written data will be buffered and only flushed upon reaching this size or closing the channel. The default 15MB value is used if not set explicitly. Read more here.","type":"integer","minimum":1,"maximum":15,"default":15,"examples":["15"],"order":6}}},"supportsIncremental":true,"supportsNormalization":true,"supportsDBT":true,"supported_destination_sync_modes":["overwrite","append","append_dedup"]} diff --git a/airbyte-commons-worker/src/test/resources/version-detection/logs-without-version.jsonl b/airbyte-commons-worker/src/test/resources/version-detection/logs-without-version.jsonl new file mode 100644 index 000000000000..ca5fdddcfa84 --- /dev/null +++ b/airbyte-commons-worker/src/test/resources/version-detection/logs-without-version.jsonl @@ -0,0 +1,5 @@ +{"type":"LOG","log":{"level":"INFO","message":"integration args: {spec=null}"}} +{"type":"LOG","log":{"level":"INFO","message":"Running integration: io.airbyte.integrations.destination.bigquery.BigQueryDestination"}} +{"type":"LOG","log":{"level":"INFO","message":"Command: SPEC"}} +{"type":"LOG","log":{"level":"INFO","message":"Integration config: IntegrationConfig{command=SPEC, configPath='null', catalogPath='null', statePath='null'}"}} +{"type":"SPEC","spec":{"documentationUrl":"https://docs.airbyte.io/integrations/destinations/bigquery","connectionSpecification":{"$schema":"http://json-schema.org/draft-07/schema#","title":"BigQuery Destination Spec","type":"object","required":["project_id","dataset_location","dataset_id"],"additionalProperties":true,"properties":{"project_id":{"type":"string","description":"The GCP project ID for the project containing the target BigQuery dataset. Read more here.","title":"Project ID","order":0},"dataset_location":{"type":"string","description":"The location of the dataset. Warning: Changes made after creation will not be applied. Read more here.","title":"Dataset Location","order":1,"enum":["US","EU","asia-east1","asia-east2","asia-northeast1","asia-northeast2","asia-northeast3","asia-south1","asia-south2","asia-southeast1","asia-southeast2","australia-southeast1","australia-southeast2","europe-central2","europe-north1","europe-west1","europe-west2","europe-west3","europe-west4","europe-west6","northamerica-northeast1","northamerica-northeast2","southamerica-east1","southamerica-west1","us-central1","us-east1","us-east4","us-west1","us-west2","us-west3","us-west4"]},"dataset_id":{"type":"string","description":"The default BigQuery Dataset ID that tables are replicated to if the source does not specify a namespace. Read more here.","title":"Default Dataset ID","order":2},"loading_method":{"type":"object","title":"Loading Method","description":"Loading method used to send select the way data will be uploaded to BigQuery.
    Standard Inserts - Direct uploading using SQL INSERT statements. This method is extremely inefficient and provided only for quick testing. In almost all cases, you should use staging.
    GCS Staging - Writes large batches of records to a file, uploads the file to GCS, then uses COPY INTO table to upload the file. Recommended for most workloads for better speed and scalability. Read more about GCS Staging here.","order":3,"oneOf":[{"title":"Standard Inserts","required":["method"],"properties":{"method":{"type":"string","const":"Standard"}}},{"title":"GCS Staging","required":["method","gcs_bucket_name","gcs_bucket_path","credential"],"properties":{"method":{"type":"string","const":"GCS Staging","order":0},"credential":{"title":"Credential","description":"An HMAC key is a type of credential and can be associated with a service account or a user account in Cloud Storage. Read more here.","type":"object","order":1,"oneOf":[{"title":"HMAC key","required":["credential_type","hmac_key_access_id","hmac_key_secret"],"properties":{"credential_type":{"type":"string","const":"HMAC_KEY","order":0},"hmac_key_access_id":"**********","type":"string","description":"HMAC key access ID. When linked to a service account, this ID is 61 characters long; when linked to a user account, it is 24 characters long.","title":"HMAC Key Access ID","airbyte_secret":true,"examples":["1234567890abcdefghij1234"],"order":1},"hmac_key_secret":"**********","type":"string","description":"The corresponding secret for the access ID. It is a 40-character base-64 encoded string.","title":"HMAC Key Secret","airbyte_secret":true,"examples":["1234567890abcdefghij1234567890ABCDEFGHIJ"],"order":2}]},"gcs_bucket_name":{"title":"GCS Bucket Name","type":"string","description":"The name of the GCS bucket. Read more here.","examples":["airbyte_sync"],"order":2},"gcs_bucket_path":{"title":"GCS Bucket Path","description":"Directory under the GCS bucket where data will be written.","type":"string","examples":["data_sync/test"],"order":3},"keep_files_in_gcs-bucket":{"type":"string","description":"This upload method is supposed to temporary store records in GCS bucket. By this select you can chose if these records should be removed from GCS when migration has finished. The default \"Delete all tmp files from GCS\" value is used if not set explicitly.","title":"GCS Tmp Files Afterward Processing (Optional)","default":"Delete all tmp files from GCS","enum":["Delete all tmp files from GCS","Keep all tmp files in GCS"],"order":4}}}]},"credentials_json":"**********","type":"string","description":"The contents of the JSON service account key. Check out the docs if you need help generating this key. Default credentials will be used if this field is left empty.","title":"Service Account Key JSON (Required for cloud, optional for open-source)","airbyte_secret":true,"order":4},"transformation_priority":{"type":"string","description":"Interactive run type means that the query is executed as soon as possible, and these queries count towards concurrent rate limit and daily limit. Read more about interactive run type here. Batch queries are queued and started as soon as idle resources are available in the BigQuery shared resource pool, which usually occurs within a few minutes. Batch queries don’t count towards your concurrent rate limit. Read more about batch queries here. The default \"interactive\" value is used if not set explicitly.","title":"Transformation Query Run Type (Optional)","default":"interactive","enum":["interactive","batch"],"order":5},"big_query_client_buffer_size_mb":{"title":"Google BigQuery Client Chunk Size (Optional)","description":"Google BigQuery client's chunk (buffer) size (MIN=1, MAX = 15) for each table. The size that will be written by a single RPC. Written data will be buffered and only flushed upon reaching this size or closing the channel. The default 15MB value is used if not set explicitly. Read more here.","type":"integer","minimum":1,"maximum":15,"default":15,"examples":["15"],"order":6}}},"supportsIncremental":true,"supportsNormalization":true,"supportsDBT":true,"supported_destination_sync_modes":["overwrite","append","append_dedup"]} diff --git a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java index 6bed007bbe68..b1584e3f2c0b 100644 --- a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java +++ b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java @@ -1152,8 +1152,8 @@ void testResetAllWhenSchemaIsModifiedForLegacySource() throws Exception { testHarness.assertSourceAndDestinationDbInSync(false); } finally { // Set source back to version it was set to at beginning of test - testHarness.updateSourceDefinitionVersion(sourceDefinitionId, currentSourceDefinitionVersion); LOGGER.info("Set source connector back to per-stream state supported version {}.", currentSourceDefinitionVersion); + testHarness.updateSourceDefinitionVersion(sourceDefinitionId, currentSourceDefinitionVersion); } } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java index fe76c615d360..8e5cd32e486e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java @@ -11,8 +11,11 @@ import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; import io.airbyte.commons.functional.CheckedSupplier; +import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; +import io.airbyte.commons.protocol.AirbyteMessageVersionedMigratorFactory; import io.airbyte.commons.temporal.CancellationHandler; import io.airbyte.commons.temporal.config.WorkerMode; +import io.airbyte.commons.version.Version; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.JobGetSpecConfig; @@ -23,6 +26,8 @@ import io.airbyte.workers.Worker; import io.airbyte.workers.WorkerConfigs; import io.airbyte.workers.general.DefaultGetSpecWorker; +import io.airbyte.workers.internal.AirbyteStreamFactory; +import io.airbyte.workers.internal.VersionedAirbyteStreamFactory; import io.airbyte.workers.process.AirbyteIntegrationLauncher; import io.airbyte.workers.process.IntegrationLauncher; import io.airbyte.workers.process.ProcessFactory; @@ -48,6 +53,8 @@ public class SpecActivityImpl implements SpecActivity { private final LogConfigs logConfigs; private final AirbyteApiClient airbyteApiClient; private final String airbyteVersion; + private final AirbyteMessageSerDeProvider serDeProvider; + private final AirbyteMessageVersionedMigratorFactory migratorFactory; public SpecActivityImpl(@Named("specWorkerConfigs") final WorkerConfigs workerConfigs, @Named("specProcessFactory") final ProcessFactory processFactory, @@ -55,7 +62,9 @@ public SpecActivityImpl(@Named("specWorkerConfigs") final WorkerConfigs workerCo final WorkerEnvironment workerEnvironment, final LogConfigs logConfigs, final AirbyteApiClient airbyteApiClient, - @Value("${airbyte.version}") final String airbyteVersion) { + @Value("${airbyte.version}") final String airbyteVersion, + final AirbyteMessageSerDeProvider serDeProvider, + final AirbyteMessageVersionedMigratorFactory migratorFactory) { this.workerConfigs = workerConfigs; this.processFactory = processFactory; this.workspaceRoot = workspaceRoot; @@ -63,6 +72,8 @@ public SpecActivityImpl(@Named("specWorkerConfigs") final WorkerConfigs workerCo this.logConfigs = logConfigs; this.airbyteApiClient = airbyteApiClient; this.airbyteVersion = airbyteVersion; + this.serDeProvider = serDeProvider; + this.migratorFactory = migratorFactory; } @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @@ -92,6 +103,7 @@ public ConnectorJobOutput run(final JobRunConfig jobRunConfig, final Integration private CheckedSupplier, Exception> getWorkerFactory( final IntegrationLauncherConfig launcherConfig) { return () -> { + final AirbyteStreamFactory streamFactory = getStreamFactory(launcherConfig); final IntegrationLauncher integrationLauncher = new AirbyteIntegrationLauncher( launcherConfig.getJobId(), launcherConfig.getAttemptId().intValue(), @@ -99,8 +111,15 @@ private CheckedSupplier, Exception> processFactory, workerConfigs.getResourceRequirements()); - return new DefaultGetSpecWorker(integrationLauncher); + return new DefaultGetSpecWorker(integrationLauncher, streamFactory); }; } + private AirbyteStreamFactory getStreamFactory(final IntegrationLauncherConfig launcherConfig) { + final Version protocolVersion = + launcherConfig.getProtocolVersion() != null ? launcherConfig.getProtocolVersion() : migratorFactory.getMostRecentVersion(); + // Try to detect version from the stream + return new VersionedAirbyteStreamFactory<>(serDeProvider, migratorFactory, protocolVersion).withDetectVersion(true); + } + } From 0abc5f63737e574e8e1895fc0a2f277a7c56aa87 Mon Sep 17 00:00:00 2001 From: Parker Mossman Date: Fri, 21 Oct 2022 11:34:47 -0700 Subject: [PATCH 255/498] only rm file if it exists (#18310) --- tools/bin/load_test/cleanup_load_test.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/bin/load_test/cleanup_load_test.sh b/tools/bin/load_test/cleanup_load_test.sh index 49e339944bea..8394efddff48 100755 --- a/tools/bin/load_test/cleanup_load_test.sh +++ b/tools/bin/load_test/cleanup_load_test.sh @@ -92,7 +92,8 @@ function deleteConnections { removeFirstLineFromFile $CONNECTION_CLEANUP_FILE done - if ! test -s $CONNECTION_CLEANUP_FILE + # if file exists and is empty + if test -e $CONNECTION_CLEANUP_FILE && ! test -s $CONNECTION_CLEANUP_FILE then rm $CONNECTION_CLEANUP_FILE echo "removed cleanup file $CONNECTION_CLEANUP_FILE" @@ -110,7 +111,8 @@ function deleteSources { removeFirstLineFromFile $SOURCE_CLEANUP_FILE done - if ! test -s $SOURCE_CLEANUP_FILE + # if file exists and is empty + if test -e $SOURCE_CLEANUP_FILE && ! test -s $SOURCE_CLEANUP_FILE then rm $SOURCE_CLEANUP_FILE echo "removed cleanup file $SOURCE_CLEANUP_FILE" @@ -128,7 +130,8 @@ function deleteDestinations { removeFirstLineFromFile $DESTINATION_CLEANUP_FILE done - if test -z $DESTINATION_CLEANUP_FILE + # if file exists and is empty + if test -e $DESTINATION_CLEANUP_FILE && ! test -s $DESTINATION_CLEANUP_FILE then rm $DESTINATION_CLEANUP_FILE echo "removed cleanup file $DESTINATION_CLEANUP_FILE" From 0c5e6ba23f1009e283cb5fab21aeb75f4802393e Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 21 Oct 2022 13:35:25 -0500 Subject: [PATCH 256/498] remove extra check (#10554) * remove extra check * simplify logic --- .../workers/general/DefaultReplicationWorker.java | 6 ++++-- .../workers/internal/DefaultAirbyteDestination.java | 13 +++++-------- .../workers/internal/DefaultAirbyteSource.java | 12 +++++------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java index f2c67ccd9b3d..68f433a7072e 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java @@ -158,7 +158,8 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path // thrown final CompletableFuture destinationOutputThreadFuture = CompletableFuture.runAsync( getDestinationOutputRunnable(destination, cancelled, messageTracker, mdc, timeTracker), - executors).whenComplete((msg, ex) -> { + executors) + .whenComplete((msg, ex) -> { if (ex != null) { if (ex.getCause() instanceof DestinationException) { destinationRunnableFailureRef.set(FailureHelper.destinationFailure(ex, Long.valueOf(jobId), attempt)); @@ -170,7 +171,8 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path final CompletableFuture replicationThreadFuture = CompletableFuture.runAsync( getReplicationRunnable(source, destination, cancelled, mapper, messageTracker, mdc, recordSchemaValidator, metricReporter, timeTracker), - executors).whenComplete((msg, ex) -> { + executors) + .whenComplete((msg, ex) -> { if (ex != null) { if (ex.getCause() instanceof SourceException) { replicationRunnableFailureRef.set(FailureHelper.sourceFailure(ex, Long.valueOf(jobId), attempt)); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java index 87b1a7f79c11..b866d9dbf740 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java @@ -132,14 +132,11 @@ public void cancel() throws Exception { @Override public boolean isFinished() { Preconditions.checkState(destinationProcess != null); - // As this check is done on every message read, it is important for this operation to be efficient. - // Short circuit early to avoid checking the underlying process. - final var isEmpty = !messageIterator.hasNext(); // hasNext is blocking. - if (!isEmpty) { - return false; - } - - return !destinationProcess.isAlive(); + /* + * As this check is done on every message read, it is important for this operation to be efficient. + * Short circuit early to avoid checking the underlying process. Note: hasNext is blocking. + */ + return !messageIterator.hasNext() && !destinationProcess.isAlive(); } @Override diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java index 5e8415fc96d4..8a94d3a05599 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java @@ -88,14 +88,12 @@ public void start(final WorkerSourceConfig sourceConfig, final Path jobRoot) thr @Override public boolean isFinished() { Preconditions.checkState(sourceProcess != null); - // As this check is done on every message read, it is important for this operation to be efficient. - // Short circuit early to avoid checking the underlying process. - final var isEmpty = !messageIterator.hasNext(); // hasNext is blocking. - if (!isEmpty) { - return false; - } - return !sourceProcess.isAlive() && !messageIterator.hasNext(); + /* + * As this check is done on every message read, it is important for this operation to be efficient. + * Short circuit early to avoid checking the underlying process. note: hasNext is blocking. + */ + return !messageIterator.hasNext() && !sourceProcess.isAlive(); } @Override From e1c220a1d4f2474099fadc7d1565db36e34c5f15 Mon Sep 17 00:00:00 2001 From: Alexandre Girard Date: Fri, 21 Oct 2022 11:45:11 -0700 Subject: [PATCH 257/498] Google ads: Upgrade CDK to incorporate config error handling (#18309) * Bump version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-google-ads/Dockerfile | 2 +- .../connectors/source-google-ads/setup.py | 2 +- docs/integrations/sources/google-ads.md | 11 ++++++----- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 75aadbd817d0..b8f9c1193a40 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -392,7 +392,7 @@ - name: Google Ads sourceDefinitionId: 253487c0-2246-43ba-a21f-5116b20a2c50 dockerRepository: airbyte/source-google-ads - dockerImageTag: 0.2.1 + dockerImageTag: 0.2.2 documentationUrl: https://docs.airbyte.com/integrations/sources/google-ads icon: google-adwords.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 71503972f7cc..f872c4bad656 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3785,7 +3785,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-google-ads:0.2.1" +- dockerImage: "airbyte/source-google-ads:0.2.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-ads" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-google-ads/Dockerfile b/airbyte-integrations/connectors/source-google-ads/Dockerfile index ccb8d79694f3..977fe479843d 100644 --- a/airbyte-integrations/connectors/source-google-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-google-ads/Dockerfile @@ -13,5 +13,5 @@ COPY main.py ./ ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.1 +LABEL io.airbyte.version=0.2.2 LABEL io.airbyte.name=airbyte/source-google-ads diff --git a/airbyte-integrations/connectors/source-google-ads/setup.py b/airbyte-integrations/connectors/source-google-ads/setup.py index 5d271ee09994..fa702d2f0963 100644 --- a/airbyte-integrations/connectors/source-google-ads/setup.py +++ b/airbyte-integrations/connectors/source-google-ads/setup.py @@ -7,7 +7,7 @@ # pin protobuf==3.20.0 as other versions may cause problems on different architectures # (see https://github.com/airbytehq/airbyte/issues/13580) -MAIN_REQUIREMENTS = ["airbyte-cdk", "google-ads==17.0.0", "protobuf==3.20.0", "pendulum"] +MAIN_REQUIREMENTS = ["airbyte-cdk>=0.2.2", "google-ads==17.0.0", "protobuf==3.20.0", "pendulum"] TEST_REQUIREMENTS = ["pytest~=6.1", "pytest-mock", "freezegun", "requests-mock"] diff --git a/docs/integrations/sources/google-ads.md b/docs/integrations/sources/google-ads.md index 6acaa39a3d69..1863ce72c8a7 100644 --- a/docs/integrations/sources/google-ads.md +++ b/docs/integrations/sources/google-ads.md @@ -13,11 +13,11 @@ This page contains the setup guide and reference information for the Google Ads :::note You'll need to create a [Google Ads Manager account](https://ads.google.com/home/tools/manager-accounts/) since Google Ads accounts cannot generate a developer token. -::: +::: To set up the Google Ads source connector with Airbyte Open Source, you'll need a developer token. This token allows you to access your data from the Google Ads API. However, Google is selective about which software and use cases can get a developer token. The Airbyte team has worked with the Google Ads team to allowlist Airbyte and make sure you can get a developer token (see [issue 1981](https://github.com/airbytehq/airbyte/issues/1981) for more information). -Follow [Google's instructions](https://developers.google.com/google-ads/api/docs/first-call/dev-token) to apply for the token. Note that you will _not_ be able to access your data via the Google Ads API until this token is approved. You cannot use a test developer token; it has to be at least a basic developer token. It usually takes Google 24 hours to respond to these applications. +Follow [Google's instructions](https://developers.google.com/google-ads/api/docs/first-call/dev-token) to apply for the token. Note that you will _not_ be able to access your data via the Google Ads API until this token is approved. You cannot use a test developer token; it has to be at least a basic developer token. It usually takes Google 24 hours to respond to these applications. When you apply for a token, make sure to mention: @@ -42,7 +42,7 @@ To set up Google Ads as a source in Airbyte Cloud: 8. (Optional) Enter a custom [GAQL](#custom-query-understanding-google-ads-query-language) query. 9. (Optional) If the access to your account is through a [Google Ads Manager account](https://ads.google.com/home/tools/manager-accounts/), enter the [**Login Customer ID for Managed Accounts**](https://developers.google.com/google-ads/api/docs/concepts/call-structure#cid) of the Google Ads Manager account. 10. (Optional) Enter a [**Conversion Window**](https://support.google.com/google-ads/answer/3123169?hl=en). -11. (Optional) Enter the **End Date** in YYYY-MM-DD format. The data added after this date will not be replicated. +11. (Optional) Enter the **End Date** in YYYY-MM-DD format. The data added after this date will not be replicated. 12. Click **Set up source**. #### For Airbyte Open Source @@ -60,7 +60,7 @@ To set up Google Ads as a source in Airbyte Open Source: 9. (Optional) Enter a custom [GAQL](#custom-query-understanding-google-ads-query-language) query. 10. (Optional) If the access to your account is through a [Google Ads Manager account](https://ads.google.com/home/tools/manager-accounts/), enter the [**Login Customer ID for Managed Accounts**](https://developers.google.com/google-ads/api/docs/concepts/call-structure#cid) of the Google Ads Manager account. 11. (Optional) Enter a [**Conversion Window**](https://support.google.com/google-ads/answer/3123169?hl=en). -12. (Optional) Enter the **End Date** in YYYY-MM-DD format. The data added after this date will not be replicated. +12. (Optional) Enter the **End Date** in YYYY-MM-DD format. The data added after this date will not be replicated. 13. Click **Set up source**. ## Supported sync modes @@ -118,12 +118,13 @@ Follow Google's guidance on [Selectability between segments and metrics](https:/ This source is constrained by the [Google Ads API limits](https://developers.google.com/google-ads/api/docs/best-practices/quotas) -Due to a limitation in the Google Ads API which does not allow getting performance data at a granularity level smaller than a day, the Google Ads connector usually pulls data up until the previous day. For example, if the sync runs on Wednesday at 5 PM, then data up until Tuesday midnight is pulled. Data for Wednesday is exported only if a sync runs after Wednesday (for example, 12:01 AM on Thursday) and so on. This avoids syncing partial performance data, only to have to resync it again once the full day's data has been recorded by Google. For example, without this functionality, a sync which runs on Wednesday at 5 PM would get ads performance data for Wednesday between 12:01 AM - 5 PM on Wednesday, then it would need to run again at the end of the day to get all of Wednesday's data. +Due to a limitation in the Google Ads API which does not allow getting performance data at a granularity level smaller than a day, the Google Ads connector usually pulls data up until the previous day. For example, if the sync runs on Wednesday at 5 PM, then data up until Tuesday midnight is pulled. Data for Wednesday is exported only if a sync runs after Wednesday (for example, 12:01 AM on Thursday) and so on. This avoids syncing partial performance data, only to have to resync it again once the full day's data has been recorded by Google. For example, without this functionality, a sync which runs on Wednesday at 5 PM would get ads performance data for Wednesday between 12:01 AM - 5 PM on Wednesday, then it would need to run again at the end of the day to get all of Wednesday's data. ## Changelog | Version | Date | Pull Request | Subject | |:---------|:-----------|:---------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------| +| `0.2.2` | 2022-10-21 | [17412](https://github.com/airbytehq/airbyte/pull/17412) | Release with CDK >= 0.2.2 | | `0.2.1` | 2022-09-29 | [17412](https://github.com/airbytehq/airbyte/pull/17412) | Always use latest CDK version | | `0.2.0` | 2022-08-23 | [15858](https://github.com/airbytehq/airbyte/pull/15858) | Mark the `query` and `table_name` fields in `custom_queries` as required | | `0.1.44` | 2022-07-27 | [15084](https://github.com/airbytehq/airbyte/pull/15084) | Fix data type `ad_group_criterion.topic.path` in `display_topics_performance_report` and shifted `campaigns` to non-managers streams | From ccdbbfca6fb6895dde5da41909561f5f0f278c39 Mon Sep 17 00:00:00 2001 From: midavadim Date: Fri, 21 Oct 2022 22:25:53 +0300 Subject: [PATCH 258/498] :tada: Source Linkedin Ads - for adDirectSponsoredContents stream skip accounts which are not part of organization (#18111) * for adDirectSponsoredContents stream skip accounts which are part of organization * updated PR number * updated log message, added unit tests * additional formatting * updated connector version in source_definitions.yaml * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-linkedin-ads/Dockerfile | 2 +- .../source_linkedin_ads/source.py | 18 ++++++++++++++++++ .../unit_tests/source_tests/test_source.py | 13 +++++++++++++ docs/integrations/sources/linkedin-ads.md | 1 + 6 files changed, 35 insertions(+), 3 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index b8f9c1193a40..b8883ed27b28 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -594,7 +594,7 @@ - name: LinkedIn Ads sourceDefinitionId: 137ece28-5434-455c-8f34-69dc3782f451 dockerRepository: airbyte/source-linkedin-ads - dockerImageTag: 0.1.11 + dockerImageTag: 0.1.12 documentationUrl: https://docs.airbyte.com/integrations/sources/linkedin-ads icon: linkedin.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index f872c4bad656..2d842b06fcab 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -5864,7 +5864,7 @@ path_in_connector_config: - "credentials" - "client_secret" -- dockerImage: "airbyte/source-linkedin-ads:0.1.11" +- dockerImage: "airbyte/source-linkedin-ads:0.1.12" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/linkedin-ads" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile b/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile index 068b60e44cce..d0bfa957d1b1 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-linkedin-ads/Dockerfile @@ -33,5 +33,5 @@ COPY source_linkedin_ads ./source_linkedin_ads ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.11 +LABEL io.airbyte.version=0.1.12 LABEL io.airbyte.name=airbyte/source-linkedin-ads diff --git a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py index 0274264e6337..a4a3bcb5a692 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/source_linkedin_ads/source.py @@ -254,6 +254,24 @@ def request_params(self, stream_state: Mapping[str, Any], stream_slice: Mapping[ params["q"] = self.search_param return params + def read_records( + self, stream_state: Mapping[str, Any] = None, stream_slice: Optional[Mapping[str, Any]] = None, **kwargs + ) -> Iterable[Mapping[str, Any]]: + stream_state = stream_state or {} + parent_stream = self.parent_stream(config=self.config) + for record in parent_stream.read_records(**kwargs): + + if record.get("reference", "").startswith("urn:li:person"): + self.logger.warn( + f'Skip {record.get("name")} account, ORGANIZATION permissions required, but referenced to PERSON {record.get("reference")}' + ) + continue + + child_stream_slice = super(LinkedInAdsStreamSlicing, self).read_records( + stream_slice=get_parent_stream_values(record, self.parent_values_map), **kwargs + ) + yield from self.filter_records_newer_than_state(stream_state=stream_state, records_slice=child_stream_slice) + class LinkedInAdsAnalyticsStream(IncrementalLinkedinAdsStream): """ diff --git a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/source_tests/test_source.py b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/source_tests/test_source.py index 2f666c1605c3..e01aa865e85e 100644 --- a/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/source_tests/test_source.py +++ b/airbyte-integrations/connectors/source-linkedin-ads/unit_tests/source_tests/test_source.py @@ -177,6 +177,19 @@ def test_request_headers(self): assert result == expected +class TestAccountUsers: + stream: AccountUsers = AccountUsers(TEST_CONFIG) + + def test_state_checkpoint_interval(self): + assert self.stream.state_checkpoint_interval == 500 + + def test_get_updated_state(self): + state = self.stream.get_updated_state( + current_stream_state={"lastModified": "2021-01-01"}, latest_record={"lastModified": "2021-08-01"} + ) + assert state == {"lastModified": "2021-08-01"} + + class TestLinkedInAdsStreamSlicing: @pytest.mark.parametrize( "stream_cls, slice, expected", diff --git a/docs/integrations/sources/linkedin-ads.md b/docs/integrations/sources/linkedin-ads.md index b044c4138fb5..30ce727ef6c9 100644 --- a/docs/integrations/sources/linkedin-ads.md +++ b/docs/integrations/sources/linkedin-ads.md @@ -182,6 +182,7 @@ After 5 unsuccessful attempts - the connector will stop the sync operation. In s | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------| +| 0.1.12 | 2022-10-18 | [18111](https://github.com/airbytehq/airbyte/pull/18111) | for adDirectSponsoredContents stream skip accounts which are part of organization | | 0.1.11 | 2022-10-07 | [17724](https://github.com/airbytehq/airbyte/pull/17724) | Retry 429/5xx errors when refreshing access token | | 0.1.10 | 2022-09-28 | [17326](https://github.com/airbytehq/airbyte/pull/17326) | Migrate to per-stream states. | | 0.1.9 | 2022-07-21 | [14924](https://github.com/airbytehq/airbyte/pull/14924) | Remove `additionalProperties` field from schemas | From 5082ebfe2abe6e3f0d1b07ee487bfa993bb8d38b Mon Sep 17 00:00:00 2001 From: Anne <102554163+alovew@users.noreply.github.com> Date: Fri, 21 Oct 2022 13:08:03 -0700 Subject: [PATCH 259/498] Add SchemaChange to WebBackendConnectionRead object (#17969) * add schemaChange field to WebBackendConnectionRead and breakingChange to ConnectionRead --- airbyte-api/src/main/openapi/config.yaml | 12 ++ .../main/resources/types/StandardSync.yaml | 3 + .../config/persistence/ConfigRepository.java | 37 ++-- .../DatabaseConfigPersistence.java | 2 + .../config/persistence/DbConverter.java | 8 + .../ConfigRepositoryE2EReadWriteTest.java | 52 +++++ .../airbyte/config/persistence/MockData.java | 34 +++- .../server/converters/ApiPojoConverters.java | 1 + .../server/handlers/ConnectionsHandler.java | 3 +- .../WebBackendConnectionsHandler.java | 48 ++++- .../handlers/helpers/ConnectionMatcher.java | 1 + .../handlers/ConnectionsHandlerTest.java | 56 ++---- .../WebBackendConnectionsHandlerTest.java | 180 ++++++++++++------ .../server/helpers/ConnectionHelpers.java | 21 +- .../test-utils/mock-data/mockConnection.json | 3 +- airbyte-webapp/src/test-utils/testutils.tsx | 1 + .../api/generated-api-html/index.html | 18 ++ .../examples/airbyte.local/openapi.yaml | 7 + 18 files changed, 344 insertions(+), 143 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index 73b65e2634a1..143987726ade 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -3397,6 +3397,7 @@ components: - destinationId - syncCatalog - status + - breakingChange properties: connectionId: $ref: "#/components/schemas/ConnectionId" @@ -3437,6 +3438,14 @@ components: format: uuid geography: $ref: "#/components/schemas/Geography" + breakingChange: + type: boolean + SchemaChange: + enum: + - no_change + - non_breaking + - breaking + type: string ConnectionSearch: type: object properties: @@ -4677,6 +4686,7 @@ components: - source - destination - isSyncing + - schemaChange properties: connectionId: $ref: "#/components/schemas/ConnectionId" @@ -4733,6 +4743,8 @@ components: $ref: "#/components/schemas/CatalogDiff" geography: $ref: "#/components/schemas/Geography" + schemaChange: + $ref: "#/components/schemas/SchemaChange" WebBackendConnectionReadList: type: object required: diff --git a/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml b/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml index 276428f8e112..979144028714 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml @@ -12,6 +12,7 @@ required: - manual - namespaceDefinition - geography + - breakingChange additionalProperties: false properties: namespaceDefinition: @@ -117,3 +118,5 @@ properties: "$ref": ResourceRequirements.yaml geography: "$ref": Geography.yaml + breakingChange: + type: boolean diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 771e902b7492..3fb6c2b23b3c 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -29,6 +29,7 @@ import io.airbyte.commons.lang.MoreBooleans; import io.airbyte.commons.version.AirbyteProtocolVersion; import io.airbyte.config.ActorCatalog; +import io.airbyte.config.ActorCatalogFetchEvent; import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; @@ -143,10 +144,7 @@ public Optional getWorkspaceBySlugOptional(final String slug, .where(WORKSPACE.SLUG.eq(slug)).andNot(WORKSPACE.TOMBSTONE)).fetch(); } - if (result.size() == 0) { - return Optional.empty(); - } - return Optional.of(DbConverter.buildStandardWorkspace(result.get(0))); + return result.stream().findFirst().map(DbConverter::buildStandardWorkspace); } public StandardWorkspace getWorkspaceBySlug(final String slug, final boolean includeTombstone) @@ -788,10 +786,7 @@ public Optional getSourceOAuthParamByDefinitionIdOptional( ACTOR_OAUTH_PARAMETER.ACTOR_DEFINITION_ID.eq(sourceDefinitionId)).fetch(); }); - if (result.size() == 0) { - return Optional.empty(); - } - return Optional.of(DbConverter.buildSourceOAuthParameter(result.get(0))); + return result.stream().findFirst().map(DbConverter::buildSourceOAuthParameter); } public void writeSourceOAuthParam(final SourceOAuthParameter sourceOAuthParameter) throws JsonValidationException, IOException { @@ -817,10 +812,7 @@ public Optional getDestinationOAuthParamByDefinitionI ACTOR_OAUTH_PARAMETER.ACTOR_DEFINITION_ID.eq(destinationDefinitionId)).fetch(); }); - if (result.size() == 0) { - return Optional.empty(); - } - return Optional.of(DbConverter.buildDestinationOAuthParameter(result.get(0))); + return result.stream().findFirst().map(DbConverter::buildDestinationOAuthParameter); } public void writeDestinationOAuthParam(final DestinationOAuthParameter destinationOAuthParameter) throws JsonValidationException, IOException { @@ -917,6 +909,7 @@ public ActorCatalog getActorCatalogById(final UUID actorCatalogId) throws IOException, ConfigNotFoundException { final Result result = database.query(ctx -> ctx.select(ACTOR_CATALOG.asterisk()) .from(ACTOR_CATALOG).where(ACTOR_CATALOG.ID.eq(actorCatalogId))).fetch(); + if (result.size() > 0) { return DbConverter.buildActorCatalog(result.get(0)); } @@ -934,8 +927,8 @@ public ActorCatalog getActorCatalogById(final UUID actorCatalogId) * @return the db identifier for the cached catalog. */ private UUID getOrInsertActorCatalog(final AirbyteCatalog airbyteCatalog, - final DSLContext context) { - final OffsetDateTime timestamp = OffsetDateTime.now(); + final DSLContext context, + final OffsetDateTime timestamp) { final HashFunction hashFunction = Hashing.murmur3_32_fixed(); final String catalogHash = hashFunction.hashBytes(Jsons.serialize(airbyteCatalog).getBytes( Charsets.UTF_8)).toString(); @@ -969,11 +962,17 @@ public Optional getActorCatalog(final UUID actorId, .and(ACTOR_CATALOG_FETCH_EVENT.CONFIG_HASH.eq(configHash)) .orderBy(ACTOR_CATALOG_FETCH_EVENT.CREATED_AT.desc()).limit(1)).fetch(); - if (records.size() >= 1) { - return Optional.of(DbConverter.buildActorCatalog(records.get(0))); - } - return Optional.empty(); + return records.stream().findFirst().map(DbConverter::buildActorCatalog); + } + + public Optional getMostRecentActorCatalogFetchEventForSource(final UUID sourceId) throws IOException { + + final Result records = database.query(ctx -> ctx.select(ACTOR_CATALOG_FETCH_EVENT.asterisk()) + .from(ACTOR_CATALOG_FETCH_EVENT) + .where(ACTOR_CATALOG_FETCH_EVENT.ACTOR_ID.eq(sourceId)) + .orderBy(ACTOR_CATALOG_FETCH_EVENT.CREATED_AT.desc()).limit(1).fetch()); + return records.stream().findFirst().map(DbConverter::buildActorCatalogFetchEvent); } /** @@ -1001,7 +1000,7 @@ public UUID writeActorCatalogFetchEvent(final AirbyteCatalog catalog, final OffsetDateTime timestamp = OffsetDateTime.now(); final UUID fetchEventID = UUID.randomUUID(); return database.transaction(ctx -> { - final UUID catalogId = getOrInsertActorCatalog(catalog, ctx); + final UUID catalogId = getOrInsertActorCatalog(catalog, ctx, timestamp); ctx.insertInto(ACTOR_CATALOG_FETCH_EVENT) .set(ACTOR_CATALOG_FETCH_EVENT.ID, fetchEventID) .set(ACTOR_CATALOG_FETCH_EVENT.ACTOR_ID, actorId) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index 11014b91b022..7083412db7f7 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -1104,6 +1104,7 @@ private void writeStandardSync(final List configs, final DSLContex JSONB.valueOf(Jsons.serialize(standardSync.getResourceRequirements()))) .set(CONNECTION.UPDATED_AT, timestamp) .set(CONNECTION.SOURCE_CATALOG_ID, standardSync.getSourceCatalogId()) + .set(CONNECTION.BREAKING_CHANGE, standardSync.getBreakingChange()) .set(CONNECTION.GEOGRAPHY, Enums.toEnum(standardSync.getGeography().value(), io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType.class).orElseThrow()) .where(CONNECTION.ID.eq(standardSync.getConnectionId())) @@ -1148,6 +1149,7 @@ private void writeStandardSync(final List configs, final DSLContex .set(CONNECTION.SOURCE_CATALOG_ID, standardSync.getSourceCatalogId()) .set(CONNECTION.GEOGRAPHY, Enums.toEnum(standardSync.getGeography().value(), io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType.class).orElseThrow()) + .set(CONNECTION.BREAKING_CHANGE, standardSync.getBreakingChange()) .set(CONNECTION.CREATED_AT, timestamp) .set(CONNECTION.UPDATED_AT, timestamp) .execute(); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index 20426a93734e..6bdc881bc269 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -6,6 +6,7 @@ import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_CATALOG; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_CATALOG_FETCH_EVENT; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_OAUTH_PARAMETER; import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; @@ -15,6 +16,7 @@ import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; import io.airbyte.config.ActorCatalog; +import io.airbyte.config.ActorCatalogFetchEvent; import io.airbyte.config.ActorDefinitionResourceRequirements; import io.airbyte.config.DestinationConnection; import io.airbyte.config.DestinationOAuthParameter; @@ -74,6 +76,7 @@ public static StandardSync buildStandardSync(final Record record, final List fetchEvents = MockData.actorCatalogFetchEventsSameSource(); + ActorCatalogFetchEvent fetchEvent1 = fetchEvents.get(0); + ActorCatalogFetchEvent fetchEvent2 = fetchEvents.get(1); + + database.transaction(ctx -> { + insertCatalogFetchEvent( + ctx, + fetchEvent1.getActorId(), + fetchEvent1.getActorCatalogId(), + yesterday); + insertCatalogFetchEvent( + ctx, + fetchEvent2.getActorId(), + fetchEvent2.getActorCatalogId(), + now); + + return null; + }); + + Optional result = + configRepository.getMostRecentActorCatalogFetchEventForSource(fetchEvent1.getActorId()); + + assertEquals(fetchEvent2.getActorCatalogId(), result.get().getActorCatalogId()); + } + + private void insertCatalogFetchEvent(DSLContext ctx, UUID sourceId, UUID catalogId, OffsetDateTime creationDate) { + ctx.insertInto(ACTOR_CATALOG_FETCH_EVENT) + .columns( + ACTOR_CATALOG_FETCH_EVENT.ID, + ACTOR_CATALOG_FETCH_EVENT.ACTOR_ID, + ACTOR_CATALOG_FETCH_EVENT.ACTOR_CATALOG_ID, + ACTOR_CATALOG_FETCH_EVENT.CONFIG_HASH, + ACTOR_CATALOG_FETCH_EVENT.ACTOR_VERSION, + ACTOR_CATALOG_FETCH_EVENT.CREATED_AT, + ACTOR_CATALOG_FETCH_EVENT.MODIFIED_AT) + .values(UUID.randomUUID(), sourceId, catalogId, "", "", creationDate, creationDate) + .execute(); + } + } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java index 06716e1a0b88..dddd705c30e3 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java @@ -475,7 +475,8 @@ public static List standardSyncs() { .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) .withSchedule(schedule) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); final StandardSync standardSync2 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) @@ -491,7 +492,8 @@ public static List standardSyncs() { .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) .withSchedule(schedule) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); final StandardSync standardSync3 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) @@ -507,7 +509,8 @@ public static List standardSyncs() { .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) .withSchedule(schedule) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); final StandardSync standardSync4 = new StandardSync() .withOperationIds(Collections.emptyList()) @@ -523,7 +526,8 @@ public static List standardSyncs() { .withResourceRequirements(resourceRequirements) .withStatus(Status.DEPRECATED) .withSchedule(schedule) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); final StandardSync standardSync5 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_3)) @@ -539,7 +543,8 @@ public static List standardSyncs() { .withResourceRequirements(resourceRequirements) .withStatus(Status.ACTIVE) .withSchedule(schedule) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); final StandardSync standardSync6 = new StandardSync() .withOperationIds(Arrays.asList()) @@ -555,7 +560,8 @@ public static List standardSyncs() { .withResourceRequirements(resourceRequirements) .withStatus(Status.DEPRECATED) .withSchedule(schedule) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); return Arrays.asList(standardSync1, standardSync2, standardSync3, standardSync4, standardSync5, standardSync6); } @@ -621,6 +627,22 @@ public static List actorCatalogFetchEvents() { return Arrays.asList(actorCatalogFetchEvent1, actorCatalogFetchEvent2); } + public static List actorCatalogFetchEventsSameSource() { + final ActorCatalogFetchEvent actorCatalogFetchEvent1 = new ActorCatalogFetchEvent() + .withId(ACTOR_CATALOG_FETCH_EVENT_ID_1) + .withActorCatalogId(ACTOR_CATALOG_ID_1) + .withActorId(SOURCE_ID_1) + .withConfigHash("CONFIG_HASH") + .withConnectorVersion("1.0.0"); + final ActorCatalogFetchEvent actorCatalogFetchEvent2 = new ActorCatalogFetchEvent() + .withId(ACTOR_CATALOG_FETCH_EVENT_ID_2) + .withActorCatalogId(ACTOR_CATALOG_ID_2) + .withActorId(SOURCE_ID_1) + .withConfigHash("1394") + .withConnectorVersion("1.2.0"); + return Arrays.asList(actorCatalogFetchEvent1, actorCatalogFetchEvent2); + } + public static List workspaceServiceAccounts() { final WorkspaceServiceAccount workspaceServiceAccount = new WorkspaceServiceAccount() .withWorkspaceId(WORKSPACE_ID_1) diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java b/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java index e3f6b3d81282..9583d790c1d9 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java @@ -93,6 +93,7 @@ public static ConnectionRead internalToConnectionRead(final StandardSync standar .prefix(standardSync.getPrefix()) .syncCatalog(CatalogConverter.toApi(standardSync.getCatalog())) .sourceCatalogId(standardSync.getSourceCatalogId()) + .breakingChange(standardSync.getBreakingChange()) .geography(Enums.convertTo(standardSync.getGeography(), Geography.class)); if (standardSync.getResourceRequirements() != null) { diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java index 6472de841091..3e79d8bcb33b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/ConnectionsHandler.java @@ -141,7 +141,8 @@ public ConnectionRead createConnection(final ConnectionCreate connectionCreate) .withOperationIds(operationIds) .withStatus(ApiPojoConverters.toPersistenceStatus(connectionCreate.getStatus())) .withSourceCatalogId(connectionCreate.getSourceCatalogId()) - .withGeography(getGeographyFromConnectionCreateOrWorkspace(connectionCreate)); + .withGeography(getGeographyFromConnectionCreateOrWorkspace(connectionCreate)) + .withBreakingChange(false); if (connectionCreate.getResourceRequirements() != null) { standardSync.withResourceRequirements(ApiPojoConverters.resourceRequirementsToInternal(connectionCreate.getResourceRequirements())); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index c2b95dcd7957..60d980b09847 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -24,6 +24,7 @@ import io.airbyte.api.model.generated.OperationCreate; import io.airbyte.api.model.generated.OperationReadList; import io.airbyte.api.model.generated.OperationUpdate; +import io.airbyte.api.model.generated.SchemaChange; import io.airbyte.api.model.generated.SourceDiscoverSchemaRead; import io.airbyte.api.model.generated.SourceDiscoverSchemaRequestBody; import io.airbyte.api.model.generated.SourceIdRequestBody; @@ -43,6 +44,7 @@ import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.MoreBooleans; +import io.airbyte.config.ActorCatalogFetchEvent; import io.airbyte.config.StandardSync; import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigRepository; @@ -155,7 +157,7 @@ private Map getDestinationReadById(final List desti return destinationReads.stream().collect(Collectors.toMap(DestinationRead::getDestinationId, Function.identity())); } - private WebBackendConnectionRead buildWebBackendConnectionRead(final ConnectionRead connectionRead) + private WebBackendConnectionRead buildWebBackendConnectionRead(final ConnectionRead connectionRead, final Optional currentSourceCatalogId) throws ConfigNotFoundException, IOException, JsonValidationException { final SourceRead source = getSourceRead(connectionRead.getSourceId()); final DestinationRead destination = getDestinationRead(connectionRead.getDestinationId()); @@ -173,9 +175,40 @@ private WebBackendConnectionRead buildWebBackendConnectionRead(final ConnectionR webBackendConnectionRead.setLatestSyncJobStatus(job.getStatus()); }); + SchemaChange schemaChange = getSchemaChange(connectionRead, currentSourceCatalogId); + + webBackendConnectionRead.setSchemaChange(schemaChange); + return webBackendConnectionRead; } + /* + * A breakingChange boolean is stored on the connectionRead object and corresponds to the boolean + * breakingChange field on the connection table. If there is not a breaking change, we still have to + * check whether there is a non-breaking schema change by fetching the most recent + * ActorCatalogFetchEvent. A new ActorCatalogFetchEvent is stored each time there is a source schema + * refresh, so if the most recent ActorCatalogFetchEvent has a different actor catalog than the + * existing actor catalog, there is a schema change. + */ + private SchemaChange getSchemaChange(ConnectionRead connectionRead, Optional currentSourceCatalogId) throws IOException { + SchemaChange schemaChange = SchemaChange.NO_CHANGE; + + if (connectionRead.getBreakingChange()) { + schemaChange = SchemaChange.BREAKING; + } else if (currentSourceCatalogId.isPresent()) { + final Optional mostRecentFetchEvent = + configRepository.getMostRecentActorCatalogFetchEventForSource(connectionRead.getSourceId()); + + if (mostRecentFetchEvent.isPresent()) { + if (!mostRecentFetchEvent.get().getActorCatalogId().equals(currentSourceCatalogId.get())) { + schemaChange = SchemaChange.NON_BREAKING; + } + } + } + + return schemaChange; + } + private WebBackendConnectionListItem buildWebBackendConnectionListItem( final StandardSync standardSync, final Map sourceReadById, @@ -259,6 +292,7 @@ public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnecti .connectionId(webBackendConnectionRequestBody.getConnectionId()); final ConnectionRead connection = connectionsHandler.getConnection(connectionIdRequestBody.getConnectionId()); + /* * This variable contains all configuration but will be missing streams that were not selected. */ @@ -283,8 +317,9 @@ public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnecti final CatalogDiff diff; final AirbyteCatalog syncCatalog; + final Optional currentSourceCatalogId = Optional.ofNullable(connection.getSourceCatalogId()); if (refreshedCatalog.isPresent()) { - connection.setSourceCatalogId(refreshedCatalog.get().getCatalogId()); + connection.sourceCatalogId(refreshedCatalog.get().getCatalogId()); /* * constructs a full picture of all existing configured + all new / updated streams in the newest * catalog. @@ -300,6 +335,7 @@ public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnecti */ diff = connectionsHandler.getDiff(catalogUsedToMakeConfiguredCatalog.orElse(configuredCatalog), refreshedCatalog.get().getCatalog(), CatalogConverter.toProtocol(configuredCatalog)); + } else if (catalogUsedToMakeConfiguredCatalog.isPresent()) { // reconstructs a full picture of the full schema at the time the catalog was configured. syncCatalog = updateSchemaWithDiscovery(configuredCatalog, catalogUsedToMakeConfiguredCatalog.get()); @@ -313,7 +349,7 @@ public WebBackendConnectionRead webBackendGetConnection(final WebBackendConnecti } connection.setSyncCatalog(syncCatalog); - return buildWebBackendConnectionRead(connection).catalogDiff(diff); + return buildWebBackendConnectionRead(connection, currentSourceCatalogId).catalogDiff(diff); } private Optional getRefreshedSchema(final UUID sourceId) @@ -394,7 +430,8 @@ public WebBackendConnectionRead webBackendCreateConnection(final WebBackendConne final List operationIds = createOperations(webBackendConnectionCreate); final ConnectionCreate connectionCreate = toConnectionCreate(webBackendConnectionCreate, operationIds); - return buildWebBackendConnectionRead(connectionsHandler.createConnection(connectionCreate)); + final Optional currentSourceCatalogId = Optional.ofNullable(connectionCreate.getSourceCatalogId()); + return buildWebBackendConnectionRead(connectionsHandler.createConnection(connectionCreate), currentSourceCatalogId); } /** @@ -441,7 +478,8 @@ public WebBackendConnectionRead webBackendUpdateConnection(final WebBackendConne connectionRead.setSyncCatalog(syncCatalog); } - return buildWebBackendConnectionRead(connectionRead); + final Optional currentSourceCatalogId = Optional.ofNullable(connectionRead.getSourceCatalogId()); + return buildWebBackendConnectionRead(connectionRead, currentSourceCatalogId); } /** diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java index c98f628c12ef..2afaf63aec8b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/helpers/ConnectionMatcher.java @@ -44,6 +44,7 @@ public ConnectionRead match(final ConnectionRead query) { fromSearch.operationIds(query.getOperationIds()); fromSearch.sourceCatalogId(query.getSourceCatalogId()); fromSearch.geography(query.getGeography()); + fromSearch.breakingChange(query.getBreakingChange()); return fromSearch; } diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java index b3bb02c8b986..778cc821ed49 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/ConnectionsHandlerTest.java @@ -150,7 +150,8 @@ void setUp() throws IOException, JsonValidationException, ConfigNotFoundExceptio .withScheduleData(ConnectionHelpers.generateBasicScheduleData()) .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS) .withSourceCatalogId(UUID.randomUUID()) - .withGeography(Geography.AUTO); + .withGeography(Geography.AUTO) + .withBreakingChange(false); standardSyncDeleted = new StandardSync() .withConnectionId(connectionId) .withName("presto to hudi2") @@ -382,13 +383,7 @@ void testUpdateConnectionPatchSingleField() throws Exception { .connectionId(standardSync.getConnectionId()) .name("newName"); - final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead( - standardSync.getConnectionId(), - standardSync.getSourceId(), - standardSync.getDestinationId(), - standardSync.getOperationIds(), - standardSync.getSourceCatalogId(), - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync) .name("newName"); final StandardSync expectedPersistedSync = Jsons.clone(standardSync).withName("newName"); @@ -407,13 +402,7 @@ void testUpdateConnectionPatchScheduleToManual() throws Exception { .connectionId(standardSync.getConnectionId()) .scheduleType(ConnectionScheduleType.MANUAL); - final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead( - standardSync.getConnectionId(), - standardSync.getSourceId(), - standardSync.getDestinationId(), - standardSync.getOperationIds(), - standardSync.getSourceCatalogId(), - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync) .schedule(null) .scheduleType(ConnectionScheduleType.MANUAL) .scheduleData(null); @@ -444,13 +433,7 @@ void testUpdateConnectionPatchScheduleToCron() throws Exception { .scheduleType(ConnectionScheduleType.CRON) .scheduleData(cronScheduleData); - final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead( - standardSync.getConnectionId(), - standardSync.getSourceId(), - standardSync.getDestinationId(), - standardSync.getOperationIds(), - standardSync.getSourceCatalogId(), - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync) .schedule(null) .scheduleType(ConnectionScheduleType.CRON) .scheduleData(cronScheduleData); @@ -481,13 +464,7 @@ void testUpdateConnectionPatchBasicSchedule() throws Exception { .scheduleType(ConnectionScheduleType.BASIC) // update route requires this to be set even if it isn't changing .scheduleData(newScheduleData); - final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead( - standardSync.getConnectionId(), - standardSync.getSourceId(), - standardSync.getDestinationId(), - standardSync.getOperationIds(), - standardSync.getSourceCatalogId(), - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync) .schedule(new ConnectionSchedule().timeUnit(ConnectionSchedule.TimeUnitEnum.DAYS).units(10L)) // still dual-writing to legacy field .scheduleType(ConnectionScheduleType.BASIC) .scheduleData(newScheduleData); @@ -531,13 +508,7 @@ void testUpdateConnectionPatchAddingNewStream() throws Exception { .connectionId(standardSync.getConnectionId()) .syncCatalog(catalogForUpdate); - final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead( - standardSync.getConnectionId(), - standardSync.getSourceId(), - standardSync.getDestinationId(), - standardSync.getOperationIds(), - standardSync.getSourceCatalogId(), - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync) .syncCatalog(catalogForUpdate); final StandardSync expectedPersistedSync = Jsons.clone(standardSync) @@ -574,13 +545,7 @@ void testUpdateConnectionPatchEditExistingStreamWhileAddingNewStream() throws Ex .connectionId(standardSync.getConnectionId()) .syncCatalog(catalogForUpdate); - final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead( - standardSync.getConnectionId(), - standardSync.getSourceId(), - standardSync.getDestinationId(), - standardSync.getOperationIds(), - standardSync.getSourceCatalogId(), - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + final ConnectionRead expectedRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync) .syncCatalog(catalogForUpdate); final StandardSync expectedPersistedSync = Jsons.clone(standardSync) @@ -651,7 +616,7 @@ void testUpdateConnectionPatchingSeveralFieldsAndReplaceAStream() throws JsonVal standardSync.getDestinationId(), standardSync.getOperationIds(), newSourceCatalogId, - ApiPojoConverters.toApiGeography(standardSync.getGeography())) + ApiPojoConverters.toApiGeography(standardSync.getGeography()), false) .status(ConnectionStatus.INACTIVE) .scheduleType(ConnectionScheduleType.MANUAL) .scheduleData(null) @@ -745,7 +710,8 @@ void testSearchConnections() throws JsonValidationException, ConfigNotFoundExcep .withOperationIds(List.of(operationId)) .withManual(true) .withResourceRequirements(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS) - .withGeography(Geography.US); + .withGeography(Geography.US) + .withBreakingChange(false); final ConnectionRead connectionRead2 = ConnectionHelpers.connectionReadFromStandardSync(standardSync2); final StandardSourceDefinition sourceDefinition = new StandardSourceDefinition() .withName(SOURCE_TEST) diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index 72754b65c631..9efd1ca4d6e8 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -47,6 +47,7 @@ import io.airbyte.api.model.generated.OperationReadList; import io.airbyte.api.model.generated.OperationUpdate; import io.airbyte.api.model.generated.ResourceRequirements; +import io.airbyte.api.model.generated.SchemaChange; import io.airbyte.api.model.generated.SourceDiscoverSchemaRead; import io.airbyte.api.model.generated.SourceDiscoverSchemaRequestBody; import io.airbyte.api.model.generated.SourceIdRequestBody; @@ -67,6 +68,8 @@ import io.airbyte.api.model.generated.WorkspaceIdRequestBody; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.temporal.TemporalClient.ManualOperationResult; +import io.airbyte.config.ActorCatalog; +import io.airbyte.config.ActorCatalogFetchEvent; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; @@ -112,10 +115,14 @@ class WebBackendConnectionsHandlerTest { private SourceRead sourceRead; private ConnectionRead connectionRead; + private ConnectionRead brokenConnectionRead; private WebBackendConnectionListItem expectedListItem; private OperationReadList operationReadList; + private OperationReadList brokenOperationReadList; private WebBackendConnectionRead expected; private WebBackendConnectionRead expectedWithNewSchema; + private WebBackendConnectionRead expectedWithNewSchemaBroken; + private WebBackendConnectionRead expectedNoDiscoveryWithNewSchema; private EventRunner eventRunner; private ConfigRepository configRepository; @@ -163,7 +170,10 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio final DestinationConnection destination = DestinationHelpers.generateDestination(destinationDefinition.getDestinationDefinitionId()); final DestinationRead destinationRead = DestinationHelpers.getDestinationRead(destination, destinationDefinition); - final StandardSync standardSync = ConnectionHelpers.generateSyncWithSourceAndDestinationId(source.getSourceId(), destination.getDestinationId()); + final StandardSync standardSync = + ConnectionHelpers.generateSyncWithSourceAndDestinationId(source.getSourceId(), destination.getDestinationId(), false); + final StandardSync brokenStandardSync = + ConnectionHelpers.generateSyncWithSourceAndDestinationId(source.getSourceId(), destination.getDestinationId(), true); when(configRepository.listWorkspaceStandardSyncs(sourceRead.getWorkspaceId(), false)) .thenReturn(Collections.singletonList(standardSync)); when(configRepository.getSourceAndDefinitionsFromSourceIds(Collections.singletonList(source.getSourceId()))) @@ -172,10 +182,15 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio .thenReturn(Collections.singletonList(new DestinationAndDefinition(destination, destinationDefinition))); connectionRead = ConnectionHelpers.generateExpectedConnectionRead(standardSync); + brokenConnectionRead = ConnectionHelpers.generateExpectedConnectionRead(brokenStandardSync); operationReadList = new OperationReadList() .operations(List.of(new OperationRead() .operationId(connectionRead.getOperationIds().get(0)) .name("Test Operation"))); + brokenOperationReadList = new OperationReadList() + .operations(List.of(new OperationRead() + .operationId(brokenConnectionRead.getOperationIds().get(0)) + .name("Test Operation"))); final SourceIdRequestBody sourceIdRequestBody = new SourceIdRequestBody(); sourceIdRequestBody.setSourceId(connectionRead.getSourceId()); @@ -208,6 +223,28 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio when(jobHistoryHandler.getLatestSyncJobsForConnections(Collections.singletonList(connectionRead.getConnectionId()))) .thenReturn(Collections.singletonList(jobRead.getJob())); + final JobWithAttemptsRead brokenJobRead = new JobWithAttemptsRead() + .job(new JobRead() + .configId(brokenConnectionRead.getConnectionId().toString()) + .configType(JobConfigType.SYNC) + .id(10L) + .status(JobStatus.SUCCEEDED) + .createdAt(now.getEpochSecond()) + .updatedAt(now.getEpochSecond())) + .attempts(Lists.newArrayList(new AttemptRead() + .id(12L) + .status(AttemptStatus.SUCCEEDED) + .bytesSynced(100L) + .recordsSynced(15L) + .createdAt(now.getEpochSecond()) + .updatedAt(now.getEpochSecond()) + .endedAt(now.getEpochSecond()))); + + when(jobHistoryHandler.getLatestSyncJob(brokenConnectionRead.getConnectionId())).thenReturn(Optional.of(brokenJobRead.getJob())); + + when(jobHistoryHandler.getLatestSyncJobsForConnections(Collections.singletonList(brokenConnectionRead.getConnectionId()))) + .thenReturn(Collections.singletonList(brokenJobRead.getJob())); + expectedListItem = ConnectionHelpers.generateExpectedWebBackendConnectionListItem( standardSync, sourceRead, @@ -216,7 +253,43 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio jobRead.getJob().getCreatedAt(), jobRead.getJob().getStatus()); - expected = new WebBackendConnectionRead() + expected = expectedWebBackendConnectionReadObject(connectionRead, sourceRead, destinationRead, operationReadList, SchemaChange.NO_CHANGE, now, + connectionRead.getSyncCatalog(), connectionRead.getSourceCatalogId()); + expectedNoDiscoveryWithNewSchema = expectedWebBackendConnectionReadObject(connectionRead, sourceRead, destinationRead, operationReadList, + SchemaChange.NON_BREAKING, now, connectionRead.getSyncCatalog(), connectionRead.getSourceCatalogId()); + + final AirbyteCatalog modifiedCatalog = ConnectionHelpers.generateMultipleStreamsApiCatalog(2); + final SourceDiscoverSchemaRequestBody sourceDiscoverSchema = new SourceDiscoverSchemaRequestBody(); + sourceDiscoverSchema.setSourceId(connectionRead.getSourceId()); + sourceDiscoverSchema.setDisableCache(true); + when(schedulerHandler.discoverSchemaForSourceFromSourceId(sourceDiscoverSchema)).thenReturn( + new SourceDiscoverSchemaRead() + .jobInfo(mock(SynchronousJobRead.class)) + .catalog(modifiedCatalog)); + + expectedWithNewSchema = expectedWebBackendConnectionReadObject(connectionRead, sourceRead, destinationRead, + new OperationReadList().operations(expected.getOperations()), SchemaChange.NON_BREAKING, now, modifiedCatalog, null) + .catalogDiff(new CatalogDiff().transforms(List.of( + new StreamTransform().transformType(TransformTypeEnum.ADD_STREAM) + .streamDescriptor(new io.airbyte.api.model.generated.StreamDescriptor().name("users-data1")) + .updateStream(null)))); + + expectedWithNewSchemaBroken = expectedWebBackendConnectionReadObject(brokenConnectionRead, sourceRead, destinationRead, brokenOperationReadList, + SchemaChange.BREAKING, now, connectionRead.getSyncCatalog(), brokenConnectionRead.getSourceCatalogId()); + when(schedulerHandler.resetConnection(any(ConnectionIdRequestBody.class))) + .thenReturn(new JobInfoRead().job(new JobRead().status(JobStatus.SUCCEEDED))); + } + + WebBackendConnectionRead expectedWebBackendConnectionReadObject( + final ConnectionRead connectionRead, + final SourceRead sourceRead, + final DestinationRead destinationRead, + final OperationReadList operationReadList, + final SchemaChange schemaChange, + final Instant now, + final AirbyteCatalog syncCatalog, + final UUID catalogId) { + return new WebBackendConnectionRead() .connectionId(connectionRead.getConnectionId()) .sourceId(connectionRead.getSourceId()) .destinationId(connectionRead.getDestinationId()) @@ -225,7 +298,8 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio .namespaceDefinition(connectionRead.getNamespaceDefinition()) .namespaceFormat(connectionRead.getNamespaceFormat()) .prefix(connectionRead.getPrefix()) - .syncCatalog(connectionRead.getSyncCatalog()) + .syncCatalog(syncCatalog) + .catalogId(catalogId) .status(connectionRead.getStatus()) .schedule(connectionRead.getSchedule()) .scheduleType(connectionRead.getScheduleType()) @@ -236,54 +310,12 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio .latestSyncJobCreatedAt(now.getEpochSecond()) .latestSyncJobStatus(JobStatus.SUCCEEDED) .isSyncing(false) + .schemaChange(schemaChange) .resourceRequirements(new ResourceRequirements() .cpuRequest(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getCpuRequest()) .cpuLimit(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getCpuLimit()) .memoryRequest(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getMemoryRequest()) .memoryLimit(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getMemoryLimit())); - - final AirbyteCatalog modifiedCatalog = ConnectionHelpers.generateMultipleStreamsApiCatalog(2); - - final SourceDiscoverSchemaRequestBody sourceDiscoverSchema = new SourceDiscoverSchemaRequestBody(); - sourceDiscoverSchema.setSourceId(connectionRead.getSourceId()); - sourceDiscoverSchema.setDisableCache(true); - when(schedulerHandler.discoverSchemaForSourceFromSourceId(sourceDiscoverSchema)).thenReturn( - new SourceDiscoverSchemaRead() - .jobInfo(mock(SynchronousJobRead.class)) - .catalog(modifiedCatalog)); - - expectedWithNewSchema = new WebBackendConnectionRead() - .connectionId(expected.getConnectionId()) - .sourceId(expected.getSourceId()) - .destinationId(expected.getDestinationId()) - .operationIds(expected.getOperationIds()) - .name(expected.getName()) - .namespaceDefinition(expected.getNamespaceDefinition()) - .namespaceFormat(expected.getNamespaceFormat()) - .prefix(expected.getPrefix()) - .syncCatalog(modifiedCatalog) - .status(expected.getStatus()) - .schedule(expected.getSchedule()) - .scheduleType(expected.getScheduleType()) - .scheduleData(expected.getScheduleData()) - .source(expected.getSource()) - .destination(expected.getDestination()) - .operations(expected.getOperations()) - .latestSyncJobCreatedAt(expected.getLatestSyncJobCreatedAt()) - .latestSyncJobStatus(expected.getLatestSyncJobStatus()) - .isSyncing(expected.getIsSyncing()) - .catalogDiff(new CatalogDiff().transforms(List.of( - new StreamTransform().transformType(TransformTypeEnum.ADD_STREAM) - .streamDescriptor(new io.airbyte.api.model.generated.StreamDescriptor().name("users-data1")) - .updateStream(null)))) - .resourceRequirements(new ResourceRequirements() - .cpuRequest(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getCpuRequest()) - .cpuLimit(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getCpuLimit()) - .memoryRequest(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getMemoryRequest()) - .memoryLimit(ConnectionHelpers.TESTING_RESOURCE_REQUIREMENTS.getMemoryLimit())); - - when(schedulerHandler.resetConnection(any(ConnectionIdRequestBody.class))) - .thenReturn(new JobInfoRead().job(new JobRead().status(JobStatus.SUCCEEDED))); } @Test @@ -348,7 +380,9 @@ void testWebBackendGetConnection() throws ConfigNotFoundException, IOException, assertTrue(expected.getDestination().getIcon().startsWith(SVG)); } - WebBackendConnectionRead testWebBackendGetConnection(final boolean withCatalogRefresh) + WebBackendConnectionRead testWebBackendGetConnection(final boolean withCatalogRefresh, + final ConnectionRead connectionRead, + final OperationReadList operationReadList) throws JsonValidationException, ConfigNotFoundException, IOException { final ConnectionIdRequestBody connectionIdRequestBody = new ConnectionIdRequestBody(); connectionIdRequestBody.setConnectionId(connectionRead.getConnectionId()); @@ -366,9 +400,15 @@ WebBackendConnectionRead testWebBackendGetConnection(final boolean withCatalogRe } @Test - void testWebBackendGetConnectionWithDiscovery() throws ConfigNotFoundException, IOException, JsonValidationException { - when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(expectedWithNewSchema.getCatalogDiff()); - final WebBackendConnectionRead result = testWebBackendGetConnection(true); + void testWebBackendGetConnectionWithDiscoveryAndNewSchema() throws ConfigNotFoundException, + IOException, JsonValidationException { + when(connectionsHandler.getDiff(any(), any(), + any())).thenReturn(expectedWithNewSchema.getCatalogDiff()); + when(configRepository.getMostRecentActorCatalogFetchEventForSource(any())) + .thenReturn(Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(UUID.randomUUID()))); + when(configRepository.getActorCatalogById(any())).thenReturn(new ActorCatalog().withId(UUID.randomUUID())); + final WebBackendConnectionRead result = testWebBackendGetConnection(true, connectionRead, + operationReadList); verify(schedulerHandler).discoverSchemaForSourceFromSourceId(any()); assertEquals(expectedWithNewSchema, result); } @@ -376,11 +416,30 @@ void testWebBackendGetConnectionWithDiscovery() throws ConfigNotFoundException, @Test void testWebBackendGetConnectionNoRefreshCatalog() throws JsonValidationException, ConfigNotFoundException, IOException { - final WebBackendConnectionRead result = testWebBackendGetConnection(false); + final WebBackendConnectionRead result = testWebBackendGetConnection(false, connectionRead, operationReadList); verify(schedulerHandler, never()).discoverSchemaForSourceFromSourceId(any()); assertEquals(expected, result); } + @Test + void testWebBackendGetConnectionNoDiscoveryWithNewSchema() throws JsonValidationException, ConfigNotFoundException, IOException { + when(configRepository.getMostRecentActorCatalogFetchEventForSource(any())) + .thenReturn(Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(UUID.randomUUID()))); + when(configRepository.getActorCatalogById(any())).thenReturn(new ActorCatalog().withId(UUID.randomUUID())); + final WebBackendConnectionRead result = testWebBackendGetConnection(false, connectionRead, operationReadList); + assertEquals(expectedNoDiscoveryWithNewSchema, result); + } + + @Test + void testWebBackendGetConnectionNoDiscoveryWithNewSchemaBreaking() throws JsonValidationException, ConfigNotFoundException, IOException { + when(connectionsHandler.getConnection(brokenConnectionRead.getConnectionId())).thenReturn(brokenConnectionRead); + when(configRepository.getMostRecentActorCatalogFetchEventForSource(any())) + .thenReturn(Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(UUID.randomUUID()))); + when(configRepository.getActorCatalogById(any())).thenReturn(new ActorCatalog().withId(UUID.randomUUID())); + final WebBackendConnectionRead result = testWebBackendGetConnection(false, brokenConnectionRead, brokenOperationReadList); + assertEquals(expectedWithNewSchemaBroken, result); + } + @Test void testToConnectionCreate() throws IOException { final SourceConnection source = SourceHelpers.generateSource(UUID.randomUUID()); @@ -549,7 +608,7 @@ void testUpdateConnection() throws JsonValidationException, ConfigNotFoundExcept .prefix(expected.getPrefix()) .syncCatalog(expected.getSyncCatalog()) .status(expected.getStatus()) - .schedule(expected.getSchedule())); + .schedule(expected.getSchedule()).breakingChange(false)); when(operationsHandler.listOperationsForConnection(any())).thenReturn(operationReadList); final ConnectionIdRequestBody connectionId = new ConnectionIdRequestBody().connectionId(connectionRead.getConnectionId()); @@ -593,7 +652,8 @@ void testUpdateConnectionWithOperations() throws JsonValidationException, Config when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn( new ConnectionRead() .connectionId(expected.getConnectionId()) - .operationIds(connectionRead.getOperationIds())); + .operationIds(connectionRead.getOperationIds()) + .breakingChange(false)); when(connectionsHandler.updateConnection(any())).thenReturn( new ConnectionRead() .connectionId(expected.getConnectionId()) @@ -606,7 +666,7 @@ void testUpdateConnectionWithOperations() throws JsonValidationException, Config .prefix(expected.getPrefix()) .syncCatalog(expected.getSyncCatalog()) .status(expected.getStatus()) - .schedule(expected.getSchedule())); + .schedule(expected.getSchedule()).breakingChange(false)); when(operationsHandler.updateOperation(operationUpdate)).thenReturn(new OperationRead().operationId(operationUpdate.getOperationId())); when(operationsHandler.listOperationsForConnection(any())).thenReturn(operationReadList); @@ -653,7 +713,7 @@ void testUpdateConnectionWithUpdatedSchemaLegacy() throws JsonValidationExceptio .prefix(expected.getPrefix()) .syncCatalog(expectedWithNewSchema.getSyncCatalog()) .status(expected.getStatus()) - .schedule(expected.getSchedule()); + .schedule(expected.getSchedule()).breakingChange(false); when(connectionsHandler.updateConnection(any())).thenReturn(connectionRead); when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn(connectionRead); @@ -710,7 +770,7 @@ void testUpdateConnectionWithUpdatedSchemaPerStream() throws JsonValidationExcep when(operationsHandler.listOperationsForConnection(any())).thenReturn(operationReadList); when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn( - new ConnectionRead().connectionId(expected.getConnectionId())); + new ConnectionRead().connectionId(expected.getConnectionId()).breakingChange(false)); final ConnectionRead connectionRead = new ConnectionRead() .connectionId(expected.getConnectionId()) .sourceId(expected.getSourceId()) @@ -721,7 +781,8 @@ void testUpdateConnectionWithUpdatedSchemaPerStream() throws JsonValidationExcep .prefix(expected.getPrefix()) .syncCatalog(expectedWithNewSchema.getSyncCatalog()) .status(expected.getStatus()) - .schedule(expected.getSchedule()); + .schedule(expected.getSchedule()) + .breakingChange(false); when(connectionsHandler.updateConnection(any())).thenReturn(connectionRead); when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn(connectionRead); @@ -780,7 +841,7 @@ void testUpdateConnectionNoStreamsToReset() throws JsonValidationException, Conf .prefix(expected.getPrefix()) .syncCatalog(expectedWithNewSchema.getSyncCatalog()) .status(expected.getStatus()) - .schedule(expected.getSchedule()); + .schedule(expected.getSchedule()).breakingChange(false); when(connectionsHandler.updateConnection(any())).thenReturn(connectionRead); when(connectionsHandler.getConnection(expected.getConnectionId())).thenReturn(connectionRead); @@ -828,7 +889,8 @@ void testUpdateConnectionWithSkipReset() throws JsonValidationException, ConfigN .prefix(expected.getPrefix()) .syncCatalog(expectedWithNewSchema.getSyncCatalog()) .status(expected.getStatus()) - .schedule(expected.getSchedule()); + .schedule(expected.getSchedule()) + .breakingChange(false); when(connectionsHandler.updateConnection(any())).thenReturn(connectionRead); final WebBackendConnectionRead result = wbHandler.webBackendUpdateConnection(updateBody); diff --git a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java index 43e0b155e00a..89984a323944 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java +++ b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java @@ -85,7 +85,8 @@ public static StandardSync generateSyncWithSourceId(final UUID sourceId) { .withOperationIds(List.of(UUID.randomUUID())) .withManual(false) .withSchedule(generateBasicSchedule()) - .withResourceRequirements(TESTING_RESOURCE_REQUIREMENTS); + .withResourceRequirements(TESTING_RESOURCE_REQUIREMENTS) + .withBreakingChange(false); } public static StandardSync generateSyncWithDestinationId(final UUID destinationId) { @@ -105,7 +106,7 @@ public static StandardSync generateSyncWithDestinationId(final UUID destinationI .withManual(true); } - public static StandardSync generateSyncWithSourceAndDestinationId(final UUID sourceId, final UUID destinationId) { + public static StandardSync generateSyncWithSourceAndDestinationId(final UUID sourceId, final UUID destinationId, final boolean isBroken) { final UUID connectionId = UUID.randomUUID(); return new StandardSync() @@ -116,10 +117,12 @@ public static StandardSync generateSyncWithSourceAndDestinationId(final UUID sou .withPrefix(STANDARD_SYNC_PREFIX) .withStatus(StandardSync.Status.ACTIVE) .withCatalog(generateBasicConfiguredAirbyteCatalog()) + .withSourceCatalogId(UUID.randomUUID()) .withSourceId(sourceId) .withDestinationId(destinationId) .withOperationIds(List.of(UUID.randomUUID())) - .withManual(true); + .withManual(true) + .withBreakingChange(isBroken); } public static ConnectionSchedule generateBasicConnectionSchedule() { @@ -150,7 +153,8 @@ public static ConnectionRead generateExpectedConnectionRead(final UUID connectio final UUID destinationId, final List operationIds, final UUID sourceCatalogId, - final Geography geography) { + final Geography geography, + final boolean breaking) { return new ConnectionRead() .connectionId(connectionId) @@ -172,7 +176,8 @@ public static ConnectionRead generateExpectedConnectionRead(final UUID connectio .memoryRequest(TESTING_RESOURCE_REQUIREMENTS.getMemoryRequest()) .memoryLimit(TESTING_RESOURCE_REQUIREMENTS.getMemoryLimit())) .sourceCatalogId(sourceCatalogId) - .geography(geography); + .geography(geography) + .breakingChange(breaking); } public static ConnectionRead generateExpectedConnectionRead(final StandardSync standardSync) { @@ -182,7 +187,8 @@ public static ConnectionRead generateExpectedConnectionRead(final StandardSync s standardSync.getDestinationId(), standardSync.getOperationIds(), standardSync.getSourceCatalogId(), - Enums.convertTo(standardSync.getGeography(), Geography.class)); + Enums.convertTo(standardSync.getGeography(), Geography.class), + standardSync.getBreakingChange()); if (standardSync.getSchedule() == null) { connectionRead.schedule(null); @@ -206,7 +212,8 @@ public static ConnectionRead connectionReadFromStandardSync(final StandardSync s .namespaceFormat(standardSync.getNamespaceFormat()) .prefix(standardSync.getPrefix()) .sourceCatalogId(standardSync.getSourceCatalogId()) - .geography(ApiPojoConverters.toApiGeography(standardSync.getGeography())); + .geography(ApiPojoConverters.toApiGeography(standardSync.getGeography())) + .breakingChange(standardSync.getBreakingChange()); if (standardSync.getNamespaceDefinition() != null) { connectionRead diff --git a/airbyte-webapp/src/test-utils/mock-data/mockConnection.json b/airbyte-webapp/src/test-utils/mock-data/mockConnection.json index bf44cc7251fb..0f582582606e 100644 --- a/airbyte-webapp/src/test-utils/mock-data/mockConnection.json +++ b/airbyte-webapp/src/test-utils/mock-data/mockConnection.json @@ -345,5 +345,6 @@ "latestSyncJobCreatedAt": 1660227512, "latestSyncJobStatus": "succeeded", "isSyncing": false, - "catalogId": "bf31d1df-d7ba-4bae-b1ec-dac617b4f70c" + "catalogId": "bf31d1df-d7ba-4bae-b1ec-dac617b4f70c", + "schemaChange": "no_change" } diff --git a/airbyte-webapp/src/test-utils/testutils.tsx b/airbyte-webapp/src/test-utils/testutils.tsx index 0ca47361146b..254c1d62d383 100644 --- a/airbyte-webapp/src/test-utils/testutils.tsx +++ b/airbyte-webapp/src/test-utils/testutils.tsx @@ -114,4 +114,5 @@ export const mockConnection: WebBackendConnectionRead = { operations: [], catalogId: "", isSyncing: false, + schemaChange: "no_change", }; diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 8bf8380da920..97d50bbbf698 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -488,6 +488,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -733,6 +734,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -990,6 +992,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1052,6 +1055,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1172,6 +1176,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1234,6 +1239,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1529,6 +1535,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1591,6 +1598,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1885,6 +1893,7 @@

    Example data

    "units" : 0, "timeUnit" : "minutes" }, + "breakingChange" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -9940,6 +9949,7 @@

    Table of Contents

  • ReleaseStage -
  • ResetConfig -
  • ResourceRequirements -
  • +
  • SchemaChange -
  • SetInstancewideDestinationOauthParamsRequestBody -
  • SetInstancewideSourceOauthParamsRequestBody -
  • SetWorkflowInAttemptRequestBody -
  • @@ -10240,6 +10250,7 @@

    ConnectionRead - resourceRequirements (optional)

    sourceCatalogId (optional)
    UUID format: uuid
    geography (optional)
    +
    breakingChange
    +
    +

    SchemaChange - Up

    +
    +
    +
    +

    SetInstancewideDestinationOauthParamsRequestBody - Up

    @@ -11421,6 +11438,7 @@

    WebBackendConnectionRead - <
    catalogId (optional)
    UUID format: uuid
    catalogDiff (optional)
    geography (optional)
    +
    schemaChange

    diff --git a/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml b/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml index a347eda3fc06..19943b678639 100644 --- a/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml +++ b/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml @@ -2688,6 +2688,12 @@ components: $ref: "#/components/schemas/ConnectionStatus" syncCatalog: $ref: "#/components/schemas/AirbyteCatalog" + schemaChange: + enum: + - no_change + - non_breaking + - breaking + type: string required: - connectionId - name @@ -2698,6 +2704,7 @@ components: - source - destination - isSyncing + - schemaChange type: object WebBackendConnectionReadList: properties: From 85e93cb0062e7aab93e1f0efd5f5c43b495997bd Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Fri, 21 Oct 2022 13:34:48 -0700 Subject: [PATCH 260/498] Destinations BigQuery and GCS: update docs about bucket encryption (#18315) --- docs/integrations/destinations/bigquery.md | 2 ++ docs/integrations/destinations/gcs.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/integrations/destinations/bigquery.md b/docs/integrations/destinations/bigquery.md index eb9ba2c9157f..994adcce3001 100644 --- a/docs/integrations/destinations/bigquery.md +++ b/docs/integrations/destinations/bigquery.md @@ -36,6 +36,8 @@ To use a Google Cloud Storage bucket: 3. Grant the [`Storage Object Admin` role](https://cloud.google.com/storage/docs/access-control/iam-roles#standard-roles) to the Google Cloud [Service Account](https://cloud.google.com/iam/docs/service-accounts). 4. Make sure your Cloud Storage bucket is accessible from the machine running Airbyte. The easiest way to verify if Airbyte is able to connect to your bucket is via the check connection tool in the UI. +Your bucket must be encrypted using a Google-managed encryption key (this is the default setting when creating a new bucket). We currently do not support buckets using customer-managed encryption keys (CMEK). You can view this setting under the "Configuration" tab of your GCS bucket, in the `Encryption type` row. + #### Using `INSERT` You can use BigQuery's [`INSERT`](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax) statement to upload data directly from your source to BigQuery. While this is faster to set up initially, we strongly recommend not using this option for anything other than a quick demo. Due to the Google BigQuery SDK client limitations, using `INSERT` is 10x slower than using a Google Cloud Storage bucket, and you may see some failures for big datasets and slow sources (For example, if reading from a source takes more than 10-12 hours). For more details, refer to https://github.com/airbytehq/airbyte/issues/3549 diff --git a/docs/integrations/destinations/gcs.md b/docs/integrations/destinations/gcs.md index 2e1661fa0e5e..aa07d098ed17 100644 --- a/docs/integrations/destinations/gcs.md +++ b/docs/integrations/destinations/gcs.md @@ -31,6 +31,8 @@ The Airbyte GCS destination allows you to sync data to cloud storage buckets. Ea Currently, only the [HMAC key](https://cloud.google.com/storage/docs/authentication/hmackeys) is supported. More credential types will be added in the future, please [submit an issue](https://github.com/airbytehq/airbyte/issues/new?assignees=&labels=type%2Fenhancement%2C+needs-triage&template=feature-request.md&title=) with your request. +Additionally, your bucket must be encrypted using a Google-managed encryption key (this is the default setting when creating a new bucket). We currently do not support buckets using customer-managed encryption keys (CMEK). You can view this setting under the "Configuration" tab of your GCS bucket, in the `Encryption type` row. + ⚠️ Please note that under "Full Refresh Sync" mode, data in the configured bucket and path will be wiped out before each sync. We recommend you to provision a dedicated S3 resource for this sync to prevent unexpected data deletion from misconfiguration. ⚠️ The full path of the output data is: From 73336ff4de70a406718da0ca466d3c50be1c3a50 Mon Sep 17 00:00:00 2001 From: Octavia Squidington III <90398440+octavia-squidington-iii@users.noreply.github.com> Date: Fri, 21 Oct 2022 16:00:39 -0500 Subject: [PATCH 261/498] Bump Airbyte version from 0.40.16 to 0.40.17 (#18316) Co-authored-by: edgao --- .bumpversion.cfg | 2 +- .env | 2 +- airbyte-bootloader/Dockerfile | 2 +- airbyte-container-orchestrator/Dockerfile | 2 +- airbyte-cron/Dockerfile | 2 +- airbyte-metrics/reporter/Dockerfile | 2 +- airbyte-proxy/Dockerfile | 2 +- airbyte-server/Dockerfile | 2 +- airbyte-webapp/package-lock.json | 4 ++-- airbyte-webapp/package.json | 2 +- airbyte-workers/Dockerfile | 2 +- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-server/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.yaml | 2 +- charts/airbyte/README.md | 4 ++-- docs/operator-guides/upgrading-airbyte.md | 2 +- kube/overlays/stable-with-resource-limits/.env | 2 +- .../stable-with-resource-limits/kustomization.yaml | 12 ++++++------ kube/overlays/stable/.env | 2 +- kube/overlays/stable/kustomization.yaml | 12 ++++++------ octavia-cli/Dockerfile | 2 +- octavia-cli/README.md | 4 ++-- octavia-cli/install.sh | 2 +- octavia-cli/setup.py | 2 +- 27 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 05dbd9ce8ba5..0aa3bd4af929 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.40.16 +current_version = 0.40.17 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-[a-z]+)? diff --git a/.env b/.env index d58019616892..fdf824b82731 100644 --- a/.env +++ b/.env @@ -10,7 +10,7 @@ ### SHARED ### -VERSION=0.40.16 +VERSION=0.40.17 # When using the airbyte-db via default docker image CONFIG_ROOT=/data diff --git a/airbyte-bootloader/Dockerfile b/airbyte-bootloader/Dockerfile index bb2d03ee3924..2652c0af1710 100644 --- a/airbyte-bootloader/Dockerfile +++ b/airbyte-bootloader/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-bootloader ENV VERSION ${VERSION} diff --git a/airbyte-container-orchestrator/Dockerfile b/airbyte-container-orchestrator/Dockerfile index 6f82f3871cb9..b42d0050da95 100644 --- a/airbyte-container-orchestrator/Dockerfile +++ b/airbyte-container-orchestrator/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-container-orchestrator ENV VERSION=${VERSION} diff --git a/airbyte-cron/Dockerfile b/airbyte-cron/Dockerfile index 88cd040dfb3d..3f1c7b9ce3ca 100644 --- a/airbyte-cron/Dockerfile +++ b/airbyte-cron/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS cron -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-cron ENV VERSION ${VERSION} diff --git a/airbyte-metrics/reporter/Dockerfile b/airbyte-metrics/reporter/Dockerfile index 9bce1e5d6d16..3bde3162e52b 100644 --- a/airbyte-metrics/reporter/Dockerfile +++ b/airbyte-metrics/reporter/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS metrics-reporter -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-metrics-reporter ENV VERSION ${VERSION} diff --git a/airbyte-proxy/Dockerfile b/airbyte-proxy/Dockerfile index 4c7c6b0c9681..ae61793f24c1 100644 --- a/airbyte-proxy/Dockerfile +++ b/airbyte-proxy/Dockerfile @@ -2,7 +2,7 @@ FROM nginx:latest -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-proxy ENV VERSION ${VERSION} diff --git a/airbyte-server/Dockerfile b/airbyte-server/Dockerfile index bdcb33c35968..6b3417eec972 100644 --- a/airbyte-server/Dockerfile +++ b/airbyte-server/Dockerfile @@ -3,7 +3,7 @@ FROM ${JDK_IMAGE} AS server EXPOSE 8000 -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-server ENV VERSION ${VERSION} diff --git a/airbyte-webapp/package-lock.json b/airbyte-webapp/package-lock.json index 02695734a588..47a41ec05ca5 100644 --- a/airbyte-webapp/package-lock.json +++ b/airbyte-webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "airbyte-webapp", - "version": "0.40.16", + "version": "0.40.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "airbyte-webapp", - "version": "0.40.16", + "version": "0.40.17", "dependencies": { "@datadog/browser-rum": "^4.21.2", "@floating-ui/react-dom": "^1.0.0", diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index 4add14e9465e..e71d58b91a68 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -1,6 +1,6 @@ { "name": "airbyte-webapp", - "version": "0.40.16", + "version": "0.40.17", "private": true, "engines": { "node": ">=16.0.0" diff --git a/airbyte-workers/Dockerfile b/airbyte-workers/Dockerfile index f601ffd2be13..63e97461ad83 100644 --- a/airbyte-workers/Dockerfile +++ b/airbyte-workers/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.16 +ARG VERSION=0.40.17 ENV APPLICATION airbyte-workers ENV VERSION ${VERSION} diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 42c59cd39f57..ce32d8c33561 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.16" +appVersion: "0.40.17" dependencies: - name: common diff --git a/charts/airbyte-server/Chart.yaml b/charts/airbyte-server/Chart.yaml index 65930ba8894b..2074d108b9a8 100644 --- a/charts/airbyte-server/Chart.yaml +++ b/charts/airbyte-server/Chart.yaml @@ -21,7 +21,7 @@ version: 0.45.3 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.16" +appVersion: "0.40.17" dependencies: - name: common diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index b5a8ce4030d6..b2ebcefa54cc 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.16" +appVersion: "0.40.17" dependencies: - name: common diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index aae4365420af..033e1735df0d 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.16" +appVersion: "0.40.17" dependencies: - name: common diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 7ab6fe96553f..ac2137fbc51f 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.27" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.16" +appVersion: "0.40.17" dependencies: - name: common diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 8b4abb63d7ce..15dd6cc78bdf 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -22,7 +22,7 @@ version: 0.40.27 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.16" +appVersion: "0.40.17" dependencies: - name: common diff --git a/charts/airbyte/README.md b/charts/airbyte/README.md index cc841057bea0..7efbd55200ee 100644 --- a/charts/airbyte/README.md +++ b/charts/airbyte/README.md @@ -1,6 +1,6 @@ # airbyte -![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.16](https://img.shields.io/badge/AppVersion-0.40.16-informational?style=flat-square) +![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.17](https://img.shields.io/badge/AppVersion-0.40.17-informational?style=flat-square) Helm chart to deploy airbyte @@ -248,7 +248,7 @@ Helm chart to deploy airbyte | worker.hpa.enabled | bool | `false` | | | worker.image.pullPolicy | string | `"IfNotPresent"` | | | worker.image.repository | string | `"airbyte/worker"` | | -| worker.image.tag | string | `"0.40.16"` | | +| worker.image.tag | string | `"0.40.17"` | | | worker.livenessProbe.enabled | bool | `true` | | | worker.livenessProbe.failureThreshold | int | `3` | | | worker.livenessProbe.initialDelaySeconds | int | `30` | | diff --git a/docs/operator-guides/upgrading-airbyte.md b/docs/operator-guides/upgrading-airbyte.md index ac394608a0cc..02738a5d9ba5 100644 --- a/docs/operator-guides/upgrading-airbyte.md +++ b/docs/operator-guides/upgrading-airbyte.md @@ -103,7 +103,7 @@ If you are upgrading from (i.e. your current version of Airbyte is) Airbyte vers Here's an example of what it might look like with the values filled in. It assumes that the downloaded `airbyte_archive.tar.gz` is in `/tmp`. ```bash - docker run --rm -v /tmp:/config airbyte/migration:0.40.16 --\ + docker run --rm -v /tmp:/config airbyte/migration:0.40.17 --\ --input /config/airbyte_archive.tar.gz\ --output /config/airbyte_archive_migrated.tar.gz ``` diff --git a/kube/overlays/stable-with-resource-limits/.env b/kube/overlays/stable-with-resource-limits/.env index b18d147d78f3..286bdc188a50 100644 --- a/kube/overlays/stable-with-resource-limits/.env +++ b/kube/overlays/stable-with-resource-limits/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.16 +AIRBYTE_VERSION=0.40.17 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable-with-resource-limits/kustomization.yaml b/kube/overlays/stable-with-resource-limits/kustomization.yaml index b38172a1a64b..624c5994f469 100644 --- a/kube/overlays/stable-with-resource-limits/kustomization.yaml +++ b/kube/overlays/stable-with-resource-limits/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/bootloader - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/server - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/webapp - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/worker - newTag: 0.40.16 + newTag: 0.40.17 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.16 + newTag: 0.40.17 configMapGenerator: - name: airbyte-env diff --git a/kube/overlays/stable/.env b/kube/overlays/stable/.env index b2643a499a36..05f61be870ab 100644 --- a/kube/overlays/stable/.env +++ b/kube/overlays/stable/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.16 +AIRBYTE_VERSION=0.40.17 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable/kustomization.yaml b/kube/overlays/stable/kustomization.yaml index 2ebbb39c4509..ec9936bd0c5d 100644 --- a/kube/overlays/stable/kustomization.yaml +++ b/kube/overlays/stable/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/bootloader - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/server - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/webapp - newTag: 0.40.16 + newTag: 0.40.17 - name: airbyte/worker - newTag: 0.40.16 + newTag: 0.40.17 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.16 + newTag: 0.40.17 configMapGenerator: - name: airbyte-env diff --git a/octavia-cli/Dockerfile b/octavia-cli/Dockerfile index 3fc1f9802744..19712f3a250c 100644 --- a/octavia-cli/Dockerfile +++ b/octavia-cli/Dockerfile @@ -14,5 +14,5 @@ USER octavia-cli WORKDIR /home/octavia-project ENTRYPOINT ["octavia"] -LABEL io.airbyte.version=0.40.16 +LABEL io.airbyte.version=0.40.17 LABEL io.airbyte.name=airbyte/octavia-cli diff --git a/octavia-cli/README.md b/octavia-cli/README.md index 79c8ce0c26de..dede649cbc38 100644 --- a/octavia-cli/README.md +++ b/octavia-cli/README.md @@ -104,7 +104,7 @@ This script: ```bash touch ~/.octavia # Create a file to store env variables that will be mapped the octavia-cli container mkdir my_octavia_project_directory # Create your octavia project directory where YAML configurations will be stored. -docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.16 +docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.17 ``` ### Using `docker-compose` @@ -712,7 +712,7 @@ You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment | Version | Date | Description | PR | | ------- | ---------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | 0.41.0 | 2022-10-13 | Use Basic Authentication for making API requests | [#17982](https://github.com/airbytehq/airbyte/pull/17982) | -| 0.40.16 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | +| 0.40.17 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | | 0.39.33 | 2022-07-05 | Add `octavia import all` command | [#14374](https://github.com/airbytehq/airbyte/pull/14374) | | 0.39.32 | 2022-06-30 | Create import command to import and manage existing Airbyte resource from octavia-cli | [#14137](https://github.com/airbytehq/airbyte/pull/14137) | | 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | diff --git a/octavia-cli/install.sh b/octavia-cli/install.sh index e5e9597c32fa..30daa1d8e956 100755 --- a/octavia-cli/install.sh +++ b/octavia-cli/install.sh @@ -3,7 +3,7 @@ # This install scripts currently only works for ZSH and Bash profiles. # It creates an octavia alias in your profile bound to a docker run command and your current user. -VERSION=0.40.16 +VERSION=0.40.17 OCTAVIA_ENV_FILE=${HOME}/.octavia detect_profile() { diff --git a/octavia-cli/setup.py b/octavia-cli/setup.py index ce00e1004812..4262184b420e 100644 --- a/octavia-cli/setup.py +++ b/octavia-cli/setup.py @@ -15,7 +15,7 @@ setup( name="octavia-cli", - version="0.40.16", + version="0.40.17", description="A command line interface to manage Airbyte configurations", long_description=README, author="Airbyte", From cc41385f507d8711445bbb713e98872741caf19d Mon Sep 17 00:00:00 2001 From: Ryan Fu Date: Fri, 21 Oct 2022 16:04:39 -0700 Subject: [PATCH 262/498] Updates `getPrivateKeyPair` to throw exception when private key pair does not exist (#18263) * Changes method from returning null to throwing Exception to match method structure * Added javadoc explaining that KeyPair should not be returning null * Moved ConnectionErrorException to commons package * Adds error handling for SshWrappedDestinations and markdown to include changes * Bumps version numbers for Postgres, MySQL, MSSQL * auto-bump connector version * auto-bump connector version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../exceptions}/ConnectionErrorException.java | 2 +- .../resources/seed/source_definitions.yaml | 6 +- .../src/main/resources/seed/source_specs.yaml | 6 +- .../airbyte/db/jdbc/DefaultJdbcDatabase.java | 2 +- .../io/airbyte/db/mongodb/MongoDatabase.java | 2 +- .../integrations/base/ssh/SshTunnel.java | 6 +- .../base/ssh/SshWrappedDestination.java | 18 ++- .../base/ssh/SshWrappedSource.java | 3 +- .../jdbc/AbstractJdbcDestination.java | 2 +- .../jdbc/copy/CopyDestination.java | 2 +- .../mongodb/MongodbDestination.java | 2 +- .../destination/mysql/MySQLDestination.java | 2 +- .../RedshiftStagingS3Destination.java | 2 +- .../MongoDbSource.java | 2 +- .../source-mssql-strict-encrypt/Dockerfile | 2 +- .../connectors/source-mssql/Dockerfile | 2 +- .../source-mysql-strict-encrypt/Dockerfile | 2 +- .../connectors/source-mysql/Dockerfile | 2 +- .../source-postgres-strict-encrypt/Dockerfile | 2 +- .../connectors/source-postgres/Dockerfile | 2 +- .../source/relationaldb/AbstractDbSource.java | 2 +- docs/integrations/sources/mssql.md | 113 +++++++------- docs/integrations/sources/mysql.md | 143 +++++++++--------- docs/integrations/sources/postgres.md | 3 +- 24 files changed, 174 insertions(+), 156 deletions(-) rename {airbyte-db/db-lib/src/main/java/io/airbyte/db/exception => airbyte-commons/src/main/java/io/airbyte/commons/exceptions}/ConnectionErrorException.java (97%) diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/exception/ConnectionErrorException.java b/airbyte-commons/src/main/java/io/airbyte/commons/exceptions/ConnectionErrorException.java similarity index 97% rename from airbyte-db/db-lib/src/main/java/io/airbyte/db/exception/ConnectionErrorException.java rename to airbyte-commons/src/main/java/io/airbyte/commons/exceptions/ConnectionErrorException.java index 8687ae228e98..8028e80f3028 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/exception/ConnectionErrorException.java +++ b/airbyte-commons/src/main/java/io/airbyte/commons/exceptions/ConnectionErrorException.java @@ -2,7 +2,7 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.db.exception; +package io.airbyte.commons.exceptions; public class ConnectionErrorException extends RuntimeException { diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index b8883ed27b28..a8740c70f461 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -658,7 +658,7 @@ - name: Microsoft SQL Server (MSSQL) sourceDefinitionId: b5ea17b1-f170-46dc-bc31-cc744ca984c1 dockerRepository: airbyte/source-mssql - dockerImageTag: 0.4.22 + dockerImageTag: 0.4.23 documentationUrl: https://docs.airbyte.com/integrations/sources/mssql icon: mssql.svg sourceType: database @@ -706,7 +706,7 @@ - name: MySQL sourceDefinitionId: 435bb9a5-7887-4809-aa58-28c27df0d7ad dockerRepository: airbyte/source-mysql - dockerImageTag: 1.0.6 + dockerImageTag: 1.0.7 documentationUrl: https://docs.airbyte.com/integrations/sources/mysql icon: mysql.svg sourceType: database @@ -864,7 +864,7 @@ - name: Postgres sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres - dockerImageTag: 1.0.18 + dockerImageTag: 1.0.19 documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 2d842b06fcab..1497c564277d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -6325,7 +6325,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mssql:0.4.22" +- dockerImage: "airbyte/source-mssql:0.4.23" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/mssql" connectionSpecification: @@ -7155,7 +7155,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mysql:1.0.6" +- dockerImage: "airbyte/source-mysql:1.0.7" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/mysql" connectionSpecification: @@ -8787,7 +8787,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-postgres:1.0.18" +- dockerImage: "airbyte/source-postgres:1.0.19" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DefaultJdbcDatabase.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DefaultJdbcDatabase.java index b8376f4415cd..faf328623007 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DefaultJdbcDatabase.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/jdbc/DefaultJdbcDatabase.java @@ -5,10 +5,10 @@ package io.airbyte.db.jdbc; import com.google.errorprone.annotations.MustBeClosed; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.commons.functional.CheckedFunction; import io.airbyte.db.JdbcCompatibleSourceOperations; -import io.airbyte.db.exception.ConnectionErrorException; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/mongodb/MongoDatabase.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/mongodb/MongoDatabase.java index 23482ba79c09..4ccc1130c61d 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/mongodb/MongoDatabase.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/mongodb/MongoDatabase.java @@ -14,10 +14,10 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import com.mongodb.client.MongoIterable; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.functional.CheckedFunction; import io.airbyte.commons.util.MoreIterators; import io.airbyte.db.AbstractDatabase; -import io.airbyte.db.exception.ConnectionErrorException; import java.util.Collections; import java.util.List; import java.util.Optional; diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java index 313dfe46d053..0647f775ea34 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Preconditions; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.commons.functional.CheckedFunction; import io.airbyte.commons.json.Jsons; @@ -298,10 +299,11 @@ public void close() { * From the OPENSSH private key string, use mina-sshd to deserialize the key pair, reconstruct the * keys from the key info, and return the key pair for use in authentication. * + * @return The {@link KeyPair} to add - may not be {@code null} * @see loadKeyPairs() */ - KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException { + KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException, ConnectionErrorException { final String validatedKey = validateKey(); final var keyPairs = SecurityUtils .getKeyPairResourceParser() @@ -310,7 +312,7 @@ KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException { if (keyPairs != null && keyPairs.iterator().hasNext()) { return keyPairs.iterator().next(); } - return null; + throw new ConnectionErrorException("Unable to load private key pairs, verify key pairs are properly inputted"); } private String validateKey() { diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java index 9dfea5c37729..2e1735bdc18a 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java @@ -6,16 +6,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.resources.MoreResources; import io.airbyte.integrations.base.AirbyteMessageConsumer; +import io.airbyte.integrations.base.AirbyteTraceMessageUtility; import io.airbyte.integrations.base.Destination; import io.airbyte.protocol.models.AirbyteConnectionStatus; +import io.airbyte.protocol.models.AirbyteConnectionStatus.Status; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConnectorSpecification; import java.util.List; import java.util.function.Consumer; +import org.apache.sshd.common.SshException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +46,7 @@ public SshWrappedDestination(final Destination delegate, } public SshWrappedDestination(final Destination delegate, - String endPointKey) { + final String endPointKey) { this.delegate = delegate; this.endPointKey = endPointKey; this.portKey = null; @@ -60,8 +64,16 @@ public ConnectorSpecification spec() throws Exception { @Override public AirbyteConnectionStatus check(final JsonNode config) throws Exception { - return (endPointKey != null) ? SshTunnel.sshWrap(config, endPointKey, delegate::check) - : SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); + try { + return (endPointKey != null) ? SshTunnel.sshWrap(config, endPointKey, delegate::check) + : SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); + } catch (final SshException | ConnectionErrorException e) { + final String sshErrorMessage = "Could not connect with provided SSH configuration. Error: " + e.getMessage(); + AirbyteTraceMessageUtility.emitConfigErrorTrace(e, sshErrorMessage); + return new AirbyteConnectionStatus() + .withStatus(Status.FAILED) + .withMessage(sshErrorMessage); + } } @Override diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java index 9c0a8f334743..eec5220391c5 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java @@ -5,6 +5,7 @@ package io.airbyte.integrations.base.ssh; import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.util.AutoCloseableIterator; import io.airbyte.commons.util.AutoCloseableIterators; import io.airbyte.integrations.base.AirbyteTraceMessageUtility; @@ -42,7 +43,7 @@ public ConnectorSpecification spec() throws Exception { public AirbyteConnectionStatus check(final JsonNode config) throws Exception { try { return SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); - } catch (final SshException e) { + } catch (final SshException | ConnectionErrorException e) { final String sshErrorMessage = "Could not connect with provided SSH configuration. Error: " + e.getMessage(); AirbyteTraceMessageUtility.emitConfigErrorTrace(e, sshErrorMessage); return new AirbyteConnectionStatus() diff --git a/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/AbstractJdbcDestination.java b/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/AbstractJdbcDestination.java index e1adaf9c51f9..556cc15def61 100644 --- a/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/AbstractJdbcDestination.java +++ b/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/AbstractJdbcDestination.java @@ -7,8 +7,8 @@ import static io.airbyte.integrations.base.errors.messages.ErrorMessage.getErrorMessage; import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.map.MoreMaps; -import io.airbyte.db.exception.ConnectionErrorException; import io.airbyte.db.factory.DataSourceFactory; import io.airbyte.db.jdbc.DefaultJdbcDatabase; import io.airbyte.db.jdbc.JdbcDatabase; diff --git a/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/copy/CopyDestination.java b/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/copy/CopyDestination.java index 3a2a8d94f57c..1ed3752ab0a4 100644 --- a/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/copy/CopyDestination.java +++ b/airbyte-integrations/connectors/destination-jdbc/src/main/java/io/airbyte/integrations/destination/jdbc/copy/CopyDestination.java @@ -7,7 +7,7 @@ import static io.airbyte.integrations.base.errors.messages.ErrorMessage.getErrorMessage; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.db.exception.ConnectionErrorException; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.db.factory.DataSourceFactory; import io.airbyte.db.jdbc.JdbcDatabase; import io.airbyte.integrations.BaseConnector; diff --git a/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java b/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java index 09f51393edf2..88ac858e0507 100644 --- a/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java +++ b/airbyte-integrations/connectors/destination-mongodb/src/main/java/io/airbyte/integrations/destination/mongodb/MongodbDestination.java @@ -14,8 +14,8 @@ import com.mongodb.MongoSecurityException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.util.MoreIterators; -import io.airbyte.db.exception.ConnectionErrorException; import io.airbyte.db.jdbc.JdbcUtils; import io.airbyte.db.mongodb.MongoDatabase; import io.airbyte.db.mongodb.MongoUtils.MongoInstanceType; diff --git a/airbyte-integrations/connectors/destination-mysql/src/main/java/io/airbyte/integrations/destination/mysql/MySQLDestination.java b/airbyte-integrations/connectors/destination-mysql/src/main/java/io/airbyte/integrations/destination/mysql/MySQLDestination.java index 19009ba7e494..2cd849e123d5 100644 --- a/airbyte-integrations/connectors/destination-mysql/src/main/java/io/airbyte/integrations/destination/mysql/MySQLDestination.java +++ b/airbyte-integrations/connectors/destination-mysql/src/main/java/io/airbyte/integrations/destination/mysql/MySQLDestination.java @@ -8,9 +8,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.ImmutableMap; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.map.MoreMaps; -import io.airbyte.db.exception.ConnectionErrorException; import io.airbyte.db.factory.DataSourceFactory; import io.airbyte.db.factory.DatabaseDriver; import io.airbyte.db.jdbc.JdbcDatabase; diff --git a/airbyte-integrations/connectors/destination-redshift/src/main/java/io/airbyte/integrations/destination/redshift/RedshiftStagingS3Destination.java b/airbyte-integrations/connectors/destination-redshift/src/main/java/io/airbyte/integrations/destination/redshift/RedshiftStagingS3Destination.java index 4398de3bc58f..4ccbd465a357 100644 --- a/airbyte-integrations/connectors/destination-redshift/src/main/java/io/airbyte/integrations/destination/redshift/RedshiftStagingS3Destination.java +++ b/airbyte-integrations/connectors/destination-redshift/src/main/java/io/airbyte/integrations/destination/redshift/RedshiftStagingS3Destination.java @@ -11,8 +11,8 @@ import static io.airbyte.integrations.destination.s3.S3DestinationConfig.getS3DestinationConfig; import com.fasterxml.jackson.databind.JsonNode; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.json.Jsons; -import io.airbyte.db.exception.ConnectionErrorException; import io.airbyte.db.factory.DataSourceFactory; import io.airbyte.db.jdbc.DefaultJdbcDatabase; import io.airbyte.db.jdbc.JdbcDatabase; diff --git a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java index cd09d9b22eef..7ebfb9d36ef3 100644 --- a/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java +++ b/airbyte-integrations/connectors/source-mongodb-v2/src/main/java/io.airbyte.integrations.source.mongodb/MongoDbSource.java @@ -12,11 +12,11 @@ import com.mongodb.MongoException; import com.mongodb.MongoSecurityException; import com.mongodb.client.MongoCollection; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.util.AutoCloseableIterator; import io.airbyte.commons.util.AutoCloseableIterators; -import io.airbyte.db.exception.ConnectionErrorException; import io.airbyte.db.jdbc.JdbcUtils; import io.airbyte.db.mongodb.MongoDatabase; import io.airbyte.db.mongodb.MongoUtils; diff --git a/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile index 1fc8e4596ce5..8ba8453b09ed 100644 --- a/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-mssql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.4.22 +LABEL io.airbyte.version=0.4.23 LABEL io.airbyte.name=airbyte/source-mssql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mssql/Dockerfile b/airbyte-integrations/connectors/source-mssql/Dockerfile index 629582c214fb..44c38200c998 100644 --- a/airbyte-integrations/connectors/source-mssql/Dockerfile +++ b/airbyte-integrations/connectors/source-mssql/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-mssql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.4.22 +LABEL io.airbyte.version=0.4.23 LABEL io.airbyte.name=airbyte/source-mssql diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile index 6cee3b779db5..f0a549848bbb 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.6 +LABEL io.airbyte.version=1.0.7 LABEL io.airbyte.name=airbyte/source-mysql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mysql/Dockerfile b/airbyte-integrations/connectors/source-mysql/Dockerfile index 2a800c988d31..74587aade9e7 100644 --- a/airbyte-integrations/connectors/source-mysql/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.6 +LABEL io.airbyte.version=1.0.7 LABEL io.airbyte.name=airbyte/source-mysql diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile index 097a9a115d06..4b79ea19f4b7 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.18 +LABEL io.airbyte.version=1.0.19 LABEL io.airbyte.name=airbyte/source-postgres-strict-encrypt diff --git a/airbyte-integrations/connectors/source-postgres/Dockerfile b/airbyte-integrations/connectors/source-postgres/Dockerfile index b5f35cc799af..029074f82560 100644 --- a/airbyte-integrations/connectors/source-postgres/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.18 +LABEL io.airbyte.version=1.0.19 LABEL io.airbyte.name=airbyte/source-postgres diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java index 8cd496575e3e..e553483da683 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.functional.CheckedConsumer; @@ -21,7 +22,6 @@ import io.airbyte.config.helpers.StateMessageHelper; import io.airbyte.db.AbstractDatabase; import io.airbyte.db.IncrementalUtils; -import io.airbyte.db.exception.ConnectionErrorException; import io.airbyte.db.jdbc.JdbcDatabase; import io.airbyte.integrations.BaseConnector; import io.airbyte.integrations.base.AirbyteStreamNameNamespacePair; diff --git a/docs/integrations/sources/mssql.md b/docs/integrations/sources/mssql.md index 564ab681bea0..30273680457a 100644 --- a/docs/integrations/sources/mssql.md +++ b/docs/integrations/sources/mssql.md @@ -339,60 +339,61 @@ WHERE actor_definition_id ='b5ea17b1-f170-46dc-bc31-cc744ca984c1' AND (configura ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0.4.22 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | -| 0.4.21 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | -| 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | -| 0.4.17 | 2022-09-01 | [16261](https://github.com/airbytehq/airbyte/pull/16261) | Emit state messages more frequently | -| 0.4.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.4.15 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.4.14 | 2022-08-10 | [15430](https://github.com/airbytehq/airbyte/pull/15430) | fixed a bug on handling special character on database name | -| 0.4.13 | 2022-08-04 | [15268](https://github.com/airbytehq/airbyte/pull/15268) | Added [] enclosing to escape special character in the database name | -| 0.4.12 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.4.11 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.4.10 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.4.9 | 2022-07-05 | [14379](https://github.com/airbytehq/airbyte/pull/14379) | Aligned Normal and CDC migration + added some fixes for datatypes processing | -| 0.4.8 | 2022-06-24 | [14121](https://github.com/airbytehq/airbyte/pull/14121) | Omit using 'USE' keyword on Azure SQL with CDC | -| 0.4.5 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.4.3 | 2022-06-17 | [13887](https://github.com/airbytehq/airbyte/pull/13887) | Increase version to include changes from [13854](https://github.com/airbytehq/airbyte/pull/13854) | -| 0.4.2 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | -| 0.4.1 | 2022-05-25 | [13419](https://github.com/airbytehq/airbyte/pull/13419) | Correct enum for Standard method. | -| 0.4.0 | 2022-05-25 | [12759](https://github.com/airbytehq/airbyte/pull/12759) [13168](https://github.com/airbytehq/airbyte/pull/13168) | For CDC, Add option to ignore existing data and only sync new changes from the database. | -| 0.3.22 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.3.21 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.3.19 | 2022-03-31 | [11495](https://github.com/airbytehq/airbyte/pull/11495) | Adds Support to Chinese MSSQL Server Agent | -| 0.3.18 | 2022-03-29 | [11010](https://github.com/airbytehq/airbyte/pull/11010) | Adds JDBC Params | -| 0.3.17 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.3.16 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.3.15 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | -| 0.3.14 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | -| 0.3.13 | 2022-01-07 | [9094](https://github.com/airbytehq/airbyte/pull/9094) | Added support for missed data types | -| 0.3.12 | 2021-12-30 | [9206](https://github.com/airbytehq/airbyte/pull/9206) | Update connector fields title/description | -| 0.3.11 | 2021-12-24 | [8958](https://github.com/airbytehq/airbyte/pull/8958) | Add support for JdbcType.ARRAY | -| 0.3.10 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | | -| 0.3.9 | 2021-11-09 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Improve support for binary and varbinary data types | | -| 0.3.8 | 2021-10-26 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Fixed data type (smalldatetime, smallmoney) conversion from mssql source | | -| 0.3.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | | -| 0.3.6 | 2021-09-17 | [6318](https://github.com/airbytehq/airbyte/pull/6318) | Added option to connect to DB via SSH | | -| 0.3.4 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | | -| 0.3.3 | 2021-07-05 | [4689](https://github.com/airbytehq/airbyte/pull/4689) | Add CDC support | | -| 0.3.2 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | | -| 0.3.1 | 2021-06-08 | [3893](https://github.com/airbytehq/airbyte/pull/3893) | Enable SSL connection | | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | | -| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | | -| 0.1.11 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | \] | -| 0.1.10 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | | -| 0.1.9 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | | -| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | | -| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | | -| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | | -| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.4.23 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | +| 0.4.22 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | +| 0.4.21 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | +| 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | +| 0.4.17 | 2022-09-01 | [16261](https://github.com/airbytehq/airbyte/pull/16261) | Emit state messages more frequently | +| 0.4.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.4.15 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.4.14 | 2022-08-10 | [15430](https://github.com/airbytehq/airbyte/pull/15430) | fixed a bug on handling special character on database name | +| 0.4.13 | 2022-08-04 | [15268](https://github.com/airbytehq/airbyte/pull/15268) | Added [] enclosing to escape special character in the database name | +| 0.4.12 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.4.11 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.4.10 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.4.9 | 2022-07-05 | [14379](https://github.com/airbytehq/airbyte/pull/14379) | Aligned Normal and CDC migration + added some fixes for datatypes processing | +| 0.4.8 | 2022-06-24 | [14121](https://github.com/airbytehq/airbyte/pull/14121) | Omit using 'USE' keyword on Azure SQL with CDC | +| 0.4.5 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.4.3 | 2022-06-17 | [13887](https://github.com/airbytehq/airbyte/pull/13887) | Increase version to include changes from [13854](https://github.com/airbytehq/airbyte/pull/13854) | +| 0.4.2 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | +| 0.4.1 | 2022-05-25 | [13419](https://github.com/airbytehq/airbyte/pull/13419) | Correct enum for Standard method. | +| 0.4.0 | 2022-05-25 | [12759](https://github.com/airbytehq/airbyte/pull/12759) [13168](https://github.com/airbytehq/airbyte/pull/13168) | For CDC, Add option to ignore existing data and only sync new changes from the database. | +| 0.3.22 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.3.21 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.3.19 | 2022-03-31 | [11495](https://github.com/airbytehq/airbyte/pull/11495) | Adds Support to Chinese MSSQL Server Agent | +| 0.3.18 | 2022-03-29 | [11010](https://github.com/airbytehq/airbyte/pull/11010) | Adds JDBC Params | +| 0.3.17 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.3.16 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.3.15 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | +| 0.3.14 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | +| 0.3.13 | 2022-01-07 | [9094](https://github.com/airbytehq/airbyte/pull/9094) | Added support for missed data types | +| 0.3.12 | 2021-12-30 | [9206](https://github.com/airbytehq/airbyte/pull/9206) | Update connector fields title/description | +| 0.3.11 | 2021-12-24 | [8958](https://github.com/airbytehq/airbyte/pull/8958) | Add support for JdbcType.ARRAY | +| 0.3.10 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | | +| 0.3.9 | 2021-11-09 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Improve support for binary and varbinary data types | | +| 0.3.8 | 2021-10-26 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Fixed data type (smalldatetime, smallmoney) conversion from mssql source | | +| 0.3.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | | +| 0.3.6 | 2021-09-17 | [6318](https://github.com/airbytehq/airbyte/pull/6318) | Added option to connect to DB via SSH | | +| 0.3.4 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | | +| 0.3.3 | 2021-07-05 | [4689](https://github.com/airbytehq/airbyte/pull/4689) | Add CDC support | | +| 0.3.2 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | | +| 0.3.1 | 2021-06-08 | [3893](https://github.com/airbytehq/airbyte/pull/3893) | Enable SSL connection | | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | | +| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | | +| 0.1.11 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | \] | +| 0.1.10 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | | +| 0.1.9 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | | +| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | | +| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | | +| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | | +| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | | diff --git a/docs/integrations/sources/mysql.md b/docs/integrations/sources/mysql.md index fb65f12ef551..40801c2bdddc 100644 --- a/docs/integrations/sources/mysql.md +++ b/docs/integrations/sources/mysql.md @@ -249,75 +249,76 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura ``` ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:-----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.6 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | -| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | -| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | -| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | -| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | -| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | -| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | -| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | -| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | -| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | -| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | -| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | -| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | -| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | -| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | -| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | -| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | -| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | -| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | -| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | -| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | -| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | -| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | -| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | -| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | -| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | -| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | -| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | -| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | -| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | -| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | -| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | -| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | -| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | -| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | -| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | -| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | -| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | -| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | -| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | -| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | -| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | -| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | -| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | -| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | -| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | -| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | -| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | -| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:-----------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.7 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | +| 1.0.6 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | +| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | +| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | +| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | +| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | +| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | +| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | +| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | +| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | +| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | +| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | +| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | +| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | +| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | +| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | +| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | +| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | +| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | +| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | +| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | +| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | +| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | +| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | +| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | +| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | +| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | +| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | +| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | +| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | +| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | +| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | +| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | +| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | +| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | +| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | +| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | +| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | +| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | +| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | +| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | +| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | +| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | +| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | +| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | +| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | +| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | +| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | +| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | +| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index 3a801ca9bf2b..20fa4a1c7399 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -398,7 +398,8 @@ The root causes is that the WALs needed for the incremental sync has been remove | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.18 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | | +| 1.0.19 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Destinations | +| 1.0.18 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | | 1.0.17 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | | 1.0.16 | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | From f080d4de94f50c112c7d215bbe4958e035580fdd Mon Sep 17 00:00:00 2001 From: sarafonseca Date: Fri, 21 Oct 2022 23:26:12 -0300 Subject: [PATCH 263/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20xkcd=20[?= =?UTF-8?q?python=20cdk]=20(#18049)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add new source: xkcd * Add documentation for xckd connector * Fix unit test test_parse_response * Fix xkcd changelog * Improve iteration method * Update tests test_next_page_token and test_parse_response * add timeout for full read * add seed config and eof * correct source def id * auto-bump connector version Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 7 + .../src/main/resources/seed/source_specs.yaml | 10 ++ airbyte-integrations/builds.md | 1 + .../connectors/source-xkcd/.dockerignore | 6 + .../connectors/source-xkcd/Dockerfile | 38 +++++ .../connectors/source-xkcd/README.md | 132 ++++++++++++++++++ .../source-xkcd/acceptance-test-config.yml | 16 +++ .../source-xkcd/acceptance-test-docker.sh | 16 +++ .../connectors/source-xkcd/bootstrap.md | 18 +++ .../connectors/source-xkcd/build.gradle | 9 ++ .../source-xkcd/integration_tests/__init__.py | 3 + .../integration_tests/acceptance.py | 13 ++ .../integration_tests/catalog.json | 14 ++ .../integration_tests/configured_catalog.json | 13 ++ .../connectors/source-xkcd/main.py | 13 ++ .../connectors/source-xkcd/requirements.txt | 2 + .../connectors/source-xkcd/setup.py | 29 ++++ .../source-xkcd/source_xkcd/__init__.py | 8 ++ .../source-xkcd/source_xkcd/schemas/xkcd.json | 39 ++++++ .../source-xkcd/source_xkcd/source.py | 69 +++++++++ .../source-xkcd/source_xkcd/spec.yaml | 5 + .../source-xkcd/unit_tests/__init__.py | 3 + .../source-xkcd/unit_tests/test_source.py | 21 +++ .../source-xkcd/unit_tests/test_streams.py | 114 +++++++++++++++ docs/integrations/README.md | 1 + docs/integrations/sources/xkcd.md | 25 ++++ 26 files changed, 625 insertions(+) create mode 100644 airbyte-integrations/connectors/source-xkcd/.dockerignore create mode 100644 airbyte-integrations/connectors/source-xkcd/Dockerfile create mode 100644 airbyte-integrations/connectors/source-xkcd/README.md create mode 100644 airbyte-integrations/connectors/source-xkcd/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-xkcd/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-xkcd/bootstrap.md create mode 100644 airbyte-integrations/connectors/source-xkcd/build.gradle create mode 100644 airbyte-integrations/connectors/source-xkcd/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-xkcd/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-xkcd/integration_tests/catalog.json create mode 100644 airbyte-integrations/connectors/source-xkcd/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-xkcd/main.py create mode 100644 airbyte-integrations/connectors/source-xkcd/requirements.txt create mode 100644 airbyte-integrations/connectors/source-xkcd/setup.py create mode 100644 airbyte-integrations/connectors/source-xkcd/source_xkcd/__init__.py create mode 100644 airbyte-integrations/connectors/source-xkcd/source_xkcd/schemas/xkcd.json create mode 100644 airbyte-integrations/connectors/source-xkcd/source_xkcd/source.py create mode 100644 airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml create mode 100644 airbyte-integrations/connectors/source-xkcd/unit_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-xkcd/unit_tests/test_source.py create mode 100644 airbyte-integrations/connectors/source-xkcd/unit_tests/test_streams.py create mode 100644 docs/integrations/sources/xkcd.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index a8740c70f461..31afafc3b732 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1148,6 +1148,13 @@ icon: victorops.svg sourceType: api releaseStage: alpha +- name: xkcd + sourceDefinitionId: 80fddd16-17bd-4c0c-bf4a-80df7863fc9d + dockerRepository: airbyte/source-xkcd + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/xkcd + sourceType: api + releaseStage: alpha - name: Webflow sourceDefinitionId: ef580275-d9a9-48bb-af5e-db0f5855be04 dockerRepository: airbyte/source-webflow diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 1497c564277d..20abda6b515b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11815,6 +11815,16 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-xkcd:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.io/integrations/sources/xkcd" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Xkcd Spec" + type: "object" + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-webflow:0.1.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/webflow" diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index 1385df6ad8fb..28fc978af3bf 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -121,6 +121,7 @@ | Whisky Hunter | [![source-whisky-hunter](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-whisky-hunter%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-whisky-hunter) | | Wrike | [![source-wrike](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-wrike%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-wrike) | | YouTube Analytics | [![source-youtube-analytics](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-youtube-analytics%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-youtube-analytics) | +| Xkcd | [![source-xkcd](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-xkcd%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-xkcd) | | Zendesk Chat | [![source-zendesk-chat](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-zendesk-chat%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-zendesk-chat) | | Zendesk Support | [![source-zendesk-support](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-zendesk-support%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-zendesk-support) | | Zendesk Talk | [![source-zendesk-talk](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-zendesk-talk%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-zendesk-talk) | diff --git a/airbyte-integrations/connectors/source-xkcd/.dockerignore b/airbyte-integrations/connectors/source-xkcd/.dockerignore new file mode 100644 index 000000000000..2724cfbbf07b --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_xkcd +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-xkcd/Dockerfile b/airbyte-integrations/connectors/source-xkcd/Dockerfile new file mode 100644 index 000000000000..4e92425299bd --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.13-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_xkcd ./source_xkcd + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-xkcd diff --git a/airbyte-integrations/connectors/source-xkcd/README.md b/airbyte-integrations/connectors/source-xkcd/README.md new file mode 100644 index 000000000000..73578a98e41b --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/README.md @@ -0,0 +1,132 @@ +# XKCD Source + +This is the repository for the Xkcd source connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/xkcd). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.9.0` + +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python3 -m venv .venv +``` + +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +pip install '.[tests]' +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-xkcd:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/xkcd) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_xkcd/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source xkcd test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-xkcd:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-xkcd:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-xkcd:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-xkcd:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-xkcd:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-xkcd:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-xkcd:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-xkcd:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-xkcd/acceptance-test-config.yml b/airbyte-integrations/connectors/source-xkcd/acceptance-test-config.yml new file mode 100644 index 000000000000..a23087f27377 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/acceptance-test-config.yml @@ -0,0 +1,16 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-xkcd:dev +tests: + spec: + - spec_path: "source_xkcd/spec.yaml" + connection: + - config_path: "secrets/config.json" + status: "succeed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + timeout_seconds: 3600 diff --git a/airbyte-integrations/connectors/source-xkcd/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-xkcd/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-xkcd/bootstrap.md b/airbyte-integrations/connectors/source-xkcd/bootstrap.md new file mode 100644 index 000000000000..89e30b2b46e9 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/bootstrap.md @@ -0,0 +1,18 @@ +# xkcd + +## Overview + +xkcd is a webcomic created in 2005 by American author Randall Munroe. The comic's tagline describes it as "a webcomic of romance, sarcasm, math, and language". Munroe states on the comic's website that the comic's name is not an initialism but "just a word with no phonetic pronunciation." + +## Endpoints + +xkcd API has only one endpoint that responds with the comic metadata. + +## Quick Notes + +- This is an open API, which means no credentials are necessary to access this data. +- This API doesn't accept query strings or POST params. The only way to iterate over the comics is through different paths, passing the comic number (https://xkcd.com/{comic_num}/json.html). + +## API Reference + +The API reference documents: https://xkcd.com/json.html diff --git a/airbyte-integrations/connectors/source-xkcd/build.gradle b/airbyte-integrations/connectors/source-xkcd/build.gradle new file mode 100644 index 000000000000..24474ecaec6d --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_xkcd' +} diff --git a/airbyte-integrations/connectors/source-xkcd/integration_tests/__init__.py b/airbyte-integrations/connectors/source-xkcd/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-xkcd/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-xkcd/integration_tests/acceptance.py new file mode 100644 index 000000000000..416093ab7624 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/integration_tests/acceptance.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + yield diff --git a/airbyte-integrations/connectors/source-xkcd/integration_tests/catalog.json b/airbyte-integrations/connectors/source-xkcd/integration_tests/catalog.json new file mode 100644 index 000000000000..0af8ac77380d --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/integration_tests/catalog.json @@ -0,0 +1,14 @@ +{ + "streams": [ + { + "name": "xkcd", + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": "num", + "json_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object" + } + } + ] +} diff --git a/airbyte-integrations/connectors/source-xkcd/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-xkcd/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..1a90c1ced3d0 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/integration_tests/configured_catalog.json @@ -0,0 +1,13 @@ +{ + "streams": [ + { + "stream": { + "name": "xkcd", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "append" + } + ] +} diff --git a/airbyte-integrations/connectors/source-xkcd/main.py b/airbyte-integrations/connectors/source-xkcd/main.py new file mode 100644 index 000000000000..a5ccb90f936f --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_xkcd import SourceXkcd + +if __name__ == "__main__": + source = SourceXkcd() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-xkcd/requirements.txt b/airbyte-integrations/connectors/source-xkcd/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-xkcd/setup.py b/airbyte-integrations/connectors/source-xkcd/setup.py new file mode 100644 index 000000000000..a0f902d0e655 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.2", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_xkcd", + description="Source implementation for Xkcd.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-xkcd/source_xkcd/__init__.py b/airbyte-integrations/connectors/source-xkcd/source_xkcd/__init__.py new file mode 100644 index 000000000000..ba55cc4ccb39 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/source_xkcd/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceXkcd + +__all__ = ["SourceXkcd"] diff --git a/airbyte-integrations/connectors/source-xkcd/source_xkcd/schemas/xkcd.json b/airbyte-integrations/connectors/source-xkcd/source_xkcd/schemas/xkcd.json new file mode 100644 index 000000000000..9f07a7634323 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/source_xkcd/schemas/xkcd.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "month": { + "type": ["null", "string"] + }, + "num": { + "type": ["null", "integer"] + }, + "link": { + "type": ["null", "string"] + }, + "year": { + "type": ["null", "string"] + }, + "news": { + "type": ["null", "string"] + }, + "safe_title": { + "type": ["null", "string"] + }, + "transcript": { + "type": ["null", "string"] + }, + "alt": { + "type": ["null", "string"] + }, + "img": { + "type": ["null", "string"] + }, + "title": { + "type": ["null", "string"] + }, + "day": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-xkcd/source_xkcd/source.py b/airbyte-integrations/connectors/source-xkcd/source_xkcd/source.py new file mode 100644 index 000000000000..6b5af2be5382 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/source_xkcd/source.py @@ -0,0 +1,69 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from typing import Any, Iterable, List, Mapping, Optional, Tuple + +import requests +from airbyte_cdk.models import SyncMode +from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.streams.http import HttpStream + + +class XkcdStream(HttpStream): + url_base = "https://xkcd.com" + last_comic = 0 + comic_number = 0 + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def path(self, next_page_token: Mapping[str, Any] = None, **kwargs: Any) -> str: + if next_page_token: + next_token: str = next_page_token["next_token"] + return f"/{next_token}/info.0.json" + return "/info.0.json" + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + if self.last_comic < response.json().get("num"): + self.last_comic = response.json().get("num") + # There is not a comic 404 + if self.comic_number == 403: + self.comic_number = 405 + if self.comic_number < self.last_comic: + self.comic_number = self.comic_number + 1 + return {"next_token": self.comic_number} + return None + + def parse_response( + self, + response: requests.Response, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, + ) -> Iterable[Mapping]: + record = response.json() + yield record + + +class Xkcd(XkcdStream): + primary_key = "num" + + +# Source +class SourceXkcd(AbstractSource): + def check_connection(self, logger, config) -> Tuple[bool, any]: + try: + xkcd = Xkcd() + xkcd_gen = xkcd.read_records(sync_mode=SyncMode.full_refresh) + next(xkcd_gen) + return True, None + except Exception as error: + return ( + False, + f"Unable to connect to XKCD - {repr(error)}", + ) + + def streams(self, config: Mapping[str, Any]) -> List[Stream]: + return [Xkcd()] diff --git a/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml b/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml new file mode 100644 index 000000000000..1a78fb6b518a --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml @@ -0,0 +1,5 @@ +documentationUrl: https://docs.airbyte.io/integrations/sources/xkcd +connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Xkcd Spec + type: object diff --git a/airbyte-integrations/connectors/source-xkcd/unit_tests/__init__.py b/airbyte-integrations/connectors/source-xkcd/unit_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/unit_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-xkcd/unit_tests/test_source.py b/airbyte-integrations/connectors/source-xkcd/unit_tests/test_source.py new file mode 100644 index 000000000000..732408e7fd39 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/unit_tests/test_source.py @@ -0,0 +1,21 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from unittest.mock import MagicMock + +from source_xkcd.source import SourceXkcd + + +def test_check_connection(mocker): + source = SourceXkcd() + logger_mock, config_mock = MagicMock(), MagicMock() + assert source.check_connection(logger_mock, config_mock) == (True, None) + + +def test_streams(mocker): + source = SourceXkcd() + config_mock = MagicMock() + streams = source.streams(config_mock) + expected_streams_number = 1 + assert len(streams) == expected_streams_number diff --git a/airbyte-integrations/connectors/source-xkcd/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-xkcd/unit_tests/test_streams.py new file mode 100644 index 000000000000..e3db6627ce24 --- /dev/null +++ b/airbyte-integrations/connectors/source-xkcd/unit_tests/test_streams.py @@ -0,0 +1,114 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from unittest.mock import MagicMock + +import pytest +from source_xkcd.source import XkcdStream + + +@pytest.fixture +def patch_base_class(mocker): + # Mock abstract methods to enable instantiating abstract class + mocker.patch.object(XkcdStream, "path", "v0/example_endpoint") + mocker.patch.object(XkcdStream, "primary_key", "test_primary_key") + mocker.patch.object(XkcdStream, "__abstractmethods__", set()) + + +def test_request_params(patch_base_class): + stream = XkcdStream() + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_params = {} + assert stream.request_params(**inputs) == expected_params + + +def test_next_page_token(patch_base_class): + stream = XkcdStream() + response = MagicMock() + response.json.return_value = { + "month": "10", + "num": 2685, + "link": "", + "year": "2022", + "news": "", + "safe_title": "2045", + "transcript": "", + "alt": '"Sorry, doctor, I\'m going to have to come in on a different day--I have another appointment that would be really hard to move, in terms of the kinetic energy requirements."', + "img": "https://imgs.xkcd.com/comics/2045.png", + "title": "2045", + "day": "14", + } + inputs = {"response": response} + expected_token = {"next_token": 1} + assert stream.next_page_token(**inputs) == expected_token + + +def test_parse_response(patch_base_class): + stream = XkcdStream() + response = MagicMock() + response.json.return_value = { + "month": "1", + "num": 1, + "link": "", + "year": "2006", + "news": "", + "safe_title": "Barrel - Part 1", + "transcript": "[[A boy sits in a barrel which is floating in an ocean.]]\nBoy: I wonder where I'll float next?\n[[The barrel drifts into the distance. Nothing else can be seen.]]\n{{Alt: Don't we all.}}", + "alt": "Don't we all.", + "img": "https://imgs.xkcd.com/comics/barrel_cropped_(1).jpg", + "title": "Barrel - Part 1", + "day": "1", + } + inputs = {"response": response, "stream_state": None} + expected_parsed_object = { + "month": "1", + "num": 1, + "link": "", + "year": "2006", + "news": "", + "safe_title": "Barrel - Part 1", + "transcript": "[[A boy sits in a barrel which is floating in an ocean.]]\nBoy: I wonder where I'll float next?\n[[The barrel drifts into the distance. Nothing else can be seen.]]\n{{Alt: Don't we all.}}", + "alt": "Don't we all.", + "img": "https://imgs.xkcd.com/comics/barrel_cropped_(1).jpg", + "title": "Barrel - Part 1", + "day": "1", + } + assert next(stream.parse_response(**inputs)) == expected_parsed_object + + +def test_request_headers(patch_base_class): + stream = XkcdStream() + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_headers = {} + assert stream.request_headers(**inputs) == expected_headers + + +def test_http_method(patch_base_class): + stream = XkcdStream() + expected_method = "GET" + assert stream.http_method == expected_method + + +@pytest.mark.parametrize( + ("http_status", "should_retry"), + [ + (HTTPStatus.OK, False), + (HTTPStatus.BAD_REQUEST, False), + (HTTPStatus.TOO_MANY_REQUESTS, True), + (HTTPStatus.INTERNAL_SERVER_ERROR, True), + ], +) +def test_should_retry(patch_base_class, http_status, should_retry): + response_mock = MagicMock() + response_mock.status_code = http_status + stream = XkcdStream() + assert stream.should_retry(response_mock) == should_retry + + +def test_backoff_time(patch_base_class): + response_mock = MagicMock() + stream = XkcdStream() + expected_backoff_time = None + assert stream.backoff_time(response_mock) == expected_backoff_time diff --git a/docs/integrations/README.md b/docs/integrations/README.md index f21e8a5b2946..aca4349b68c5 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -175,6 +175,7 @@ For more information about the grading system, see [Product Release Stages](http | [Wordpress](sources/wordpress.md) | Alpha | No | | [Wrike](sources/wrike.md) | Alpha | No | | [YouTube Analytics](sources/youtube-analytics.md) | Beta | Yes | +| [Xkcd](sources/xkcd.md) | Alpha | No | | [Zencart](sources/zencart.md) | Alpha | No | | [Zendesk Chat](sources/zendesk-chat.md) | Generally Available | Yes | | [Zendesk Sunshine](sources/zendesk-sunshine.md) | Alpha | Yes | diff --git a/docs/integrations/sources/xkcd.md b/docs/integrations/sources/xkcd.md new file mode 100644 index 000000000000..9088fa705eb8 --- /dev/null +++ b/docs/integrations/sources/xkcd.md @@ -0,0 +1,25 @@ +# XKCD + +This page guides you through the process of setting up the xkcd source connector. + +## Prerequisites + +XKCD is an open API, so no credentials are needed to set up the surce. + +## Supported sync modes + +The xkcd source connector supports on full sync refresh. + +## Supported Streams + +That is just one stream for xkcd, that retrieves a given comic metadata. + +### Performance considerations + +XKCD does not perform rate limiting. + +## Changelog + +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.1.0 | 2022-10-17 | [18049](https://github.com/airbytehq/airbyte/pull/18049) | Initial version/release of the connector. From e7bf365d6d94b113a8f2844b0737f219747339b6 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Sat, 22 Oct 2022 18:17:32 +0300 Subject: [PATCH 264/498] Source pinterest: Fix type of `start_date` (#18285) Signed-off-by: Sergey Chvalyuk --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-pinterest/Dockerfile | 2 +- .../connectors/source-pinterest/setup.py | 4 +-- .../source_pinterest/source.py | 35 ++++++++++--------- docs/integrations/sources/pinterest.md | 3 +- 6 files changed, 25 insertions(+), 23 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 31afafc3b732..e4f28b0fc94a 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -817,7 +817,7 @@ - name: Pinterest sourceDefinitionId: 5cb7e5fe-38c2-11ec-8d3d-0242ac130003 dockerRepository: airbyte/source-pinterest - dockerImageTag: 0.1.7 + dockerImageTag: 0.1.8 documentationUrl: https://docs.airbyte.com/integrations/sources/pinterest icon: pinterest.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 20abda6b515b..348a435fc32e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -8478,7 +8478,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-pinterest:0.1.7" +- dockerImage: "airbyte/source-pinterest:0.1.8" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/pinterest" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-pinterest/Dockerfile b/airbyte-integrations/connectors/source-pinterest/Dockerfile index 65269be425f6..5b53c149a444 100644 --- a/airbyte-integrations/connectors/source-pinterest/Dockerfile +++ b/airbyte-integrations/connectors/source-pinterest/Dockerfile @@ -34,5 +34,5 @@ COPY source_pinterest ./source_pinterest ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.7 +LABEL io.airbyte.version=0.1.8 LABEL io.airbyte.name=airbyte/source-pinterest diff --git a/airbyte-integrations/connectors/source-pinterest/setup.py b/airbyte-integrations/connectors/source-pinterest/setup.py index d11df2f2cdd4..32f7388f2937 100644 --- a/airbyte-integrations/connectors/source-pinterest/setup.py +++ b/airbyte-integrations/connectors/source-pinterest/setup.py @@ -5,9 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = [ - "airbyte-cdk", -] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2", "pendulum~=2.1.2"] TEST_REQUIREMENTS = [ "pytest~=6.1", diff --git a/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py b/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py index f29fe9587a0d..4d97641c766c 100644 --- a/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py +++ b/airbyte-integrations/connectors/source-pinterest/source_pinterest/source.py @@ -128,8 +128,8 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: class IncrementalPinterestStream(PinterestStream, ABC): def get_updated_state(self, current_stream_state: MutableMapping[str, Any], latest_record: Mapping[str, Any]) -> Mapping[str, Any]: - latest_state = latest_record.get(self.cursor_field, self.start_date) - current_state = current_stream_state.get(self.cursor_field, self.start_date) + latest_state = latest_record.get(self.cursor_field, self.start_date.format("YYYY-MM-DD")) + current_state = current_stream_state.get(self.cursor_field, self.start_date.format("YYYY-MM-DD")) if isinstance(latest_state, int) and isinstance(current_state, str): current_state = datetime.strptime(current_state, "%Y-%m-%d").timestamp() @@ -152,7 +152,7 @@ def stream_slices( ...] """ - start_date = pendulum.parse(self.start_date) + start_date = self.start_date end_date = pendulum.now() # determine stream_state, if no stream_state we use start_date @@ -293,6 +293,20 @@ def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: class SourcePinterest(AbstractSource): + def _validate_and_transform(self, config: Mapping[str, Any]): + today = pendulum.today() + AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP = 914 + latest_date_allowed_by_api = today.subtract(days=AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP) + + start_date = config["start_date"] + if not start_date: + config["start_date"] = latest_date_allowed_by_api + else: + config["start_date"] = pendulum.from_format(config["start_date"], "YYYY-MM-DD") + if (today - config["start_date"]).days > AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP: + config["start_date"] = latest_date_allowed_by_api + return config + @staticmethod def get_authenticator(config): config = config.get("credentials") or config @@ -310,6 +324,7 @@ def get_authenticator(config): ) def check_connection(self, logger, config) -> Tuple[bool, any]: + config = self._validate_and_transform(config) authenticator = self.get_authenticator(config) url = f"{PinterestStream.url_base}user_account" auth_headers = {"Accept": "application/json", **authenticator.get_auth_header()} @@ -321,19 +336,7 @@ def check_connection(self, logger, config) -> Tuple[bool, any]: return False, e def streams(self, config: Mapping[str, Any]) -> List[Stream]: - today = pendulum.today() - AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP = 914 - latest_date_allowed_by_api = today.subtract(days=AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP) - - start_date = config.get("start_date") - if not start_date: - config["start_date"] = latest_date_allowed_by_api - else: - start_date_formatted = pendulum.from_format(config["start_date"], "YYYY-MM-DD") - delta_today_start_date = today - start_date_formatted - if delta_today_start_date.days > AMOUNT_OF_DAYS_ALLOWED_FOR_LOOKUP: - config["start_date"] = latest_date_allowed_by_api - + config = self._validate_and_transform(config) config["authenticator"] = self.get_authenticator(config) return [ AdAccountAnalytics(AdAccounts(config), config=config), diff --git a/docs/integrations/sources/pinterest.md b/docs/integrations/sources/pinterest.md index 429baf005300..cbcb3262e828 100644 --- a/docs/integrations/sources/pinterest.md +++ b/docs/integrations/sources/pinterest.md @@ -73,7 +73,8 @@ Boards streams - 10 calls per sec / per user / per app | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------ | -| 0.1.7 | 2022-09-29 | [17387](https://github.com/airbytehq/airbyte/pull/17387) | Set `start_date` dynamically based on API restrictions. +| 0.1.8 | 2022-10-21 | [18285](https://github.com/airbytehq/airbyte/pull/18285) | Fix type of `start_date` | +| 0.1.7 | 2022-09-29 | [17387](https://github.com/airbytehq/airbyte/pull/17387) | Set `start_date` dynamically based on API restrictions. | | 0.1.6 | 2022-09-28 | [17304](https://github.com/airbytehq/airbyte/pull/17304) | Use CDK 0.1.89 | | 0.1.5 | 2022-09-16 | [16799](https://github.com/airbytehq/airbyte/pull/16799) | Migrate to per-stream state | | 0.1.4 | 2022-09-06 | [16161](https://github.com/airbytehq/airbyte/pull/16161) | Added ability to handle `429 - Too Many Requests` error with respect to `Max Rate Limit Exceeded Error` | From 69092edf1782efd82687d9c3688c498c27508882 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Sat, 22 Oct 2022 20:47:32 +0300 Subject: [PATCH 265/498] requester = MagicMock(use_cache=False) (#18289) Signed-off-by: Sergey Chvalyuk --- .../retrievers/test_simple_retriever.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py b/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py index 064bef0f8786..c8ca6d730719 100644 --- a/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py +++ b/airbyte-cdk/python/unit_tests/sources/declarative/retrievers/test_simple_retriever.py @@ -115,7 +115,7 @@ def test_simple_retriever_full(mock_http_stream): ], ) def test_should_retry(test_name, requester_response, expected_should_retry, expected_backoff_time): - requester = MagicMock() + requester = MagicMock(use_cache=False) retriever = SimpleRetriever(name="stream_name", primary_key=primary_key, requester=requester, record_selector=MagicMock(), options={}) requester.should_retry.return_value = requester_response assert retriever.should_retry(requests.Response()) == expected_should_retry @@ -132,7 +132,7 @@ def test_should_retry(test_name, requester_response, expected_should_retry, expe ], ) def test_parse_response(test_name, status_code, response_status, len_expected_records): - requester = MagicMock() + requester = MagicMock(use_cache=False) record_selector = MagicMock() record_selector.select_records.return_value = [{"id": 100}] retriever = SimpleRetriever( @@ -162,7 +162,7 @@ def test_parse_response(test_name, status_code, response_status, len_expected_re ], ) def test_backoff_time(test_name, response_action, retry_in, expected_backoff_time): - requester = MagicMock() + requester = MagicMock(use_cache=False) record_selector = MagicMock() record_selector.select_records.return_value = [{"id": 100}] response = requests.Response() @@ -205,7 +205,7 @@ def test_get_request_options_from_pagination(test_name, paginator_mapping, strea stream_slicer.get_request_body_json.return_value = stream_slicer_mapping base_mapping = {"key": "value"} - requester = MagicMock() + requester = MagicMock(use_cache=False) requester.get_request_params.return_value = base_mapping requester.get_request_body_data.return_value = base_mapping requester.get_request_body_json.return_value = base_mapping @@ -251,7 +251,7 @@ def test_get_request_headers(test_name, paginator_mapping, expected_mapping): # This test is separate from the other request options because request headers must be strings paginator = MagicMock() paginator.get_request_headers.return_value = paginator_mapping - requester = MagicMock() + requester = MagicMock(use_cache=False) base_mapping = {"key": "value"} requester.get_request_headers.return_value = base_mapping @@ -290,7 +290,7 @@ def test_get_request_headers(test_name, paginator_mapping, expected_mapping): def test_request_body_data(test_name, requester_body_data, paginator_body_data, expected_body_data): paginator = MagicMock() paginator.get_request_body_data.return_value = paginator_body_data - requester = MagicMock() + requester = MagicMock(use_cache=False) requester.get_request_body_data.return_value = requester_body_data @@ -325,7 +325,7 @@ def test_request_body_data(test_name, requester_body_data, paginator_body_data, def test_path(test_name, requester_path, paginator_path, expected_path): paginator = MagicMock() paginator.path.return_value = paginator_path - requester = MagicMock() + requester = MagicMock(use_cache=False) requester.get_path.return_value = requester_path From 710c432f00e4985981efb8a001839eff1241247a Mon Sep 17 00:00:00 2001 From: Augustin Date: Mon, 24 Oct 2022 11:19:21 +0200 Subject: [PATCH 266/498] fix Connector Base build (#18351) --- .../src/main/resources/seed/source_definitions.yaml | 7 ------- .../init/src/main/resources/seed/source_specs.yaml | 10 ---------- .../destination/s3/avro/AvroSerializedBufferTest.java | 2 +- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index e4f28b0fc94a..de0d83896049 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1148,13 +1148,6 @@ icon: victorops.svg sourceType: api releaseStage: alpha -- name: xkcd - sourceDefinitionId: 80fddd16-17bd-4c0c-bf4a-80df7863fc9d - dockerRepository: airbyte/source-xkcd - dockerImageTag: 0.1.0 - documentationUrl: https://docs.airbyte.com/integrations/sources/xkcd - sourceType: api - releaseStage: alpha - name: Webflow sourceDefinitionId: ef580275-d9a9-48bb-af5e-db0f5855be04 dockerRepository: airbyte/source-webflow diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 348a435fc32e..e3f0004fddf3 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11815,16 +11815,6 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-xkcd:0.1.0" - spec: - documentationUrl: "https://docs.airbyte.io/integrations/sources/xkcd" - connectionSpecification: - $schema: "http://json-schema.org/draft-07/schema#" - title: "Xkcd Spec" - type: "object" - supportsNormalization: false - supportsDBT: false - supported_destination_sync_modes: [] - dockerImage: "airbyte/source-webflow:0.1.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/webflow" diff --git a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/avro/AvroSerializedBufferTest.java b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/avro/AvroSerializedBufferTest.java index e59a03fadf08..77ea3af80dbe 100644 --- a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/avro/AvroSerializedBufferTest.java +++ b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/avro/AvroSerializedBufferTest.java @@ -53,7 +53,7 @@ public class AvroSerializedBufferTest { public void testSnappyAvroWriter() throws Exception { final S3AvroFormatConfig config = new S3AvroFormatConfig(Jsons.jsonNode(Map.of("compression_codec", Map.of( "codec", "snappy")))); - runTest(new InMemoryBuffer(AvroSerializedBuffer.DEFAULT_SUFFIX), 965L, 985L, config, getExpectedString()); + runTest(new InMemoryBuffer(AvroSerializedBuffer.DEFAULT_SUFFIX), 964L, 985L, config, getExpectedString()); } @Test From a7fd147018ecd8bd03461655b356306bfb8b9bbc Mon Sep 17 00:00:00 2001 From: Augustin Date: Mon, 24 Oct 2022 12:25:23 +0200 Subject: [PATCH 267/498] SAT: new `test_strictness_level` field in config (#18218) --- .../bases/source-acceptance-test/CHANGELOG.md | 3 ++ .../bases/source-acceptance-test/Dockerfile | 2 +- .../source_acceptance_test/config.py | 8 ++++- .../source_acceptance_test/conftest.py | 13 ++++--- .../unit_tests/test_config.py | 36 +++++++++++++++++++ 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index 9f06e9a56a2d..acacdf56897b 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.11 +Declare `test_strictness_level` field in test configuration. [#18218](https://github.com/airbytehq/airbyte/pull/18218). + ## 0.2.10 Bump `airbyte-cdk~=0.2.0` diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index fc9bcf64a2a0..833ef107eeef 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.10 +LABEL io.airbyte.version=0.2.11 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py index 1e1e6f8ee4da..158f1e70529a 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py @@ -143,6 +143,12 @@ class TestConfig(BaseConfig): class Config(BaseConfig): + class TestStrictnessLevel(str, Enum): + high = "high" + connector_image: str = Field(description="Docker image to test, for example 'airbyte/source-hubspot:dev'") - base_path: Optional[str] = Field(description="Base path for all relative paths") tests: TestConfig = Field(description="List of the tests with their configs") + base_path: Optional[str] = Field(description="Base path for all relative paths") + test_strictness_level: Optional[TestStrictnessLevel] = Field( + description="Corresponds to a strictness level of the test suite and will change which tests are mandatory for a successful run." + ) diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/conftest.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/conftest.py index 8c71b02db7fb..ac0f79760c1d 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/conftest.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/conftest.py @@ -28,6 +28,12 @@ from source_acceptance_test.utils import ConnectorRunner, SecretDict, filter_output, load_config, load_yaml_or_json_path +@pytest.fixture(name="acceptance_test_config", scope="session") +def acceptance_test_config_fixture(pytestconfig) -> Config: + """Fixture with test's config""" + return load_config(pytestconfig.getoption("--acceptance-test-config", skip=True)) + + @pytest.fixture(name="base_path") def base_path_fixture(pytestconfig, acceptance_test_config) -> Path: """Fixture to define base path for every path-like fixture""" @@ -36,10 +42,9 @@ def base_path_fixture(pytestconfig, acceptance_test_config) -> Path: return Path(pytestconfig.getoption("--acceptance-test-config")).absolute() -@pytest.fixture(name="acceptance_test_config", scope="session") -def acceptance_test_config_fixture(pytestconfig) -> Config: - """Fixture with test's config""" - return load_config(pytestconfig.getoption("--acceptance-test-config", skip=True)) +@pytest.fixture(name="test_strictness_level", scope="session") +def test_strictness_level_fixture(acceptance_test_config: Config): + return acceptance_test_config.test_strictness_level @pytest.fixture(name="connector_config_path") diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py new file mode 100644 index 000000000000..6778b9c68b11 --- /dev/null +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py @@ -0,0 +1,36 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import pytest +from pydantic import ValidationError +from source_acceptance_test import config + +from .conftest import does_not_raise + + +class TestConfig: + @pytest.mark.parametrize( + "raw_config, expected_test_strictness_level, expected_error", + [ + pytest.param( + {"connector_image": "foo", "tests": {}}, None, does_not_raise(), id="No test_strictness_level declared defaults to None." + ), + pytest.param( + {"connector_image": "foo", "tests": {}, "test_strictness_level": "high"}, + config.Config.TestStrictnessLevel.high, + does_not_raise(), + id="The test_strictness_level set to strict is a valid enum value is provided.", + ), + pytest.param( + {"connector_image": "foo", "tests": {}, "test_strictness_level": "unknown"}, + None, + pytest.raises(ValidationError), + id="Validation error is raised when an invalid enum is passed.", + ), + ], + ) + def test_test_strictness_level(self, raw_config, expected_test_strictness_level, expected_error): + with expected_error: + parsed_config = config.Config.parse_obj(raw_config) + assert parsed_config.test_strictness_level == expected_test_strictness_level From f06bd40fcea18a0c6ce50d30ab1c2900d77dd12c Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 24 Oct 2022 15:22:15 +0300 Subject: [PATCH 268/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=90=9B=20Fixed=20s?= =?UTF-8?q?tatic=20=20width=20(#18233)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed ConditionSection width * fixed relative scss colors import --- .../components/Sections/ConditionSection.module.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/ConditionSection.module.scss b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/ConditionSection.module.scss index 98e1c235709f..abd86c78739e 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/ConditionSection.module.scss +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/ConditionSection.module.scss @@ -1,8 +1,8 @@ -@use "../../../../../scss/colors"; +@use "scss/colors"; .groupDropdown { margin-left: auto; padding: 0 2px; background-color: colors.$white; - min-width: 240px; + min-width: calc(50% - 100px); } From 5e302e0a8a9a47a95dd9e80841ff075ea926d55d Mon Sep 17 00:00:00 2001 From: Baz Date: Mon, 24 Oct 2022 15:23:15 +0300 Subject: [PATCH 269/498] =?UTF-8?q?=F0=9F=8E=89=20Source=20Shopify:=20upda?= =?UTF-8?q?te=20API=20version=20to=20`2022-10`=20(#18298)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-shopify/Dockerfile | 2 +- .../source-shopify/acceptance-test-config.yml | 11 ++---- .../schemas/abandoned_checkouts.json | 1 + .../source_shopify/schemas/articles.json | 1 + .../schemas/balance_transactions.json | 1 + .../source_shopify/schemas/blogs.json | 1 + .../source_shopify/schemas/collections.json | 1 + .../source_shopify/schemas/collects.json | 1 + .../schemas/custom_collections.json | 1 + .../source_shopify/schemas/customers.json | 1 + .../schemas/discount_codes.json | 1 + .../source_shopify/schemas/draft_orders.json | 1 + .../schemas/fulfillment_orders.json | 1 + .../source_shopify/schemas/fulfillments.json | 1 + .../schemas/inventory_items.json | 1 + .../schemas/inventory_levels.json | 1 + .../source_shopify/schemas/locations.json | 1 + .../schemas/metafield_articles.json | 1 + .../schemas/metafield_blogs.json | 1 + .../schemas/metafield_collections.json | 1 + .../schemas/metafield_customers.json | 1 + .../schemas/metafield_draft_orders.json | 1 + .../schemas/metafield_locations.json | 1 + .../schemas/metafield_orders.json | 1 + .../schemas/metafield_pages.json | 1 + .../schemas/metafield_product_images.json | 1 + .../schemas/metafield_product_variants.json | 1 + .../schemas/metafield_products.json | 1 + .../schemas/metafield_shops.json | 1 + .../schemas/metafield_smart_collections.json | 1 + .../source_shopify/schemas/order_refunds.json | 1 + .../source_shopify/schemas/order_risks.json | 1 + .../source_shopify/schemas/orders.json | 1 + .../source_shopify/schemas/pages.json | 1 + .../source_shopify/schemas/price_rules.json | 1 + .../schemas/product_images.json | 1 + .../schemas/product_variants.json | 1 + .../source_shopify/schemas/products.json | 1 + .../source_shopify/schemas/shop.json | 1 + .../schemas/smart_collections.json | 1 + .../schemas/tender_transactions.json | 1 + .../source_shopify/schemas/transactions.json | 1 + .../source-shopify/source_shopify/source.py | 35 ++++++++++--------- docs/integrations/sources/shopify.md | 1 + 46 files changed, 65 insertions(+), 28 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index de0d83896049..4dee616385ea 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -983,7 +983,7 @@ - name: Shopify sourceDefinitionId: 9da77001-af33-4bcd-be46-6252bf9342b9 dockerRepository: airbyte/source-shopify - dockerImageTag: 0.1.39 + dockerImageTag: 0.2.0 documentationUrl: https://docs.airbyte.com/integrations/sources/shopify icon: shopify.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index e3f0004fddf3..0aeba3f5630f 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -10197,7 +10197,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-shopify:0.1.39" +- dockerImage: "airbyte/source-shopify:0.2.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/shopify" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-shopify/Dockerfile b/airbyte-integrations/connectors/source-shopify/Dockerfile index 62bbff5455f6..5925cb39c485 100644 --- a/airbyte-integrations/connectors/source-shopify/Dockerfile +++ b/airbyte-integrations/connectors/source-shopify/Dockerfile @@ -28,5 +28,5 @@ COPY source_shopify ./source_shopify ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.39 +LABEL io.airbyte.version=0.2.0 LABEL io.airbyte.name=airbyte/source-shopify diff --git a/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml b/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml index 5c542bfe96e0..af7a2ec30b0c 100644 --- a/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-shopify/acceptance-test-config.yml @@ -17,18 +17,12 @@ tests: status: "failed" discovery: - config_path: "secrets/config.json" - backward_compatibility_tests_config: - disable_for_version: "0.1.38" - config_path: "secrets/config_old.json" - backward_compatibility_tests_config: - disable_for_version: "0.1.38" - config_path: "secrets/config_oauth.json" - backward_compatibility_tests_config: - disable_for_version: "0.1.38" basic_read: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" - timeout_seconds: 3600 + timeout_seconds: 7200 # some streams hold data only for some time, therefore certain streams could be empty while sync. # 'abandoned_checkouts' stream holds data up to 1 month. empty_streams: ["abandoned_checkouts", "balance_transactions"] @@ -36,7 +30,8 @@ tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" future_state_path: "integration_tests/abnormal_state.json" + timeout_seconds: 3600 full_refresh: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" - timeout_seconds: 3600 + timeout_seconds: 7200 diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/abandoned_checkouts.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/abandoned_checkouts.json index f9be732c12d5..6c9d434e6462 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/abandoned_checkouts.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/abandoned_checkouts.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "note_attributes": { "type": ["null", "array"], diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json index f1b09743f014..85ec2388ea75 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/articles.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/balance_transactions.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/balance_transactions.json index 25fc3606f6ac..6ada82c01327 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/balance_transactions.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/balance_transactions.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "id": { "type": "integer" diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json index 003a867b5672..a9ea23eea82e 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/blogs.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "commentable": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json index 6243a695693e..bd34860778a7 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collections.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collects.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collects.json index 05495af8ce4d..30b93e0e7ca0 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collects.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/collects.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/custom_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/custom_collections.json index a52a84d3ed67..85041b590019 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/custom_collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/custom_collections.json @@ -57,5 +57,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/customers.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/customers.json index b7ef5f06b574..6e3b3b41d756 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/customers.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/customers.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "last_order_name": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/discount_codes.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/discount_codes.json index 7f5f0d425bca..f3e208e014c5 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/discount_codes.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/discount_codes.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/draft_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/draft_orders.json index 3c30979cfb68..d08ac15f22da 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/draft_orders.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/draft_orders.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillment_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillment_orders.json index b4ae5dc07d09..9d3a417eefe5 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillment_orders.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillment_orders.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "assigned_location_id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillments.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillments.json index c106c0b50f76..8822ba1be0cc 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillments.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/fulfillments.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "created_at": { "type": ["null", "string"], diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_items.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_items.json index 908bf2a21b50..bba96062be2d 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_items.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_items.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_levels.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_levels.json index 729bbbc28e0b..6a0ce92a52c7 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_levels.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/inventory_levels.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/locations.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/locations.json index a25575e2a3bd..50dde9482014 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/locations.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/locations.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "active": { "type": ["null", "boolean"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_articles.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_blogs.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_blogs.json index 3a4a7bc1fcbc..d90da9f20257 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_blogs.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_blogs.json @@ -39,5 +39,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json index 3a4a7bc1fcbc..d90da9f20257 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_collections.json @@ -39,5 +39,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_customers.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_draft_orders.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_locations.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_orders.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_pages.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_images.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_product_variants.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_products.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_shops.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json index 1e91c726368f..b83ed8ea6279 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/metafield_smart_collections.json @@ -42,5 +42,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_refunds.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_refunds.json index dbb0b7fbf1ea..764854896123 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_refunds.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_refunds.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "order_id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_risks.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_risks.json index 04d364c93f80..ea6f5a04b671 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_risks.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/order_risks.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/orders.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/orders.json index 18da58fcc2c2..7100f8be9934 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/orders.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/orders.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/pages.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/pages.json index 95de888f2080..444ae832bc8f 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/pages.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/pages.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "author": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/price_rules.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/price_rules.json index b04a19349cd0..eb95deb9da9e 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/price_rules.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/price_rules.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "allocation_method": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json index 378cb2463ca8..8463b07565b6 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_images.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "created_at": { "type": ["null", "string"], diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json index 9b958269e78c..1a9554c48751 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/product_variants.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/products.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/products.json index ca403f562201..90066b9aefeb 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/products.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/products.json @@ -1,5 +1,6 @@ { "type": ["object", "null"], + "additionalProperties": true, "properties": { "published_at": { "type": ["null", "string"], diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/shop.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/shop.json index d29981f47486..9a41fd623e13 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/shop.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/shop.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "address1": { "type": ["null", "string"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json index a931b2c72b76..8289d6d03ba5 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/smart_collections.json @@ -1,5 +1,6 @@ { "type": ["null", "object"], + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/tender_transactions.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/tender_transactions.json index 5fce094cc681..2e52c04a203a 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/tender_transactions.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/tender_transactions.json @@ -1,5 +1,6 @@ { "type": "object", + "additionalProperties": true, "properties": { "id": { "type": ["null", "integer"] diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/transactions.json b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/transactions.json index a3410fcf8a70..22590970d6cf 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/transactions.json +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/schemas/transactions.json @@ -96,5 +96,6 @@ "type": ["null", "string"] } }, + "additionalProperties": true, "type": ["null", "object"] } diff --git a/airbyte-integrations/connectors/source-shopify/source_shopify/source.py b/airbyte-integrations/connectors/source-shopify/source_shopify/source.py index 700d9113bd1b..9f32c1209d49 100644 --- a/airbyte-integrations/connectors/source-shopify/source_shopify/source.py +++ b/airbyte-integrations/connectors/source-shopify/source_shopify/source.py @@ -22,7 +22,7 @@ class ShopifyStream(HttpStream, ABC): # Latest Stable Release - api_version = "2021-07" + api_version = "2022-10" # Page size limit = 250 # Define primary key as sort key for full_refresh, or very first sync for incremental_refresh @@ -67,26 +67,27 @@ def request_params(self, next_page_token: Mapping[str, Any] = None, **kwargs) -> @limiter.balance_rate_limit() def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: - json_response = response.json() or {} - records = json_response.get(self.data_field, []) if self.data_field is not None else json_response - # transform method was implemented according to issue 4841 - # Shopify API returns price fields as a string and it should be converted to number - # this solution designed to convert string into number, but in future can be modified for general purpose - if isinstance(records, dict): - # for cases when we have a single record as dict - # add shop_url to the record to make querying easy - records["shop_url"] = self.config["shop"] - yield self._transformer.transform(records) - else: - # for other cases - for record in records: + if response.status_code is requests.codes.OK: + json_response = response.json() + records = json_response.get(self.data_field, []) if self.data_field is not None else json_response + # transform method was implemented according to issue 4841 + # Shopify API returns price fields as a string and it should be converted to number + # this solution designed to convert string into number, but in future can be modified for general purpose + if isinstance(records, dict): + # for cases when we have a single record as dict # add shop_url to the record to make querying easy - record["shop_url"] = self.config["shop"] - yield self._transformer.transform(record) + records["shop_url"] = self.config["shop"] + yield self._transformer.transform(records) + else: + # for other cases + for record in records: + # add shop_url to the record to make querying easy + record["shop_url"] = self.config["shop"] + yield self._transformer.transform(record) def should_retry(self, response: requests.Response) -> bool: if response.status_code == 404: - self.logger.warn(f"Stream `{self.name}` is not available, skipping.") + self.logger.warn(f"Stream `{self.name}` is not available, skipping...") setattr(self, "raise_on_http_errors", False) return False return super().should_retry(response) diff --git a/docs/integrations/sources/shopify.md b/docs/integrations/sources/shopify.md index da8df47995ec..c8bd98223f2a 100644 --- a/docs/integrations/sources/shopify.md +++ b/docs/integrations/sources/shopify.md @@ -145,6 +145,7 @@ This is expected when the connector hits the 429 - Rate Limit Exceeded HTTP Erro | Version | Date | Pull Request | Subject | |:--------|:-----------|:----------------------------------------------------------|:----------------------------------------------------------------------------------------------------------| +| 0.2.0 | 2022-10-21 | [18298](https://github.com/airbytehq/airbyte/pull/18298) | Updated API version to the `2022-10`, make stream schemas backward cpmpatible | | 0.1.39 | 2022-10-13 | [17962](https://github.com/airbytehq/airbyte/pull/17962) | Add metafield streams; support for nested list streams | | 0.1.38 | 2022-10-10 | [17777](https://github.com/airbytehq/airbyte/pull/17777) | Fixed `404` for configured streams, fix missing `cursor` error for old records | | 0.1.37 | 2022-04-30 | [12500](https://github.com/airbytehq/airbyte/pull/12500) | Improve input configuration copy | From 51cfebce60d022dfc09f62722ea9950e7f9f6a6c Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Mon, 24 Oct 2022 16:53:59 +0300 Subject: [PATCH 270/498] Update airbyte-db reference (#18368) --- charts/airbyte/templates/airbyte-db.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/airbyte/templates/airbyte-db.yaml b/charts/airbyte/templates/airbyte-db.yaml index 8aad9bdfb920..681e841a569b 100644 --- a/charts/airbyte/templates/airbyte-db.yaml +++ b/charts/airbyte/templates/airbyte-db.yaml @@ -38,7 +38,7 @@ spec: spec: containers: - name: airbyte-db-container - image: airbyte/db:{{ ((.Values.global.image).tag) | default "a03509b" }} + image: airbyte/db:{{ ((.Values.global.image).tag) | default .Chart.AppVersion }} env: - name: POSTGRES_DB value: {{ .Values.postgresql.postgresqlDatabase }} From 3524a8aeab8e3e873daf98b2d2a6188a5c7ddaa7 Mon Sep 17 00:00:00 2001 From: Joey Marshment-Howell Date: Mon, 24 Oct 2022 15:59:04 +0200 Subject: [PATCH 271/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=90=9B=20Load=20gl?= =?UTF-8?q?obal=20CSS=20in=20storybook=20(#18353)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airbyte-webapp/.storybook/preview.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/airbyte-webapp/.storybook/preview.ts b/airbyte-webapp/.storybook/preview.ts index 339b28592c02..c3ea6ed642fe 100644 --- a/airbyte-webapp/.storybook/preview.ts +++ b/airbyte-webapp/.storybook/preview.ts @@ -2,6 +2,8 @@ import { addDecorator } from "@storybook/react"; import { withProviders } from "./withProvider"; +import "!style-loader!css-loader!sass-loader!../public/index.css"; + addDecorator(withProviders); export const parameters = {}; From f61a420d140f8fec388daed0ba67fe8d92cb2400 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 18:21:06 +0300 Subject: [PATCH 272/498] Bump helm chart version reference to 0.40.33 (#18371) Co-authored-by: xpuska513 Co-authored-by: Kyryl Skobylko --- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-metrics/Chart.yaml | 2 +- charts/airbyte-pod-sweeper/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 19 +++++++++---------- charts/airbyte/Chart.yaml | 16 ++++++++-------- 8 files changed, 23 insertions(+), 24 deletions(-) diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index ce32d8c33561..95207349d5a0 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.27" +version: "0.40.33" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index 20d195f11521..f0bfb04e7636 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.27" +version: "0.40.33" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index 1a944c4034c8..8320b8101fbf 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.27" +version: "0.40.33" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index b2ebcefa54cc..fd982a1a1407 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.27" +version: "0.40.33" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index 033e1735df0d..0ff920018f81 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.27" +version: "0.40.33" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index ac2137fbc51f..e908126a1d1f 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.27" +version: "0.40.33" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index bf4e6856b98d..1cc2f56e434d 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -4,25 +4,24 @@ dependencies: version: 1.17.1 - name: airbyte-bootloader repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 + version: 0.40.33 - name: temporal repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 + version: 0.40.33 - name: webapp repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 + version: 0.40.33 - name: server repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 + version: 0.40.33 - name: worker repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 + version: 0.40.33 - name: pod-sweeper repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 + version: 0.40.33 - name: metrics repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.27 -digest: sha256:23e474cb8e2a7ff58cc911ef61eec81f7e2068c9a3b7ca6bfb22a45018a0fbed -generated: "2022-10-19T07:02:23.354665679Z" - + version: 0.40.33 +digest: sha256:2b6db6ac5a15f3bc2e53c7453ef704f4a6f64ee276ff60a2d7f7d0947fe0a2df +generated: "2022-10-24T14:01:11.885475748Z" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 15dd6cc78bdf..9b243aeec257 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.40.27 +version: 0.40.33 # This is the version number of the application being deployed. This version number should be @@ -33,29 +33,29 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.27 + version: 0.40.33 From 08eb62d118c5593eac3c8f33493d81070f8ce780 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Mon, 24 Oct 2022 08:55:53 -0700 Subject: [PATCH 273/498] enable support for publishing datadog metrics from a local environment (#18282) * local datadog docker-compose * add datadog docker-compose * update comments --- .gitignore | 3 ++ docker-compose.datadog.yaml | 55 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 docker-compose.datadog.yaml diff --git a/.gitignore b/.gitignore index f8e7ba1032cc..090995b262b4 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ docs/SUMMARY.md # Helm charts .tgz dependencies charts/**/charts + +# Datadog +dd-java-agent.jar diff --git a/docker-compose.datadog.yaml b/docker-compose.datadog.yaml new file mode 100644 index 000000000000..0463bfcb2df4 --- /dev/null +++ b/docker-compose.datadog.yaml @@ -0,0 +1,55 @@ +# Adds settings for allowing datadog metrics to be published from a local environment. +# This exists _only_ for testing datadog integrations! +# +# Usage: +# 1. create an API Key in datadog +# 2. wget -O dd-java-agent.jar 'https://dtdg.co/latest-java-tracer' +# 3. DD_API_KEY=[datadog api key] VERSION=dev docker-compose -f docker-compose.yaml -f docker-compose.datadog.yaml up -d +version: "3.7" + +x-datadog: &datadogged + volumes: + - type: bind + source: ./dd-java-agent.jar + target: /dd-java-agent.jar + environment: + - DD_AGENT_HOST=airbyte-datadog + - DD_TRACE_AGENT_URL=http://airbyte-datadog:8126 + - JAVA_OPTS=-javaagent:/dd-java-agent.jar + +services: + datadog: + image: datadog/agent:7-rc + container_name: airbyte-datadog + environment: + - DD_API_KEY + - DD_ENV=${DD_ENV:-local} + - DD_SITE=datadoghq.com + - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true + - DD_APM_ENABLED=true + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /proc/:/host/proc/:ro + - /sys/fs/cgroup:/host/sys/fs/cgroup:ro + networks: + - airbyte_internal + init: + <<: *datadogged + environment: + - DD_SERVICE=airbyte-init + bootloader: + <<: *datadogged + environment: + - DD_SERVICE=airbyte-bootloader + worker: + <<: *datadogged + environment: + - DD_SERVICE=airbyte-worker + server: + <<: *datadogged + environment: + - DD_SERVICE=airbyte-server + airbyte-cron: + <<: *datadogged + environment: + - DD_SERVICE=airbyte-cron From 2a2639a52bf97593d026acd8f909edf6f724f95f Mon Sep 17 00:00:00 2001 From: Taras Korenko Date: Mon, 24 Oct 2022 19:20:01 +0300 Subject: [PATCH 274/498] Simplify the OSS documentation deploy system (#2670) (#18377) + Adds a placeholder for 'Deploy Docs' Github Workflow. --- .github/workflows/deploy-docs-site.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/deploy-docs-site.yml diff --git a/.github/workflows/deploy-docs-site.yml b/.github/workflows/deploy-docs-site.yml new file mode 100644 index 000000000000..497804a288ff --- /dev/null +++ b/.github/workflows/deploy-docs-site.yml @@ -0,0 +1,22 @@ +name: Deploy docs.airbyte.com [DUMMY workflow] + +on: + ## XXX uncomment the following when this code gets in good shape + #push: + # branches: + # - master + # paths: + # - 'docs/**' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + dummy-job: + name: A placeholder job + runs-on: ubuntu-latest + steps: + - name: A placeholder step + shell: bash + run: |- + echo "Hello from 'Deploy Docs' workflow!" From 74defd1f399c829b9bc81699f608fff1ed2f8405 Mon Sep 17 00:00:00 2001 From: Conor Date: Mon, 24 Oct 2022 11:20:52 -0500 Subject: [PATCH 275/498] fix: add task to list all dependencies (#18304) * fix: add task to list all dependencies * fix: remove newline --- build.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/build.gradle b/build.gradle index 4ec3532b4cec..7f60e6eff722 100644 --- a/build.gradle +++ b/build.gradle @@ -209,6 +209,12 @@ allprojects { // Java projects common configurations subprojects { subproj -> + + configurations { + runtimeClasspath + } + + // Common Docker Configuration: // If subprojects have the dockerImageName property configured in their gradle.properties file, // register: @@ -508,6 +514,7 @@ subprojects { licenseTask.dependsOn generateFilesTask } } + task listAllDependencies(type: DependencyReportTask) {} } task('generate-docker') { From b23565e13bea310bca6a14e345bbba92124bea44 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Mon, 24 Oct 2022 09:37:02 -0700 Subject: [PATCH 276/498] remove useless json_size metric (#18247) --- .../airbyte/workers/internal/DefaultAirbyteStreamFactory.java | 1 - .../main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java | 4 ---- 2 files changed, 5 deletions(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java index 8e002e39c21a..c8bd56efa0ab 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java @@ -59,7 +59,6 @@ public Stream create(final BufferedReader bufferedReader) { .lines() .peek(str -> metricClient.distribution(OssMetricsRegistry.JSON_STRING_LENGTH, str.length())) .flatMap(this::parseJson) - .peek(json -> metricClient.distribution(OssMetricsRegistry.JSON_SIZE, json.size())) .filter(this::validate) .flatMap(this::toAirbyteMessage) .filter(this::filterLog); diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java index 3c830e2a85f1..6962b7c56c01 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/OssMetricsRegistry.java @@ -71,10 +71,6 @@ public enum OssMetricsRegistry implements MetricsRegistry { MetricEmittingApps.WORKER, "json_string_length", "string length of a raw json string"), - JSON_SIZE( - MetricEmittingApps.WORKER, - "json_size", - "size of the json object"), KUBE_POD_PROCESS_CREATE_TIME_MILLISECS( MetricEmittingApps.WORKER, "kube_pod_process_create_time_millisecs", From 5ab81e6ced064704b9a720fe622f7b5f382d0794 Mon Sep 17 00:00:00 2001 From: Eugene Date: Mon, 24 Oct 2022 19:45:37 +0300 Subject: [PATCH 277/498] =?UTF-8?q?=F0=9F=8E=89Destination-elasticsearch:?= =?UTF-8?q?=20added=20custom=20sertificate=20support=20(#18177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [11356] Destination-elasticsearch: added custom certificate support --- .../seed/destination_definitions.yaml | 2 +- .../resources/seed/destination_specs.yaml | 8 +- .../airbyte/db/util/SSLCertificateUtils.java | 20 +++ .../Dockerfile | 2 +- ...trictEncryptDestinationAcceptanceTest.java | 26 +++- .../src/test/resources/expected_spec.json | 7 + .../destination-elasticsearch/Dockerfile | 2 +- .../destination-elasticsearch/build.gradle | 1 + .../elasticsearch/ConnectorConfiguration.java | 123 +----------------- .../ElasticsearchConnection.java | 14 +- .../src/main/resources/spec.json | 7 + ...lasticsearchDestinationAcceptanceTest.java | 11 +- deps.toml | 3 +- .../destinations/elasticsearch.md | 8 +- 14 files changed, 102 insertions(+), 132 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 6daff601bdb5..780c0dc5312f 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -100,7 +100,7 @@ - destinationDefinitionId: 68f351a7-2745-4bef-ad7f-996b8e51bb8c name: ElasticSearch dockerRepository: airbyte/destination-elasticsearch - dockerImageTag: 0.1.4 + dockerImageTag: 0.1.5 documentationUrl: https://docs.airbyte.com/integrations/destinations/elasticsearch icon: elasticsearch.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 81d2e83655eb..1f71e6f53c4c 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -1700,7 +1700,7 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-elasticsearch:0.1.4" +- dockerImage: "airbyte/destination-elasticsearch:0.1.5" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/elasticsearch" connectionSpecification: @@ -1722,6 +1722,12 @@ \ will be performed using the primary key value as the elasticsearch doc\ \ id. Does not support composite primary keys." default: true + ca_certificate: + type: "string" + title: "CA certificate" + description: "CA certificate" + airbyte_secret: true + multiline: true authenticationMethod: title: "Authentication Method" type: "object" diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java index 29a87ccc52c5..32062cabfe46 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java @@ -28,6 +28,9 @@ import java.util.Objects; import java.util.Random; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -160,4 +163,21 @@ public static URI keyStoreFromClientCertificate( return keyStoreFromClientCertificate(certString, keyString, keyStorePassword, FileSystems.getDefault(), directory); } + public static SSLContext createContextFromCaCert(String caCertificate) { + try { + CertificateFactory factory = CertificateFactory.getInstance(X509); + Certificate trustedCa = factory.generateCertificate( + new ByteArrayInputStream(caCertificate.getBytes(StandardCharsets.UTF_8)) + ); + KeyStore trustStore = KeyStore.getInstance(PKCS_12); + trustStore.load(null, null); + trustStore.setCertificateEntry("ca", trustedCa); + SSLContextBuilder sslContextBuilder = + SSLContexts.custom().loadTrustMaterial(trustStore, null); + return sslContextBuilder.build(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } diff --git a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile index 830bab62e8f2..97bf1f52f693 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-elasticsearch-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.4 +LABEL io.airbyte.version=0.1.5 LABEL io.airbyte.name=airbyte/destination-elasticsearch-strict-encrypt diff --git a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java index 31232025fcbe..68859c436a47 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java @@ -4,30 +4,43 @@ package io.airbyte.integrations.destination.elasticsearch; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; +import io.airbyte.config.StandardCheckConnectionOutput.Status; import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; import io.airbyte.integrations.standardtest.destination.comparator.AdvancedTestDataComparator; import io.airbyte.integrations.standardtest.destination.comparator.TestDataComparator; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.testcontainers.elasticsearch.ElasticsearchContainer; public class ElasticsearchStrictEncryptDestinationAcceptanceTest extends DestinationAcceptanceTest { - private final ObjectMapper mapper = new ObjectMapper(); private static ElasticsearchContainer container; + private static final String IMAGE_NAME = "docker.elastic.co/elasticsearch/elasticsearch:8.3.3"; + private final ObjectMapper mapper = new ObjectMapper(); @BeforeAll public static void beforeAll() { - container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.15.1") - .withPassword("MagicWord"); + container = new ElasticsearchContainer(IMAGE_NAME) + .withEnv("discovery.type", "single-node") + .withEnv("network.host", "0.0.0.0") + .withEnv("logger.org.elasticsearch", "INFO") + .withEnv("ingest.geoip.downloader.enabled", "false") + .withExposedPorts(9200) + .withPassword("s3cret"); container.start(); } @@ -84,11 +97,14 @@ protected JsonNode getConfig() { final JsonNode authConfig = Jsons.jsonNode(Map.of( "method", "basic", "username", "elastic", - "password", "MagicWord")); + "password", "s3cret")); return Jsons.jsonNode(ImmutableMap.builder() - .put("endpoint", String.format("http://%s:%s", container.getHost(), container.getMappedPort(9200))) + .put("endpoint", String.format("https://%s:%s", container.getHost(), container.getMappedPort(9200))) .put("authenticationMethod", authConfig) + .put("ca_certificate", new String(container.copyFileFromContainer( + "/usr/share/elasticsearch/config/certs/http_ca.crt", + InputStream::readAllBytes), StandardCharsets.UTF_8)) .build()); } diff --git a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json index a951230fe95a..7fd2ba80689d 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json +++ b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test/resources/expected_spec.json @@ -23,6 +23,13 @@ "description": "If a primary key identifier is defined in the source, an upsert will be performed using the primary key value as the elasticsearch doc id. Does not support composite primary keys.", "default": true }, + "ca_certificate": { + "type": "string", + "title": "CA certificate", + "description": "CA certificate", + "airbyte_secret": true, + "multiline": true + }, "authenticationMethod": { "title": "Authentication Method", "type": "object", diff --git a/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile b/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile index 362a4df7beaf..f78e88860504 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile +++ b/airbyte-integrations/connectors/destination-elasticsearch/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION destination-elasticsearch COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.1.4 +LABEL io.airbyte.version=0.1.5 LABEL io.airbyte.name=airbyte/destination-elasticsearch diff --git a/airbyte-integrations/connectors/destination-elasticsearch/build.gradle b/airbyte-integrations/connectors/destination-elasticsearch/build.gradle index dc5b8e7c8788..469ad4991a16 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/build.gradle +++ b/airbyte-integrations/connectors/destination-elasticsearch/build.gradle @@ -10,6 +10,7 @@ application { } dependencies { + implementation project(':airbyte-db:db-lib') implementation project(':airbyte-config:config-models') implementation project(':airbyte-protocol:protocol-models') implementation project(':airbyte-integrations:bases:base-java') diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java index 27c2c91eb9fe..a156e1011d80 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java @@ -5,71 +5,28 @@ package io.airbyte.integrations.destination.elasticsearch; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Objects; +import lombok.Data; @JsonIgnoreProperties(ignoreUnknown = true) +@Data public class ConnectorConfiguration { private String endpoint; private boolean upsert; + @JsonProperty("ca_certificate") + private String caCertificate; private AuthenticationMethod authenticationMethod = new AuthenticationMethod(); - public ConnectorConfiguration() {} - public static ConnectorConfiguration fromJsonNode(JsonNode config) { return new ObjectMapper().convertValue(config, ConnectorConfiguration.class); } - public String getEndpoint() { - return this.endpoint; - } - - public boolean isUpsert() { - return this.upsert; - } - - public AuthenticationMethod getAuthenticationMethod() { - return this.authenticationMethod; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public void setUpsert(boolean upsert) { - this.upsert = upsert; - } - - public void setAuthenticationMethod(AuthenticationMethod authenticationMethod) { - this.authenticationMethod = authenticationMethod; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - ConnectorConfiguration that = (ConnectorConfiguration) o; - return upsert == that.upsert && Objects.equals(endpoint, that.endpoint) && Objects.equals(authenticationMethod, that.authenticationMethod); - } - - @Override - public int hashCode() { - return Objects.hash(endpoint, upsert, authenticationMethod); - } - - @Override - public String toString() { - return "ConnectorConfiguration{" + - "endpoint='" + endpoint + '\'' + - ", upsert=" + upsert + - ", authenticationMethod=" + authenticationMethod + - '}'; - } + @Data static class AuthenticationMethod { private ElasticsearchAuthenticationMethod method = ElasticsearchAuthenticationMethod.none; @@ -78,46 +35,6 @@ static class AuthenticationMethod { private String apiKeyId; private String apiKeySecret; - public ElasticsearchAuthenticationMethod getMethod() { - return this.method; - } - - public String getUsername() { - return this.username; - } - - public String getPassword() { - return this.password; - } - - public String getApiKeyId() { - return this.apiKeyId; - } - - public String getApiKeySecret() { - return this.apiKeySecret; - } - - public void setMethod(ElasticsearchAuthenticationMethod method) { - this.method = method; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setApiKeyId(String apiKeyId) { - this.apiKeyId = apiKeyId; - } - - public void setApiKeySecret(String apiKeySecret) { - this.apiKeySecret = apiKeySecret; - } - public boolean isValid() { return switch (this.method) { case none -> true; @@ -126,34 +43,6 @@ public boolean isValid() { }; } - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - AuthenticationMethod that = (AuthenticationMethod) o; - return method == that.method && - Objects.equals(username, that.username) && - Objects.equals(password, that.password) && - Objects.equals(apiKeyId, that.apiKeyId) && - Objects.equals(apiKeySecret, that.apiKeySecret); - } - - @Override - public int hashCode() { - return Objects.hash(method, username, password, apiKeyId, apiKeySecret); - } - - @Override - public String toString() { - return "AuthenticationMethod{" + - "method=" + method + - ", username='" + username + '\'' + - ", apiKeyId='" + apiKeyId + '\'' + - '}'; - } - } } diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java index 100fb60a03c0..ecb346207706 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.JsonPointer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import io.airbyte.db.util.SSLCertificateUtils; import io.airbyte.protocol.models.AirbyteRecordMessage; import jakarta.json.JsonValue; import java.io.IOException; @@ -28,6 +29,7 @@ import org.apache.http.message.BasicHeader; import org.elasticsearch.client.Node; import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.RestClientBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +58,17 @@ public ElasticsearchConnection(ConnectorConfiguration config) { // Create the low-level client httpHost = HttpHost.create(config.getEndpoint()); - restClient = RestClient.builder(httpHost) + final RestClientBuilder builder = RestClient.builder(httpHost); + + // Set custom user's certificate if provided + if (config.getCaCertificate() != null && !config.getCaCertificate().isEmpty()){ + builder.setHttpClientConfigCallback(clientBuilder -> { + clientBuilder.setSSLContext(SSLCertificateUtils.createContextFromCaCert(config.getCaCertificate())); + return clientBuilder; + }); + } + + restClient = builder .setDefaultHeaders(configureHeaders(config)) .setFailureListener(new FailureListener()) .build(); diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json b/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json index 546f962d51b2..9375ab8ed5a2 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/resources/spec.json @@ -23,6 +23,13 @@ "description": "If a primary key identifier is defined in the source, an upsert will be performed using the primary key value as the elasticsearch doc id. Does not support composite primary keys.", "default": true }, + "ca_certificate": { + "type": "string", + "title": "CA certificate", + "description": "CA certificate", + "airbyte_secret": true, + "multiline": true + }, "authenticationMethod": { "title": "Authentication Method", "type": "object", diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java index ce37a6fee19a..790eaa8724ec 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchDestinationAcceptanceTest.java @@ -13,23 +13,28 @@ import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testcontainers.elasticsearch.ElasticsearchContainer; public class ElasticsearchDestinationAcceptanceTest extends DestinationAcceptanceTest { + private static final String IMAGE_NAME = "docker.elastic.co/elasticsearch/elasticsearch:8.3.3"; + private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchDestinationAcceptanceTest.class); + private ObjectMapper mapper = new ObjectMapper(); private static ElasticsearchContainer container; @BeforeAll public static void beforeAll() { - container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:7.15.1") - .withEnv("ES_JAVA_OPTS", "-Xms512m -Xms512m") + container = new ElasticsearchContainer(IMAGE_NAME) .withEnv("discovery.type", "single-node") .withEnv("network.host", "0.0.0.0") .withEnv("logger.org.elasticsearch", "INFO") .withEnv("ingest.geoip.downloader.enabled", "false") - .withEnv("xpack.security.enabled", "false") + .withPassword("s3cret") .withExposedPorts(9200) + .withEnv("xpack.security.enabled", "false") .withStartupTimeout(Duration.ofSeconds(60)); container.start(); } diff --git a/deps.toml b/deps.toml index 5d2079050562..ce0c16b79912 100644 --- a/deps.toml +++ b/deps.toml @@ -21,6 +21,7 @@ connectors-testcontainers-scylla = "1.16.2" connectors-testcontainers-tidb = "1.16.3" connectors-destination-testcontainers-clickhouse = "1.17.3" connectors-destination-testcontainers-oracle-xe = "1.17.3" +connectors-destination-testcontainers-elasticsearch = "1.17.3" connectors-source-testcontainers-clickhouse = "1.17.3" platform-testcontainers = "1.17.3" @@ -54,7 +55,7 @@ connectors-testcontainers = { module = "org.testcontainers:testcontainers", vers connectors-testcontainers-cassandra = { module = "org.testcontainers:cassandra", version.ref = "connectors-testcontainers-cassandra" } connectors-testcontainers-cockroachdb = { module = "org.testcontainers:cockroachdb", version.ref = "connectors-testcontainers" } connectors-testcontainers-db2 = { module = "org.testcontainers:db2", version.ref = "connectors-testcontainers" } -connectors-testcontainers-elasticsearch = { module = "org.testcontainers:elasticsearch", version.ref = "connectors-testcontainers" } +connectors-testcontainers-elasticsearch = { module = "org.testcontainers:elasticsearch", version.ref = "connectors-destination-testcontainers-elasticsearch" } connectors-testcontainers-jdbc = { module = "org.testcontainers:jdbc", version.ref = "connectors-testcontainers" } connectors-testcontainers-kafka = { module = "org.testcontainers:kafka", version.ref = "connectors-testcontainers" } connectors-testcontainers-mariadb = { module = "org.testcontainers:mariadb", version.ref = "connectors-testcontainers-mariadb" } diff --git a/docs/integrations/destinations/elasticsearch.md b/docs/integrations/destinations/elasticsearch.md index ccae1d4a4d57..720753d7576c 100644 --- a/docs/integrations/destinations/elasticsearch.md +++ b/docs/integrations/destinations/elasticsearch.md @@ -57,11 +57,16 @@ The connector should be enhanced to support variable batch sizes. * Endpoint URL [ex. https://elasticsearch.savantly.net:9423] * Username [optional] (basic auth) * Password [optional] (basic auth) + * CA certificate [optional] * Api key ID [optional] * Api key secret [optional] * If authentication is used, the user should have permission to create an index if it doesn't exist, and/or be able to `create` documents - +### CA certificate +Ca certificate may be fetched from the Elasticsearch server from /usr/share/elasticsearch/config/certs/http_ca.crt +Fetching example from dockerized Elasticsearch: +`docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt .` where es01 is a container's name. For more details please visit https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html + ### Setup guide Enter the endpoint URL, select authentication method, and whether to use 'upsert' method when indexing new documents. @@ -89,6 +94,7 @@ Using this feature requires additional configuration, when creating the source. | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| 0.1.5 | 2022-10-24 | [18177](https://github.com/airbytehq/airbyte/pull/18177) | add custom CA certificate processing | | 0.1.4 | 2022-10-14 | [17805](https://github.com/airbytehq/airbyte/pull/17805) | add SSH Tunneling | | 0.1.3 | 2022-05-30 | [14640](https://github.com/airbytehq/airbyte/pull/14640) | Include lifecycle management | | 0.1.2 | 2022-04-19 | [11752](https://github.com/airbytehq/airbyte/pull/11752) | Reduce batch size to 32Mb | From 6b5587d7d6557a0f50333d8adba1165080da4364 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Mon, 24 Oct 2022 10:37:48 -0700 Subject: [PATCH 278/498] update spotbugs to latest (#18208) --- build.gradle | 2 +- deps.toml | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 7f60e6eff722..0c4c7f42b41d 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ plugins { id 'pmd' id 'com.diffplug.spotless' version '6.7.1' id 'com.github.hierynomus.license' version '0.16.1' - id 'com.github.spotbugs' version '5.0.6' + id 'com.github.spotbugs' version '5.0.13' // The distribution plugin has been added to address the an issue with the copyGeneratedTar // task depending on "distTar". When that dependency has been refactored away, this plugin // can be removed. diff --git a/deps.toml b/deps.toml index ce0c16b79912..18b9a054d76c 100644 --- a/deps.toml +++ b/deps.toml @@ -44,7 +44,7 @@ log4j-web = { module = "org.apache.logging.log4j:log4j-web", version.ref = "log4 jul-to-slf4j = { module = "org.slf4j:jul-to-slf4j", version.ref = "slf4j" } jcl-over-slf4j = { module = "org.slf4j:jcl-over-slf4j", version.ref = "slf4j" } hikaricp = { module = "com.zaxxer:HikariCP", version.ref = "hikaricp" } -java-dogstatsd-client = { module = "com.datadoghq:java-dogstatsd-client", version = "4.1.0"} +java-dogstatsd-client = { module = "com.datadoghq:java-dogstatsd-client", version = "4.1.0" } javax-databind = { module = "javax.xml.bind:jaxb-api", version = "2.4.0-b180830.0359" } jooq = { module = "org.jooq:jooq", version.ref = "jooq" } jooq-codegen = { module = "org.jooq:jooq-codegen", version.ref = "jooq" } @@ -86,16 +86,16 @@ junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", vers mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version = "4.0.0" } assertj-core = { module = "org.assertj:assertj-core", version = "3.21.0" } junit-pioneer = { module = "org.junit-pioneer:junit-pioneer", version = "1.7.1" } -findsecbugs-plugin = { module = "com.h3xstream.findsecbugs:findsecbugs-plugin", version = "1.11.0" } -spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version = "4.6.0" } -otel-bom = {module = "io.opentelemetry:opentelemetry-bom", version = "1.14.0"} -otel-semconv = {module = "io.opentelemetry:opentelemetry-semconv", version = "1.14.0-alpha"} -otel-sdk = {module = "io.opentelemetry:opentelemetry-sdk-metrics", version = "1.14.0"} -otel-sdk-testing = {module = "io.opentelemetry:opentelemetry-sdk-metrics-testing", version = "1.13.0-alpha"} -micrometer-statsd = {module = "io.micrometer:micrometer-registry-statsd", version = "1.9.3"} +findsecbugs-plugin = { module = "com.h3xstream.findsecbugs:findsecbugs-plugin", version = "1.12.0" } +spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version = "4.7.3" } +otel-bom = { module = "io.opentelemetry:opentelemetry-bom", version = "1.14.0" } +otel-semconv = { module = "io.opentelemetry:opentelemetry-semconv", version = "1.14.0-alpha" } +otel-sdk = { module = "io.opentelemetry:opentelemetry-sdk-metrics", version = "1.14.0" } +otel-sdk-testing = { module = "io.opentelemetry:opentelemetry-sdk-metrics-testing", version = "1.13.0-alpha" } +micrometer-statsd = { module = "io.micrometer:micrometer-registry-statsd", version = "1.9.3" } datadog-trace-api = { module = "com.datadoghq:dd-trace-api", version.ref = "datadog-version" } datadog-trace-ot = { module = "com.datadoghq:dd-trace-ot", version.ref = "datadog-version" } -quartz-scheduler = {module="org.quartz-scheduler:quartz", version = "2.3.2"} +quartz-scheduler = { module = "org.quartz-scheduler:quartz", version = "2.3.2" } # Micronaut-related dependencies h2-database = { module = "com.h2database:h2", version = "2.1.214" } From 80e77801dd05d7d4fcac59b83dd60c820581de75 Mon Sep 17 00:00:00 2001 From: antonioneto-hotmart <91587245+antonioneto-hotmart@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:58:10 -0300 Subject: [PATCH 279/498] Source Jira - Pagination fix on jira users stream (#16944) * pagination fix on jira users stream * Update spec.json * Update spec.json * Update streams.py * bump docker version and changelog * Update spec.json * Update streams.py * update jsons and source * update source * Update source.py * auto-bump connector version Co-authored-by: Vincent Koc Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/source_definitions.yaml | 2 +- .../init/src/main/resources/seed/source_specs.yaml | 7 ++++++- airbyte-integrations/connectors/source-jira/Dockerfile | 2 +- .../source-jira/integration_tests/invalid_config.json | 3 ++- .../source-jira/integration_tests/sample_config.json | 3 ++- .../connectors/source-jira/source_jira/source.py | 3 ++- .../connectors/source-jira/source_jira/spec.json | 6 ++++++ .../connectors/source-jira/source_jira/streams.py | 8 ++++++++ docs/integrations/sources/jira.md | 1 + 9 files changed, 29 insertions(+), 6 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 4dee616385ea..1a2a300be72e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -548,7 +548,7 @@ - name: Jira sourceDefinitionId: 68e63de2-bb83-4c7e-93fa-a8a9051e3993 dockerRepository: airbyte/source-jira - dockerImageTag: 0.2.21 + dockerImageTag: 0.2.22 documentationUrl: https://docs.airbyte.com/integrations/sources/jira icon: jira.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 0aeba3f5630f..69224c12cecf 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -5270,7 +5270,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-jira:0.2.21" +- dockerImage: "airbyte/source-jira:0.2.22" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/jira" connectionSpecification: @@ -5300,6 +5300,11 @@ type: "string" title: "Email" description: "The user email for your Jira account." + max_results: + type: "number" + title: "Max Results" + description: "Pagination max results (only for users stream)" + default: 50 projects: type: "array" title: "Projects" diff --git a/airbyte-integrations/connectors/source-jira/Dockerfile b/airbyte-integrations/connectors/source-jira/Dockerfile index 132501f53d0b..9a103dfeaf2d 100644 --- a/airbyte-integrations/connectors/source-jira/Dockerfile +++ b/airbyte-integrations/connectors/source-jira/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.21 +LABEL io.airbyte.version=0.2.22 LABEL io.airbyte.name=airbyte/source-jira diff --git a/airbyte-integrations/connectors/source-jira/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-jira/integration_tests/invalid_config.json index 9232da9f03d8..8545578f3b81 100644 --- a/airbyte-integrations/connectors/source-jira/integration_tests/invalid_config.json +++ b/airbyte-integrations/connectors/source-jira/integration_tests/invalid_config.json @@ -3,5 +3,6 @@ "domain": "invaliddomain.atlassian.net", "email": "test@test.com", "projects": ["invalidproject"], - "start_date": "2021-09-25T00:00:00Z" + "start_date": "2021-09-25T00:00:00Z", + "max_results": 0 } diff --git a/airbyte-integrations/connectors/source-jira/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-jira/integration_tests/sample_config.json index 8688c1e94a71..30b75408a890 100644 --- a/airbyte-integrations/connectors/source-jira/integration_tests/sample_config.json +++ b/airbyte-integrations/connectors/source-jira/integration_tests/sample_config.json @@ -2,5 +2,6 @@ "api_token": "", "domain": "", "email": "", - "projects": [] + "projects": [], + "max_results": "" } diff --git a/airbyte-integrations/connectors/source-jira/source_jira/source.py b/airbyte-integrations/connectors/source-jira/source_jira/source.py index bffe9cb299a4..b89378131877 100644 --- a/airbyte-integrations/connectors/source-jira/source_jira/source.py +++ b/airbyte-integrations/connectors/source-jira/source_jira/source.py @@ -101,6 +101,7 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> def streams(self, config: Mapping[str, Any]) -> List[Stream]: authenticator = self.get_authenticator(config) args = {"authenticator": authenticator, "domain": config["domain"], "projects": config.get("projects", [])} + users_args = {**args, "max_results": config.get("max_results", 50)} incremental_args = {**args, "start_date": config.get("start_date", "")} render_fields = config.get("render_fields", False) issues_stream = Issues( @@ -162,7 +163,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: Sprints(**args), SprintIssues(**incremental_args), TimeTracking(**args), - Users(**args), + Users(**users_args), Workflows(**args), WorkflowSchemes(**args), WorkflowStatuses(**args), diff --git a/airbyte-integrations/connectors/source-jira/source_jira/spec.json b/airbyte-integrations/connectors/source-jira/source_jira/spec.json index 1f21bc250d87..576685c5f437 100644 --- a/airbyte-integrations/connectors/source-jira/source_jira/spec.json +++ b/airbyte-integrations/connectors/source-jira/source_jira/spec.json @@ -25,6 +25,12 @@ "title": "Email", "description": "The user email for your Jira account." }, + "max_results": { + "type": "number", + "title": "Max Results", + "description": "Pagination max results (only for users stream)", + "default": 50 + }, "projects": { "type": "array", "title": "Projects", diff --git a/airbyte-integrations/connectors/source-jira/source_jira/streams.py b/airbyte-integrations/connectors/source-jira/source_jira/streams.py index 9e4648c1398f..858a6c804fa1 100644 --- a/airbyte-integrations/connectors/source-jira/source_jira/streams.py +++ b/airbyte-integrations/connectors/source-jira/source_jira/streams.py @@ -1083,7 +1083,15 @@ class Users(JiraStream): primary_key = None + def __init__(self, domain: str, projects: List[str], max_results: int, **kwargs): + super(JiraStream, self).__init__(**kwargs) + self._domain = domain + self._projects = projects + self._max_results = max_results + def path(self, **kwargs) -> str: + if int(self._max_results) > 0: + return "user/search?maxResults=" + str(self._max_results) + "&query=" return "user/search?query=" diff --git a/docs/integrations/sources/jira.md b/docs/integrations/sources/jira.md index 84ac413b1202..acb547b9a38c 100644 --- a/docs/integrations/sources/jira.md +++ b/docs/integrations/sources/jira.md @@ -95,6 +95,7 @@ The Jira connector should not run into Jira API limitations under normal usage. | Version | Date | Pull Request | Subject | |:--------|:-----------|:------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------| +| 0.2.22 | 2022-10-03 | [\#16944](hhttps://github.com/airbytehq/airbyte/pull/16944) | Adds support for `max_results` to `users` stream | | 0.2.21 | 2022-07-28 | [\#15135](hhttps://github.com/airbytehq/airbyte/pull/15135) | Adds components to `fields` object on `issues` stream | | 0.2.20 | 2022-05-25 | [\#13202](https://github.com/airbytehq/airbyte/pull/13202) | Adds resolutiondate to `fields` object on `issues` stream | | 0.2.19 | 2022-05-04 | [\#10835](https://github.com/airbytehq/airbyte/pull/10835) | Change description for array fields | From 9763cbcf18d0593cac5b155bb2fbae40c2e6a650 Mon Sep 17 00:00:00 2001 From: Santiago Aquino Stachuk <106173005+mutt-santiago-stachuk@users.noreply.github.com> Date: Mon, 24 Oct 2022 14:59:25 -0300 Subject: [PATCH 280/498] =?UTF-8?q?=20=F0=9F=8E=89=20=20Source=20Google=20?= =?UTF-8?q?Ads:=20Add=20fields=20to=20the=20campaign=20stream=20?= =?UTF-8?q?=F0=9F=9A=A8=20=20(#18069)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add new fields to campaign stream * docs: updated docs and bumped Dockerfile version * Update campaigns.json * Update Dockerfile * auto-bump connector version Co-authored-by: Marcos Marx Co-authored-by: Vincent Koc Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-google-ads/BOOTSTRAP.md | 2 +- .../connectors/source-google-ads/Dockerfile | 2 +- .../source_google_ads/schemas/campaigns.json | 12 +++++++++ docs/integrations/sources/google-ads.md | 27 ++++++++++--------- 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 1a2a300be72e..57916e0cec40 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -392,7 +392,7 @@ - name: Google Ads sourceDefinitionId: 253487c0-2246-43ba-a21f-5116b20a2c50 dockerRepository: airbyte/source-google-ads - dockerImageTag: 0.2.2 + dockerImageTag: 0.2.3 documentationUrl: https://docs.airbyte.com/integrations/sources/google-ads icon: google-adwords.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 69224c12cecf..34982afd0a3d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -3785,7 +3785,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-google-ads:0.2.2" +- dockerImage: "airbyte/source-google-ads:0.2.3" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/google-ads" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-google-ads/BOOTSTRAP.md b/airbyte-integrations/connectors/source-google-ads/BOOTSTRAP.md index 5fc2389eabc8..89c07e4750cb 100644 --- a/airbyte-integrations/connectors/source-google-ads/BOOTSTRAP.md +++ b/airbyte-integrations/connectors/source-google-ads/BOOTSTRAP.md @@ -9,7 +9,7 @@ The resources are listed [here](https://developers.google.com/google-ads/api/ref When querying data, there are three categories of information that can be fetched: - **Attributes**: These are properties of the various entities in the API e.g: the title or ID of an ad campaign. -- **Metrics**: metrics are statistics related to entities in the API. For example, the number of impressions for an ad or an ad campaign. All available metrics can be found [here](https://developers.google.com/google-ads/api/fields/v8/metrics). +- **Metrics**: metrics are statistics related to entities in the API. For example, the number of impressions for an ad or an ad campaign. All available metrics can be found [here](https://developers.google.com/google-ads/api/fields/v11/metrics). - **Segments**: These are ways to partition metrics returned in the query by particular attributes. For example, one could query for the number of impressions (views of an ad) by running SELECT metrics.impressions FROM campaigns which would return the number of impressions for each campaign e.g: 10k impressions. Or you could query for impressions segmented by device type e.g; SELECT metrics.impressions, segments.device FROM campaigns which would return the number of impressions broken down by device type e.g: 3k iOS and 7k Android. When summing the result across all segments, diff --git a/airbyte-integrations/connectors/source-google-ads/Dockerfile b/airbyte-integrations/connectors/source-google-ads/Dockerfile index 977fe479843d..04b924655c80 100644 --- a/airbyte-integrations/connectors/source-google-ads/Dockerfile +++ b/airbyte-integrations/connectors/source-google-ads/Dockerfile @@ -13,5 +13,5 @@ COPY main.py ./ ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.2 +LABEL io.airbyte.version=0.2.3 LABEL io.airbyte.name=airbyte/source-google-ads diff --git a/airbyte-integrations/connectors/source-google-ads/source_google_ads/schemas/campaigns.json b/airbyte-integrations/connectors/source-google-ads/source_google_ads/schemas/campaigns.json index 329bf21a1ec1..99c9e0d1013a 100644 --- a/airbyte-integrations/connectors/source-google-ads/source_google_ads/schemas/campaigns.json +++ b/airbyte-integrations/connectors/source-google-ads/source_google_ads/schemas/campaigns.json @@ -245,6 +245,15 @@ "metrics.clicks": { "type": ["null", "integer"] }, + "metrics.ctr": { + "type": ["null", "number"] + }, + "metrics.conversions": { + "type": ["null", "integer"] + }, + "metrics.conversions_value": { + "type": ["null", "integer"] + }, "metrics.cost_micros": { "type": ["null", "integer"] }, @@ -260,6 +269,9 @@ "segments.date": { "type": ["null", "string"], "format": "date" + }, + "segments.hour": { + "type": ["null", "number"] } } } diff --git a/docs/integrations/sources/google-ads.md b/docs/integrations/sources/google-ads.md index 1863ce72c8a7..5b16096255ee 100644 --- a/docs/integrations/sources/google-ads.md +++ b/docs/integrations/sources/google-ads.md @@ -78,33 +78,33 @@ The Google Ads source connector can sync the following tables. It can also sync ### Main Tables -- [accounts](https://developers.google.com/google-ads/api/fields/v9/customer) -- [ad_group_ads](https://developers.google.com/google-ads/api/fields/v9/ad_group_ad) -- [ad_group_ad_labels](https://developers.google.com/google-ads/api/fields/v9/ad_group_ad_label) -- [ad_groups](https://developers.google.com/google-ads/api/fields/v9/ad_group) -- [ad_group_labels](https://developers.google.com/google-ads/api/fields/v9/ad_group_label) -- [campaign_labels](https://developers.google.com/google-ads/api/fields/v9/campaign_label) -- [click_view](https://developers.google.com/google-ads/api/reference/rpc/v9/ClickView) -- [keyword](https://developers.google.com/google-ads/api/fields/v9/keyword_view) -- [geographic](https://developers.google.com/google-ads/api/fields/v9/geographic_view) +- [accounts](https://developers.google.com/google-ads/api/fields/v11/customer) +- [ad_group_ads](https://developers.google.com/google-ads/api/fields/v11/ad_group_ad) +- [ad_group_ad_labels](https://developers.google.com/google-ads/api/fields/v11/ad_group_ad_label) +- [ad_groups](https://developers.google.com/google-ads/api/fields/v11/ad_group) +- [ad_group_labels](https://developers.google.com/google-ads/api/fields/v11/ad_group_label) +- [campaign_labels](https://developers.google.com/google-ads/api/fields/v11/campaign_label) +- [click_view](https://developers.google.com/google-ads/api/reference/rpc/v11/ClickView) +- [keyword](https://developers.google.com/google-ads/api/fields/v11/keyword_view) +- [geographic](https://developers.google.com/google-ads/api/fields/v11/geographic_view) Note that `ad_groups`, `ad_group_ads`, and `campaigns` contain a `labels` field, which should be joined against their respective `*_labels` streams if you want to view the actual labels. For example, the `ad_groups` stream contains an `ad_group.labels` field, which you would join against the `ad_group_labels` stream's `label.resource_name` field. ### Report Tables -- [campaigns](https://developers.google.com/google-ads/api/fields/v9/campaign) +- [campaigns](https://developers.google.com/google-ads/api/fields/v11/campaign) - [account_performance_report](https://developers.google.com/google-ads/api/docs/migration/mapping#account_performance) - [ad_group_ad_report](https://developers.google.com/google-ads/api/docs/migration/mapping#ad_performance) - [display_keyword_report](https://developers.google.com/google-ads/api/docs/migration/mapping#display_keyword_performance) - [display_topics_report](https://developers.google.com/google-ads/api/docs/migration/mapping#display_topics_performance) - [shopping_performance_report](https://developers.google.com/google-ads/api/docs/migration/mapping#shopping_performance) -- [user_location_report](https://developers.google.com/google-ads/api/fields/v9/user_location_view) +- [user_location_report](https://developers.google.com/google-ads/api/fields/v11/user_location_view) :::note -Due to Google Ads API constraints, the `click_view` stream retrieves data one day at a time and can only retrieve data newer than 90 days ago. Also, [metrics](https://developers.google.com/google-ads/api/fields/v9/metrics) cannot be requested for a Google Ads Manager account. Report streams are only available when pulling data from a non-manager account. +Due to Google Ads API constraints, the `click_view` stream retrieves data one day at a time and can only retrieve data newer than 90 days ago. Also, [metrics](https://developers.google.com/google-ads/api/fields/v11/metrics) cannot be requested for a Google Ads Manager account. Report streams are only available when pulling data from a non-manager account. ::: -For incremental streams, data is synced up to the previous day using your Google Ads account time zone since Google Ads can filter data only by [date](https://developers.google.com/google-ads/api/fields/v9/ad_group_ad#segments.date) without time. Also, some reports cannot load data real-time due to Google Ads [limitations](https://support.google.com/google-ads/answer/2544985?hl=en). +For incremental streams, data is synced up to the previous day using your Google Ads account time zone since Google Ads can filter data only by [date](https://developers.google.com/google-ads/api/fields/v11/ad_group_ad#segments.date) without time. Also, some reports cannot load data real-time due to Google Ads [limitations](https://support.google.com/google-ads/answer/2544985?hl=en). ## Custom Query: Understanding Google Ads Query Language @@ -124,6 +124,7 @@ Due to a limitation in the Google Ads API which does not allow getting performan | Version | Date | Pull Request | Subject | |:---------|:-----------|:---------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------| +| `0.2.3` | 2022-10-17 | [18069](https://github.com/airbytehq/airbyte/pull/18069) | Add `segments.hour`, `metrics.ctr`, `metrics.conversions` and `metrics.conversions_values` fields to `campaigns` report stream | | `0.2.2` | 2022-10-21 | [17412](https://github.com/airbytehq/airbyte/pull/17412) | Release with CDK >= 0.2.2 | | `0.2.1` | 2022-09-29 | [17412](https://github.com/airbytehq/airbyte/pull/17412) | Always use latest CDK version | | `0.2.0` | 2022-08-23 | [15858](https://github.com/airbytehq/airbyte/pull/15858) | Mark the `query` and `table_name` fields in `custom_queries` as required | From a3c00fadb35772d939ab23c25d4c8fec07eff785 Mon Sep 17 00:00:00 2001 From: Eugene Date: Mon, 24 Oct 2022 21:02:23 +0300 Subject: [PATCH 281/498] fixed check-style (#18390) --- .../airbyte/db/util/SSLCertificateUtils.java | 3 +- ...trictEncryptDestinationAcceptanceTest.java | 5 - .../elasticsearch/ConnectorConfiguration.java | 1 - .../ElasticsearchConnection.java | 2 +- .../source_chargebee/streams.py | 2 +- .../integration_tests/catalog.json | 374 ++++-------------- .../source_gocardless/gocardless.yaml | 1 - .../source_gocardless/schemas/mandates.json | 8 +- .../source_gocardless/schemas/payments.json | 12 +- .../source_gocardless/schemas/payouts.json | 8 +- .../source_gocardless/schemas/refunds.json | 8 +- .../source_gocardless/spec.yaml | 2 +- .../integration_tests/configured_catalog.json | 10 +- .../source_insightly/schemas/tasks.json | 292 +++++++------- .../source/relationaldb/AbstractDbSource.java | 5 +- .../integration_tests/catalog.json | 12 +- .../integration_tests/configured_catalog.json | 14 +- .../source_whisky_hunter/whisky_hunter.yaml | 1 - 18 files changed, 256 insertions(+), 504 deletions(-) diff --git a/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java b/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java index 32062cabfe46..d642345e8050 100644 --- a/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java +++ b/airbyte-db/db-lib/src/main/java/io/airbyte/db/util/SSLCertificateUtils.java @@ -167,8 +167,7 @@ public static SSLContext createContextFromCaCert(String caCertificate) { try { CertificateFactory factory = CertificateFactory.getInstance(X509); Certificate trustedCa = factory.generateCertificate( - new ByteArrayInputStream(caCertificate.getBytes(StandardCharsets.UTF_8)) - ); + new ByteArrayInputStream(caCertificate.getBytes(StandardCharsets.UTF_8))); KeyStore trustStore = KeyStore.getInstance(PKCS_12); trustStore.load(null, null); trustStore.setCertificateEntry("ca", trustedCa); diff --git a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java index 68859c436a47..c1e0ec6940bc 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-elasticsearch-strict-encrypt/src/test-integration/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchStrictEncryptDestinationAcceptanceTest.java @@ -4,25 +4,20 @@ package io.airbyte.integrations.destination.elasticsearch; -import static org.junit.jupiter.api.Assertions.assertEquals; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.StandardCheckConnectionOutput.Status; import io.airbyte.integrations.standardtest.destination.DestinationAcceptanceTest; import io.airbyte.integrations.standardtest.destination.comparator.AdvancedTestDataComparator; import io.airbyte.integrations.standardtest.destination.comparator.TestDataComparator; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.List; import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.testcontainers.elasticsearch.ElasticsearchContainer; public class ElasticsearchStrictEncryptDestinationAcceptanceTest extends DestinationAcceptanceTest { diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java index a156e1011d80..771b46a9c305 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ConnectorConfiguration.java @@ -25,7 +25,6 @@ public static ConnectorConfiguration fromJsonNode(JsonNode config) { return new ObjectMapper().convertValue(config, ConnectorConfiguration.class); } - @Data static class AuthenticationMethod { diff --git a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java index ecb346207706..6631cf355710 100644 --- a/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java +++ b/airbyte-integrations/connectors/destination-elasticsearch/src/main/java/io/airbyte/integrations/destination/elasticsearch/ElasticsearchConnection.java @@ -61,7 +61,7 @@ public ElasticsearchConnection(ConnectorConfiguration config) { final RestClientBuilder builder = RestClient.builder(httpHost); // Set custom user's certificate if provided - if (config.getCaCertificate() != null && !config.getCaCertificate().isEmpty()){ + if (config.getCaCertificate() != null && !config.getCaCertificate().isEmpty()) { builder.setHttpClientConfigCallback(clientBuilder -> { clientBuilder.setSSLContext(SSLCertificateUtils.createContextFromCaCert(config.getCaCertificate())); return clientBuilder; diff --git a/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py b/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py index d42ebd38def5..d807c6764c10 100644 --- a/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py +++ b/airbyte-integrations/connectors/source-chargebee/source_chargebee/streams.py @@ -7,7 +7,7 @@ import pendulum from airbyte_cdk.models import SyncMode from airbyte_cdk.sources.streams import Stream -from airbyte_cdk.sources.utils.transform import TypeTransformer, TransformConfig +from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer from chargebee import APIError from chargebee.list_result import ListResult from chargebee.model import Model diff --git a/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json b/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json index 80905e66c30f..fbba608fabaf 100644 --- a/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json +++ b/airbyte-integrations/connectors/source-gocardless/integration_tests/catalog.json @@ -2,9 +2,7 @@ "streams": [ { "name": "payments", - "supported_sync_modes": [ - "full_refresh" - ], + "supported_sync_modes": ["full_refresh"], "source_defined_cursor": true, "default_cursor_field": "id", "json_schema": { @@ -12,240 +10,132 @@ "type": "object", "properties": { "id": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "created_at": { - "type": [ - "null", - "string" - ], + "type": ["null", "string"], "format": "date-time" }, "charge_date": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "amount": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "description": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "status": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "reference": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "metadata": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "order_dispatch_date": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "amount_refunded": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "fx": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "fx_currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "fx_amount": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "exchange_rate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "estimated_exchange_rate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "links": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "mandate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "creditor": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "retry_if_possible": { - "type": [ - "null", - "boolean" - ] + "type": ["null", "boolean"] } } } }, { "name": "refunds", - "supported_sync_modes": [ - "full_refresh", - "incremental" - ], + "supported_sync_modes": ["full_refresh", "incremental"], "source_defined_cursor": true, "default_cursor_field": "id", "json_schema": { "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-refunds", "type": "object", "properties": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "id": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "created_at": { - "type": [ - "null", - "string" - ], + "type": ["null", "string"], "format": "date-time" }, "amount": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "reference": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "metadata": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "reason": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "fx": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "fx_currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "fx_amount": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "exchange_rate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "estimated_exchange_rate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "links": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "payment": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } } @@ -255,101 +145,56 @@ }, { "name": "mandates", - "supported_sync_modes": [ - "full_refresh", - "incremental" - ], + "supported_sync_modes": ["full_refresh", "incremental"], "source_defined_cursor": true, "default_cursor_field": "id", "json_schema": { "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-mandates", "type": "object", "properties": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "id": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "created_at": { - "type": [ - "null", - "string" - ], + "type": ["null", "string"], "format": "date-time" }, "reference": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "status": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "scheme": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "next_possible_charge_date": { - "type": [ - "null", - "string" - ], + "type": ["null", "string"], "format": "date" }, "metadata": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "contract": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "links": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "customer_bank_account": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "creditor": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "customer": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } } @@ -360,149 +205,80 @@ }, { "name": "payouts", - "supported_sync_modes": [ - "full_refresh", - "incremental" - ], + "supported_sync_modes": ["full_refresh", "incremental"], "source_defined_cursor": true, "default_cursor_field": "id", "json_schema": { "$schema": "https://developer.gocardless.com/api-reference/#core-endpoints-payouts", "type": "object", "properties": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "id": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "amount": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "arrival_date": { - "type": [ - "null", - "string" - ], + "type": ["null", "string"], "format": "date" }, "deducted_fees": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "created_at": { - "type": [ - "null", - "string" - ], + "type": ["null", "string"], "format": "date-time" }, "payout_type": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "reference": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "status": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "fx": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "fx_currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "fx_amount": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "exchange_rate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "estimated_exchange_rate": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } }, "tax_currency": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "metadata": { - "type": [ - "null", - "object" - ] + "type": ["null", "object"] }, "amount_refunded": { - "type": [ - "null", - "integer" - ] + "type": ["null", "integer"] }, "links": { - "type": [ - "null", - "object" - ], + "type": ["null", "object"], "properties": { "creditor_bank_account": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] }, "creditor": { - "type": [ - "null", - "string" - ] + "type": ["null", "string"] } } } diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml b/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml index 92c614baf017..4953d02f4ee8 100644 --- a/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/gocardless.yaml @@ -46,7 +46,6 @@ definitions: field_name: "limit" inject_into: "request_parameter" - streams: - type: DeclarativeStream $options: diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json index 18cbdb36e22b..9a42f74b2967 100644 --- a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/mandates.json @@ -23,7 +23,7 @@ }, "metadata": { "type": ["null", "object"], - "properties":{ + "properties": { "contract": { "type": ["null", "string"] } @@ -32,13 +32,13 @@ "links": { "type": ["null", "object"], "properties": { - "customer_bank_account": { + "customer_bank_account": { "type": ["null", "string"] }, - "creditor": { + "creditor": { "type": ["null", "string"] }, - "customer": { + "customer": { "type": ["null", "string"] } } diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json index e85fdd2123ff..cbe61d50858e 100644 --- a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payments.json @@ -28,7 +28,7 @@ }, "metadata": { "type": ["null", "object"], - "properties":{ + "properties": { "order_dispatch_date": { "type": ["null", "string"] } @@ -43,10 +43,10 @@ "fx_currency": { "type": ["null", "string"] }, - "fx_amount": { + "fx_amount": { "type": ["null", "integer"] }, - "exchange_rate": { + "exchange_rate": { "type": ["null", "string"] }, "estimated_exchange_rate": { @@ -57,15 +57,15 @@ "links": { "type": ["null", "object"], "properties": { - "mandate": { + "mandate": { "type": ["null", "string"] }, - "creditor": { + "creditor": { "type": ["null", "string"] } } }, - "retry_if_possible": { + "retry_if_possible": { "type": ["null", "boolean"] } } diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json index 6ceaa0f98422..81cf1becc462 100644 --- a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/payouts.json @@ -36,10 +36,10 @@ "fx_currency": { "type": ["null", "string"] }, - "fx_amount": { + "fx_amount": { "type": ["null", "integer"] }, - "exchange_rate": { + "exchange_rate": { "type": ["null", "string"] }, "estimated_exchange_rate": { @@ -59,10 +59,10 @@ "links": { "type": ["null", "object"], "properties": { - "creditor_bank_account": { + "creditor_bank_account": { "type": ["null", "string"] }, - "creditor": { + "creditor": { "type": ["null", "string"] } } diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json index 7484e680f53f..ca826a0af1f1 100644 --- a/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/schemas/refunds.json @@ -19,7 +19,7 @@ }, "metadata": { "type": ["null", "object"], - "properties":{ + "properties": { "reason": { "type": ["null", "string"] } @@ -31,10 +31,10 @@ "fx_currency": { "type": ["null", "string"] }, - "fx_amount": { + "fx_amount": { "type": ["null", "integer"] }, - "exchange_rate": { + "exchange_rate": { "type": ["null", "string"] }, "estimated_exchange_rate": { @@ -45,7 +45,7 @@ "links": { "type": ["null", "object"], "properties": { - "payment": { + "payment": { "type": ["null", "string"] } } diff --git a/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml b/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml index 8ec92a712000..fc9603b502bb 100644 --- a/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml +++ b/airbyte-integrations/connectors/source-gocardless/source_gocardless/spec.yaml @@ -41,5 +41,5 @@ connectionSpecification: UTC date and time in the format 2017-01-25T00:00:00Z. Any data before this date will not be replicated. examples: - - '2017-01-25T00:00:00Z' + - "2017-01-25T00:00:00Z" order: 3 diff --git a/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json index 1f4508d73195..0873c8cb593f 100644 --- a/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-insightly/integration_tests/configured_catalog.json @@ -14,11 +14,11 @@ "stream": { "name": "users", "json_schema": {}, - "supported_sync_modes": ["full_refresh", "incremental"], - "source_defined_cursor": true, - "default_cursor_field": ["DATE_UPDATED_UTC"], - "source_defined_primary_key": [["USER_ID"]] - }, + "supported_sync_modes": ["full_refresh", "incremental"], + "source_defined_cursor": true, + "default_cursor_field": ["DATE_UPDATED_UTC"], + "source_defined_primary_key": [["USER_ID"]] + }, "sync_mode": "incremental", "destination_sync_mode": "append" } diff --git a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json index 448b0d9c5644..6b41220e9e10 100644 --- a/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json +++ b/airbyte-integrations/connectors/source-insightly/source_insightly/schemas/tasks.json @@ -1,153 +1,149 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "TASK_ID": { - "type": "integer" - }, - "TITLE": { - "type": ["string", "null"] - }, - "CATEGORY_ID": { - "type": ["integer", "null"] - }, - "DUE_DATE": { - "type": ["string", "null"], - "format": "date-time" - }, - "COMPLETED_DATE_UTC": { - "type": ["string", "null"], - "format": "date-time" - }, - "COMPLETED": { - "type": ["boolean", "null"] - }, - "DETAILS": { - "type": ["string", "null"] - }, - "STATUS": { - "type": ["string", "null"] - }, - "PRIORITY": { - "type": ["integer", "null"] - }, - "PERCENT_COMPLETE": { - "type": ["integer", "null"] - }, - "START_DATE": { - "type": ["string", "null"], - "format": "date-time" - }, - "MILESTONE_ID": { - "type": ["integer", "null"] - }, - "RESPONSIBLE_USER_ID": { - "type": ["integer", "null"] - }, - "OWNER_USER_ID": { - "type": ["integer", "null"] - }, - "DATE_CREATED_UTC": { - "type": ["string", "null"], - "format": "date-time" - }, - "DATE_UPDATED_UTC": { - "type": ["string", "null"], - "format": "date-time" - }, - "EMAIL_ID": { - "type": ["integer", "null"] - }, - "PROJECT_ID": { - "type": ["integer", "null"] - }, - "REMINDER_DATE_UTC": { - "type": ["string", "null"], - "format": "date-time" - }, - "REMINDER_SENT": { - "type": ["boolean", "null"] - }, - "OWNER_VISIBLE": { - "type": ["boolean", "null"] - }, - "STAGE_ID": { - "type": ["integer", "null"] - }, - "ASSIGNED_BY_USER_ID": { - "type": ["integer", "null"] - }, - "PARENT_TASK_ID": { - "type": ["integer", "null"] - }, - "RECURRENCE": { - "type": ["string", "null"] - }, - "OPPORTUNITY_ID": { - "type": ["integer", "null"] - }, - "ASSIGNED_TEAM_ID": { - "type": ["integer", "null"] - }, - "ASSIGNED_DATE_UTC": { - "type": ["string", "null"], - "format": "date-time" - }, - "CREATED_USER_ID": { - "type": ["integer", "null"] - }, - "CUSTOMFIELDS": { - "type": "array", - "items": - { - "type": "object", - "properties": { - "FIELD_NAME": { - "type": ["string", "null"] - }, - "FIELD_VALUE": { - "type": "object" - } - } + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "TASK_ID": { + "type": "integer" + }, + "TITLE": { + "type": ["string", "null"] + }, + "CATEGORY_ID": { + "type": ["integer", "null"] + }, + "DUE_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "COMPLETED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "COMPLETED": { + "type": ["boolean", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "STATUS": { + "type": ["string", "null"] + }, + "PRIORITY": { + "type": ["integer", "null"] + }, + "PERCENT_COMPLETE": { + "type": ["integer", "null"] + }, + "START_DATE": { + "type": ["string", "null"], + "format": "date-time" + }, + "MILESTONE_ID": { + "type": ["integer", "null"] + }, + "RESPONSIBLE_USER_ID": { + "type": ["integer", "null"] + }, + "OWNER_USER_ID": { + "type": ["integer", "null"] + }, + "DATE_CREATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "DATE_UPDATED_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "EMAIL_ID": { + "type": ["integer", "null"] + }, + "PROJECT_ID": { + "type": ["integer", "null"] + }, + "REMINDER_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "REMINDER_SENT": { + "type": ["boolean", "null"] + }, + "OWNER_VISIBLE": { + "type": ["boolean", "null"] + }, + "STAGE_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_BY_USER_ID": { + "type": ["integer", "null"] + }, + "PARENT_TASK_ID": { + "type": ["integer", "null"] + }, + "RECURRENCE": { + "type": ["string", "null"] + }, + "OPPORTUNITY_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_TEAM_ID": { + "type": ["integer", "null"] + }, + "ASSIGNED_DATE_UTC": { + "type": ["string", "null"], + "format": "date-time" + }, + "CREATED_USER_ID": { + "type": ["integer", "null"] + }, + "CUSTOMFIELDS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "FIELD_NAME": { + "type": ["string", "null"] + }, + "FIELD_VALUE": { + "type": "object" } - - }, - "LINKS": { - "type": "array", - "items": - { - "type": "object", - "properties": { - "LINK_ID": { - "type": ["integer", "null"] - }, - "OBJECT_NAME": { - "type": ["string", "null"] - }, - "OBJECT_ID": { - "type": ["integer", "null"] - }, - "LINK_OBJECT_NAME": { - "type": ["string", "null"] - }, - "LINK_OBJECT_ID": { - "type": ["integer", "null"] - }, - "ROLE": { - "type": ["string", "null"] - }, - "DETAILS": { - "type": ["string", "null"] - }, - "RELATIONSHIP_ID": { - "type": ["integer", "null"] - }, - "IS_FORWARD": { - "type": ["boolean", "null"] - } - } + } + } + }, + "LINKS": { + "type": "array", + "items": { + "type": "object", + "properties": { + "LINK_ID": { + "type": ["integer", "null"] + }, + "OBJECT_NAME": { + "type": ["string", "null"] + }, + "OBJECT_ID": { + "type": ["integer", "null"] + }, + "LINK_OBJECT_NAME": { + "type": ["string", "null"] + }, + "LINK_OBJECT_ID": { + "type": ["integer", "null"] + }, + "ROLE": { + "type": ["string", "null"] + }, + "DETAILS": { + "type": ["string", "null"] + }, + "RELATIONSHIP_ID": { + "type": ["integer", "null"] + }, + "IS_FORWARD": { + "type": ["boolean", "null"] } - + } } } - } \ No newline at end of file + } +} diff --git a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java index e553483da683..eed23df04b81 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java +++ b/airbyte-integrations/connectors/source-relational-db/src/main/java/io/airbyte/integrations/source/relationaldb/AbstractDbSource.java @@ -135,7 +135,7 @@ public AirbyteCatalog discover(final JsonNode config) throws Exception { public AutoCloseableIterator read(final JsonNode config, final ConfiguredAirbyteCatalog catalog, final JsonNode state) - throws Exception { + throws Exception { try { final StateManager stateManager = StateManagerFactory.createStateManager(getSupportedStateType(config), deserializeInitialState(state, config), catalog); @@ -175,7 +175,8 @@ public AutoCloseableIterator read(final JsonNode config, } private boolean isConfigError(final Exception exception) { - // For now, enhanced error details should only be shown for InvalidCursorException. In the future, enhanced error messages will exist for + // For now, enhanced error details should only be shown for InvalidCursorException. In the future, + // enhanced error messages will exist for // additional error types. return exception instanceof InvalidCursorException; } diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json index b5acb2a9ab76..06186f102c2c 100644 --- a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/catalog.json @@ -37,9 +37,7 @@ } } }, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, { "name": "auctions_info", @@ -73,9 +71,7 @@ } } }, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, { "name": "distilleries_info", @@ -103,9 +99,7 @@ } } }, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] } ] } diff --git a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json index 74fdfedae11c..a8533af50b5f 100644 --- a/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-whisky-hunter/integration_tests/configured_catalog.json @@ -39,9 +39,7 @@ } } }, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -82,9 +80,7 @@ } } }, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -119,12 +115,10 @@ } } }, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" } ] -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml index 26c9ee9a6060..006dc12027c8 100644 --- a/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml +++ b/airbyte-integrations/connectors/source-whisky-hunter/source_whisky_hunter/whisky_hunter.yaml @@ -33,7 +33,6 @@ definitions: name: "distilleries_info" path: "/distilleries_info/?format=json" - streams: - "*ref(definitions.auctions_data_stream)" - "*ref(definitions.auctions_info_stream)" From 1ee4ea77635c0522597cc6736f5e78bd07391b6a Mon Sep 17 00:00:00 2001 From: Marcos Marx Date: Mon, 24 Oct 2022 14:31:06 -0400 Subject: [PATCH 282/498] Readded xkcd to source def yaml (#18386) * readded xkcd to source def yaml * remove changes in klavyio * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../src/main/resources/seed/source_definitions.yaml | 7 +++++++ .../init/src/main/resources/seed/source_specs.yaml | 11 +++++++++++ .../connectors/source-xkcd/Dockerfile | 2 +- .../connectors/source-xkcd/source_xkcd/spec.yaml | 1 + 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 57916e0cec40..f673a484605c 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1148,6 +1148,13 @@ icon: victorops.svg sourceType: api releaseStage: alpha +- name: xkcd + sourceDefinitionId: 80fddd16-17bd-4c0c-bf4a-80df7863fc9d + dockerRepository: airbyte/source-xkcd + dockerImageTag: 0.1.1 + documentationUrl: https://docs.airbyte.com/integrations/sources/xkcd + sourceType: api + releaseStage: alpha - name: Webflow sourceDefinitionId: ef580275-d9a9-48bb-af5e-db0f5855be04 dockerRepository: airbyte/source-webflow diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 34982afd0a3d..ad20ec663c8b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11820,6 +11820,17 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-xkcd:0.1.1" + spec: + documentationUrl: "https://docs.airbyte.io/integrations/sources/xkcd" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Xkcd Spec" + type: "object" + properties: {} + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-webflow:0.1.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/webflow" diff --git a/airbyte-integrations/connectors/source-xkcd/Dockerfile b/airbyte-integrations/connectors/source-xkcd/Dockerfile index 4e92425299bd..f0f790541eb2 100644 --- a/airbyte-integrations/connectors/source-xkcd/Dockerfile +++ b/airbyte-integrations/connectors/source-xkcd/Dockerfile @@ -34,5 +34,5 @@ COPY source_xkcd ./source_xkcd ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.version=0.1.1 LABEL io.airbyte.name=airbyte/source-xkcd diff --git a/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml b/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml index 1a78fb6b518a..ca394e6189c0 100644 --- a/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml +++ b/airbyte-integrations/connectors/source-xkcd/source_xkcd/spec.yaml @@ -3,3 +3,4 @@ connectionSpecification: $schema: http://json-schema.org/draft-07/schema# title: Xkcd Spec type: object + properties: {} From ef08d8845558661199c98aed9a20fd378ba088ab Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Mon, 24 Oct 2022 14:36:22 -0400 Subject: [PATCH 283/498] Trace errors in orchestrator workers (#18381) * Trace errors in orchestrator workers * Trace cancel method of workers * Formatting * Restore final modifier --- airbyte-commons-worker/build.gradle | 1 + .../general/DbtTransformationWorker.java | 14 ++++++- .../general/DefaultCheckConnectionWorker.java | 10 ++++- .../general/DefaultDiscoverCatalogWorker.java | 30 ++++++++++++++ .../workers/general/DefaultGetSpecWorker.java | 9 ++++ .../general/DefaultNormalizationWorker.java | 15 ++++++- .../general/DefaultReplicationWorker.java | 33 ++++++++++++++- .../DbtJobOrchestrator.java | 6 +-- .../NormalizationJobOrchestrator.java | 6 +-- .../ReplicationJobOrchestrator.java | 8 ++-- .../TraceConstants.java | 41 ------------------- .../metrics/lib/ApmTraceConstants.java | 41 ++++++++++++++++--- .../io/airbyte/metrics/lib/ApmTraceUtils.java | 26 +++++++++++- .../CheckConnectionActivityImpl.java | 6 +-- .../CheckConnectionWorkflowImpl.java | 6 +-- .../catalog/DiscoverCatalogActivityImpl.java | 6 +-- .../catalog/DiscoverCatalogWorkflowImpl.java | 6 +-- .../ConnectionManagerWorkflowImpl.java | 6 +-- .../AutoDisableConnectionActivityImpl.java | 4 +- .../activities/ConfigFetchActivityImpl.java | 4 +- .../ConnectionDeletionActivityImpl.java | 4 +- .../activities/GenerateInputActivityImpl.java | 4 +- ...obCreationAndStatusUpdateActivityImpl.java | 24 +++++------ .../activities/RecordMetricActivityImpl.java | 6 +-- .../RouteToSyncTaskQueueActivityImpl.java | 4 +- .../activities/StreamResetActivityImpl.java | 6 +-- .../WorkflowConfigActivityImpl.java | 2 +- .../temporal/spec/SpecActivityImpl.java | 6 +-- .../temporal/spec/SpecWorkflowImpl.java | 6 +-- .../sync/DbtTransformationActivityImpl.java | 6 +-- .../sync/NormalizationActivityImpl.java | 6 +-- ...NormalizationSummaryCheckActivityImpl.java | 4 +- .../sync/PersistStateActivityImpl.java | 4 +- .../sync/ReplicationActivityImpl.java | 8 ++-- .../temporal/sync/SyncWorkflowImpl.java | 10 ++--- .../sync/WebhookOperationActivityImpl.java | 16 ++++++-- 36 files changed, 260 insertions(+), 134 deletions(-) delete mode 100644 airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java rename airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java => airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceConstants.java (50%) diff --git a/airbyte-commons-worker/build.gradle b/airbyte-commons-worker/build.gradle index 1421de6da4f9..8f93185c9410 100644 --- a/airbyte-commons-worker/build.gradle +++ b/airbyte-commons-worker/build.gradle @@ -13,6 +13,7 @@ dependencies { implementation 'io.temporal:temporal-sdk:1.8.1' implementation 'org.apache.ant:ant:1.10.10' implementation 'org.apache.commons:commons-text:1.10.0' + implementation libs.bundles.datadog implementation project(':airbyte-api') implementation project(':airbyte-commons-protocol') diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java index abc454b28787..111b4041ae55 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DbtTransformationWorker.java @@ -4,14 +4,21 @@ package io.airbyte.workers.general; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.io.LineGobbler; import io.airbyte.config.OperatorDbtInput; import io.airbyte.config.ResourceRequirements; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.workers.Worker; import io.airbyte.workers.exception.WorkerException; import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,10 +47,12 @@ public DbtTransformationWorker(final String jobId, this.cancelled = new AtomicBoolean(false); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Void run(final OperatorDbtInput operatorDbtInput, final Path jobRoot) throws WorkerException { final long startTime = System.currentTimeMillis(); LineGobbler.startSection("DBT TRANSFORMATION"); + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot)); try (dbtTransformationRunner) { LOGGER.info("Running dbt transformation."); @@ -59,6 +68,7 @@ public Void run(final OperatorDbtInput operatorDbtInput, final Path jobRoot) thr throw new WorkerException("DBT Transformation Failed."); } } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); throw new WorkerException("Dbt Transformation Failed.", e); } if (cancelled.get()) { @@ -72,6 +82,7 @@ public Void run(final OperatorDbtInput operatorDbtInput, final Path jobRoot) thr return null; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() { LOGGER.info("Cancelling Dbt Transformation runner..."); @@ -79,7 +90,8 @@ public void cancel() { cancelled.set(true); dbtTransformationRunner.close(); } catch (final Exception e) { - e.printStackTrace(); + ApmTraceUtils.addExceptionToTrace(e); + LOGGER.error("Unable to cancel Dbt Transformation runner.", e); } } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java index c29af2c84a96..66bc2d271b24 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultCheckConnectionWorker.java @@ -4,6 +4,10 @@ package io.airbyte.workers.general; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.io.IOs; import io.airbyte.commons.io.LineGobbler; @@ -13,6 +17,7 @@ import io.airbyte.config.StandardCheckConnectionInput; import io.airbyte.config.StandardCheckConnectionOutput; import io.airbyte.config.StandardCheckConnectionOutput.Status; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.protocol.models.AirbyteConnectionStatus; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteMessage.Type; @@ -52,10 +57,11 @@ public DefaultCheckConnectionWorker(final IntegrationLauncher integrationLaunche this(integrationLauncher, new DefaultAirbyteStreamFactory()); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public ConnectorJobOutput run(final StandardCheckConnectionInput input, final Path jobRoot) throws WorkerException { LineGobbler.startSection("CHECK"); - + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ROOT_KEY, jobRoot)); try { process = integrationLauncher.check( jobRoot, @@ -95,11 +101,13 @@ public ConnectorJobOutput run(final StandardCheckConnectionInput input, final Pa } } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); LOGGER.error("Unexpected error while checking connection: ", e); throw new WorkerException("Unexpected error while getting checking connection.", e); } } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() { WorkerUtils.cancelProcess(process); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java index 209fdbcdb559..8c5a961f1fdd 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultDiscoverCatalogWorker.java @@ -4,6 +4,12 @@ package io.airbyte.workers.general; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTOR_VERSION_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.SOURCE_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.io.IOs; import io.airbyte.commons.io.LineGobbler; import io.airbyte.commons.json.Jsons; @@ -11,6 +17,7 @@ import io.airbyte.config.ConnectorJobOutput.OutputType; import io.airbyte.config.StandardDiscoverCatalogInput; import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.protocol.models.AirbyteCatalog; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteMessage.Type; @@ -23,6 +30,7 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -56,8 +64,10 @@ public DefaultDiscoverCatalogWorker(final ConfigRepository configRepository, this(configRepository, integrationLauncher, new DefaultAirbyteStreamFactory()); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public ConnectorJobOutput run(final StandardDiscoverCatalogInput discoverSchemaInput, final Path jobRoot) throws WorkerException { + ApmTraceUtils.addTagsToTrace(generateTraceTags(discoverSchemaInput, jobRoot)); try { process = integrationLauncher.discover( jobRoot, @@ -101,12 +111,32 @@ public ConnectorJobOutput run(final StandardDiscoverCatalogInput discoverSchemaI String.format("Discover job subprocess finished with exit code %s", exitCode)); } } catch (final WorkerException e) { + ApmTraceUtils.addExceptionToTrace(e); throw e; } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); throw new WorkerException("Error while discovering schema", e); } } + private Map generateTraceTags(final StandardDiscoverCatalogInput discoverSchemaInput, final Path jobRoot) { + final Map tags = new HashMap<>(); + + tags.put(JOB_ROOT_KEY, jobRoot); + + if (discoverSchemaInput != null) { + if (discoverSchemaInput.getSourceId() != null) { + tags.put(SOURCE_ID_KEY, discoverSchemaInput.getSourceId()); + } + if (discoverSchemaInput.getConnectorVersion() != null) { + tags.put(CONNECTOR_VERSION_KEY, discoverSchemaInput.getConnectorVersion()); + } + } + + return tags; + } + + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() { WorkerUtils.cancelProcess(process); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java index cf6fbbb417a7..867093e23e22 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultGetSpecWorker.java @@ -4,11 +4,17 @@ package io.airbyte.workers.general; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.io.IOs; import io.airbyte.commons.io.LineGobbler; import io.airbyte.config.ConnectorJobOutput; import io.airbyte.config.ConnectorJobOutput.OutputType; import io.airbyte.config.JobGetSpecConfig; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteMessage.Type; import io.airbyte.protocol.models.ConnectorSpecification; @@ -47,8 +53,10 @@ public DefaultGetSpecWorker(final IntegrationLauncher integrationLauncher) { this(integrationLauncher, new DefaultAirbyteStreamFactory()); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public ConnectorJobOutput run(final JobGetSpecConfig config, final Path jobRoot) throws WorkerException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ROOT_KEY, jobRoot, DOCKER_IMAGE_KEY, config.getDockerImage())); try { process = integrationLauncher.spec(jobRoot); @@ -90,6 +98,7 @@ public ConnectorJobOutput run(final JobGetSpecConfig config, final Path jobRoot) } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() { WorkerUtils.cancelProcess(process); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java index d48c196b9ff3..00a4a45b3e94 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultNormalizationWorker.java @@ -4,11 +4,17 @@ package io.airbyte.workers.general; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.io.LineGobbler; import io.airbyte.config.Configs.WorkerEnvironment; import io.airbyte.config.FailureReason; import io.airbyte.config.NormalizationInput; import io.airbyte.config.NormalizationSummary; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.protocol.models.AirbyteTraceMessage; import io.airbyte.workers.exception.WorkerException; import io.airbyte.workers.helper.FailureHelper; @@ -19,6 +25,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.lang3.time.DurationFormatUtils; import org.slf4j.Logger; @@ -50,10 +57,13 @@ public DefaultNormalizationWorker(final String jobId, this.cancelled = new AtomicBoolean(false); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public NormalizationSummary run(final NormalizationInput input, final Path jobRoot) throws WorkerException { final long startTime = System.currentTimeMillis(); + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot)); + try (normalizationRunner) { LineGobbler.startSection("DEFAULT NORMALIZATION"); normalizationRunner.start(); @@ -69,6 +79,7 @@ public NormalizationSummary run(final NormalizationInput input, final Path jobRo buildFailureReasonsAndSetFailure(); } } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); buildFailureReasonsAndSetFailure(); } @@ -105,6 +116,7 @@ private void buildFailureReasonsAndSetFailure() { failed = true; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() { LOGGER.info("Cancelling normalization runner..."); @@ -112,7 +124,8 @@ public void cancel() { cancelled.set(true); normalizationRunner.close(); } catch (final Exception e) { - e.printStackTrace(); + ApmTraceUtils.addExceptionToTrace(e); + LOGGER.error("Unable to cancel normalization runner.", e); } } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java index 68f433a7072e..6ab4ef51bab7 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/general/DefaultReplicationWorker.java @@ -4,7 +4,13 @@ package io.airbyte.workers.general; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.fasterxml.jackson.databind.ObjectMapper; +import datadog.trace.api.Trace; import io.airbyte.commons.io.LineGobbler; import io.airbyte.config.FailureReason; import io.airbyte.config.ReplicationAttemptSummary; @@ -16,6 +22,7 @@ import io.airbyte.config.SyncStats; import io.airbyte.config.WorkerDestinationConfig; import io.airbyte.config.WorkerSourceConfig; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteMessage.Type; import io.airbyte.protocol.models.AirbyteRecordMessage; @@ -120,6 +127,7 @@ public DefaultReplicationWorker(final String jobId, * @return output of the replication attempt (including state) * @throws WorkerException */ + @Trace(operationName = WORKER_OPERATION_NAME) @Override public final ReplicationOutput run(final StandardSyncInput syncInput, final Path jobRoot) throws WorkerException { LOGGER.info("start sync worker. job id: {} attempt id: {}", jobId, attempt); @@ -146,6 +154,8 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path final Map mdc = MDC.getCopyOfContextMap(); + ApmTraceUtils.addTagsToTrace(generateTraceTags(destinationConfig, jobRoot)); + // note: resources are closed in the opposite order in which they are declared. thus source will be // closed first (which is what we want). try (destination; source) { @@ -195,6 +205,7 @@ public final ReplicationOutput run(final StandardSyncInput syncInput, final Path } catch (final Exception e) { hasFailed.set(true); + ApmTraceUtils.addExceptionToTrace(e); LOGGER.error("Sync worker failed.", e); } finally { executors.shutdownNow(); @@ -321,6 +332,7 @@ else if (hasFailed.get()) { LineGobbler.endSection("REPLICATION"); return output; } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); throw new WorkerException("Sync failed", e); } @@ -489,6 +501,7 @@ private static Runnable getDestinationOutputRunnable(final AirbyteDestination de }; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() { // Resources are closed in the opposite order they are declared. @@ -496,7 +509,8 @@ public void cancel() { try { executors.awaitTermination(10, TimeUnit.SECONDS); } catch (final InterruptedException e) { - e.printStackTrace(); + ApmTraceUtils.addExceptionToTrace(e); + LOGGER.error("Unable to cancel due to interruption.", e); } cancelled.set(true); @@ -504,6 +518,7 @@ public void cancel() { try { destination.cancel(); } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); LOGGER.info("Error cancelling destination: ", e); } @@ -511,11 +526,27 @@ public void cancel() { try { source.cancel(); } catch (final Exception e) { + ApmTraceUtils.addExceptionToTrace(e); LOGGER.info("Error cancelling source: ", e); } } + private Map generateTraceTags(final WorkerDestinationConfig destinationConfig, final Path jobRoot) { + final Map tags = new HashMap<>(); + + tags.put(JOB_ID_KEY, jobId); + tags.put(JOB_ROOT_KEY, jobRoot); + + if (destinationConfig != null) { + if (destinationConfig.getConnectionId() != null) { + tags.put(CONNECTION_ID_KEY, destinationConfig.getConnectionId()); + } + } + + return tags; + } + private static class SourceException extends RuntimeException { SourceException(final String message) { diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java index 7911a0545dc7..fe3d6da7dab7 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/DbtJobOrchestrator.java @@ -4,9 +4,9 @@ package io.airbyte.container_orchestrator; -import static io.airbyte.container_orchestrator.TraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.TemporalUtils; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java index dbb4ada1f4c8..1c602bb35c1b 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/NormalizationJobOrchestrator.java @@ -4,9 +4,9 @@ package io.airbyte.container_orchestrator; -import static io.airbyte.container_orchestrator.TraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java index b7f093434a68..c33c7ccabc80 100644 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java +++ b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/ReplicationJobOrchestrator.java @@ -4,10 +4,10 @@ package io.airbyte.container_orchestrator; -import static io.airbyte.container_orchestrator.TraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.container_orchestrator.TraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.JOB_ORCHESTRATOR_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.features.FeatureFlags; diff --git a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java b/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java deleted file mode 100644 index 00c5263c171d..000000000000 --- a/airbyte-container-orchestrator/src/main/java/io/airbyte/container_orchestrator/TraceConstants.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.container_orchestrator; - -/** - * Collection of constants for APM tracing. - */ -public class TraceConstants { - - public static final String JOB_ORCHESTRATOR_OPERATION_NAME = "job.orchestrator"; - - private TraceConstants() {} - - /** - * Trace tag constants. - */ - public static final class Tags { - - /** - * Name of the APM trace tag that holds the destination Docker image value associated with the - * trace. - */ - public static final String DESTINATION_DOCKER_IMAGE_KEY = "destination.docker_image"; - - /** - * Name of the APM trace tag that holds the job ID value associated with the trace. - */ - public static final String JOB_ID_KEY = "job_id"; - - /** - * Name of the APM trace tag that holds the source Docker image value associated with the trace. - */ - public static final String SOURCE_DOCKER_IMAGE_KEY = "source.docker_image"; - - private Tags() {} - - } - -} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceConstants.java similarity index 50% rename from airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java rename to airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceConstants.java index 80f6aa6b8c19..c636a9d935d6 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/trace/TemporalTraceConstants.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceConstants.java @@ -2,24 +2,34 @@ * Copyright (c) 2022 Airbyte, Inc., all rights reserved. */ -package io.airbyte.workers.temporal.trace; +package io.airbyte.metrics.lib; /** - * Collection of constants for APM tracing of Temporal activities and workflows. + * Collection of constants for APM tracing. */ -public final class TemporalTraceConstants { +public final class ApmTraceConstants { /** * Operation name for an APM trace of a Temporal activity. */ public static final String ACTIVITY_TRACE_OPERATION_NAME = "activity"; + /** + * Operation name for an APM trace of a job orchestrator. + */ + public static final String JOB_ORCHESTRATOR_OPERATION_NAME = "job.orchestrator"; + + /** + * Operation name for an APM trace of a worker implementation. + */ + public static final String WORKER_OPERATION_NAME = "worker"; + /** * Operation name for an APM trace of a Temporal workflow. */ public static final String WORKFLOW_TRACE_OPERATION_NAME = "workflow"; - private TemporalTraceConstants() {} + private ApmTraceConstants() {} /** * Trace tag constants. @@ -27,10 +37,16 @@ private TemporalTraceConstants() {} public static final class Tags { /** - * Name of the APM trace tag that holds the connection ID value associated with the trace. + * Name of the APM trace tag that holds the destination Docker image value associated with the + * trace. */ public static final String CONNECTION_ID_KEY = "connection_id"; + /** + * Name of the APM trace tag that holds the connector version value associated with the trace. + */ + public static final String CONNECTOR_VERSION_KEY = "connector_version"; + /** * Name of the APM trace tag that holds the destination Docker image value associated with the * trace. @@ -47,11 +63,26 @@ public static final class Tags { */ public static final String JOB_ID_KEY = "job_id"; + /** + * Name of the APM trace tag that holds the job root value associated with the trace. + */ + public static final String JOB_ROOT_KEY = "job_root"; + /** * Name of the APM trace tag that holds the source Docker image value associated with the trace. */ public static final String SOURCE_DOCKER_IMAGE_KEY = "source.docker_image"; + /** + * Name of the APM trace tag that holds the source ID value associated with the trace. + */ + public static final String SOURCE_ID_KEY = "source.id"; + + /** + * Name of the APM trace tag that holds the webhook config ID value associated with the trace. + */ + public static final String WEBHOOK_CONFIG_ID_KEY = "webhook.config_id"; + private Tags() {} } diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java index e7dd884a3a17..ab1ea21964cf 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/ApmTraceUtils.java @@ -5,6 +5,8 @@ package io.airbyte.metrics.lib; import io.opentracing.Span; +import io.opentracing.log.Fields; +import io.opentracing.tag.Tags; import io.opentracing.util.GlobalTracer; import java.util.Map; @@ -45,7 +47,7 @@ public static void addTagsToTrace(final Map tags, final String t } /** - * Adds all the provided tags to the currently active span, if one exists. + * Adds all the provided tags to the provided span, if one exists. * * @param span The {@link Span} that will be associated with the tags. * @param tags A map of tags to be added to the currently active span. @@ -59,4 +61,26 @@ public static void addTagsToTrace(final Span span, final Map tag } } + /** + * Adds an exception to the currently active span, if one exists. + * + * @param e The {@link Exception} to be added to the currently active span. + */ + public static void addExceptionToTrace(final Exception e) { + addExceptionToTrace(GlobalTracer.get().activeSpan(), e); + } + + /** + * Adds an exception to the provided span, if one exists. + * + * @param span The {@link Span} that will be associated with the exception. + * @param e The {@link Exception} to be added to the provided span. + */ + public static void addExceptionToTrace(final Span span, final Exception e) { + if (span != null) { + span.setTag(Tags.ERROR, true); + span.log(Map.of(Fields.ERROR_OBJECT, e)); + } + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java index 937935134f19..608c50630095 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.check.connection; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import com.fasterxml.jackson.databind.JsonNode; import datadog.trace.api.Trace; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java index a6f24a32c835..5a00a44f6c11 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/check/connection/CheckConnectionWorkflowImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.check.connection; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.CheckConnectionWorkflow; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java index 94da4bc7e348..4626ffc1aebb 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.discover.catalog; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import com.fasterxml.jackson.databind.JsonNode; import datadog.trace.api.Trace; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java index fafb9ca380c5..127ed8162d96 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/discover/catalog/DiscoverCatalogWorkflowImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.discover.catalog; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.DiscoverCatalogWorkflow; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java index a6e662a205fb..b35e1680a4ea 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/ConnectionManagerWorkflowImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.scheduling; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; import com.fasterxml.jackson.databind.JsonNode; import datadog.trace.api.Trace; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java index b07f704e3e8d..661b487fa86b 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/AutoDisableConnectionActivityImpl.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.scheduling.activities; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; import static io.airbyte.persistence.job.JobNotifier.CONNECTION_DISABLED_NOTIFICATION; import static io.airbyte.persistence.job.JobNotifier.CONNECTION_DISABLED_WARNING_NOTIFICATION; import static io.airbyte.persistence.job.models.Job.REPLICATION_TYPES; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; import static java.time.temporal.ChronoUnit.DAYS; import datadog.trace.api.Trace; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java index 11b1b9f7f75b..5005526cb222 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConfigFetchActivityImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java index 3d59402bbdda..28c00e99fe0e 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/ConnectionDeletionActivityImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java index eae169f52d11..3633af029aed 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/GenerateInputActivityImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java index 0dd4cffe1c27..22bd8ac5dc8f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java @@ -5,10 +5,10 @@ package io.airbyte.workers.temporal.scheduling.activities; import static io.airbyte.config.JobConfig.ConfigType.SYNC; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import static io.airbyte.persistence.job.models.AttemptStatus.FAILED; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; import com.google.common.collect.Lists; import datadog.trace.api.Trace; @@ -380,24 +380,24 @@ public void ensureCleanJobState(final EnsureCleanJobStateInput input) { } @Override - public boolean isLastJobOrAttemptFailure(JobCheckFailureInput input) { + public boolean isLastJobOrAttemptFailure(final JobCheckFailureInput input) { final int limit = 2; boolean lastAttemptCheck = false; boolean lastJobCheck = false; - Set configTypes = new HashSet<>(); + final Set configTypes = new HashSet<>(); configTypes.add(SYNC); try { - List jobList = jobPersistence.listJobsIncludingId(configTypes, input.getConnectionId().toString(), input.getJobId(), limit); - Optional optionalActiveJob = jobList.stream().filter(job -> job.getId() == input.getJobId()).findFirst(); + final List jobList = jobPersistence.listJobsIncludingId(configTypes, input.getConnectionId().toString(), input.getJobId(), limit); + final Optional optionalActiveJob = jobList.stream().filter(job -> job.getId() == input.getJobId()).findFirst(); if (optionalActiveJob.isPresent()) { lastAttemptCheck = checkActiveJobPreviousAttempt(optionalActiveJob.get(), input.getAttemptId()); } - OptionalLong previousJobId = getPreviousJobId(input.getJobId(), jobList.stream().map(Job::getId).toList()); + final OptionalLong previousJobId = getPreviousJobId(input.getJobId(), jobList.stream().map(Job::getId).toList()); if (previousJobId.isPresent()) { - Optional optionalPreviousJob = jobList.stream().filter(job -> job.getId() == previousJobId.getAsLong()).findFirst(); + final Optional optionalPreviousJob = jobList.stream().filter(job -> job.getId() == previousJobId.getAsLong()).findFirst(); if (optionalPreviousJob.isPresent()) { lastJobCheck = optionalPreviousJob.get().getStatus().equals(io.airbyte.persistence.job.models.JobStatus.FAILED); } @@ -409,18 +409,18 @@ public boolean isLastJobOrAttemptFailure(JobCheckFailureInput input) { } } - private OptionalLong getPreviousJobId(Long activeJobId, List jobIdsList) { + private OptionalLong getPreviousJobId(final Long activeJobId, final List jobIdsList) { return jobIdsList.stream() .filter(jobId -> !Objects.equals(jobId, activeJobId)) .mapToLong(jobId -> jobId).max(); } - private boolean checkActiveJobPreviousAttempt(Job activeJob, int attemptId) { + private boolean checkActiveJobPreviousAttempt(final Job activeJob, final int attemptId) { final int minAttemptSize = 1; boolean result = false; if (activeJob.getAttempts().size() > minAttemptSize) { - Optional optionalAttempt = activeJob.getAttempts().stream() + final Optional optionalAttempt = activeJob.getAttempts().stream() .filter(attempt -> attempt.getId() == (attemptId - 1)).findFirst(); result = optionalAttempt.isPresent() && optionalAttempt.get().getStatus().equals(FAILED); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java index 7c7708135f74..3d82095a5224 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RecordMetricActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java index 95dd69e976b6..30cbacb97c55 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/RouteToSyncTaskQueueActivityImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.exception.RetryableException; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java index a879ad7fe669..a94f7c7c3e4c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/StreamResetActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.StreamResetRecordsHelper; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java index e77d9f48562d..4ddfda6be5c1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/WorkflowConfigActivityImpl.java @@ -4,7 +4,7 @@ package io.airbyte.workers.temporal.scheduling.activities; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.config.WorkerMode; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java index 8e5cd32e486e..a80a1389cd52 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.spec; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java index 76805d794f4f..a13493f56ae1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/spec/SpecWorkflowImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.spec; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.SpecWorkflow; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java index cab8ad5ff8b7..df06d31662c0 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/DbtTransformationActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.sync; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java index 4d954c8e2735..71d058051e0a 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationActivityImpl.java @@ -4,9 +4,9 @@ package io.airbyte.workers.temporal.sync; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.api.client.AirbyteApiClient; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java index c46243c1e6ac..8d508538f214 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java @@ -4,8 +4,8 @@ package io.airbyte.workers.temporal.sync; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; import io.airbyte.metrics.lib.ApmTraceUtils; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java index e64339911be3..40c37265b79f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java @@ -5,9 +5,9 @@ package io.airbyte.workers.temporal.sync; import static io.airbyte.config.helpers.StateMessageHelper.isMigration; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; import static io.airbyte.workers.helper.StateConverter.convertClientStateTypeToInternal; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; import com.google.common.annotations.VisibleForTesting; import datadog.trace.api.Trace; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java index f0fbc5b075f3..f5ebbd7fafc5 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/ReplicationActivityImpl.java @@ -4,10 +4,10 @@ package io.airbyte.workers.temporal.sync; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; import datadog.trace.api.Trace; import edu.umd.cs.findbugs.annotations.Nullable; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java index f2c6b3f021d1..8270db6c7493 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java @@ -4,11 +4,11 @@ package io.airbyte.workers.temporal.sync; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.CONNECTION_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.JOB_ID_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; -import static io.airbyte.workers.temporal.trace.TemporalTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.CONNECTION_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DESTINATION_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.SOURCE_DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKFLOW_TRACE_OPERATION_NAME; import datadog.trace.api.Trace; import io.airbyte.commons.temporal.scheduling.SyncWorkflow; diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java index 8e55be47e09c..d4aaf732112f 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/WebhookOperationActivityImpl.java @@ -4,17 +4,23 @@ package io.airbyte.workers.temporal.sync; +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.WEBHOOK_CONFIG_ID_KEY; + import com.fasterxml.jackson.databind.JsonNode; +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.config.OperatorWebhookInput; import io.airbyte.config.WebhookConfig; import io.airbyte.config.WebhookOperationConfigs; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; +import io.airbyte.metrics.lib.ApmTraceUtils; import jakarta.inject.Singleton; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.Map; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,8 +41,9 @@ public WebhookOperationActivityImpl(final HttpClient httpClient, final SecretsHy } + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override - public boolean invokeWebhook(OperatorWebhookInput input) { + public boolean invokeWebhook(final OperatorWebhookInput input) { LOGGER.debug("Webhook operation input: {}", input); LOGGER.debug("Found webhook config: {}", input.getWorkspaceWebhookConfigs()); final JsonNode fullWebhookConfigJson = secretsHydrator.hydrate(input.getWorkspaceWebhookConfigs()); @@ -47,6 +54,7 @@ public boolean invokeWebhook(OperatorWebhookInput input) { throw new RuntimeException(String.format("Cannot find webhook config %s", input.getWebhookConfigId().toString())); } + ApmTraceUtils.addTagsToTrace(Map.of(WEBHOOK_CONFIG_ID_KEY, input.getWebhookConfigId())); LOGGER.info("Invoking webhook operation {}", webhookConfig.get().getName()); final HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() @@ -64,14 +72,14 @@ public boolean invokeWebhook(OperatorWebhookInput input) { // TODO(mfsiega-airbyte): replace this loop with retries configured on the HttpClient impl. for (int i = 0; i < MAX_RETRIES; i++) { try { - HttpResponse response = this.httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + final HttpResponse response = this.httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); LOGGER.debug("Webhook response: {}", response == null ? null : response.body()); LOGGER.info("Webhook response status: {}", response == null ? "empty response" : response.statusCode()); // Return true if the request was successful. - boolean isSuccessful = response != null && response.statusCode() >= 200 && response.statusCode() <= 300; + final boolean isSuccessful = response != null && response.statusCode() >= 200 && response.statusCode() <= 300; LOGGER.info("Webhook {} execution status {}", webhookConfig.get().getName(), isSuccessful ? "successful" : "failed"); return isSuccessful; - } catch (Exception e) { + } catch (final Exception e) { LOGGER.warn(e.getMessage()); finalException = e; } From 384aabbdf6ac28d798b85c05ffbc34cf6aa8504f Mon Sep 17 00:00:00 2001 From: Arsen Losenko <20901439+arsenlosenko@users.noreply.github.com> Date: Mon, 24 Oct 2022 21:41:42 +0300 Subject: [PATCH 284/498] Source Shopify: add unit tests (#17944) --- .../source-shopify/unit_tests/test_auth.py | 52 ++++++ .../source-shopify/unit_tests/test_source.py | 148 ++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py create mode 100644 airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py diff --git a/airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py b/airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py new file mode 100644 index 000000000000..f14a59829166 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/unit_tests/test_auth.py @@ -0,0 +1,52 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest +from source_shopify.auth import NotImplementedAuth, ShopifyAuthenticator + +TEST_ACCESS_TOKEN = "test_access_token" +TEST_API_PASSWORD = "test_api_password" + + +@pytest.fixture +def config_access_token(): + return {"credentials": {"access_token": TEST_ACCESS_TOKEN, "auth_method": "access_token"}} + + +@pytest.fixture +def config_api_password(): + return {"credentials": {"api_password": TEST_API_PASSWORD, "auth_method": "api_password"}} + + +@pytest.fixture +def config_not_implemented_auth_method(): + return {"credentials": {"auth_method": "not_implemented_auth_method"}} + + +@pytest.fixture +def expected_auth_header_access_token(): + return {"X-Shopify-Access-Token": TEST_ACCESS_TOKEN} + + +@pytest.fixture +def expected_auth_header_api_password(): + return {"X-Shopify-Access-Token": TEST_API_PASSWORD} + + +def test_shopify_authenticator_access_token(config_access_token, expected_auth_header_access_token): + authenticator = ShopifyAuthenticator(config=config_access_token) + assert authenticator.get_auth_header() == expected_auth_header_access_token + + +def test_shopify_authenticator_api_password(config_api_password, expected_auth_header_api_password): + authenticator = ShopifyAuthenticator(config=config_api_password) + assert authenticator.get_auth_header() == expected_auth_header_api_password + + +def test_raises_notimplemented_auth(config_not_implemented_auth_method): + authenticator = ShopifyAuthenticator(config=(config_not_implemented_auth_method)) + with pytest.raises(NotImplementedAuth) as not_implemented_exc: + print(not_implemented_exc) + authenticator.get_auth_header() diff --git a/airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py b/airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py new file mode 100644 index 000000000000..d57662efc6e8 --- /dev/null +++ b/airbyte-integrations/connectors/source-shopify/unit_tests/test_source.py @@ -0,0 +1,148 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from unittest.mock import MagicMock + +import pytest +from source_shopify.auth import ShopifyAuthenticator +from source_shopify.source import ( + AbandonedCheckouts, + Articles, + Blogs, + Collects, + CustomCollections, + Customers, + DiscountCodes, + DraftOrders, + FulfillmentOrders, + Fulfillments, + InventoryLevels, + Locations, + MetafieldArticles, + MetafieldBlogs, + MetafieldCollections, + MetafieldCustomers, + MetafieldDraftOrders, + MetafieldLocations, + MetafieldOrders, + MetafieldPages, + MetafieldProducts, + MetafieldProductVariants, + MetafieldShops, + MetafieldSmartCollections, + OrderRefunds, + OrderRisks, + Orders, + Pages, + PriceRules, + ProductImages, + Products, + ProductVariants, + Shop, + SourceShopify, + TenderTransactions, + Transactions, +) + + +@pytest.fixture +def config(basic_config): + basic_config["start_date"] = "2020-11-01" + basic_config["authenticator"] = ShopifyAuthenticator(basic_config) + return basic_config + + +@pytest.mark.parametrize( + "stream,stream_slice,expected_path", + [ + (Articles, None, "articles.json"), + (Blogs, None, "blogs.json"), + (MetafieldBlogs, {"id": 123}, "blogs/123/metafields.json"), + (MetafieldArticles, {"id": 123}, "articles/123/metafields.json"), + (MetafieldCustomers, {"id": 123}, "customers/123/metafields.json"), + (MetafieldOrders, {"id": 123}, "orders/123/metafields.json"), + (MetafieldDraftOrders, {"id": 123}, "draft_orders/123/metafields.json"), + (MetafieldProducts, {"id": 123}, "products/123/metafields.json"), + (MetafieldProductVariants, {"variants": 123}, "variants/123/metafields.json"), + (MetafieldSmartCollections, {"id": 123}, "smart_collections/123/metafields.json"), + (MetafieldCollections, {"collection_id": 123}, "collections/123/metafields.json"), + (MetafieldPages, {"id": 123}, "pages/123/metafields.json"), + (MetafieldLocations, {"id": 123}, "locations/123/metafields.json"), + (MetafieldShops, None, "metafields.json"), + (ProductImages, {"product_id": 123}, "products/123/images.json"), + (ProductVariants, {"product_id": 123}, "products/123/variants.json"), + (Customers, None, "customers.json"), + (Orders, None, "orders.json"), + (DraftOrders, None, "draft_orders.json"), + (Products, None, "products.json"), + (AbandonedCheckouts, None, "checkouts.json"), + (Collects, None, "collects.json"), + (TenderTransactions, None, "tender_transactions.json"), + (Pages, None, "pages.json"), + (PriceRules, None, "price_rules.json"), + (Locations, None, "locations.json"), + (Shop, None, "shop.json"), + (CustomCollections, None, "custom_collections.json"), + ], +) +def test_customers_path(stream, stream_slice, expected_path, config): + stream = stream(config) + if stream_slice: + result = stream.path(stream_slice) + else: + result = stream.path() + assert result == expected_path + + +@pytest.mark.parametrize( + "stream,stream_slice,expected_path", + [ + (OrderRefunds, {"order_id": 12345}, "orders/12345/refunds.json"), + (OrderRisks, {"order_id": 12345}, "orders/12345/risks.json"), + (Transactions, {"order_id": 12345}, "orders/12345/transactions.json"), + (DiscountCodes, {"price_rule_id": 12345}, "price_rules/12345/discount_codes.json"), + (InventoryLevels, {"location_id": 12345}, "locations/12345/inventory_levels.json"), + (FulfillmentOrders, {"order_id": 12345}, "orders/12345/fulfillment_orders.json"), + (Fulfillments, {"order_id": 12345}, "orders/12345/fulfillments.json"), + ], +) +def test_customers_path_with_stream_slice_param(stream, stream_slice, expected_path, config): + stream = stream(config) + assert stream.path(stream_slice) == expected_path + + +def test_check_connection(config, mocker): + mocker.patch("source_shopify.source.Shop.read_records", return_value=[{"id": 1}]) + source = SourceShopify() + logger_mock = MagicMock() + assert source.check_connection(logger_mock, config) == (True, None) + + +def test_read_records(config, mocker): + records = [{"created_at": "2022-10-10T06:21:53-07:00", "orders": {"updated_at": "2022-10-10T06:21:53-07:00"}}] + stream_slice = records[0] + stream = OrderRefunds(config) + mocker.patch("source_shopify.source.IncrementalShopifyStream.read_records", return_value=records) + assert next(stream.read_records(stream_slice=stream_slice)) == records[0] + + +@pytest.mark.parametrize( + "stream, expected", + [ + (OrderRefunds, {"limit": 250}), + (Orders, {"limit": 250, "status": "any", "order": "updated_at asc", "updated_at_min": "2020-11-01"}), + (AbandonedCheckouts, {"limit": 250, "status": "any", "order": "updated_at asc", "updated_at_min": "2020-11-01"}), + ], +) +def test_request_params(config, stream, expected): + assert stream(config).request_params() == expected + + +def test_get_updated_state(config): + current_stream_state = {"created_at": ""} + latest_record = {"created_at": "2022-10-10T06:21:53-07:00"} + updated_state = {"created_at": "2022-10-10T06:21:53-07:00", "orders": None} + stream = OrderRefunds(config) + assert stream.get_updated_state(current_stream_state=current_stream_state, latest_record=latest_record) == updated_state From d77f913373b85786ead8b1c6e29e11d0565ea0d2 Mon Sep 17 00:00:00 2001 From: Nipunn Koorapati Date: Mon, 24 Oct 2022 12:20:43 -0700 Subject: [PATCH 285/498] Export python type information for airbyte-cdk (#18387) Fixes #18384 --- airbyte-cdk/python/airbyte_cdk/py.typed | 0 airbyte-cdk/python/setup.py | 1 + 2 files changed, 1 insertion(+) create mode 100644 airbyte-cdk/python/airbyte_cdk/py.typed diff --git a/airbyte-cdk/python/airbyte_cdk/py.typed b/airbyte-cdk/python/airbyte_cdk/py.typed new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index 34416a842a39..5bc6577ecf4a 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -42,6 +42,7 @@ "Tracker": "https://github.com/airbytehq/airbyte/issues", }, packages=find_packages(exclude=("unit_tests",)), + package_data={"airbyte_cdk": ["py.typed"]}, install_requires=[ "backoff", # pinned to the last working version for us temporarily while we fix From 75493f9129c57e4f8eca52dafa7c32b189a9d45e Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Mon, 24 Oct 2022 22:30:10 +0200 Subject: [PATCH 286/498] Fix error highlight of select boxes (#18396) --- .../src/components/ui/DropDown/CustomSelect.tsx | 8 ++++++-- .../Connector/ServiceForm/components/Property/Control.tsx | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx b/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx index 7482c87af1f6..323b79aafa79 100644 --- a/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx +++ b/airbyte-webapp/src/components/ui/DropDown/CustomSelect.tsx @@ -13,7 +13,7 @@ export const CustomSelect = styled(Select)< box-shadow: none; border: 1px solid ${({ theme, $withBorder, $error }) => - $error ? theme.dangerColor : $withBorder ? theme.greyColor30 : theme.greyColor0}; + $error ? theme.red100 : $withBorder ? theme.greyColor30 : theme.greyColor0}; background: ${({ theme }) => theme.greyColor0}; border-radius: 4px; font-size: 14px; @@ -21,8 +21,12 @@ export const CustomSelect = styled(Select)< min-height: 36px; flex-wrap: nowrap; + &:not(:focus-within, :disabled):hover { + border-color: ${({ theme, $error }) => ($error ? theme.red : undefined)}; + } + &:hover { - border-color: ${({ theme, $error }) => ($error ? theme.dangerColor : theme.greyColor10)}; + border-color: ${({ theme }) => theme.greyColor10}; } &.react-select__control--menu-is-open, diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx index 54ec635623d7..bc012afc39b2 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Property/Control.tsx @@ -78,6 +78,7 @@ export const Control: React.FC = ({ onChange={(selectedItem) => selectedItem && helpers.setValue(selectedItem.value)} value={value} isDisabled={disabled} + error={error} /> ); } else if (property.multiline && !property.isSecret) { From 25292b7891ab6f0b17da8204a4b811dd98d3cd04 Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Mon, 24 Oct 2022 23:31:52 +0300 Subject: [PATCH 287/498] Source Hubspot: update `contacts` scope in docs (#18380) Signed-off-by: Sergey Chvalyuk --- docs/integrations/sources/hubspot.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/integrations/sources/hubspot.md b/docs/integrations/sources/hubspot.md index 781934d7a6bd..ceaa37fd2706 100644 --- a/docs/integrations/sources/hubspot.md +++ b/docs/integrations/sources/hubspot.md @@ -9,21 +9,21 @@ You can use OAuth, API key, or Private App to authenticate your HubSpot account. | Stream | Required Scope | | :-------------------------- | :------------------------------------------------------------------------------- | | `campaigns` | `content` | -| `companies` | `contacts` | -| `contact_lists` | `contacts` | -| `contacts` | `contacts` | -| `contacts_list_memberships` | `contacts` | -| `deal_pipelines` | either the `contacts` scope \(to fetch deals pipelines\) or the `tickets` scope. | -| `deals` | `contacts` | +| `companies` | `crm.objects.contacts.read` | +| `contact_lists` | `crm.objects.contacts.read` | +| `contacts` | `crm.objects.contacts.read` | +| `contacts_list_memberships` | `crm.objects.contacts.read` | +| `deal_pipelines` | either the `crm.objects.contacts.read` scope \(to fetch deals pipelines\) or the `tickets` scope. | +| `deals` | `crm.objects.contacts.read` | | `email_events` | `content` | -| `engagements` | `contacts` | +| `engagements` | `crm.objects.contacts.read` | | `engagements_emails` | `sales-email-read` | | `forms` | `forms` | | `form_submissions` | `forms` | | `line_items` | `e-commerce` | -| `owners` | `contacts` | +| `owners` | `crm.objects.contacts.read` | | `products` | `e-commerce` | -| `property_history` | `contacts` | +| `property_history` | `crm.objects.contacts.read` | | `quotes` | no scope required | | `subscription_changes` | `content` | | `tickets` | `tickets` | From 4b93a9b436ead9657c5fa56cdef76c974151ea85 Mon Sep 17 00:00:00 2001 From: Cole Snodgrass Date: Mon, 24 Oct 2022 14:21:33 -0700 Subject: [PATCH 288/498] remove extra ' in version (#18400) - rm extra `'` in version string; `"2.1.1'"` -> `"2.1.1"` --- deps.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps.toml b/deps.toml index 18b9a054d76c..3284821545f6 100644 --- a/deps.toml +++ b/deps.toml @@ -100,7 +100,7 @@ quartz-scheduler = { module = "org.quartz-scheduler:quartz", version = "2.3.2" } # Micronaut-related dependencies h2-database = { module = "com.h2database:h2", version = "2.1.214" } hibernate-types = { module = "com.vladmihalcea:hibernate-types-52", version = "2.16.3" } -jakarta-inject = { module = "jakarta.annotation:jakarta.annotation-api", version = "2.1.1'" } +jakarta-inject = { module = "jakarta.annotation:jakarta.annotation-api", version = "2.1.1" } javax-transaction = { module = "javax.transaction:javax.transaction-api", version = "1.3" } micronaut-bom = { module = "io.micronaut:micronaut-bom", version.ref = "micronaut" } micronaut-data-processor = { module = "io.micronaut.data:micronaut-data-processor", version = "3.8.1" } From 1ee02d416c8e07bffe222945b0b01f383bf8f1ce Mon Sep 17 00:00:00 2001 From: Malik Diarra Date: Mon, 24 Oct 2022 14:37:19 -0700 Subject: [PATCH 289/498] Improve listSourcesForWorkspace route (#18207) --- .../config/persistence/ConfigRepository.java | 17 +++++++++++++++++ .../ConfigRepositoryE2EReadWriteTest.java | 9 +++++++++ .../airbyte/server/handlers/SourceHandler.java | 5 +---- .../server/handlers/SourceHandlerTest.java | 2 +- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 3fb6c2b23b3c..f65c701d2b34 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -591,6 +591,23 @@ public List listSourceConnection() throws JsonValidationExcept return persistence.listConfigs(ConfigSchema.SOURCE_CONNECTION, SourceConnection.class); } + /** + * Returns all sources for a workspace. Does not contain secrets. + * + * @param workspaceId - id of the workspace + * @return sources + * @throws JsonValidationException - throws if returned sources are invalid + * @throws IOException - you never know when you IO + */ + public List listWorkspaceSourceConnection(final UUID workspaceId) throws IOException { + final Result result = database.query(ctx -> ctx.select(asterisk()) + .from(ACTOR) + .where(ACTOR.ACTOR_TYPE.eq(ActorType.source)) + .and(ACTOR.WORKSPACE_ID.eq(workspaceId)) + .andNot(ACTOR.TOMBSTONE).fetch()); + return result.stream().map(DbConverter::buildSourceConnection).collect(Collectors.toList()); + } + /** * Returns destination with a given id. Does not contain secrets. To hydrate with secrets see * { @link SecretsRepositoryReader#getDestinationConnectionWithSecrets(final UUID destinationId) }. diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index 07df60011d7e..eea254e61d55 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -297,6 +297,15 @@ void testListPublicSourceDefinitions() throws IOException { assertEquals(List.of(MockData.publicSourceDefinition()), actualDefinitions); } + @Test + void testListWorkspaceSources() throws IOException { + UUID workspaceId = MockData.standardWorkspaces().get(1).getWorkspaceId(); + final List expectedSources = MockData.sourceConnections().stream() + .filter(source -> source.getWorkspaceId().equals(workspaceId)).collect(Collectors.toList()); + final List sources = configRepository.listWorkspaceSourceConnection(workspaceId); + assertThat(sources).hasSameElementsAs(expectedSources); + } + @Test void testSourceDefinitionGrants() throws IOException { final UUID workspaceId = MockData.standardWorkspaces().get(0).getWorkspaceId(); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java index 57c751e3e589..743c4b9e4f74 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java @@ -162,10 +162,7 @@ public SourceRead cloneSource(final SourceCloneRequestBody sourceCloneRequestBod public SourceReadList listSourcesForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) throws ConfigNotFoundException, IOException, JsonValidationException { - final List sourceConnections = configRepository.listSourceConnection() - .stream() - .filter(sc -> sc.getWorkspaceId().equals(workspaceIdRequestBody.getWorkspaceId()) && !MoreBooleans.isTruthy(sc.getTombstone())) - .toList(); + final List sourceConnections = configRepository.listWorkspaceSourceConnection(workspaceIdRequestBody.getWorkspaceId()); final List reads = Lists.newArrayList(); for (final SourceConnection sc : sourceConnections) { diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/SourceHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/SourceHandlerTest.java index c8157a8ae493..49b0651fbdb6 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/SourceHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/SourceHandlerTest.java @@ -261,7 +261,7 @@ void testListSourcesForWorkspace() throws JsonValidationException, ConfigNotFoun when(configRepository.getSourceConnection(sourceConnection.getSourceId())).thenReturn(sourceConnection); when(configRepository.getSourceConnection(sourceConnection.getSourceId())).thenReturn(sourceConnection); - when(configRepository.listSourceConnection()).thenReturn(Lists.newArrayList(sourceConnection)); + when(configRepository.listWorkspaceSourceConnection(sourceConnection.getWorkspaceId())).thenReturn(Lists.newArrayList(sourceConnection)); when(configRepository.getStandardSourceDefinition(sourceDefinitionSpecificationRead.getSourceDefinitionId())) .thenReturn(standardSourceDefinition); when(configRepository.getSourceDefinitionFromSource(sourceConnection.getSourceId())).thenReturn(standardSourceDefinition); From ce88962dc53343834014f9d13c93a40ce4d161dc Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 25 Oct 2022 01:21:17 +0300 Subject: [PATCH 290/498] =?UTF-8?q?=F0=9F=AA=9F=20=F0=9F=90=9B=20=20Fix=20?= =?UTF-8?q?additional=20buttons=20for=20connection=20creation=20(#18372)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make button enabled regardless credit balance * remove FeatureItem.AllowCreateConnection at all, since we don't need it anymore * update tests * Replace "AllowCreateConnection" feature flag with "AllowDBTCloudIntegration" in tests --- .../ConnectorBlocks/TableItemTitle.tsx | 4 +- .../services/Feature/FeatureService.test.tsx | 43 ++++++++++--------- .../src/hooks/services/Feature/constants.ts | 1 - .../src/hooks/services/Feature/types.tsx | 1 - airbyte-webapp/src/packages/cloud/App.tsx | 4 +- .../src/packages/cloud/cloudRoutes.tsx | 4 +- 6 files changed, 26 insertions(+), 31 deletions(-) diff --git a/airbyte-webapp/src/components/ConnectorBlocks/TableItemTitle.tsx b/airbyte-webapp/src/components/ConnectorBlocks/TableItemTitle.tsx index f51a44621c47..a946d7c1cd0c 100644 --- a/airbyte-webapp/src/components/ConnectorBlocks/TableItemTitle.tsx +++ b/airbyte-webapp/src/components/ConnectorBlocks/TableItemTitle.tsx @@ -8,7 +8,6 @@ import { Popout } from "components/ui/Popout"; import { Text } from "components/ui/Text"; import { ReleaseStage } from "core/request/AirbyteClient"; -import { FeatureItem, useFeature } from "hooks/services/Feature"; import styles from "./TableItemTitle.module.scss"; @@ -31,7 +30,6 @@ const TableItemTitle: React.FC = ({ entityIcon, releaseStage, }) => { - const allowCreateConnection = useFeature(FeatureItem.AllowCreateConnection); const { formatMessage } = useIntl(); const options = [ { @@ -76,7 +74,7 @@ const TableItemTitle: React.FC = ({ menuShouldBlockScroll={false} onChange={onSelect} targetComponent={({ onOpen }) => ( - )} diff --git a/airbyte-webapp/src/hooks/services/Feature/FeatureService.test.tsx b/airbyte-webapp/src/hooks/services/Feature/FeatureService.test.tsx index 7eaf61ccd256..53c670739ad8 100644 --- a/airbyte-webapp/src/hooks/services/Feature/FeatureService.test.tsx +++ b/airbyte-webapp/src/hooks/services/Feature/FeatureService.test.tsx @@ -1,12 +1,12 @@ import { render } from "@testing-library/react"; import { renderHook } from "@testing-library/react-hooks"; -import { useEffect } from "react"; +import React, { useEffect } from "react"; import { FeatureService, IfFeatureEnabled, useFeature, useFeatureService } from "./FeatureService"; import { FeatureItem, FeatureSet } from "./types"; const wrapper: React.FC> = ({ children }) => ( - {children} + {children} ); type FeatureOverwrite = FeatureItem[] | FeatureSet | undefined; @@ -44,7 +44,7 @@ describe("Feature Service", () => { describe("FeatureService", () => { it("should allow setting default features", () => { const getFeature = (feature: FeatureItem) => renderHook(() => useFeature(feature), { wrapper }).result.current; - expect(getFeature(FeatureItem.AllowCreateConnection)).toBe(true); + expect(getFeature(FeatureItem.AllowDBTCloudIntegration)).toBe(true); expect(getFeature(FeatureItem.AllowCustomDBT)).toBe(false); expect(getFeature(FeatureItem.AllowSync)).toBe(true); expect(getFeature(FeatureItem.AllowUpdateConnectors)).toBe(false); @@ -56,8 +56,8 @@ describe("Feature Service", () => { workspace: [FeatureItem.AllowCustomDBT, FeatureItem.AllowUploadCustomImage], }).result.current.sort() ).toEqual([ - FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT, + FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowSync, FeatureItem.AllowUploadCustomImage, ]); @@ -66,7 +66,10 @@ describe("Feature Service", () => { it("workspace features can disable default features", () => { expect( getFeatures({ - workspace: { [FeatureItem.AllowCustomDBT]: true, [FeatureItem.AllowCreateConnection]: false } as FeatureSet, + workspace: { + [FeatureItem.AllowCustomDBT]: true, + [FeatureItem.AllowDBTCloudIntegration]: false, + } as FeatureSet, }).result.current.sort() ).toEqual([FeatureItem.AllowCustomDBT, FeatureItem.AllowSync]); }); @@ -78,8 +81,8 @@ describe("Feature Service", () => { user: [FeatureItem.AllowOAuthConnector], }).result.current.sort() ).toEqual([ - FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT, + FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowOAuthConnector, FeatureItem.AllowSync, FeatureItem.AllowUploadCustomImage, @@ -93,7 +96,7 @@ describe("Feature Service", () => { user: { [FeatureItem.AllowOAuthConnector]: true, [FeatureItem.AllowUploadCustomImage]: false, - [FeatureItem.AllowCreateConnection]: false, + [FeatureItem.AllowDBTCloudIntegration]: false, } as FeatureSet, }).result.current.sort() ).toEqual([FeatureItem.AllowCustomDBT, FeatureItem.AllowOAuthConnector, FeatureItem.AllowSync]); @@ -106,27 +109,27 @@ describe("Feature Service", () => { user: [FeatureItem.AllowOAuthConnector, FeatureItem.AllowSync], }).result.current.sort() ).toEqual([ - FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT, + FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowOAuthConnector, FeatureItem.AllowSync, ]); }); - it("overwritte features can overwrite workspace and user features", () => { + it("overwrite features can overwrite workspace and user features", () => { expect( getFeatures({ workspace: { [FeatureItem.AllowCustomDBT]: true, [FeatureItem.AllowSync]: false } as FeatureSet, user: { [FeatureItem.AllowOAuthConnector]: true, [FeatureItem.AllowSync]: true, - [FeatureItem.AllowCreateConnection]: false, + [FeatureItem.AllowDBTCloudIntegration]: false, } as FeatureSet, - overwrite: [FeatureItem.AllowUploadCustomImage, FeatureItem.AllowCreateConnection], + overwrite: [FeatureItem.AllowUploadCustomImage, FeatureItem.AllowDBTCloudIntegration], }).result.current.sort() ).toEqual([ - FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT, + FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowOAuthConnector, FeatureItem.AllowSync, FeatureItem.AllowUploadCustomImage, @@ -137,34 +140,34 @@ describe("Feature Service", () => { const { result, rerender } = getFeatures({ workspace: { [FeatureItem.AllowCustomDBT]: true, [FeatureItem.AllowSync]: false } as FeatureSet, }); - expect(result.current.sort()).toEqual([FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT]); + expect(result.current.sort()).toEqual([FeatureItem.AllowCustomDBT, FeatureItem.AllowDBTCloudIntegration]); rerender({ workspace: undefined }); - expect(result.current.sort()).toEqual([FeatureItem.AllowCreateConnection, FeatureItem.AllowSync]); + expect(result.current.sort()).toEqual([FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowSync]); }); it("user features can be cleared again", () => { const { result, rerender } = getFeatures({ user: { [FeatureItem.AllowCustomDBT]: true, [FeatureItem.AllowSync]: false } as FeatureSet, }); - expect(result.current.sort()).toEqual([FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT]); + expect(result.current.sort()).toEqual([FeatureItem.AllowCustomDBT, FeatureItem.AllowDBTCloudIntegration]); rerender({ user: undefined }); - expect(result.current.sort()).toEqual([FeatureItem.AllowCreateConnection, FeatureItem.AllowSync]); + expect(result.current.sort()).toEqual([FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowSync]); }); it("overwritten features can be cleared again", () => { const { result, rerender } = getFeatures({ overwrite: { [FeatureItem.AllowCustomDBT]: true, [FeatureItem.AllowSync]: false } as FeatureSet, }); - expect(result.current.sort()).toEqual([FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT]); + expect(result.current.sort()).toEqual([FeatureItem.AllowCustomDBT, FeatureItem.AllowDBTCloudIntegration]); rerender({ overwrite: undefined }); - expect(result.current.sort()).toEqual([FeatureItem.AllowCreateConnection, FeatureItem.AllowSync]); + expect(result.current.sort()).toEqual([FeatureItem.AllowDBTCloudIntegration, FeatureItem.AllowSync]); }); }); describe("IfFeatureEnabled", () => { it("renders its children if the given feature is enabled", () => { const { getByTestId } = render( - + , { wrapper } @@ -184,7 +187,7 @@ describe("Feature Service", () => { it("allows changing features and rerenders correctly", () => { const { queryByTestId, rerender } = render( - + diff --git a/airbyte-webapp/src/hooks/services/Feature/constants.ts b/airbyte-webapp/src/hooks/services/Feature/constants.ts index 2a8f6179bd10..5329ea34a35c 100644 --- a/airbyte-webapp/src/hooks/services/Feature/constants.ts +++ b/airbyte-webapp/src/hooks/services/Feature/constants.ts @@ -2,7 +2,6 @@ import { FeatureItem } from "./types"; /** The default feature set that OSS releases should use. */ export const defaultFeatures = [ - FeatureItem.AllowCreateConnection, FeatureItem.AllowCustomDBT, FeatureItem.AllowSync, FeatureItem.AllowUpdateConnectors, diff --git a/airbyte-webapp/src/hooks/services/Feature/types.tsx b/airbyte-webapp/src/hooks/services/Feature/types.tsx index 2a7c9d8274a6..0b985a8385c4 100644 --- a/airbyte-webapp/src/hooks/services/Feature/types.tsx +++ b/airbyte-webapp/src/hooks/services/Feature/types.tsx @@ -4,7 +4,6 @@ export enum FeatureItem { AllowDBTCloudIntegration = "ALLOW_DBT_CLOUD_INTEGRATION", AllowUpdateConnectors = "ALLOW_UPDATE_CONNECTORS", AllowOAuthConnector = "ALLOW_OAUTH_CONNECTOR", - AllowCreateConnection = "ALLOW_CREATE_CONNECTION", AllowSync = "ALLOW_SYNC", } diff --git a/airbyte-webapp/src/packages/cloud/App.tsx b/airbyte-webapp/src/packages/cloud/App.tsx index 97226787b14b..c856fdbaf91b 100644 --- a/airbyte-webapp/src/packages/cloud/App.tsx +++ b/airbyte-webapp/src/packages/cloud/App.tsx @@ -41,9 +41,7 @@ const Services: React.FC> = ({ children }) => ( - + diff --git a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx index 0d68f80e01ee..03bd329eca6f 100644 --- a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx +++ b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx @@ -67,9 +67,7 @@ const MainRoutes: React.FC = () => { cloudWorkspace.creditStatus === CreditStatus.NEGATIVE_MAX_THRESHOLD; // If the workspace is out of credits it doesn't allow creation of new connections // or syncing existing connections. - setWorkspaceFeatures( - outOfCredits ? ({ [FeatureItem.AllowCreateConnection]: false, [FeatureItem.AllowSync]: false } as FeatureSet) : [] - ); + setWorkspaceFeatures(outOfCredits ? ({ [FeatureItem.AllowSync]: false } as FeatureSet) : []); return () => { setWorkspaceFeatures(undefined); }; From a714175997e595bf950e3693c91172785226b4e4 Mon Sep 17 00:00:00 2001 From: terencecho <3916587+terencecho@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:38:55 -0700 Subject: [PATCH 291/498] Change port for acceptance test destination db (#18391) --- .../src/main/java/io/airbyte/test/utils/GKEPostgresConfig.java | 2 +- tools/bin/gke-kube-acceptance-test/acceptance_test_kube_gke.sh | 2 +- .../gke-kube-helm-acceptance-test/acceptance_test_kube_gke.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/GKEPostgresConfig.java b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/GKEPostgresConfig.java index 8381a55a279b..c7aa224c1c8d 100644 --- a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/GKEPostgresConfig.java +++ b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/GKEPostgresConfig.java @@ -52,7 +52,7 @@ public static Database getSourceDatabase() { public static Database getDestinationDatabase() { return new Database(DSLContextFactory.create(USERNAME, PASSWORD, DatabaseDriver.POSTGRESQL.getDriverClassName(), - "jdbc:postgresql://localhost:3000/postgresdb", SQLDialect.POSTGRES)); + "jdbc:postgresql://localhost:4000/postgresdb", SQLDialect.POSTGRES)); } } diff --git a/tools/bin/gke-kube-acceptance-test/acceptance_test_kube_gke.sh b/tools/bin/gke-kube-acceptance-test/acceptance_test_kube_gke.sh index 9c8e470600bf..4464b8f7b2fe 100755 --- a/tools/bin/gke-kube-acceptance-test/acceptance_test_kube_gke.sh +++ b/tools/bin/gke-kube-acceptance-test/acceptance_test_kube_gke.sh @@ -64,7 +64,7 @@ kubectl port-forward svc/airbyte-server-svc 8001:8001 --namespace=$NAMESPACE & kubectl port-forward svc/postgres-source-svc 2000:5432 --namespace=$NAMESPACE & -kubectl port-forward svc/postgres-destination-svc 3000:5432 --namespace=$NAMESPACE & +kubectl port-forward svc/postgres-destination-svc 4000:5432 --namespace=$NAMESPACE & sleep 10s diff --git a/tools/bin/gke-kube-helm-acceptance-test/acceptance_test_kube_gke.sh b/tools/bin/gke-kube-helm-acceptance-test/acceptance_test_kube_gke.sh index 51fcfbb15761..53418fd014f3 100755 --- a/tools/bin/gke-kube-helm-acceptance-test/acceptance_test_kube_gke.sh +++ b/tools/bin/gke-kube-helm-acceptance-test/acceptance_test_kube_gke.sh @@ -66,7 +66,7 @@ kubectl port-forward svc/airbyte-server-svc 8001:8001 --namespace=$NAMESPACE & kubectl port-forward svc/postgres-source-svc 2000:5432 --namespace=$NAMESPACE & -kubectl port-forward svc/postgres-destination-svc 3000:5432 --namespace=$NAMESPACE & +kubectl port-forward svc/postgres-destination-svc 4000:5432 --namespace=$NAMESPACE & sleep 10s From 7ec6779df3f955b106d4268bb2b749b1df5e1c56 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Mon, 24 Oct 2022 16:13:38 -0700 Subject: [PATCH 292/498] Bmoric/update connection list with breaking (#18125) * add schemaChange * merge conflict * frontend tests * tests * l * fix source catalog id * test * formatting * move schema change to build backend web connection * check if actor catalog id is different * fix * tests and fixes * remove extra var * remove logging * continue to pass back new catalog id * api updates * fix mockdata * tests * add schemaChange * merge conflict * frontend tests * tests * l * fix source catalog id * test * formatting * move schema change to build backend web connection * check if actor catalog id is different * fix * tests and fixes * remove extra var * remove logging * continue to pass back new catalog id * api updates * fix mockdata * tests * tests * optional of nullable * Tmp * For diff * Add test * More test * Fix test and add some * Fix merge and test * Fix PMD * Fix test * Rm dead code * Fix pmd * Address PR comments * RM unused column Co-authored-by: alovew --- airbyte-api/src/main/openapi/config.yaml | 3 + .../config/persistence/ConfigRepository.java | 16 ++++ .../config/persistence/DbConverter.java | 1 + .../ConfigRepositoryE2EReadWriteTest.java | 40 ++++++++-- .../airbyte/config/persistence/MockData.java | 53 ++++++++++-- .../WebBackendConnectionsHandler.java | 80 +++++++++++-------- .../WebBackendConnectionsHandlerTest.java | 46 +++++++++-- .../server/helpers/ConnectionHelpers.java | 7 +- .../api/generated-api-html/index.html | 9 ++- 9 files changed, 197 insertions(+), 58 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index 143987726ade..baa488de77df 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -4649,6 +4649,7 @@ components: - destination - status - isSyncing + - schemaChange properties: connectionId: $ref: "#/components/schemas/ConnectionId" @@ -4674,6 +4675,8 @@ components: $ref: "#/components/schemas/JobStatus" isSyncing: type: boolean + schemaChange: + $ref: "#/components/schemas/SchemaChange" WebBackendConnectionRead: type: object required: diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index f65c701d2b34..da59d93e9068 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -992,6 +992,22 @@ public Optional getMostRecentActorCatalogFetchEventForSo return records.stream().findFirst().map(DbConverter::buildActorCatalogFetchEvent); } + public Map getMostRecentActorCatalogFetchEventForSources(final List sourceIds) + throws IOException { + + return database.query(ctx -> ctx.fetch( + """ + select actor_catalog_id, actor_id from + (select actor_catalog_id, actor_id, rank() over (partition by actor_id order by created_at desc) as creation_order_rank + from public.actor_catalog_fetch_event + ) table_with_rank + where creation_order_rank = 1; + """)) + .stream().map(DbConverter::buildActorCatalogFetchEvent) + .collect(Collectors.toMap(record -> record.getActorId(), + record -> record)); + } + /** * Stores source catalog information. * diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index 6bdc881bc269..ea4548d9f78a 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -198,6 +198,7 @@ public static ActorCatalog buildActorCatalog(final Record record) { public static ActorCatalogFetchEvent buildActorCatalogFetchEvent(final Record record) { return new ActorCatalogFetchEvent() + .withActorId(record.get(ACTOR_CATALOG_FETCH_EVENT.ACTOR_ID)) .withActorCatalogId(record.get(ACTOR_CATALOG_FETCH_EVENT.ACTOR_CATALOG_ID)); } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index eea254e61d55..552355066b5e 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -515,17 +515,17 @@ void testGetGeographyForConnection() throws IOException { } @Test - void testGetMostRecentActorCatalogFetchEventForSources() throws SQLException, IOException, JsonValidationException { + void testGetMostRecentActorCatalogFetchEventForSource() throws SQLException, IOException, JsonValidationException { for (final ActorCatalog actorCatalog : MockData.actorCatalogs()) { configPersistence.writeConfig(ConfigSchema.ACTOR_CATALOG, actorCatalog.getId().toString(), actorCatalog); } - OffsetDateTime now = OffsetDateTime.now(); - OffsetDateTime yesterday = now.minusDays(1l); + final OffsetDateTime now = OffsetDateTime.now(); + final OffsetDateTime yesterday = now.minusDays(1l); - List fetchEvents = MockData.actorCatalogFetchEventsSameSource(); - ActorCatalogFetchEvent fetchEvent1 = fetchEvents.get(0); - ActorCatalogFetchEvent fetchEvent2 = fetchEvents.get(1); + final List fetchEvents = MockData.actorCatalogFetchEventsSameSource(); + final ActorCatalogFetchEvent fetchEvent1 = fetchEvents.get(0); + final ActorCatalogFetchEvent fetchEvent2 = fetchEvents.get(1); database.transaction(ctx -> { insertCatalogFetchEvent( @@ -542,13 +542,37 @@ void testGetMostRecentActorCatalogFetchEventForSources() throws SQLException, IO return null; }); - Optional result = + final Optional result = configRepository.getMostRecentActorCatalogFetchEventForSource(fetchEvent1.getActorId()); assertEquals(fetchEvent2.getActorCatalogId(), result.get().getActorCatalogId()); } - private void insertCatalogFetchEvent(DSLContext ctx, UUID sourceId, UUID catalogId, OffsetDateTime creationDate) { + @Test + void testGetMostRecentActorCatalogFetchEventForSources() throws SQLException, IOException, JsonValidationException { + for (final ActorCatalog actorCatalog : MockData.actorCatalogs()) { + configPersistence.writeConfig(ConfigSchema.ACTOR_CATALOG, actorCatalog.getId().toString(), actorCatalog); + } + + database.transaction(ctx -> { + MockData.actorCatalogFetchEventsForAggregationTest().forEach(actorCatalogFetchEvent -> insertCatalogFetchEvent( + ctx, + actorCatalogFetchEvent.getActorCatalogFetchEvent().getActorId(), + actorCatalogFetchEvent.getActorCatalogFetchEvent().getActorCatalogId(), + actorCatalogFetchEvent.getCreatedAt())); + + return null; + }); + + final Map result = + configRepository.getMostRecentActorCatalogFetchEventForSources(List.of(MockData.SOURCE_ID_1, + MockData.SOURCE_ID_2)); + + assertEquals(MockData.ACTOR_CATALOG_ID_1, result.get(MockData.SOURCE_ID_1).getActorCatalogId()); + assertEquals(MockData.ACTOR_CATALOG_ID_3, result.get(MockData.SOURCE_ID_2).getActorCatalogId()); + } + + private void insertCatalogFetchEvent(final DSLContext ctx, final UUID sourceId, final UUID catalogId, final OffsetDateTime creationDate) { ctx.insertInto(ACTOR_CATALOG_FETCH_EVENT) .columns( ACTOR_CATALOG_FETCH_EVENT.ID, diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java index dddd705c30e3..2b6050b7d56a 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/MockData.java @@ -50,6 +50,7 @@ import io.airbyte.protocol.models.SyncMode; import java.net.URI; import java.time.Instant; +import java.time.OffsetDateTime; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -57,10 +58,11 @@ import java.util.TreeMap; import java.util.UUID; import java.util.stream.Collectors; +import lombok.Data; public class MockData { - private static final UUID WORKSPACE_ID_1 = UUID.randomUUID(); + public static final UUID WORKSPACE_ID_1 = UUID.randomUUID(); private static final UUID WORKSPACE_ID_2 = UUID.randomUUID(); private static final UUID WORKSPACE_ID_3 = UUID.randomUUID(); private static final UUID WORKSPACE_CUSTOMER_ID = UUID.randomUUID(); @@ -72,8 +74,8 @@ public class MockData { private static final UUID DESTINATION_DEFINITION_ID_2 = UUID.randomUUID(); private static final UUID DESTINATION_DEFINITION_ID_3 = UUID.randomUUID(); private static final UUID DESTINATION_DEFINITION_ID_4 = UUID.randomUUID(); - private static final UUID SOURCE_ID_1 = UUID.randomUUID(); - private static final UUID SOURCE_ID_2 = UUID.randomUUID(); + public static final UUID SOURCE_ID_1 = UUID.randomUUID(); + public static final UUID SOURCE_ID_2 = UUID.randomUUID(); private static final UUID SOURCE_ID_3 = UUID.randomUUID(); private static final UUID DESTINATION_ID_1 = UUID.randomUUID(); private static final UUID DESTINATION_ID_2 = UUID.randomUUID(); @@ -91,11 +93,12 @@ public class MockData { private static final UUID SOURCE_OAUTH_PARAMETER_ID_2 = UUID.randomUUID(); private static final UUID DESTINATION_OAUTH_PARAMETER_ID_1 = UUID.randomUUID(); private static final UUID DESTINATION_OAUTH_PARAMETER_ID_2 = UUID.randomUUID(); - private static final UUID ACTOR_CATALOG_ID_1 = UUID.randomUUID(); + public static final UUID ACTOR_CATALOG_ID_1 = UUID.randomUUID(); private static final UUID ACTOR_CATALOG_ID_2 = UUID.randomUUID(); - private static final UUID ACTOR_CATALOG_ID_3 = UUID.randomUUID(); + public static final UUID ACTOR_CATALOG_ID_3 = UUID.randomUUID(); private static final UUID ACTOR_CATALOG_FETCH_EVENT_ID_1 = UUID.randomUUID(); private static final UUID ACTOR_CATALOG_FETCH_EVENT_ID_2 = UUID.randomUUID(); + private static final UUID ACTOR_CATALOG_FETCH_EVENT_ID_3 = UUID.randomUUID(); public static final String MOCK_SERVICE_ACCOUNT_1 = "{\n" + " \"type\" : \"service_account\",\n" @@ -622,8 +625,8 @@ public static List actorCatalogFetchEvents() { .withId(ACTOR_CATALOG_FETCH_EVENT_ID_2) .withActorCatalogId(ACTOR_CATALOG_ID_2) .withActorId(SOURCE_ID_2) - .withConfigHash("1394") - .withConnectorVersion("1.2.0"); + .withConfigHash("1395") + .withConnectorVersion("1.42.0"); return Arrays.asList(actorCatalogFetchEvent1, actorCatalogFetchEvent2); } @@ -643,6 +646,42 @@ public static List actorCatalogFetchEventsSameSource() { return Arrays.asList(actorCatalogFetchEvent1, actorCatalogFetchEvent2); } + @Data + public static class ActorCatalogFetchEventWithCreationDate { + + private final ActorCatalogFetchEvent actorCatalogFetchEvent; + private final OffsetDateTime createdAt; + + } + + public static List actorCatalogFetchEventsForAggregationTest() { + final OffsetDateTime now = OffsetDateTime.now(); + final OffsetDateTime yesterday = OffsetDateTime.now().minusDays(1l); + + final ActorCatalogFetchEvent actorCatalogFetchEvent1 = new ActorCatalogFetchEvent() + .withId(ACTOR_CATALOG_FETCH_EVENT_ID_1) + .withActorCatalogId(ACTOR_CATALOG_ID_1) + .withActorId(SOURCE_ID_1) + .withConfigHash("CONFIG_HASH") + .withConnectorVersion("1.0.0"); + final ActorCatalogFetchEvent actorCatalogFetchEvent2 = new ActorCatalogFetchEvent() + .withId(ACTOR_CATALOG_FETCH_EVENT_ID_2) + .withActorCatalogId(ACTOR_CATALOG_ID_2) + .withActorId(SOURCE_ID_2) + .withConfigHash("1394") + .withConnectorVersion("1.2.0"); + final ActorCatalogFetchEvent actorCatalogFetchEvent3 = new ActorCatalogFetchEvent() + .withId(ACTOR_CATALOG_FETCH_EVENT_ID_3) + .withActorCatalogId(ACTOR_CATALOG_ID_3) + .withActorId(SOURCE_ID_2) + .withConfigHash("1394") + .withConnectorVersion("1.2.0"); + return Arrays.asList( + new ActorCatalogFetchEventWithCreationDate(actorCatalogFetchEvent1, now), + new ActorCatalogFetchEventWithCreationDate(actorCatalogFetchEvent2, yesterday), + new ActorCatalogFetchEventWithCreationDate(actorCatalogFetchEvent3, now)); + } + public static List workspaceServiceAccounts() { final WorkspaceServiceAccount workspaceServiceAccount = new WorkspaceServiceAccount() .withWorkspaceId(WORKSPACE_ID_1) diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index 60d980b09847..c18977a9e492 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -99,7 +99,8 @@ public ConnectionStateType getStateType(final ConnectionIdRequestBody connection return Enums.convertTo(stateHandler.getState(connectionIdRequestBody).getStateType(), ConnectionStateType.class); } - public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) throws IOException { + public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final WorkspaceIdRequestBody workspaceIdRequestBody) + throws IOException, JsonValidationException, ConfigNotFoundException { // passing 'false' so that deleted connections are not included final List standardSyncs = @@ -113,6 +114,9 @@ public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final final Map runningJobByConnectionId = getRunningJobByConnectionId(standardSyncs.stream().map(StandardSync::getConnectionId).toList()); + final Map newestFetchEventsByActorId = + configRepository.getMostRecentActorCatalogFetchEventForSources(new ArrayList<>()); + final List connectionItems = Lists.newArrayList(); for (final StandardSync standardSync : standardSyncs) { @@ -122,7 +126,8 @@ public WebBackendConnectionReadList webBackendListConnectionsForWorkspace(final sourceReadById, destinationReadById, latestJobByConnectionId, - runningJobByConnectionId)); + runningJobByConnectionId, + Optional.ofNullable(newestFetchEventsByActorId.get(standardSync.getSourceId())))); } return new WebBackendConnectionReadList().connections(connectionItems); @@ -175,51 +180,33 @@ private WebBackendConnectionRead buildWebBackendConnectionRead(final ConnectionR webBackendConnectionRead.setLatestSyncJobStatus(job.getStatus()); }); - SchemaChange schemaChange = getSchemaChange(connectionRead, currentSourceCatalogId); + final Optional mostRecentFetchEvent = + configRepository.getMostRecentActorCatalogFetchEventForSource(connectionRead.getSourceId()); + + final SchemaChange schemaChange = getSchemaChange(connectionRead, currentSourceCatalogId, mostRecentFetchEvent); webBackendConnectionRead.setSchemaChange(schemaChange); return webBackendConnectionRead; } - /* - * A breakingChange boolean is stored on the connectionRead object and corresponds to the boolean - * breakingChange field on the connection table. If there is not a breaking change, we still have to - * check whether there is a non-breaking schema change by fetching the most recent - * ActorCatalogFetchEvent. A new ActorCatalogFetchEvent is stored each time there is a source schema - * refresh, so if the most recent ActorCatalogFetchEvent has a different actor catalog than the - * existing actor catalog, there is a schema change. - */ - private SchemaChange getSchemaChange(ConnectionRead connectionRead, Optional currentSourceCatalogId) throws IOException { - SchemaChange schemaChange = SchemaChange.NO_CHANGE; - - if (connectionRead.getBreakingChange()) { - schemaChange = SchemaChange.BREAKING; - } else if (currentSourceCatalogId.isPresent()) { - final Optional mostRecentFetchEvent = - configRepository.getMostRecentActorCatalogFetchEventForSource(connectionRead.getSourceId()); - - if (mostRecentFetchEvent.isPresent()) { - if (!mostRecentFetchEvent.get().getActorCatalogId().equals(currentSourceCatalogId.get())) { - schemaChange = SchemaChange.NON_BREAKING; - } - } - } - - return schemaChange; - } - private WebBackendConnectionListItem buildWebBackendConnectionListItem( final StandardSync standardSync, final Map sourceReadById, final Map destinationReadById, final Map latestJobByConnectionId, - final Map runningJobByConnectionId) { + final Map runningJobByConnectionId, + final Optional latestFetchEvent) + throws JsonValidationException, ConfigNotFoundException, IOException { final SourceRead source = sourceReadById.get(standardSync.getSourceId()); final DestinationRead destination = destinationReadById.get(standardSync.getDestinationId()); final Optional latestSyncJob = Optional.ofNullable(latestJobByConnectionId.get(standardSync.getConnectionId())); final Optional latestRunningSyncJob = Optional.ofNullable(runningJobByConnectionId.get(standardSync.getConnectionId())); + final ConnectionRead connectionRead = connectionsHandler.getConnection(standardSync.getConnectionId()); + final Optional currentCatalogId = connectionRead == null ? Optional.empty() : Optional.ofNullable(connectionRead.getSourceCatalogId()); + + final SchemaChange schemaChange = getSchemaChange(connectionRead, currentCatalogId, latestFetchEvent); final WebBackendConnectionListItem listItem = new WebBackendConnectionListItem() .connectionId(standardSync.getConnectionId()) @@ -230,7 +217,8 @@ private WebBackendConnectionListItem buildWebBackendConnectionListItem( .scheduleType(ApiPojoConverters.toApiConnectionScheduleType(standardSync)) .scheduleData(ApiPojoConverters.toApiConnectionScheduleData(standardSync)) .source(source) - .destination(destination); + .destination(destination) + .schemaChange(schemaChange); listItem.setIsSyncing(latestRunningSyncJob.isPresent()); @@ -242,6 +230,34 @@ private WebBackendConnectionListItem buildWebBackendConnectionListItem( return listItem; } + /* + * A breakingChange boolean is stored on the connectionRead object and corresponds to the boolean + * breakingChange field on the connection table. If there is not a breaking change, we still have to + * check whether there is a non-breaking schema change by fetching the most recent + * ActorCatalogFetchEvent. A new ActorCatalogFetchEvent is stored each time there is a source schema + * refresh, so if the most recent ActorCatalogFetchEvent has a different actor catalog than the + * existing actor catalog, there is a schema change. + */ + @VisibleForTesting + SchemaChange getSchemaChange( + final ConnectionRead connectionRead, + final Optional currentSourceCatalogId, + final Optional mostRecentFetchEvent) { + if (connectionRead == null || currentSourceCatalogId.isEmpty()) { + return SchemaChange.NO_CHANGE; + } + + if (connectionRead.getBreakingChange() != null && connectionRead.getBreakingChange()) { + return SchemaChange.BREAKING; + } + + if (mostRecentFetchEvent.isPresent() && !mostRecentFetchEvent.map(ActorCatalogFetchEvent::getActorCatalogId).equals(currentSourceCatalogId)) { + return SchemaChange.NON_BREAKING; + } + + return SchemaChange.NO_CHANGE; + } + private SourceRead getSourceRead(final UUID sourceId) throws JsonValidationException, IOException, ConfigNotFoundException { final SourceIdRequestBody sourceIdRequestBody = new SourceIdRequestBody().sourceId(sourceId); return sourceHandler.getSource(sourceIdRequestBody); diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index 9efd1ca4d6e8..646c09ad0b6b 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -251,7 +251,8 @@ void setup() throws IOException, JsonValidationException, ConfigNotFoundExceptio destinationRead, false, jobRead.getJob().getCreatedAt(), - jobRead.getJob().getStatus()); + jobRead.getJob().getStatus(), + SchemaChange.NO_CHANGE); expected = expectedWebBackendConnectionReadObject(connectionRead, sourceRead, destinationRead, operationReadList, SchemaChange.NO_CHANGE, now, connectionRead.getSyncCatalog(), connectionRead.getSourceCatalogId()); @@ -345,7 +346,7 @@ void testGetWorkspaceStateEmpty() throws IOException { } @Test - void testWebBackendListConnectionsForWorkspace() throws IOException { + void testWebBackendListConnectionsForWorkspace() throws IOException, JsonValidationException, ConfigNotFoundException { final WorkspaceIdRequestBody workspaceIdRequestBody = new WorkspaceIdRequestBody(); workspaceIdRequestBody.setWorkspaceId(sourceRead.getWorkspaceId()); @@ -371,9 +372,9 @@ void testWebBackendGetConnection() throws ConfigNotFoundException, IOException, when(connectionsHandler.getConnection(connectionRead.getConnectionId())).thenReturn(connectionRead); when(operationsHandler.listOperationsForConnection(connectionIdRequestBody)).thenReturn(operationReadList); - final WebBackendConnectionRead WebBackendConnectionRead = wbHandler.webBackendGetConnection(webBackendConnectionRequestBody); + final WebBackendConnectionRead webBackendConnectionRead = wbHandler.webBackendGetConnection(webBackendConnectionRequestBody); - assertEquals(expected, WebBackendConnectionRead); + assertEquals(expected, webBackendConnectionRead); // make sure the icons were loaded into actual svg content assertTrue(expected.getSource().getIcon().startsWith(SVG)); @@ -404,8 +405,9 @@ void testWebBackendGetConnectionWithDiscoveryAndNewSchema() throws ConfigNotFoun IOException, JsonValidationException { when(connectionsHandler.getDiff(any(), any(), any())).thenReturn(expectedWithNewSchema.getCatalogDiff()); + final UUID newCatalogId = UUID.randomUUID(); when(configRepository.getMostRecentActorCatalogFetchEventForSource(any())) - .thenReturn(Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(UUID.randomUUID()))); + .thenReturn(Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(newCatalogId))); when(configRepository.getActorCatalogById(any())).thenReturn(new ActorCatalog().withId(UUID.randomUUID())); final WebBackendConnectionRead result = testWebBackendGetConnection(true, connectionRead, operationReadList); @@ -1134,4 +1136,38 @@ void testGetStreamsToReset() { streamDescriptor -> streamDescriptor.getName() == "updated_stream")); } + @Test + void testGetSchemaChangeNoChange() { + final ConnectionRead connectionReadNotBreaking = new ConnectionRead().breakingChange(false); + + assertEquals(SchemaChange.NO_CHANGE, wbHandler.getSchemaChange(null, Optional.of(UUID.randomUUID()), Optional.of(new ActorCatalogFetchEvent()))); + assertEquals(SchemaChange.NO_CHANGE, + wbHandler.getSchemaChange(connectionReadNotBreaking, Optional.empty(), Optional.of(new ActorCatalogFetchEvent()))); + + final UUID catalogId = UUID.randomUUID(); + + assertEquals(SchemaChange.NO_CHANGE, wbHandler.getSchemaChange(connectionReadNotBreaking, Optional.of(catalogId), + Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(catalogId)))); + } + + @Test + void testGetSchemaChangeBreaking() { + final UUID sourceId = UUID.randomUUID(); + final ConnectionRead connectionReadWithSourceId = new ConnectionRead().sourceCatalogId(UUID.randomUUID()).sourceId(sourceId).breakingChange(true); + + assertEquals(SchemaChange.BREAKING, wbHandler.getSchemaChange(connectionReadWithSourceId, + Optional.of(UUID.randomUUID()), Optional.empty())); + } + + @Test + void testGetSchemaChangeNotBreaking() { + final UUID catalogId = UUID.randomUUID(); + final UUID differentCatalogId = UUID.randomUUID(); + final ConnectionRead connectionReadWithSourceId = + new ConnectionRead().breakingChange(false); + + assertEquals(SchemaChange.NON_BREAKING, wbHandler.getSchemaChange(connectionReadWithSourceId, + Optional.of(catalogId), Optional.of(new ActorCatalogFetchEvent().withActorCatalogId(differentCatalogId)))); + } + } diff --git a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java index 89984a323944..ab742a079b36 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java +++ b/airbyte-server/src/test/java/io/airbyte/server/helpers/ConnectionHelpers.java @@ -21,6 +21,7 @@ import io.airbyte.api.model.generated.Geography; import io.airbyte.api.model.generated.JobStatus; import io.airbyte.api.model.generated.ResourceRequirements; +import io.airbyte.api.model.generated.SchemaChange; import io.airbyte.api.model.generated.SourceRead; import io.airbyte.api.model.generated.SyncMode; import io.airbyte.api.model.generated.WebBackendConnectionListItem; @@ -243,7 +244,8 @@ public static WebBackendConnectionListItem generateExpectedWebBackendConnectionL final DestinationRead destination, final boolean isSyncing, final Long latestSyncJobCreatedAt, - final JobStatus latestSynJobStatus) { + final JobStatus latestSynJobStatus, + final SchemaChange schemaChange) { final WebBackendConnectionListItem connectionListItem = new WebBackendConnectionListItem() .connectionId(standardSync.getConnectionId()) @@ -257,7 +259,8 @@ public static WebBackendConnectionListItem generateExpectedWebBackendConnectionL .latestSyncJobCreatedAt(latestSyncJobCreatedAt) .latestSyncJobStatus(latestSynJobStatus) .scheduleType(ApiPojoConverters.toApiConnectionScheduleType(standardSync)) - .scheduleData(ApiPojoConverters.toApiConnectionScheduleData(standardSync)); + .scheduleData(ApiPojoConverters.toApiConnectionScheduleData(standardSync)) + .schemaChange(schemaChange); return connectionListItem; } diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 97d50bbbf698..af3d93edd239 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -8797,7 +8797,6 @@

    Example data

    "connections" : [ { "sourceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "latestSyncJobCreatedAt" : 0, - "name" : "name", "destination" : { "connectionConfiguration" : { "user" : "charles" @@ -8809,7 +8808,6 @@

    Example data

    "destinationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" }, - "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "isSyncing" : true, "source" : { "sourceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", @@ -8823,6 +8821,8 @@

    Example data

    "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" }, "destinationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "name" : "name", + "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "scheduleData" : { "cron" : { "cronExpression" : "cronExpression", @@ -8836,7 +8836,6 @@

    Example data

    }, { "sourceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "latestSyncJobCreatedAt" : 0, - "name" : "name", "destination" : { "connectionConfiguration" : { "user" : "charles" @@ -8848,7 +8847,6 @@

    Example data

    "destinationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" }, - "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "isSyncing" : true, "source" : { "sourceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", @@ -8862,6 +8860,8 @@

    Example data

    "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" }, "destinationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "name" : "name", + "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", "scheduleData" : { "cron" : { "cronExpression" : "cronExpression", @@ -11409,6 +11409,7 @@

    WebBackendConnectionListItemlatestSyncJobCreatedAt (optional)

    Long epoch time of the latest sync job. null if no sync job has taken place. format: int64
    latestSyncJobStatus (optional)
    isSyncing
    +
    schemaChange
    From cdac71fc775ba109ef8610b9d13e22ee46b1825a Mon Sep 17 00:00:00 2001 From: Lake Mossman Date: Mon, 24 Oct 2022 16:52:16 -0700 Subject: [PATCH 293/498] Generate connector builder api client (#18274) * update openapi for connector builder to work with orval for now * add orval configuration for generating the connector builder client * create ConnectorBuilderApiService with methods to call the generated connector builder API client code * rename connector definition to manifest * refactor builder api service to match existing patterns * fix name of pages folder * improve comment * modify structure of StreamRead * fix path --- airbyte-webapp/.gitignore | 3 +- airbyte-webapp/orval.config.ts | 21 +++++ airbyte-webapp/src/config/defaultConfig.ts | 1 + airbyte-webapp/src/config/types.ts | 1 + .../ConnectorBuilderRequestService.ts | 82 +++++++++++++++++++ .../ConnectorBuilderPage.tsx | 0 airbyte-webapp/src/pages/routes.tsx | 2 +- .../ConnectorBuilderApiService.ts | 45 ++++++++++ .../src/main/openapi/openapi.yaml | 72 ++++++++++------ 9 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts rename airbyte-webapp/src/pages/{connector-builder => ConnectorBuilderPage}/ConnectorBuilderPage.tsx (100%) create mode 100644 airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderApiService.ts diff --git a/airbyte-webapp/.gitignore b/airbyte-webapp/.gitignore index ffcb7d3c3a8c..8e572d98ba49 100644 --- a/airbyte-webapp/.gitignore +++ b/airbyte-webapp/.gitignore @@ -29,5 +29,6 @@ yarn-error.log* storybook-static/ -# Ignore generated API client, since it's automatically generated +# Ignore generated API clients, since they're automatically generated /src/core/request/AirbyteClient.ts +/src/core/request/ConnectorBuilderClient.ts diff --git a/airbyte-webapp/orval.config.ts b/airbyte-webapp/orval.config.ts index d75bda5620e4..daac7be19c0c 100644 --- a/airbyte-webapp/orval.config.ts +++ b/airbyte-webapp/orval.config.ts @@ -22,4 +22,25 @@ export default defineConfig({ }, }, }, + connectorBuilder: { + input: "../connector-builder-server/src/main/openapi/openapi.yaml", + output: { + target: "./src/core/request/ConnectorBuilderClient.ts", + prettier: true, + override: { + header: (info) => [ + `eslint-disable`, + `Generated by orval 🍺`, + `Do not edit manually. Run "npm run generate-client" instead.`, + ...(info.title ? [info.title] : []), + ...(info.description ? [info.description] : []), + ...(info.version ? [`OpenAPI spec version: ${info.version}`] : []), + ], + mutator: { + path: "./src/core/request/apiOverride.ts", + name: "apiOverride", + }, + }, + }, + }, }); diff --git a/airbyte-webapp/src/config/defaultConfig.ts b/airbyte-webapp/src/config/defaultConfig.ts index c01720a36a4b..e4225db9a819 100644 --- a/airbyte-webapp/src/config/defaultConfig.ts +++ b/airbyte-webapp/src/config/defaultConfig.ts @@ -5,6 +5,7 @@ const defaultConfig: Config = { healthCheckInterval: 20000, version: "dev", apiUrl: `${window.location.protocol}//${window.location.hostname}:8001/api`, + connectorBuilderApiUrl: `${window.location.protocol}//${window.location.hostname}:8080/`, integrationUrl: "/docs", oauthRedirectUrl: `${window.location.protocol}//${window.location.host}`, }; diff --git a/airbyte-webapp/src/config/types.ts b/airbyte-webapp/src/config/types.ts index ad818fd46446..62a0ab434e33 100644 --- a/airbyte-webapp/src/config/types.ts +++ b/airbyte-webapp/src/config/types.ts @@ -21,6 +21,7 @@ declare global { export interface Config { segment: { token: string; enabled: boolean }; apiUrl: string; + connectorBuilderApiUrl: string; oauthRedirectUrl: string; healthCheckInterval: number; version?: string; diff --git a/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts b/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts new file mode 100644 index 000000000000..283df6e8f1c1 --- /dev/null +++ b/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts @@ -0,0 +1,82 @@ +import { + StreamRead, + StreamReadRequestBody, + StreamsListRead, + StreamsListRequestBody, +} from "core/request/ConnectorBuilderClient"; + +import { AirbyteRequestService } from "../../request/AirbyteRequestService"; + +export class ConnectorBuilderRequestService extends AirbyteRequestService { + public readStream(readParams: StreamReadRequestBody): Promise { + // TODO: uncomment this and remove mock responses once there is a real API to call + // return readStream(readParams, this.requestOptions); + console.log("------------"); + console.log(`Stream: ${readParams.stream}`); + console.log(`Connector manifest:\n${JSON.stringify(readParams.manifest)}`); + console.log(`Config:\n${JSON.stringify(readParams.config)}`); + return new Promise((resolve) => setTimeout(resolve, 200)).then(() => { + return { + logs: [ + { level: "INFO", message: "Syncing stream: rates " }, + { level: "INFO", message: "Setting state of rates stream to {'date': '2022-09-25'}" }, + ], + slices: [ + { + sliceDescriptor: { start: "Jan 1, 2022", end: "Jan 2, 2022" }, + state: { + type: "STREAM", + stream: { stream_descriptor: { name: readParams.stream }, stream_state: { date: "2022-09-26" } }, + data: { rates: { date: "2022-09-26" } }, + }, + pages: [ + { + records: [ + { + stream: readParams.stream, + data: { + id: "dp_123", + object: readParams.stream, + amount: 2000, + balance_transaction: "txn_123", + }, + }, + ], + request: { + url: "https://api.com/path", + }, + response: { + status: 200, + }, + }, + ], + }, + ], + }; + }); + } + + public listStreams(listParams: StreamsListRequestBody): Promise { + // TODO: uncomment this and remove mock responses once there is a real API to call + // return listStreams(listParams, this.requestOptions); + console.log(`Received listStreams body: ${JSON.stringify(listParams)}`); + return new Promise((resolve) => setTimeout(resolve, 200)).then(() => { + return { + streams: [ + { + name: "disputes", + url: "https://api.com/disputes", + }, + { + name: "transactions", + url: "https://api.com/transactions", + }, + { + name: "users", + url: "https://api.com/users", + }, + ], + }; + }); + } +} diff --git a/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx similarity index 100% rename from airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx rename to airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx diff --git a/airbyte-webapp/src/pages/routes.tsx b/airbyte-webapp/src/pages/routes.tsx index 1010703eba2c..ba76c4c73df1 100644 --- a/airbyte-webapp/src/pages/routes.tsx +++ b/airbyte-webapp/src/pages/routes.tsx @@ -15,7 +15,7 @@ import MainView from "views/layout/MainView"; import { WorkspaceRead } from "../core/request/AirbyteClient"; import ConnectionPage from "./ConnectionPage"; -import { ConnectorBuilderPage } from "./connector-builder/ConnectorBuilderPage"; +import { ConnectorBuilderPage } from "./ConnectorBuilderPage/ConnectorBuilderPage"; import DestinationPage from "./DestinationPage"; import OnboardingPage from "./OnboardingPage"; import PreferencesPage from "./PreferencesPage"; diff --git a/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderApiService.ts b/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderApiService.ts new file mode 100644 index 000000000000..c1fb27b0e745 --- /dev/null +++ b/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderApiService.ts @@ -0,0 +1,45 @@ +import { useQuery } from "react-query"; + +import { useConfig } from "config"; +import { ConnectorBuilderRequestService } from "core/domain/connectorBuilder/ConnectorBuilderRequestService"; +import { + StreamReadRequestBody, + StreamReadRequestBodyConfig, + StreamReadRequestBodyManifest, + StreamsListRequestBody, +} from "core/request/ConnectorBuilderClient"; +import { useSuspenseQuery } from "services/connector/useSuspenseQuery"; +import { useDefaultRequestMiddlewares } from "services/useDefaultRequestMiddlewares"; +import { useInitService } from "services/useInitService"; + +const connectorBuilderKeys = { + all: ["connectorBuilder"] as const, + read: (streamName: string, manifest: StreamReadRequestBodyManifest, config: StreamReadRequestBodyConfig) => + [...connectorBuilderKeys.all, "read", { streamName, manifest, config }] as const, + list: (manifest: StreamReadRequestBodyManifest) => [...connectorBuilderKeys.all, "list", { manifest }] as const, +}; + +function useConnectorBuilderService() { + const config = useConfig(); + const middlewares = useDefaultRequestMiddlewares(); + return useInitService( + () => new ConnectorBuilderRequestService(config.connectorBuilderApiUrl, middlewares), + [config.connectorBuilderApiUrl, middlewares] + ); +} + +export const useReadStream = (params: StreamReadRequestBody) => { + const service = useConnectorBuilderService(); + + return useQuery( + connectorBuilderKeys.read(params.stream, params.manifest, params.config), + () => service.readStream(params), + { refetchOnWindowFocus: false, enabled: false } + ); +}; + +export const useListStreams = (params: StreamsListRequestBody) => { + const service = useConnectorBuilderService(); + + return useSuspenseQuery(connectorBuilderKeys.list(params.manifest), () => service.listStreams(params)); +}; diff --git a/connector-builder-server/src/main/openapi/openapi.yaml b/connector-builder-server/src/main/openapi/openapi.yaml index 5479827c5e15..9a6145e83989 100644 --- a/connector-builder-server/src/main/openapi/openapi.yaml +++ b/connector-builder-server/src/main/openapi/openapi.yaml @@ -18,6 +18,7 @@ paths: /v1/stream/read: post: summary: Reads a specific stream in the source. TODO in a later phase - only read a single slice of data. + operationId: readStream requestBody: content: application/json: @@ -37,7 +38,8 @@ paths: $ref: "#/components/responses/InvalidInputResponse" /v1/streams/list: post: - summary: List all streams present in the connector definition, along with their specific request URLs + summary: List all streams present in the connector manifest, along with their specific request URLs + operationId: listStreams requestBody: content: application/json: @@ -61,53 +63,71 @@ components: StreamRead: type: object required: + - logs - slices properties: + logs: + type: array + description: The LOG AirbyteMessages that were emitted during the read of this slice + items: + type: object + # $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteLogMessage" slices: type: array - description: The stream slices returned from the read command + description: The stream slices returned from the read command. If no stream slicer is configured, this should contain a single item containing all of the results. items: type: object required: - - sliceDescriptor - pages properties: - sliceDescriptor: - type: object - description: 'An object describing the current slice, e.g. {start_time: "2021-01-01", end_time: "2021-01-31"}' pages: type: array - description: The pages returned from the read command + description: The pages returned from the read command. If no pagination is configured, this should contain a single item containing all of the results. items: type: object required: - - airbyteMessages + - records - request - response properties: - airbyteMessages: + records: type: array - description: The RECORD/STATE/LOG AirbyteMessages coming from the read operation for this page + description: The RECORD AirbyteMessages coming from the read operation for this page items: - $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteMessage" + type: object + # $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteRecordMessage" request: $ref: "#/components/schemas/HttpRequest" response: $ref: "#/components/schemas/HttpResponse" + sliceDescriptor: + type: object + description: 'An object describing the current slice, e.g. {start_time: "2021-01-01", end_time: "2021-01-31"}. This can be omitted if a stream slicer is not configured.' + state: + type: object + description: The STATE AirbyteMessage emitted at the end of this slice. This can be omitted if a stream slicer is not configured. + # $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage" StreamReadRequestBody: type: object required: - - definition + - manifest - stream + - config properties: - definition: - $ref: "#/components/schemas/ConnectorDefinitionBody" - description: The config-based connector definition contents + manifest: + type: object + description: The config-based connector manifest contents + # $ref: "#/components/schemas/ConnectorManifest" stream: type: string description: Name of the stream to read + config: + type: object + description: The config blob containing the user inputs for testing state: - $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage" + type: object + description: The AirbyteStateMessage object to use as the starting state for this read + # $ref: "#/components/schemas/AirbyteProtocol/definitions/AirbyteStateMessage" # --- Potential addition for a later phase --- # numPages: # type: integer @@ -144,18 +164,20 @@ components: headers: type: object description: The headers of the HTTP response, if any - ConnectorDefinitionBody: - $ref: ../../../../airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json - AirbyteProtocol: - $ref: ../../../../airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml + # --- Commenting out for now since they do not work with our orval openapi client generator --- + # ConnectorManifest: + # $ref: ../../../../airbyte-cdk/python/airbyte_cdk/sources/declarative/config_component_schema.json + # AirbyteProtocol: + # $ref: ../../../../airbyte-protocol/protocol-models/src/main/resources/airbyte_protocol/airbyte_protocol.yaml StreamsListRequestBody: type: object required: - - definition + - manifest properties: - definition: - $ref: "#/components/schemas/ConnectorDefinitionBody" - description: The config-based connector definition contents + manifest: + type: object + description: The config-based connector manifest contents + # $ref: "#/components/schemas/ConnectorManifest" StreamsListRead: type: object required: @@ -165,7 +187,7 @@ components: type: array items: type: object - description: The stream names present in the connector definition + description: The stream names present in the connector manifest required: - name - url From 182f2c6fba1bdc1050a2e20fb8402c5bf26f0bfb Mon Sep 17 00:00:00 2001 From: Lake Mossman Date: Mon, 24 Oct 2022 17:33:05 -0700 Subject: [PATCH 294/498] :window: :wrench: Generify connector documentation layout into resizable panels component (#18272) * generify connector documentation layout into resizable panels component * add intl string for connector builder expand message * update name of formatted download yaml message * fix stylelint * Update airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss Co-authored-by: Joey Marshment-Howell * move splitter styles into scss module * simplify ResizablePanels component * reorder * use variables * remove units on 0 * Update airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.module.scss Co-authored-by: Joey Marshment-Howell * Update airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss Co-authored-by: Joey Marshment-Howell * Update airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss Co-authored-by: Joey Marshment-Howell Co-authored-by: Joey Marshment-Howell --- .../YamlEditor/DownloadYamlButton.tsx | 6 +- .../ResizablePanels.module.scss | 59 ++++++++++ .../ui/ResizablePanels/ResizablePanels.tsx | 107 ++++++++++++++++++ .../components/ui/ResizablePanels/index.tsx | 1 + airbyte-webapp/src/locales/en.json | 3 +- .../ConnectorBuilderPage.module.scss | 16 +++ .../ConnectorBuilderPage.tsx | 25 +++- .../ConnectorDocumentationLayout.module.scss | 72 +----------- .../ConnectorDocumentationLayout.tsx | 95 +++++----------- 9 files changed, 241 insertions(+), 143 deletions(-) create mode 100644 airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss create mode 100644 airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx create mode 100644 airbyte-webapp/src/components/ui/ResizablePanels/index.tsx create mode 100644 airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.module.scss diff --git a/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx index 3527bfb3561f..1f8c92d010d3 100644 --- a/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx +++ b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx @@ -1,4 +1,4 @@ -import { useIntl } from "react-intl"; +import { FormattedMessage } from "react-intl"; import { Button } from "components/ui/Button"; @@ -10,8 +10,6 @@ interface DownloadYamlButtonProps { } export const DownloadYamlButton: React.FC = ({ yaml, className }) => { - const { formatMessage } = useIntl(); - const downloadYaml = () => { const file = new Blob([yaml], { type: "text/plain;charset=utf-8" }); // TODO: pull name from connector name input or generate from yaml contents @@ -20,7 +18,7 @@ export const DownloadYamlButton: React.FC = ({ yaml, cl return ( ); }; diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss new file mode 100644 index 000000000000..db855b63589a --- /dev/null +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss @@ -0,0 +1,59 @@ +@use "scss/colors"; +@use "scss/variables"; + +/* +* This eliminates flickering scrollbars when resizing using the reflex splitter. +* Class names need to be repeated so that it overrides default reflex styles. +* See more details in https://github.com/airbytehq/airbyte/pull/15996#issuecomment-1229019827 +*/ +.panelStyle.panelStyle { + overflow: hidden; +} + +.panelContainer { + overflow: auto; + height: 100%; +} + +.lightOverlay { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 10; + background-color: colors.$white; + overflow: hidden; + padding-top: variables.$spacing-2xl; + display: flex; + justify-content: center; + align-items: flex-start; +} + +.rotatedHeader { + // this causes header to be rotated clockwise by default + writing-mode: vertical-lr; + white-space: nowrap; +} + +.counterClockwise { + transform: rotate(180deg); +} + +.panelGrabber { + height: 100vh; + padding: variables.$spacing-sm; + display: flex; +} + +.grabberHandleIcon { + margin: auto; + height: 25px; + color: colors.$grey-100; +} + +.splitter { + // !important is necessary to override the default reflex styles + border: 0 !important; + background-color: transparent !important; +} diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx new file mode 100644 index 000000000000..4d184611039a --- /dev/null +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx @@ -0,0 +1,107 @@ +import { faGripLinesVertical } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classNames from "classnames"; +import React from "react"; +import { ReflexContainer, ReflexElement, ReflexSplitter } from "react-reflex"; + +import { Text } from "../Text"; +import styles from "./ResizablePanels.module.scss"; + +interface ResizablePanelsProps { + className?: string; + hideRightPanel?: boolean; + leftPanel: PanelProps; + rightPanel: PanelProps; +} + +interface PanelProps { + children: React.ReactNode; + minWidth: number; + className?: string; + startingFlex?: number; + overlay?: Overlay; +} + +interface Overlay { + displayThreshold: number; + header: string; + rotation?: "clockwise" | "counter-clockwise"; +} + +interface PanelContainerProps { + className?: string; + dimensions?: { + width: number; + height: number; + }; + overlay?: Overlay; +} + +const PanelContainer: React.FC> = ({ + children, + className, + dimensions, + overlay, +}) => { + const width = dimensions?.width ?? 0; + + return ( +
    + {overlay && width <= overlay.displayThreshold && ( +
    + + {overlay.header} + +
    + )} + {children} +
    + ); +}; + +export const ResizablePanels: React.FC = ({ + className, + hideRightPanel = false, + leftPanel, + rightPanel, +}) => { + return ( + + + + {leftPanel.children} + + + {/* NOTE: ReflexElement will not load its contents if wrapped in an empty jsx tag along with ReflexSplitter. They must be evaluated/rendered separately. */} + {!hideRightPanel && ( + +
    + +
    +
    + )} + {!hideRightPanel && ( + + + {rightPanel.children} + + + )} +
    + ); +}; diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/index.tsx b/airbyte-webapp/src/components/ui/ResizablePanels/index.tsx new file mode 100644 index 000000000000..2667256c9f69 --- /dev/null +++ b/airbyte-webapp/src/components/ui/ResizablePanels/index.tsx @@ -0,0 +1 @@ +export { ResizablePanels } from "./ResizablePanels"; diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 8bff60bd0e71..e6ae0ab05b4f 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -571,5 +571,6 @@ "airbyte.datatype.unknown": "Unknown", "airbyte.datatype.boolean": "Boolean", - "builder.downloadYaml": "Download YAML" + "connectorBuilder.downloadYaml": "Download YAML", + "connectorBuilder.expandConfiguration": "Expand this pane to continue building your connector" } diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.module.scss b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.module.scss new file mode 100644 index 000000000000..6e1024805112 --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.module.scss @@ -0,0 +1,16 @@ +@use "scss/colors"; +@use "scss/variables"; + +.leftPanel { + overflow: hidden; +} + +.rightPanel { + border-top-left-radius: variables.$border-radius-md; + border-bottom-left-radius: variables.$border-radius-md; + background-color: colors.$white; +} + +.container { + background-image: linear-gradient(colors.$dark-blue-900, colors.$dark-blue-1000); +} diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx index 47728f1ea8cf..8bc4cb3ca781 100644 --- a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx @@ -1,5 +1,28 @@ +import { ResizablePanels } from "components/ui/ResizablePanels"; import { YamlEditor } from "components/YamlEditor"; +import styles from "./ConnectorBuilderPage.module.scss"; + export const ConnectorBuilderPage: React.FC = () => { - return ; + return ( + , + className: styles.leftPanel, + minWidth: 400, + }} + rightPanel={{ + children:
    Testing panel
    , + className: styles.rightPanel, + startingFlex: 0.33, + minWidth: 60, + overlay: { + displayThreshold: 300, + header: "Stream Name", + rotation: "counter-clockwise", + }, + }} + /> + ); }; diff --git a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.module.scss b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.module.scss index 1071a7166850..9f22de946a31 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.module.scss +++ b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.module.scss @@ -1,73 +1,7 @@ -@use "../../../scss/colors"; -@use "../../../scss/variables"; +@use "scss/variables"; -.leftPanelStyle { - min-width: 200px; - position: relative; -} - -/* -* This eliminates flickering scrollbars when resizing using the reflex splitter. -* See more details in https://github.com/airbytehq/airbyte/pull/15996#issuecomment-1229019827 -*/ -.leftPanelStyle.leftPanelStyle, -.rightPanelStyle.rightPanelStyle { - overflow: hidden; -} - -.darkOverlay { - display: flex; - justify-content: center; - align-items: center; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - z-index: 10; - opacity: 0.9; - background-color: colors.$dark-blue-900; - color: colors.$white; - text-align: center; -} - -.container { - overflow: auto; - height: 100%; - - & > *:last-child { +.leftPanel { + > *:last-child { padding-bottom: variables.$spacing-page-bottom; } } - -.rightPanelContainer { - background-color: colors.$white; - height: 100%; - overflow: auto; -} - -.lightOverlay { - width: 100%; - background-color: colors.$white; - text-align: center; - vertical-align: middle; - overflow: hidden; - padding-top: 80px; -} - -.rotatedHeader { - transform: rotate(-90deg); - white-space: nowrap; -} - -.panelGrabber { - height: 100vh; - padding: 6px; - display: flex; -} - -.grabberHandleIcon { - margin: auto; - height: 25px; - color: colors.$grey-100; -} diff --git a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx index 46a2e4643ad5..f81ca5a38989 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx @@ -1,12 +1,9 @@ -import { faGripLinesVertical } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import classNames from "classnames"; import React, { lazy, Suspense } from "react"; -import { FormattedMessage } from "react-intl"; -import { ReflexContainer, ReflexElement, ReflexSplitter } from "react-reflex"; +import { useIntl } from "react-intl"; import { useWindowSize } from "react-use"; import { LoadingPage } from "components/LoadingPage"; +import { ResizablePanels } from "components/ui/ResizablePanels"; import styles from "./ConnectorDocumentationLayout.module.scss"; import { useDocumentationPanelContext } from "./DocumentationPanelContext"; @@ -15,73 +12,35 @@ const LazyDocumentationPanel = lazy(() => import("components/DocumentationPanel").then(({ DocumentationPanel }) => ({ default: DocumentationPanel })) ); -interface PanelContainerProps { - dimensions?: { - width: number; - height: number; - }; -} - -const LeftPanelContainer: React.FC> = ({ children, dimensions }) => { - const width = dimensions?.width ?? 0; - const screenWidth = useWindowSize().width; - - return ( -
    - {screenWidth > 500 && width < 550 && ( -
    -

    - -

    -
    - )} -
    {children}
    -
    - ); -}; - -const RightPanelContainer: React.FC> = ({ children, dimensions }) => { - const width = dimensions?.width ?? 0; - - return ( - <> - {width < 350 ? ( -
    -

    Setup Guide

    -
    - ) : ( -
    {children}
    - )} - - ); -}; -// NOTE: ReflexElement will not load its contents if wrapped in an empty jsx tag along with ReflexSplitter. They must be evaluated/rendered separately. - export const ConnectorDocumentationLayout: React.FC> = ({ children }) => { + const { formatMessage } = useIntl(); const { documentationPanelOpen } = useDocumentationPanelContext(); const screenWidth = useWindowSize().width; + const showDocumentationPanel = screenWidth > 500 && documentationPanelOpen; + + const documentationPanel = ( + }> + + + ); return ( - - - {children} - - {documentationPanelOpen && ( - -
    - -
    -
    - )} - {screenWidth > 500 && documentationPanelOpen && ( - - - }> - - - - - )} -
    + ); }; From e933de02e8d91067b96da0e7f9df8b200f4401d5 Mon Sep 17 00:00:00 2001 From: Augustin Date: Tue, 25 Oct 2022 08:52:35 +0200 Subject: [PATCH 295/498] SAT: declare `bypass_reason` in `acceptance-test-config.yml` (#18364) --- .../bases/source-acceptance-test/CHANGELOG.md | 3 + .../bases/source-acceptance-test/Dockerfile | 2 +- .../bases/source-acceptance-test/README.md | 13 +- .../source_acceptance_test/config.py | 93 +++++++-- .../source_acceptance_test/plugin.py | 9 +- .../utils/config_migration.py | 37 ++++ .../unit_tests/test_config.py | 181 +++++++++++++++++- .../acceptance-test-config.yml.hbs | 52 ++--- .../source-pokeapi/acceptance-test-config.yml | 22 ++- .../acceptance-test-config.yml | 52 ++--- .../acceptance-test-config.yml | 52 ++--- .../source-acceptance-tests-reference.md | 27 +-- 12 files changed, 432 insertions(+), 111 deletions(-) create mode 100644 airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/config_migration.py diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index acacdf56897b..389270e9a22c 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.12 +Declare `bypass_reason` field in test configuration. [#18364](https://github.com/airbytehq/airbyte/pull/18364). + ## 0.2.11 Declare `test_strictness_level` field in test configuration. [#18218](https://github.com/airbytehq/airbyte/pull/18218). diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index 833ef107eeef..21c9e87828cc 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.11 +LABEL io.airbyte.version=0.2.12 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/README.md b/airbyte-integrations/bases/source-acceptance-test/README.md index 2a1b2d0f206c..b527589398f0 100644 --- a/airbyte-integrations/bases/source-acceptance-test/README.md +++ b/airbyte-integrations/bases/source-acceptance-test/README.md @@ -56,4 +56,15 @@ These iterations are more conveniently achieved by remaining in the current dire 12. Open a PR on our GitHub repository 13. Run the unit test on the CI by running `/test connector=bases/source-acceptance-test` in a GitHub comment 14. Publish the new SAT version if your PR is approved by running `/publish connector=bases/source-acceptance-test auto-bump-version=false` in a GitHub comment -15. Merge your PR \ No newline at end of file +15. Merge your PR + +## Migrating `acceptance-test-config.yml` to latest configuration format +We introduced changes in the structure of `acceptance-test-config.yml` files in version 0.2.12. +The *legacy* configuration format is still supported but should be deprecated soon. +To migrate a legacy configuration to the latest configuration format please run: + +```bash +python -m venv .venv # If you don't have a virtualenv already +source ./.venv/bin/activate # If you're not in your virtualenv already +python source_acceptance_test/utils/config_migration.py ../../connectors/source-to-migrate/acceptance-test-config.yml +``` \ No newline at end of file diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py index 158f1e70529a..f73d81c0de12 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py @@ -3,11 +3,14 @@ # +import logging +from copy import deepcopy from enum import Enum from pathlib import Path -from typing import List, Mapping, Optional, Set +from typing import Generic, List, Mapping, Optional, Set, TypeVar -from pydantic import BaseModel, Field, validator +from pydantic import BaseModel, Field, root_validator, validator +from pydantic.generics import GenericModel config_path: str = Field(default="secrets/config.json", description="Path to a JSON object representing a valid connector configuration") invalid_config_path: str = Field(description="Path to a JSON object representing an invalid connector configuration") @@ -18,6 +21,7 @@ timeout_seconds: int = Field(default=None, description="Test execution timeout_seconds", ge=0) SEMVER_REGEX = r"(0|(?:[1-9]\d*))(?:\.(0|(?:[1-9]\d*))(?:\.(0|(?:[1-9]\d*)))?(?:\-([\w][\w\.\-_]*))?)?" +ALLOW_LEGACY_CONFIG = True class BaseConfig(BaseModel): @@ -25,6 +29,9 @@ class Config: extra = "forbid" +TestConfigT = TypeVar("TestConfigT") + + class BackwardCompatibilityTestsConfig(BaseConfig): previous_connector_version: str = Field( regex=SEMVER_REGEX, default="latest", description="Previous connector version to use for backward compatibility tests." @@ -133,22 +140,86 @@ class IncrementalConfig(BaseConfig): ) -class TestConfig(BaseConfig): - spec: Optional[List[SpecTestConfig]] = Field(description="TODO") - connection: Optional[List[ConnectionTestConfig]] = Field(description="TODO") - discovery: Optional[List[DiscoveryTestConfig]] = Field(description="TODO") - basic_read: Optional[List[BasicReadTestConfig]] = Field(description="TODO") - full_refresh: Optional[List[FullRefreshConfig]] = Field(description="TODO") - incremental: Optional[List[IncrementalConfig]] = Field(description="TODO") +class GenericTestConfig(GenericModel, Generic[TestConfigT]): + bypass_reason: Optional[str] + tests: Optional[List[TestConfigT]] + + @validator("tests", always=True) + def no_bypass_reason_when_tests_is_set(cls, tests, values): + if tests and values.get("bypass_reason"): + raise ValueError("You can't set a bypass_reason if tests are set.") + return tests + + +class AcceptanceTestConfigurations(BaseConfig): + spec: Optional[GenericTestConfig[SpecTestConfig]] + connection: Optional[GenericTestConfig[ConnectionTestConfig]] + discovery: Optional[GenericTestConfig[DiscoveryTestConfig]] + basic_read: Optional[GenericTestConfig[BasicReadTestConfig]] + full_refresh: Optional[GenericTestConfig[FullRefreshConfig]] + incremental: Optional[GenericTestConfig[IncrementalConfig]] class Config(BaseConfig): class TestStrictnessLevel(str, Enum): high = "high" + low = "low" connector_image: str = Field(description="Docker image to test, for example 'airbyte/source-hubspot:dev'") - tests: TestConfig = Field(description="List of the tests with their configs") + acceptance_tests: AcceptanceTestConfigurations = Field(description="List of the acceptance test to run with their configs") base_path: Optional[str] = Field(description="Base path for all relative paths") test_strictness_level: Optional[TestStrictnessLevel] = Field( - description="Corresponds to a strictness level of the test suite and will change which tests are mandatory for a successful run." + default=TestStrictnessLevel.low, + description="Corresponds to a strictness level of the test suite and will change which tests are mandatory for a successful run.", ) + + @staticmethod + def is_legacy(config: dict) -> bool: + """Check if a configuration is 'legacy'. + We consider it is legacy if a 'tests' field exists at its root level (prior to v0.2.12). + + Args: + config (dict): A configuration + + Returns: + bool: Whether the configuration is legacy. + """ + return "tests" in config + + @staticmethod + def migrate_legacy_to_current_config(legacy_config: dict) -> dict: + """Convert configuration structure created prior to v0.2.12 into the current structure. + e.g. + This structure: + {"connector_image": "my-connector-image", "tests": {"spec": [{"spec_path": "my/spec/path.json"}]} + Gets converted to: + {"connector_image": "my-connector-image", "acceptance_tests": {"spec": {"tests": [{"spec_path": "my/spec/path.json"}]}} + + Args: + legacy_config (dict): A legacy configuration + + Returns: + dict: A migrated configuration + """ + migrated_config = deepcopy(legacy_config) + migrated_config.pop("tests") + migrated_config["acceptance_tests"] = {} + for test_name, test_configs in legacy_config["tests"].items(): + migrated_config["acceptance_tests"][test_name] = {"tests": test_configs} + return migrated_config + + @root_validator(pre=True) + def legacy_format_adapter(cls, values: dict) -> dict: + """Root level validator executed 'pre' field validation to migrate a legacy config to the current structure. + + Args: + values (dict): The raw configuration. + + Returns: + dict: The migrated configuration if needed. + """ + if ALLOW_LEGACY_CONFIG and cls.is_legacy(values): + logging.warn("The acceptance-test-config.yml file is in a legacy format. Please migrate to the latest format.") + return cls.migrate_legacy_to_current_config(values) + else: + return values diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py index 0145ce88699b..74c432caa01a 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py @@ -56,10 +56,10 @@ def pytest_generate_tests(metafunc): config_key = metafunc.cls.config_key() test_name = f"{metafunc.cls.__name__}.{metafunc.function.__name__}" config = load_config(metafunc.config.getoption("--acceptance-test-config")) - if not hasattr(config.tests, config_key) or not getattr(config.tests, config_key): + if not hasattr(config.acceptance_tests, config_key) or not getattr(config.acceptance_tests, config_key): pytest.skip(f"Skipping {test_name} because not found in the config") else: - test_inputs = getattr(config.tests, config_key) + test_inputs = getattr(config.acceptance_tests, config_key).tests if not test_inputs: pytest.skip(f"Skipping {test_name} because no inputs provided") @@ -87,8 +87,9 @@ def pytest_collection_modifyitems(config, items): if not hasattr(items[0].cls, "config_key"): # Skip user defined test classes from integration_tests/ directory. continue - test_configs = getattr(config.tests, items[0].cls.config_key()) - for test_config, item in zip(test_configs, items): + test_configs = getattr(config.acceptance_tests, items[0].cls.config_key()) + + for test_config, item in zip(test_configs.tests, items): default_timeout = item.get_closest_marker("default_timeout") if test_config.timeout_seconds: item.add_marker(pytest.mark.timeout(test_config.timeout_seconds)) diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/config_migration.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/config_migration.py new file mode 100644 index 000000000000..b0a4f4f4ee15 --- /dev/null +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/utils/config_migration.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import argparse +from pathlib import Path + +import yaml +from source_acceptance_test.config import Config +from yaml import load + +try: + from yaml import CLoader as Loader +except ImportError: + from yaml import Loader + +parser = argparse.ArgumentParser(description="Migrate legacy acceptance-test-config.yml to the latest configuration format.") +parser.add_argument("config_path", type=str, help="Path to the acceptance-test-config.yml to migrate.") + + +def migrate_legacy_configuration(config_path: Path): + + with open(config_path, "r") as file: + to_migrate = load(file, Loader=Loader) + + if Config.is_legacy(to_migrate): + migrated_config = Config.migrate_legacy_to_current_config(to_migrate) + with open(config_path, "w") as output_file: + yaml.dump(migrated_config, output_file) + print(f"Your configuration was successfully migrated to the latest configuration format: {config_path}") + else: + print("Your configuration is not in a legacy format.") + + +if __name__ == "__main__": + args = parser.parse_args() + migrate_legacy_configuration(Path(args.config_path)) diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py index 6778b9c68b11..938791090943 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_config.py @@ -11,26 +11,189 @@ class TestConfig: @pytest.mark.parametrize( - "raw_config, expected_test_strictness_level, expected_error", + "raw_config, expected_output_config, expected_error", [ pytest.param( - {"connector_image": "foo", "tests": {}}, None, does_not_raise(), id="No test_strictness_level declared defaults to None." + {"connector_image": "foo", "tests": {"spec": [{"spec_path": "my-spec-path"}]}}, + config.Config( + connector_image="foo", + acceptance_tests=config.AcceptanceTestConfigurations( + spec=config.GenericTestConfig(tests=[config.SpecTestConfig(spec_path="my-spec-path")]) + ), + ), + does_not_raise(), + id="Legacy config should be parsed without error.", + ), + pytest.param( + {"connector_image": "foo", "acceptance_tests": {}, "test_strictness_level": "extra-low"}, + None, + pytest.raises(ValidationError), + id="Invalid test mode: ValidationError", + ), + pytest.param( + {"connector_image": "foo", "acceptance_tests": {}, "test_strictness_level": "low"}, + config.Config( + connector_image="foo", + test_strictness_level=config.Config.TestStrictnessLevel.low, + acceptance_tests=config.AcceptanceTestConfigurations(), + ), + does_not_raise(), + id="Valid test mode: low", + ), + pytest.param( + {"connector_image": "foo", "acceptance_tests": {}, "test_strictness_level": "high"}, + config.Config( + connector_image="foo", + test_strictness_level=config.Config.TestStrictnessLevel.high, + acceptance_tests=config.AcceptanceTestConfigurations(), + ), + does_not_raise(), + id="Valid test mode: high", ), pytest.param( - {"connector_image": "foo", "tests": {}, "test_strictness_level": "high"}, - config.Config.TestStrictnessLevel.high, + { + "connector_image": "foo", + "acceptance_tests": { + "spec": {"bypass_reason": "My good reason to bypass"}, + }, + }, + config.Config( + connector_image="foo", + acceptance_tests=config.AcceptanceTestConfigurations( + spec=config.GenericTestConfig(bypass_reason="My good reason to bypass") + ), + ), does_not_raise(), - id="The test_strictness_level set to strict is a valid enum value is provided.", + id="A test can only have a bypass reason.", ), pytest.param( - {"connector_image": "foo", "tests": {}, "test_strictness_level": "unknown"}, + { + "connector_image": "foo", + "acceptance_tests": { + "spec": {"bypass_reason": "My good reason to bypass"}, + }, + }, + config.Config( + connector_image="foo", + acceptance_tests=config.AcceptanceTestConfigurations( + spec=config.GenericTestConfig(bypass_reason="My good reason to bypass") + ), + ), + does_not_raise(), + id="A test can only have a test configuration.", + ), + pytest.param( + { + "connector_image": "foo", + "acceptance_tests": { + "spec": {"tests": [{"spec_path": "my-spec-path"}], "bypass_reason": "I'm not bypassing"}, + }, + }, None, pytest.raises(ValidationError), - id="Validation error is raised when an invalid enum is passed.", + id="A test can't have a bypass reason and a test configuration.", ), ], ) - def test_test_strictness_level(self, raw_config, expected_test_strictness_level, expected_error): + def test_config_parsing(self, raw_config, expected_output_config, expected_error): with expected_error: parsed_config = config.Config.parse_obj(raw_config) - assert parsed_config.test_strictness_level == expected_test_strictness_level + assert parsed_config == expected_output_config + + @pytest.mark.parametrize( + "legacy_config, expected_parsed_config", + [ + pytest.param( + { + "connector_image": "airbyte/source-pokeapi", + "tests": { + "connection": [ + {"config_path": "integration_tests/config.json", "status": "succeed"}, + {"config_path": "integration_tests/bad_config.json", "status": "failed"}, + ], + "discovery": [{"config_path": "integration_tests/config.json"}], + "basic_read": [ + { + "config_path": "integration_tests/config.json", + "configured_catalog_path": "integration_tests/configured_catalog.json", + } + ], + }, + }, + config.Config( + connector_image="airbyte/source-pokeapi", + test_strictness_level=config.Config.TestStrictnessLevel.low, + acceptance_tests=config.AcceptanceTestConfigurations( + connection=config.GenericTestConfig( + tests=[ + config.ConnectionTestConfig( + config_path="integration_tests/config.json", status=config.ConnectionTestConfig.Status.Succeed + ), + config.ConnectionTestConfig( + config_path="integration_tests/bad_config.json", status=config.ConnectionTestConfig.Status.Failed + ), + ] + ), + discovery=config.GenericTestConfig(tests=[config.DiscoveryTestConfig(config_path="integration_tests/config.json")]), + basic_read=config.GenericTestConfig( + tests=[ + config.BasicReadTestConfig( + config_path="integration_tests/config.json", + configured_catalog_path="integration_tests/configured_catalog.json", + ) + ] + ), + ), + ), + id="A legacy raw config is parsed into a new config structure without error.", + ), + pytest.param( + { + "connector_image": "airbyte/source-pokeapi", + "test_strictness_level": "high", + "tests": { + "connection": [ + {"config_path": "integration_tests/config.json", "status": "succeed"}, + {"config_path": "integration_tests/bad_config.json", "status": "failed"}, + ], + "discovery": [{"config_path": "integration_tests/config.json"}], + "basic_read": [ + { + "config_path": "integration_tests/config.json", + "configured_catalog_path": "integration_tests/configured_catalog.json", + } + ], + }, + }, + config.Config( + connector_image="airbyte/source-pokeapi", + test_strictness_level=config.Config.TestStrictnessLevel.high, + acceptance_tests=config.AcceptanceTestConfigurations( + connection=config.GenericTestConfig( + tests=[ + config.ConnectionTestConfig( + config_path="integration_tests/config.json", status=config.ConnectionTestConfig.Status.Succeed + ), + config.ConnectionTestConfig( + config_path="integration_tests/bad_config.json", status=config.ConnectionTestConfig.Status.Failed + ), + ] + ), + discovery=config.GenericTestConfig(tests=[config.DiscoveryTestConfig(config_path="integration_tests/config.json")]), + basic_read=config.GenericTestConfig( + tests=[ + config.BasicReadTestConfig( + config_path="integration_tests/config.json", + configured_catalog_path="integration_tests/configured_catalog.json", + ) + ] + ), + ), + ), + id="A legacy raw config, with a test_strictness_level defined, is parsed into a new config structure without error.", + ), + ], + ) + def test_legacy_config_migration(self, legacy_config, expected_parsed_config): + assert config.Config.is_legacy(legacy_config) + assert config.Config.parse_obj(legacy_config) == expected_parsed_config diff --git a/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs b/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs index b5d79a215eb4..30c2cbbc67f0 100644 --- a/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs +++ b/airbyte-integrations/connector-templates/source_acceptance_test_files/acceptance-test-config.yml.hbs @@ -1,30 +1,38 @@ # See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: {{ connectorImage }} -tests: +acceptance_tests: spec: - - spec_path: "{{ specPath }}" + tests: + - spec_path: "{{ specPath }}" connection: - - config_path: "secrets/config.json" - status: "succeed" - - config_path: "integration_tests/invalid_config.json" - status: "failed" + tests: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" discovery: - - config_path: "secrets/config.json" + tests: + - config_path: "secrets/config.json" basic_read: - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" - empty_streams: [] - # TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file - # expect_records: - # path: "integration_tests/expected_records.txt" - # extra_fields: no - # exact_order: no - # extra_records: yes - incremental: # TODO if your connector does not implement incremental sync, remove this block - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" - future_state_path: "integration_tests/abnormal_state.json" + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] +# TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file +# expect_records: +# path: "integration_tests/expected_records.txt" +# extra_fields: no +# exact_order: no +# extra_records: yes + incremental: + bypass_reason: "This connector does not implement incremental sync" +# TODO uncomment this block this block if your connector implements incremental sync: +# tests: +# - config_path: "secrets/config.json" +# configured_catalog_path: "integration_tests/configured_catalog.json" +# future_state_path: "integration_tests/abnormal_state.json" full_refresh: - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml index ef2899044223..6e648fb7f970 100644 --- a/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml @@ -1,15 +1,21 @@ # See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-pokeapi:dev -tests: +acceptance_tests: + spec: + bypass_reason: "TODO: activate this test!" connection: - - config_path: "integration_tests/config.json" - status: "succeed" + tests: + - config_path: "integration_tests/config.json" + status: "succeed" discovery: - - config_path: "integration_tests/config.json" + tests: + - config_path: "integration_tests/config.json" basic_read: - - config_path: "integration_tests/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" + tests: + - config_path: "integration_tests/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" full_refresh: - - config_path: "integration_tests/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" + tests: + - config_path: "integration_tests/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml b/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml index 3d4070bed766..7588c3ce9df5 100644 --- a/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-scaffold-source-http/acceptance-test-config.yml @@ -1,30 +1,38 @@ # See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-scaffold-source-http:dev -tests: +acceptance_tests: spec: - - spec_path: "source_scaffold_source_http/spec.yaml" + tests: + - spec_path: "source_scaffold_source_http/spec.yaml" connection: - - config_path: "secrets/config.json" - status: "succeed" - - config_path: "integration_tests/invalid_config.json" - status: "failed" + tests: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" discovery: - - config_path: "secrets/config.json" + tests: + - config_path: "secrets/config.json" basic_read: - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" - empty_streams: [] - # TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file - # expect_records: - # path: "integration_tests/expected_records.txt" - # extra_fields: no - # exact_order: no - # extra_records: yes - incremental: # TODO if your connector does not implement incremental sync, remove this block - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" - future_state_path: "integration_tests/abnormal_state.json" + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] +# TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file +# expect_records: +# path: "integration_tests/expected_records.txt" +# extra_fields: no +# exact_order: no +# extra_records: yes + incremental: + bypass_reason: "This connector does not implement incremental sync" +# TODO uncomment this block this block if your connector implements incremental sync: +# tests: +# - config_path: "secrets/config.json" +# configured_catalog_path: "integration_tests/configured_catalog.json" +# future_state_path: "integration_tests/abnormal_state.json" full_refresh: - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml b/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml index 0f74155d399b..107b721bc89c 100644 --- a/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-scaffold-source-python/acceptance-test-config.yml @@ -1,30 +1,38 @@ # See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) # for more information about how to configure these tests connector_image: airbyte/source-scaffold-source-python:dev -tests: +acceptance_tests: spec: - - spec_path: "source_scaffold_source_python/spec.yaml" + tests: + - spec_path: "source_scaffold_source_python/spec.yaml" connection: - - config_path: "secrets/config.json" - status: "succeed" - - config_path: "integration_tests/invalid_config.json" - status: "failed" + tests: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" discovery: - - config_path: "secrets/config.json" + tests: + - config_path: "secrets/config.json" basic_read: - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" - empty_streams: [] - # TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file - # expect_records: - # path: "integration_tests/expected_records.txt" - # extra_fields: no - # exact_order: no - # extra_records: yes - incremental: # TODO if your connector does not implement incremental sync, remove this block - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" - future_state_path: "integration_tests/abnormal_state.json" + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] +# TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file +# expect_records: +# path: "integration_tests/expected_records.txt" +# extra_fields: no +# exact_order: no +# extra_records: yes + incremental: + bypass_reason: "This connector does not implement incremental sync" +# TODO uncomment this block this block if your connector implements incremental sync: +# tests: +# - config_path: "secrets/config.json" +# configured_catalog_path: "integration_tests/configured_catalog.json" +# future_state_path: "integration_tests/abnormal_state.json" full_refresh: - - config_path: "secrets/config.json" - configured_catalog_path: "integration_tests/configured_catalog.json" + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md b/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md index e258e049ec48..0aa513a396c5 100644 --- a/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md +++ b/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md @@ -16,15 +16,16 @@ Each test suite has a timeout and will fail if the limit is exceeded. See all the test cases, their description, and inputs in [Source Acceptance Tests](https://github.com/airbytehq/airbyte/tree/e378d40236b6a34e1c1cb481c8952735ec687d88/docs/contributing-to-airbyte/building-new-connector/source-acceptance-tests.md). -## Setting up standard tests for your connector +## Setting up standard acceptance tests for your connector Create `acceptance-test-config.yml`. In most cases, your connector already has this file in its root folder. Here is an example of the minimal `acceptance-test-config.yml`: ```yaml connector_image: airbyte/source-some-connector:dev -tests: +acceptance-tests: spec: - - spec_path: "some_folder/spec.yaml" + tests: + - spec_path: "some_folder/spec.yaml" ``` Build your connector image if needed. @@ -78,17 +79,21 @@ These tests are configurable via `acceptance-test-config.yml`. Each test has a n Example of `acceptance-test-config.yml`: ```yaml -connector_image: string # Docker image to test, for example 'airbyte/source-hubspot:0.1.0' +connector_image: string # Docker image to test, for example 'airbyte/source-pokeapi:0.1.0' base_path: string # Base path for all relative paths, optional, default - ./ -tests: # Tests configuration +acceptance_tests: # Tests configuration spec: # list of the test inputs + bypass_reason: "Explain why you skipped this test" connection: # list of the test inputs - - config_path: string # set #1 of inputs - status: string - - config_path: string # set #2 of inputs - status: string - # discovery: # skip this test - incremental: [] # skip this test as well + tests: + - config_path: string # set #1 of inputs + status: string + - config_path: string # set #2 of inputs + status: string + # discovery: # skip this test + incremental: + bypass_reason: "Incremental sync are not supported on this connector" + ``` ## Test Spec From 848046a15848b3fc7c12dcdd47571851d3d4c80e Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Tue, 25 Oct 2022 10:53:32 +0300 Subject: [PATCH 296/498] Source Iterable - better processing of 401 and 429 errors (#18292) * #829 source iterable - better processing of 401 and 429 errors * source iterable: upd changelog * #829 source iterable: fix unit tests * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-iterable/Dockerfile | 2 +- .../source-iterable/source_iterable/source.py | 95 +++++++++++-------- .../source_iterable/streams.py | 31 ++++-- .../source-iterable/unit_tests/conftest.py | 7 +- .../test_export_adjustable_range.py | 26 ++--- .../source-iterable/unit_tests/test_source.py | 13 ++- .../unit_tests/test_streams.py | 14 +++ docs/integrations/sources/iterable.md | 1 + 10 files changed, 127 insertions(+), 66 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index f673a484605c..46a0fe2a1498 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -532,7 +532,7 @@ - name: Iterable sourceDefinitionId: 2e875208-0c0b-4ee4-9e92-1cb3156ea799 dockerRepository: airbyte/source-iterable - dockerImageTag: 0.1.19 + dockerImageTag: 0.1.20 documentationUrl: https://docs.airbyte.com/integrations/sources/iterable icon: iterable.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index ad20ec663c8b..3706bf8b0fa5 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -5192,7 +5192,7 @@ oauthFlowInitParameters: [] oauthFlowOutputParameters: - - "access_token" -- dockerImage: "airbyte/source-iterable:0.1.19" +- dockerImage: "airbyte/source-iterable:0.1.20" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/iterable" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-iterable/Dockerfile b/airbyte-integrations/connectors/source-iterable/Dockerfile index 5f9707ca0815..15e1b2a8a277 100644 --- a/airbyte-integrations/connectors/source-iterable/Dockerfile +++ b/airbyte-integrations/connectors/source-iterable/Dockerfile @@ -12,5 +12,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.19 +LABEL io.airbyte.version=0.1.20 LABEL io.airbyte.name=airbyte/source-iterable diff --git a/airbyte-integrations/connectors/source-iterable/source_iterable/source.py b/airbyte-integrations/connectors/source-iterable/source_iterable/source.py index 2cfe9358bfd1..7ac4de3f9b87 100644 --- a/airbyte-integrations/connectors/source-iterable/source_iterable/source.py +++ b/airbyte-integrations/connectors/source-iterable/source_iterable/source.py @@ -79,49 +79,68 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: # end date is provided for integration tests only start_date, end_date = config["start_date"], config.get("end_date") date_range = {"start_date": start_date, "end_date": end_date} - return [ + streams = [ Campaigns(authenticator=authenticator), CampaignsMetrics(authenticator=authenticator, **date_range), Channels(authenticator=authenticator), - EmailBounce(authenticator=authenticator, **date_range), - EmailClick(authenticator=authenticator, **date_range), - EmailComplaint(authenticator=authenticator, **date_range), - EmailOpen(authenticator=authenticator, **date_range), - EmailSend(authenticator=authenticator, **date_range), - EmailSendSkip(authenticator=authenticator, **date_range), - EmailSubscribe(authenticator=authenticator, **date_range), - EmailUnsubscribe(authenticator=authenticator, **date_range), - PushSend(authenticator=authenticator, **date_range), - PushSendSkip(authenticator=authenticator, **date_range), - PushOpen(authenticator=authenticator, **date_range), - PushUninstall(authenticator=authenticator, **date_range), - PushBounce(authenticator=authenticator, **date_range), - WebPushSend(authenticator=authenticator, **date_range), - WebPushClick(authenticator=authenticator, **date_range), - WebPushSendSkip(authenticator=authenticator, **date_range), - InAppSend(authenticator=authenticator, **date_range), - InAppOpen(authenticator=authenticator, **date_range), - InAppClick(authenticator=authenticator, **date_range), - InAppClose(authenticator=authenticator, **date_range), - InAppDelete(authenticator=authenticator, **date_range), - InAppDelivery(authenticator=authenticator, **date_range), - InAppSendSkip(authenticator=authenticator, **date_range), - InboxSession(authenticator=authenticator, **date_range), - InboxMessageImpression(authenticator=authenticator, **date_range), - SmsSend(authenticator=authenticator, **date_range), - SmsBounce(authenticator=authenticator, **date_range), - SmsClick(authenticator=authenticator, **date_range), - SmsReceived(authenticator=authenticator, **date_range), - SmsSendSkip(authenticator=authenticator, **date_range), - SmsUsageInfo(authenticator=authenticator, **date_range), - Purchase(authenticator=authenticator, **date_range), - CustomEvent(authenticator=authenticator, **date_range), - HostedUnsubscribeClick(authenticator=authenticator, **date_range), - Events(authenticator=authenticator), Lists(authenticator=authenticator), - ListUsers(authenticator=authenticator), MessageTypes(authenticator=authenticator), Metadata(authenticator=authenticator), Templates(authenticator=authenticator, **date_range), - Users(authenticator=authenticator, **date_range), ] + # Iterable supports two types of Server-side api keys: + # - read only + # - server side + # The first one has a limited set of supported APIs, so others are filtered out here. + # A simple check is done - a read operation on a stream that can be accessed only via a Server side API key. + # If read is successful - other streams should be supported as well. + # More on this - https://support.iterable.com/hc/en-us/articles/360043464871-API-Keys- + users_stream = ListUsers(authenticator=authenticator) + for slice_ in users_stream.stream_slices(sync_mode=SyncMode.full_refresh): + users = users_stream.read_records(stream_slice=slice_, sync_mode=SyncMode.full_refresh) + # first slice is enough + break + + if next(users, None): + streams.extend( + [ + Users(authenticator=authenticator, **date_range), + ListUsers(authenticator=authenticator), + EmailBounce(authenticator=authenticator, **date_range), + EmailClick(authenticator=authenticator, **date_range), + EmailComplaint(authenticator=authenticator, **date_range), + EmailOpen(authenticator=authenticator, **date_range), + EmailSend(authenticator=authenticator, **date_range), + EmailSendSkip(authenticator=authenticator, **date_range), + EmailSubscribe(authenticator=authenticator, **date_range), + EmailUnsubscribe(authenticator=authenticator, **date_range), + PushSend(authenticator=authenticator, **date_range), + PushSendSkip(authenticator=authenticator, **date_range), + PushOpen(authenticator=authenticator, **date_range), + PushUninstall(authenticator=authenticator, **date_range), + PushBounce(authenticator=authenticator, **date_range), + WebPushSend(authenticator=authenticator, **date_range), + WebPushClick(authenticator=authenticator, **date_range), + WebPushSendSkip(authenticator=authenticator, **date_range), + InAppSend(authenticator=authenticator, **date_range), + InAppOpen(authenticator=authenticator, **date_range), + InAppClick(authenticator=authenticator, **date_range), + InAppClose(authenticator=authenticator, **date_range), + InAppDelete(authenticator=authenticator, **date_range), + InAppDelivery(authenticator=authenticator, **date_range), + InAppSendSkip(authenticator=authenticator, **date_range), + InboxSession(authenticator=authenticator, **date_range), + InboxMessageImpression(authenticator=authenticator, **date_range), + SmsSend(authenticator=authenticator, **date_range), + SmsBounce(authenticator=authenticator, **date_range), + SmsClick(authenticator=authenticator, **date_range), + SmsReceived(authenticator=authenticator, **date_range), + SmsSendSkip(authenticator=authenticator, **date_range), + SmsUsageInfo(authenticator=authenticator, **date_range), + Purchase(authenticator=authenticator, **date_range), + CustomEvent(authenticator=authenticator, **date_range), + HostedUnsubscribeClick(authenticator=authenticator, **date_range), + Events(authenticator=authenticator), + ] + ) + return streams diff --git a/airbyte-integrations/connectors/source-iterable/source_iterable/streams.py b/airbyte-integrations/connectors/source-iterable/source_iterable/streams.py index c24744e11776..a759d11b5c0e 100644 --- a/airbyte-integrations/connectors/source-iterable/source_iterable/streams.py +++ b/airbyte-integrations/connectors/source-iterable/source_iterable/streams.py @@ -26,7 +26,9 @@ class IterableStream(HttpStream, ABC): raise_on_http_errors = True - + # in case we get a 401 error (api token disabled or deleted) on a stream slice, do not make further requests within the current stream + # to prevent 429 error on other streams + ignore_further_slices = False # Hardcode the value because it is not returned from the API BACKOFF_TIME_CONSTANT = 10.0 # define date-time fields with potential wrong format @@ -48,6 +50,7 @@ def data_field(self) -> str: def check_unauthorized_key(self, response: requests.Response) -> bool: if response.status_code == codes.UNAUTHORIZED: self.logger.warn(f"Provided API Key has not sufficient permissions to read from stream: {self.data_field}") + self.ignore_further_slices = True setattr(self, "raise_on_http_errors", False) return False return True @@ -63,7 +66,7 @@ def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: if not self.check_unauthorized_key(response): - yield from [] + return [] response_json = response.json() records = response_json.get(self.data_field, []) @@ -73,8 +76,18 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp def should_retry(self, response: requests.Response) -> bool: if not self.check_unauthorized_key(response): return False - else: - return super().should_retry(response) + return super().should_retry(response) + + def read_records( + self, + sync_mode: SyncMode, + cursor_field: List[str] = None, + stream_slice: Mapping[str, Any] = None, + stream_state: Mapping[str, Any] = None, + ) -> Iterable[Mapping[str, Any]]: + if self.ignore_further_slices: + return [] + yield from super().read_records(sync_mode, cursor_field=cursor_field, stream_slice=stream_slice, stream_state=stream_state) class IterableExportStream(IterableStream, ABC): @@ -169,7 +182,7 @@ def request_params( def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: if not self.check_unauthorized_key(response): - return None + return [] for obj in response.iter_lines(): record = json.loads(obj) record[self.cursor_field] = self._field_to_datetime(record[self.cursor_field]) @@ -321,7 +334,7 @@ def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: if not self.check_unauthorized_key(response): - yield from [] + return [] list_id = self._get_list_id(response.url) for user in response.iter_lines(): yield {"email": user.decode(), "listId": list_id} @@ -381,7 +394,7 @@ def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: if not self.check_unauthorized_key(response): - yield from [] + return [] content = response.content.decode() records = self._parse_csv_string_to_dict(content) @@ -480,7 +493,7 @@ def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapp Put the rest of the fields in the `data` subobject. """ if not self.check_unauthorized_key(response): - yield from [] + return [] jsonl_records = StringIO(response.text) for record in jsonl_records: record_dict = json.loads(record) @@ -643,7 +656,7 @@ def read_records(self, stream_slice: Optional[Mapping[str, Any]] = None, **kwarg def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: if not self.check_unauthorized_key(response): - yield from [] + return [] response_json = response.json() records = response_json.get(self.data_field, []) diff --git a/airbyte-integrations/connectors/source-iterable/unit_tests/conftest.py b/airbyte-integrations/connectors/source-iterable/unit_tests/conftest.py index 3453f15fdf3b..c5b82c500d04 100644 --- a/airbyte-integrations/connectors/source-iterable/unit_tests/conftest.py +++ b/airbyte-integrations/connectors/source-iterable/unit_tests/conftest.py @@ -11,7 +11,7 @@ def catalog(request): return ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( - stream=AirbyteStream(name=request.param, json_schema={}), + stream=AirbyteStream(name=request.param, json_schema={}, supported_sync_modes=["full_refresh"]), sync_mode="full_refresh", destination_sync_mode="append", ) @@ -22,3 +22,8 @@ def catalog(request): @pytest.fixture(name="config") def config_fixture(): return {"api_key": 123, "start_date": "2019-10-10T00:00:00"} + + +@pytest.fixture() +def mock_lists_resp(mocker): + mocker.patch("source_iterable.streams.Lists.read_records", return_value=iter([{"id": 1}, {"id": 2}])) diff --git a/airbyte-integrations/connectors/source-iterable/unit_tests/test_export_adjustable_range.py b/airbyte-integrations/connectors/source-iterable/unit_tests/test_export_adjustable_range.py index 22f39c9174dc..a01bfa2b9da2 100644 --- a/airbyte-integrations/connectors/source-iterable/unit_tests/test_export_adjustable_range.py +++ b/airbyte-integrations/connectors/source-iterable/unit_tests/test_export_adjustable_range.py @@ -45,7 +45,7 @@ def read_from_source(catalog): @responses.activate @pytest.mark.parametrize("catalog", (["email_send"]), indirect=True) -def test_email_stream(catalog, time_mock): +def test_email_stream(mock_lists_resp, catalog, time_mock): DAYS_DURATION = 100 DAYS_PER_MINUTE_RATE = 8 @@ -59,12 +59,14 @@ def response_cb(req): time_mock.tick(delta=datetime.timedelta(minutes=days / DAYS_PER_MINUTE_RATE)) return (200, {}, json.dumps({"createdAt": "2020"})) + responses.add(responses.GET, "https://api.iterable.com/api/lists/getUsers?listId=1", json={"lists": [{"id": 1}]}, status=200) responses.add_callback("GET", "https://api.iterable.com/api/export/data.json", callback=response_cb) records = read_from_source(catalog) assert records assert sum(ranges) == DAYS_DURATION - assert len(responses.calls) == len(ranges) + # since read is called on source instance, under the hood .streams() is called which triggers one more http call + assert len(responses.calls) == len(ranges) + 1 assert ranges == [ AdjustableSliceGenerator.INITIAL_RANGE_DAYS, *([int(DAYS_PER_MINUTE_RATE / AdjustableSliceGenerator.REQUEST_PER_MINUTE_LIMIT)] * 35), @@ -76,17 +78,17 @@ def response_cb(req): "catalog, days_duration, days_per_minute_rate", [ ("email_send", 10, 200), - # tests are commented because they take a lot of time for completion - # ("email_send", 100, 200000), - # ("email_send", 10000, 200000), - # ("email_click", 1000, 20), - # ("email_open", 1000, 1), - # ("email_open", 1, 1000), - # ("email_open", 0, 1000000), + ("email_send", 100, 200000), + ("email_send", 10000, 200000), + ("email_click", 1000, 20), + ("email_open", 1000, 1), + ("email_open", 1, 1000), + ("email_open", 0, 1000000), ], indirect=["catalog"], ) -def test_email_stream_chunked_encoding(catalog, days_duration, days_per_minute_rate, time_mock): +def test_email_stream_chunked_encoding(mocker, mock_lists_resp, catalog, days_duration, days_per_minute_rate, time_mock): + mocker.patch("time.sleep") time_mock.move_to(pendulum.parse(TEST_START_DATE) + pendulum.Duration(days=days_duration)) ranges: List[int] = [] @@ -104,9 +106,11 @@ def response_cb(req): time_mock.tick(delta=datetime.timedelta(minutes=days / days_per_minute_rate)) return (200, {}, json.dumps({"createdAt": "2020"})) + responses.add(responses.GET, "https://api.iterable.com/api/lists/getUsers?listId=1", json={"lists": [{"id": 1}]}, status=200) responses.add_callback("GET", "https://api.iterable.com/api/export/data.json", callback=response_cb) records = read_from_source(catalog) assert sum(ranges) == days_duration assert len(ranges) == len(records) - assert len(responses.calls) == 3 * len(ranges) + # since read is called on source instance, under the hood .streams() is called which triggers one more http call + assert len(responses.calls) == 3 * len(ranges) + 1 diff --git a/airbyte-integrations/connectors/source-iterable/unit_tests/test_source.py b/airbyte-integrations/connectors/source-iterable/unit_tests/test_source.py index 4c11c1b855a1..c48ea066f285 100644 --- a/airbyte-integrations/connectors/source-iterable/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-iterable/unit_tests/test_source.py @@ -4,20 +4,25 @@ from unittest.mock import MagicMock, patch +import pytest +import responses from source_iterable.source import SourceIterable from source_iterable.streams import Lists -def test_source_streams(config): +@responses.activate +@pytest.mark.parametrize("body, status, expected_streams", (({}, 401, 7), ({"lists": [{"id": 1}]}, 200, 44))) +def test_source_streams(mock_lists_resp, config, body, status, expected_streams): + responses.add(responses.GET, "https://api.iterable.com/api/lists/getUsers?listId=1", json=body, status=status) streams = SourceIterable().streams(config=config) - assert len(streams) == 44 + assert len(streams) == expected_streams def test_source_check_connection_ok(config): - with patch.object(Lists, "read_records", return_value=iter([1])): + with patch.object(Lists, "read_records", return_value=iter([{"id": 1}])): assert SourceIterable().check_connection(MagicMock(), config=config) == (True, None) def test_source_check_connection_failed(config): - with patch.object(Lists, "read_records", return_value=0): + with patch.object(Lists, "read_records", return_value=iter([])): assert SourceIterable().check_connection(MagicMock(), config=config)[0] is False diff --git a/airbyte-integrations/connectors/source-iterable/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-iterable/unit_tests/test_streams.py index 9806990a019e..777da2c9e98c 100644 --- a/airbyte-integrations/connectors/source-iterable/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-iterable/unit_tests/test_streams.py @@ -6,6 +6,7 @@ import pytest import requests import responses +from airbyte_cdk.models import SyncMode from airbyte_cdk.sources.streams.http.auth import NoAuth from source_iterable.streams import ( Campaigns, @@ -173,3 +174,16 @@ def test_get_updated_state(current_state, record_date, expected_state): latest_record={"profileUpdatedAt": pendulum.parse(record_date)}, ) assert state == expected_state + + +@responses.activate +def test_stream_stops_on_401(mock_lists_resp): + # no requests should be made after getting 401 error despite the multiple slices + users_stream = ListUsers(authenticator=NoAuth()) + responses.add(responses.GET, "https://api.iterable.com/api/lists/getUsers?listId=1", json={}, status=401) + slices = 0 + for slice_ in users_stream.stream_slices(sync_mode=SyncMode.full_refresh): + slices += 1 + _ = list(users_stream.read_records(stream_slice=slice_, sync_mode=SyncMode.full_refresh)) + assert len(responses.calls) == 1 + assert slices > 1 diff --git a/docs/integrations/sources/iterable.md b/docs/integrations/sources/iterable.md index 89cdce688ab7..fb2b68f92741 100644 --- a/docs/integrations/sources/iterable.md +++ b/docs/integrations/sources/iterable.md @@ -92,6 +92,7 @@ The Iterable source connector supports the following [sync modes](https://docs.a | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------| +| 0.1.20 | 2022-10-21 | [18292](https://github.com/airbytehq/airbyte/pull/18292) | Better processing of 401 and 429 errors | | 0.1.19 | 2022-10-05 | [17602](https://github.com/airbytehq/airbyte/pull/17602) | Add check for stream permissions | | 0.1.18 | 2022-10-04 | [17573](https://github.com/airbytehq/airbyte/pull/17573) | Limit time range for SATs | | 0.1.17 | 2022-09-02 | [16067](https://github.com/airbytehq/airbyte/pull/16067) | added new events streams | From 01d84d4e2a58937987683f71ddc755df92e66b1b Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:27:56 +0200 Subject: [PATCH 297/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Asana:=20fix=20?= =?UTF-8?q?request-cache=20test=20(#18375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Source Asana: fix request-cache test * Source Asana: Bump version * 🐛 Source Asana: revert docker label; update CDK --- airbyte-integrations/connectors/source-asana/setup.py | 2 +- .../connectors/source-asana/unit_tests/test_source.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/airbyte-integrations/connectors/source-asana/setup.py b/airbyte-integrations/connectors/source-asana/setup.py index 41cee5e6fc12..348d94cc679c 100644 --- a/airbyte-integrations/connectors/source-asana/setup.py +++ b/airbyte-integrations/connectors/source-asana/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk", + "airbyte-cdk~=0.2", ] TEST_REQUIREMENTS = ["pytest~=6.1", "requests-mock~=1.9.3", "source-acceptance-test"] diff --git a/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py b/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py index 7871fb5f4110..dd13b87ef40d 100644 --- a/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py @@ -1,6 +1,7 @@ # # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from unittest.mock import patch, PropertyMock from airbyte_cdk.logger import AirbyteLogger from source_asana.source import SourceAsana @@ -26,10 +27,11 @@ def test_check_connection_empty_config(config): def test_check_connection_exception(config): - ok, error_msg = SourceAsana().check_connection(logger, config=config) + with patch('source_asana.streams.Workspaces.use_cache', new_callable=PropertyMock, return_value=False): + ok, error_msg = SourceAsana().check_connection(logger, config=config) - assert not ok - assert error_msg + assert not ok + assert error_msg def test_streams(config): From 17ed19809ed5c485319aac045882b031ed835a81 Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:28:21 +0200 Subject: [PATCH 298/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Freshdesk:=20fi?= =?UTF-8?q?x=20test;=20update=20CDK=20(#18389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Source Freshdesk: fix test; update CDK * 🐛 Source FreshDesk: revert docker label; update CDK --- .../connectors/source-freshdesk/setup.py | 2 +- .../unit_tests/test_streams.py | 26 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/airbyte-integrations/connectors/source-freshdesk/setup.py b/airbyte-integrations/connectors/source-freshdesk/setup.py index 233deec4f97d..08f586a268bd 100644 --- a/airbyte-integrations/connectors/source-freshdesk/setup.py +++ b/airbyte-integrations/connectors/source-freshdesk/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1", + "airbyte-cdk~=0.2", "backoff==1.10.0", "requests==2.25.1", "pendulum==2.1.2", diff --git a/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py index de704263bc3d..d0b8b899456a 100644 --- a/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py @@ -4,6 +4,7 @@ import random from typing import Any, MutableMapping +from unittest.mock import patch, PropertyMock import pytest from airbyte_cdk.models import SyncMode @@ -125,18 +126,19 @@ def test_incremental(stream, resource, authenticator, config, requests_mock): highest_updated_at = "2022-04-25T22:00:00Z" other_updated_at = "2022-04-01T00:00:00Z" highest_index = random.randint(0, 24) - requests_mock.register_uri( - "GET", - f"/api/{resource}", - json=[{"id": x, "updated_at": highest_updated_at if x == highest_index else other_updated_at} for x in range(25)], - ) - - stream = stream(authenticator=authenticator, config=config) - records, state = _read_incremental(stream, {}) - - assert len(records) == 25 - assert "updated_at" in state - assert state["updated_at"] == highest_updated_at + with patch(f'source_freshdesk.streams.{stream.__name__}.use_cache', new_callable=PropertyMock, return_value=False): + requests_mock.register_uri( + "GET", + f"/api/{resource}", + json=[{"id": x, "updated_at": highest_updated_at if x == highest_index else other_updated_at} for x in range(25)], + ) + + stream = stream(authenticator=authenticator, config=config) + records, state = _read_incremental(stream, {}) + + assert len(records) == 25 + assert "updated_at" in state + assert state["updated_at"] == highest_updated_at @pytest.mark.parametrize( From 4a61e6aaa3dcebf2af52644185238dfa24f7eeb4 Mon Sep 17 00:00:00 2001 From: Pinhas Keizman <43240341+pinikeizman@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:31:56 +0300 Subject: [PATCH 299/498] Update cdk-speedrun.md (#18416) --- docs/connector-development/tutorials/cdk-speedrun.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/connector-development/tutorials/cdk-speedrun.md b/docs/connector-development/tutorials/cdk-speedrun.md index 3b7cf8205fa8..0c1f19e8758b 100644 --- a/docs/connector-development/tutorials/cdk-speedrun.md +++ b/docs/connector-development/tutorials/cdk-speedrun.md @@ -18,7 +18,7 @@ If you are a visual learner and want to see a video version of this guide going ```bash # # clone the repo if you havent already -# git clone -–depth 1 https://github.com/airbytehq/airbyte/ +# git clone --depth 1 https://github.com/airbytehq/airbyte/ # cd airbyte # start from repo root cd airbyte-integrations/connector-templates/generator ./generate.sh From f4fdb386767b395346a19165e0d01fed16d82e39 Mon Sep 17 00:00:00 2001 From: Amruta Ranade <11484018+Amruta-Ranade@users.noreply.github.com> Date: Tue, 25 Oct 2022 09:01:18 -0400 Subject: [PATCH 300/498] Update README.md --- docs/integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/README.md b/docs/integrations/README.md index aca4349b68c5..3fb03697b854 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -141,7 +141,7 @@ For more information about the grading system, see [Product Release Stages](http | [Recurly](sources/recurly.md) | Alpha | Yes | | [Redshift](sources/redshift.md) | Alpha | Yes | | [Retently](sources/retently.md) | Alpha | Yes | -| [S3](sources/s3.md) | Generally Available | No | +| [S3](sources/s3.md) | Generally Available | Yes | | [Salesforce](sources/salesforce.md) | Generally Available | Yes | | [Salesloft](sources/salesloft.md) | Alpha | No | | [SAP Business One](sources/sap-business-one.md) | Alpha | No | From 0b460f506a44a843ab2c756807b7f4361829f722 Mon Sep 17 00:00:00 2001 From: Tyler B <104733644+TBernstein4@users.noreply.github.com> Date: Tue, 25 Oct 2022 09:15:03 -0400 Subject: [PATCH 301/498] Update Facebook-marketing.md with warning about ongoing setup issue (#18422) There is an ongoing issue blocking anyone with the most recent API update from being able to setup a Facebook Marketing Source Connector. Put this warning at the top of the setup page until it is fixed. --- docs/integrations/sources/facebook-marketing.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/integrations/sources/facebook-marketing.md b/docs/integrations/sources/facebook-marketing.md index 9e7cac76388f..6afa4db5b076 100644 --- a/docs/integrations/sources/facebook-marketing.md +++ b/docs/integrations/sources/facebook-marketing.md @@ -1,5 +1,11 @@ # Facebook Marketing +:::warning +We are currently experiencing an issue for anyone setting up a Facebook Marketing Source Connector who's Facebook Graph API has been updated to v15.0. Our team is blocked until the v15.0 SDK is made available, but once we have access to it we will begin working on a fix. + +Thank you for your patience and understanding! +::: + This page guides you through the process of setting up the Facebook Marketing source connector. ## Prerequisites From 80f2ad88c6e48d57943d1ed6819c6a8ac07b474c Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Tue, 25 Oct 2022 17:45:14 +0300 Subject: [PATCH 302/498] Source Greenhouse: extend users stream schema (#18154) * #17506 source greenhouse: extend users stream schema * #17506 source greenhouse: upd changelog * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-greenhouse/Dockerfile | 2 +- .../integration_tests/expected_records.txt | 10 +-- .../source_greenhouse/schemas/users.json | 69 +++++++++++++++++++ docs/integrations/sources/greenhouse.md | 1 + 6 files changed, 78 insertions(+), 8 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 46a0fe2a1498..cac80536a205 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -448,7 +448,7 @@ - name: Greenhouse sourceDefinitionId: 59f1e50a-331f-4f09-b3e8-2e8d4d355f44 dockerRepository: airbyte/source-greenhouse - dockerImageTag: 0.2.11 + dockerImageTag: 0.3.0 documentationUrl: https://docs.airbyte.com/integrations/sources/greenhouse icon: greenhouse.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 3706bf8b0fa5..ca8f093ffcd2 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -4554,7 +4554,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-greenhouse:0.2.11" +- dockerImage: "airbyte/source-greenhouse:0.3.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/greenhouse" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-greenhouse/Dockerfile b/airbyte-integrations/connectors/source-greenhouse/Dockerfile index f0c956236759..dedbaeca1b11 100644 --- a/airbyte-integrations/connectors/source-greenhouse/Dockerfile +++ b/airbyte-integrations/connectors/source-greenhouse/Dockerfile @@ -12,5 +12,5 @@ COPY main.py ./ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.11 +LABEL io.airbyte.version=0.3.0 LABEL io.airbyte.name=airbyte/source-greenhouse diff --git a/airbyte-integrations/connectors/source-greenhouse/integration_tests/expected_records.txt b/airbyte-integrations/connectors/source-greenhouse/integration_tests/expected_records.txt index 105b06b32ab8..5e2019e3b30d 100644 --- a/airbyte-integrations/connectors/source-greenhouse/integration_tests/expected_records.txt +++ b/airbyte-integrations/connectors/source-greenhouse/integration_tests/expected_records.txt @@ -39,11 +39,11 @@ {"stream": "offers", "data": {"id": 4154100003, "version": 1, "application_id": 19215333003, "created_at": "2020-11-24T23:32:25.760Z", "updated_at": "2020-11-24T23:32:25.772Z", "sent_at": null, "resolved_at": null, "starts_at": "2020-12-04", "status": "unresolved", "job_id": 4177048003, "candidate_id": 17130848003, "opening": {"id": 4320018003, "opening_id": "4-1", "status": "open", "opened_at": "2020-11-24T23:27:45.665Z", "closed_at": null, "application_id": null, "close_reason": null}, "custom_fields": {"employment_type": "Contract"}, "keyed_custom_fields": {"employment_type": {"name": "Employment Type", "type": "single_select", "value": "Contract"}}}, "emitted_at": 1664285618339} {"stream": "scorecards", "data": {"id": 5253031003, "updated_at": "2020-11-24T23:33:10.440Z", "created_at": "2020-11-24T23:33:10.440Z", "interview": "Application Review", "interview_step": {"id": 5628634003, "name": "Application Review"}, "candidate_id": 17130848003, "application_id": 19215333003, "interviewed_at": "2020-11-25T01:00:00.000Z", "submitted_by": {"id": 4218086003, "first_name": "John", "last_name": "Lafleur", "name": "John Lafleur", "employee_id": null}, "interviewer": {"id": 4218086003, "first_name": "John", "last_name": "Lafleur", "name": "John Lafleur", "employee_id": null}, "submitted_at": "2020-11-24T23:33:10.440Z", "overall_recommendation": "no_decision", "attributes": [{"name": "Willing to do required travel", "type": "Details", "note": null, "rating": "no_decision"}, {"name": "Three to five years of experience", "type": "Qualifications", "note": null, "rating": "no_decision"}, {"name": "Personable", "type": "Personality Traits", "note": null, "rating": "no_decision"}, {"name": "Passionate", "type": "Personality Traits", "note": null, "rating": "no_decision"}, {"name": "Organizational Skills", "type": "Skills", "note": null, "rating": "no_decision"}, {"name": "Manage competing priorities", "type": "Skills", "note": null, "rating": "no_decision"}, {"name": "Fits our salary range", "type": "Details", "note": null, "rating": "no_decision"}, {"name": "Empathetic", "type": "Personality Traits", "note": null, "rating": "no_decision"}, {"name": "Currently based locally", "type": "Details", "note": null, "rating": "no_decision"}, {"name": "Communication", "type": "Skills", "note": null, "rating": "no_decision"}], "ratings": {"definitely_not": [], "no": [], "mixed": [], "yes": [], "strong_yes": []}, "questions": [{"id": null, "question": "Key Take-Aways", "answer": ""}, {"id": null, "question": "Private Notes", "answer": ""}]}, "emitted_at": 1664285619010} {"stream": "scorecards", "data": {"id": 9664505003, "updated_at": "2021-09-29T17:23:11.468Z", "created_at": "2021-09-29T17:23:11.468Z", "interview": "Preliminary Screening Call", "interview_step": {"id": 5628615003, "name": "Preliminary Screening Call"}, "candidate_id": 40517966003, "application_id": 44937562003, "interviewed_at": "2021-09-29T01:00:00.000Z", "submitted_by": {"id": 4218086003, "first_name": "John", "last_name": "Lafleur", "name": "John Lafleur", "employee_id": null}, "interviewer": {"id": 4218086003, "first_name": "John", "last_name": "Lafleur", "name": "John Lafleur", "employee_id": null}, "submitted_at": "2021-09-29T17:23:11.468Z", "overall_recommendation": "no_decision", "attributes": [{"name": "Willing to do required travel", "type": "Details", "note": null, "rating": "yes"}, {"name": "Three to five years of experience", "type": "Qualifications", "note": null, "rating": "mixed"}, {"name": "Personable", "type": "Personality Traits", "note": null, "rating": "yes"}, {"name": "Passionate", "type": "Personality Traits", "note": null, "rating": "mixed"}, {"name": "Organizational Skills", "type": "Skills", "note": null, "rating": "yes"}, {"name": "Manage competing priorities", "type": "Skills", "note": null, "rating": "yes"}, {"name": "Fits our salary range", "type": "Details", "note": null, "rating": "yes"}, {"name": "Empathetic", "type": "Personality Traits", "note": null, "rating": "strong_yes"}, {"name": "Currently based locally", "type": "Details", "note": null, "rating": "mixed"}, {"name": "Communication", "type": "Skills", "note": null, "rating": "no"}], "ratings": {"definitely_not": [], "no": ["Communication"], "mixed": ["Three to five years of experience", "Passionate", "Currently based locally"], "yes": ["Willing to do required travel", "Personable", "Organizational Skills", "Manage competing priorities", "Fits our salary range"], "strong_yes": ["Empathetic"]}, "questions": [{"id": null, "question": "Key Take-Aways", "answer": "test"}, {"id": null, "question": "Private Notes", "answer": ""}]}, "emitted_at": 1664285619011} -{"stream": "users", "data": {"id": 4218085003, "name": "Greenhouse Admin", "first_name": "Greenhouse", "last_name": "Admin", "primary_email_address": "scrubbed_email_vq8-rm4513etm7xxd9d1qq@example.com", "updated_at": "2020-11-18T14:09:08.401Z", "created_at": "2020-11-18T14:09:08.401Z", "disabled": false, "site_admin": true, "emails": ["scrubbed_email_vq8-rm4513etm7xxd9d1qq@example.com"], "employee_id": null, "linked_candidate_ids": []}, "emitted_at": 1664285619554} -{"stream": "users", "data": {"id": 4218086003, "name": "John Lafleur", "first_name": "John", "last_name": "Lafleur", "primary_email_address": "integration-test@airbyte.io", "updated_at": "2022-09-27T13:00:12.685Z", "created_at": "2020-11-18T14:09:08.481Z", "disabled": false, "site_admin": true, "emails": ["integration-test@airbyte.io"], "employee_id": null, "linked_candidate_ids": []}, "emitted_at": 1664285619555} -{"stream": "users", "data": {"id": 4218087003, "name": "emily.brooks+airbyte_integration@greenhouse.io", "first_name": null, "last_name": null, "primary_email_address": "emily.brooks+airbyte_integration@greenhouse.io", "updated_at": "2020-11-18T14:09:08.991Z", "created_at": "2020-11-18T14:09:08.809Z", "disabled": false, "site_admin": true, "emails": ["emily.brooks+airbyte_integration@greenhouse.io"], "employee_id": null, "linked_candidate_ids": []}, "emitted_at": 1664285619555} -{"stream": "users", "data": {"id": 4460715003, "name": "Vadym Ratniuk", "first_name": "Vadym", "last_name": "Ratniuk", "primary_email_address": "vadym.ratniuk@globallogic.com", "updated_at": "2021-09-18T10:09:16.846Z", "created_at": "2021-09-14T14:03:01.050Z", "disabled": false, "site_admin": false, "emails": ["vadym.ratniuk@globallogic.com"], "employee_id": null, "linked_candidate_ids": []}, "emitted_at": 1664285619556} -{"stream": "users", "data": {"id": 4481107003, "name": "Vadym Hevlich", "first_name": "Vadym", "last_name": "Hevlich", "primary_email_address": "vadym.hevlich@zazmic.com", "updated_at": "2021-10-10T17:49:28.058Z", "created_at": "2021-10-10T17:48:41.978Z", "disabled": false, "site_admin": true, "emails": ["vadym.hevlich@zazmic.com"], "employee_id": null, "linked_candidate_ids": []}, "emitted_at": 1664285619556} +{"stream": "users", "data": {"id": 4218085003, "name": "Greenhouse Admin", "first_name": "Greenhouse", "last_name": "Admin", "primary_email_address": "scrubbed_email_vq8-rm4513etm7xxd9d1qq@example.com", "updated_at": "2020-11-18T14:09:08.401Z", "created_at": "2020-11-18T14:09:08.401Z", "disabled": false, "site_admin": true, "emails": ["scrubbed_email_vq8-rm4513etm7xxd9d1qq@example.com"], "employee_id": null, "linked_candidate_ids": [], "offices": [], "departments": []}, "emitted_at": 1664285619554} +{"stream": "users", "data": {"id": 4218086003, "name": "John Lafleur", "first_name": "John", "last_name": "Lafleur", "primary_email_address": "integration-test@airbyte.io", "updated_at": "2022-09-27T13:00:12.685Z", "created_at": "2020-11-18T14:09:08.481Z", "disabled": false, "site_admin": true, "emails": ["integration-test@airbyte.io"], "employee_id": null, "linked_candidate_ids": [], "offices": [], "departments": []}, "emitted_at": 1664285619555} +{"stream": "users", "data": {"id": 4218087003, "name": "emily.brooks+airbyte_integration@greenhouse.io", "first_name": null, "last_name": null, "primary_email_address": "emily.brooks+airbyte_integration@greenhouse.io", "updated_at": "2020-11-18T14:09:08.991Z", "created_at": "2020-11-18T14:09:08.809Z", "disabled": false, "site_admin": true, "emails": ["emily.brooks+airbyte_integration@greenhouse.io"], "employee_id": null, "linked_candidate_ids": [], "offices": [], "departments": []}, "emitted_at": 1664285619555} +{"stream": "users", "data": {"id": 4460715003, "name": "Vadym Ratniuk", "first_name": "Vadym", "last_name": "Ratniuk", "primary_email_address": "vadym.ratniuk@globallogic.com", "updated_at": "2021-09-18T10:09:16.846Z", "created_at": "2021-09-14T14:03:01.050Z", "disabled": false, "site_admin": false, "emails": ["vadym.ratniuk@globallogic.com"], "employee_id": null, "linked_candidate_ids": [], "offices": [], "departments": []}, "emitted_at": 1664285619556} +{"stream": "users", "data": {"id": 4481107003, "name": "Vadym Hevlich", "first_name": "Vadym", "last_name": "Hevlich", "primary_email_address": "vadym.hevlich@zazmic.com", "updated_at": "2021-10-10T17:49:28.058Z", "created_at": "2021-10-10T17:48:41.978Z", "disabled": false, "site_admin": true, "emails": ["vadym.hevlich@zazmic.com"], "employee_id": null, "linked_candidate_ids": [], "offices": [], "departments": []}, "emitted_at": 1664285619556} {"stream": "custom_fields", "data": {"id": 4680898003, "name": "School Name", "active": true, "field_type": "candidate", "priority": 0, "value_type": "single_select", "private": false, "required": false, "require_approval": false, "trigger_new_version": false, "name_key": "school_name", "description": null, "expose_in_job_board_api": false, "api_only": false, "offices": [], "departments": [], "template_token_string": null, "custom_field_options": [{"id": 10845822003, "name": "Abraham Baldwin Agricultural College", "priority": 0, "external_id": null}, {"id": 10845823003, "name": "Academy of Art University", "priority": 1, "external_id": null}, {"id": 10845824003, "name": "Acadia University", "priority": 2, "external_id": null}, {"id": 10845825003, "name": "Adams State University", "priority": 3, "external_id": null}, {"id": 10845826003, "name": "Adelphi University", "priority": 4, "external_id": null}, {"id": 10845827003, "name": "Adrian College", "priority": 5, "external_id": null}, {"id": 10845828003, "name": "Adventist University of Health Sciences", "priority": 6, "external_id": null}, {"id": 10845829003, "name": "Agnes Scott College", "priority": 7, "external_id": null}, {"id": 10845830003, "name": "AIB College of Business", "priority": 8, "external_id": null}, {"id": 10845831003, "name": "Alaska Pacific University", "priority": 9, "external_id": null}, {"id": 10845832003, "name": "Albany College of Pharmacy and Health Sciences", "priority": 10, "external_id": null}, {"id": 10845833003, "name": "Albany State University", "priority": 11, "external_id": null}, {"id": 10845834003, "name": "Albertus Magnus College", "priority": 12, "external_id": null}, {"id": 10845835003, "name": "Albion College", "priority": 13, "external_id": null}, {"id": 10845836003, "name": "Albright College", "priority": 14, "external_id": null}, {"id": 10845837003, "name": "Alderson Broaddus University", "priority": 15, "external_id": null}, {"id": 10845838003, "name": "Alfred University", "priority": 16, "external_id": null}, {"id": 10845839003, "name": "Alice Lloyd College", "priority": 17, "external_id": null}, {"id": 10845840003, "name": "Allegheny College", "priority": 18, "external_id": null}, {"id": 10845841003, "name": "Allen College", "priority": 19, "external_id": null}, {"id": 10845842003, "name": "Allen University", "priority": 20, "external_id": null}, {"id": 10845843003, "name": "Alliant International University", "priority": 21, "external_id": null}, {"id": 10845844003, "name": "Alma College", "priority": 22, "external_id": null}, {"id": 10845845003, "name": "Alvernia University", "priority": 23, "external_id": null}, {"id": 10845846003, "name": "Alverno College", "priority": 24, "external_id": null}, {"id": 10845847003, "name": "Amberton University", "priority": 25, "external_id": null}, {"id": 10845848003, "name": "American Academy of Art", "priority": 26, "external_id": null}, {"id": 10845849003, "name": "American Indian College of the Assemblies of God", "priority": 27, "external_id": null}, {"id": 10845850003, "name": "American InterContinental University", "priority": 28, "external_id": null}, {"id": 10845851003, "name": "American International College", "priority": 29, "external_id": null}, {"id": 10845852003, "name": "American Jewish University", "priority": 30, "external_id": null}, {"id": 10845853003, "name": "American Public University System", "priority": 31, "external_id": null}, {"id": 10845854003, "name": "American University", "priority": 32, "external_id": null}, {"id": 10845855003, "name": "American University in Bulgaria", "priority": 33, "external_id": null}, {"id": 10845856003, "name": "American University in Cairo", "priority": 34, "external_id": null}, {"id": 10845857003, "name": "American University of Beirut", "priority": 35, "external_id": null}, {"id": 10845858003, "name": "American University of Paris", "priority": 36, "external_id": null}, {"id": 10845859003, "name": "American University of Puerto Rico", "priority": 37, "external_id": null}, {"id": 10845860003, "name": "Amherst College", "priority": 38, "external_id": null}, {"id": 10845861003, "name": "Amridge University", "priority": 39, "external_id": null}, {"id": 10845862003, "name": "Anderson University", "priority": 40, "external_id": null}, {"id": 10845863003, "name": "Andrews University", "priority": 41, "external_id": null}, {"id": 10845864003, "name": "Angelo State University", "priority": 42, "external_id": null}, {"id": 10845865003, "name": "Anna Maria College", "priority": 43, "external_id": null}, {"id": 10845866003, "name": "Antioch University", "priority": 44, "external_id": null}, {"id": 10845867003, "name": "Appalachian Bible College", "priority": 45, "external_id": null}, {"id": 10845868003, "name": "Aquinas College", "priority": 46, "external_id": null}, {"id": 10845869003, "name": "Arcadia University", "priority": 47, "external_id": null}, {"id": 10845870003, "name": "Argosy University", "priority": 48, "external_id": null}, {"id": 10845871003, "name": "Arizona Christian University", "priority": 49, "external_id": null}, {"id": 10845872003, "name": "Arizona State University - West", "priority": 50, "external_id": null}, {"id": 10845873003, "name": "Arkansas Baptist College", "priority": 51, "external_id": null}, {"id": 10845874003, "name": "Arkansas Tech University", "priority": 52, "external_id": null}, {"id": 10845875003, "name": "Armstrong Atlantic State University", "priority": 53, "external_id": null}, {"id": 10845876003, "name": "Art Academy of Cincinnati", "priority": 54, "external_id": null}, {"id": 10845877003, "name": "Art Center College of Design", "priority": 55, "external_id": null}, {"id": 10845878003, "name": "Art Institute of Atlanta", "priority": 56, "external_id": null}, {"id": 10845879003, "name": "Art Institute of Colorado", "priority": 57, "external_id": null}, {"id": 10845880003, "name": "Art Institute of Houston", "priority": 58, "external_id": null}, {"id": 10845881003, "name": "Art Institute of Pittsburgh", "priority": 59, "external_id": null}, {"id": 10845882003, "name": "Art Institute of Portland", "priority": 60, "external_id": null}, {"id": 10845883003, "name": "Art Institute of Seattle", "priority": 61, "external_id": null}, {"id": 10845884003, "name": "Asbury University", "priority": 62, "external_id": null}, {"id": 10845885003, "name": "Ashford University", "priority": 63, "external_id": null}, {"id": 10845886003, "name": "Ashland University", "priority": 64, "external_id": null}, {"id": 10845887003, "name": "Assumption College", "priority": 65, "external_id": null}, {"id": 10845888003, "name": "Athens State University", "priority": 66, "external_id": null}, {"id": 10845889003, "name": "Auburn University - Montgomery", "priority": 67, "external_id": null}, {"id": 10845890003, "name": "Augsburg College", "priority": 68, "external_id": null}, {"id": 10845891003, "name": "Augustana College", "priority": 69, "external_id": null}, {"id": 10845892003, "name": "Aurora University", "priority": 70, "external_id": null}, {"id": 10845893003, "name": "Austin College", "priority": 71, "external_id": null}, {"id": 10845894003, "name": "Alcorn State University", "priority": 72, "external_id": null}, {"id": 10845895003, "name": "Ave Maria University", "priority": 73, "external_id": null}, {"id": 10845896003, "name": "Averett University", "priority": 74, "external_id": null}, {"id": 10845897003, "name": "Avila University", "priority": 75, "external_id": null}, {"id": 10845898003, "name": "Azusa Pacific University", "priority": 76, "external_id": null}, {"id": 10845899003, "name": "Babson College", "priority": 77, "external_id": null}, {"id": 10845900003, "name": "Bacone College", "priority": 78, "external_id": null}, {"id": 10845901003, "name": "Baker College of Flint", "priority": 79, "external_id": null}, {"id": 10845902003, "name": "Baker University", "priority": 80, "external_id": null}, {"id": 10845903003, "name": "Baldwin Wallace University", "priority": 81, "external_id": null}, {"id": 10845904003, "name": "Christian Brothers University", "priority": 82, "external_id": null}, {"id": 10845905003, "name": "Abilene Christian University", "priority": 83, "external_id": null}, {"id": 10845906003, "name": "Arizona State University", "priority": 84, "external_id": null}, {"id": 10845907003, "name": "Auburn University", "priority": 85, "external_id": null}, {"id": 10845908003, "name": "Alabama A&M University", "priority": 86, "external_id": null}, {"id": 10845909003, "name": "Alabama State University", "priority": 87, "external_id": null}, {"id": 10845910003, "name": "Arkansas State University", "priority": 88, "external_id": null}, {"id": 10845911003, "name": "Baptist Bible College", "priority": 89, "external_id": null}, {"id": 10845912003, "name": "Baptist Bible College and Seminary", "priority": 90, "external_id": null}, {"id": 10845913003, "name": "Baptist College of Florida", "priority": 91, "external_id": null}, {"id": 10845914003, "name": "Baptist Memorial College of Health Sciences", "priority": 92, "external_id": null}, {"id": 10845915003, "name": "Baptist Missionary Association Theological Seminary", "priority": 93, "external_id": null}, {"id": 10845916003, "name": "Bard College", "priority": 94, "external_id": null}, {"id": 10845917003, "name": "Bard College at Simon's Rock", "priority": 95, "external_id": null}, {"id": 10845918003, "name": "Barnard College", "priority": 96, "external_id": null}, {"id": 10845919003, "name": "Barry University", "priority": 97, "external_id": null}, {"id": 10845920003, "name": "Barton College", "priority": 98, "external_id": null}, {"id": 10845921003, "name": "Bastyr University", "priority": 99, "external_id": null}, {"id": 10845922003, "name": "Bates College", "priority": 100, "external_id": null}, {"id": 10845923003, "name": "Bauder College", "priority": 101, "external_id": null}, {"id": 10845924003, "name": "Bay Path College", "priority": 102, "external_id": null}, {"id": 10845925003, "name": "Bay State College", "priority": 103, "external_id": null}, {"id": 10845926003, "name": "Bayamon Central University", "priority": 104, "external_id": null}, {"id": 10845927003, "name": "Beacon College", "priority": 105, "external_id": null}, {"id": 10845928003, "name": "Becker College", "priority": 106, "external_id": null}, {"id": 10845929003, "name": "Belhaven University", "priority": 107, "external_id": null}, {"id": 10845930003, "name": "Bellarmine University", "priority": 108, "external_id": null}, {"id": 10845931003, "name": "Bellevue College", "priority": 109, "external_id": null}, {"id": 10845932003, "name": "Bellevue University", "priority": 110, "external_id": null}, {"id": 10845933003, "name": "Bellin College", "priority": 111, "external_id": null}, {"id": 10845934003, "name": "Belmont Abbey College", "priority": 112, "external_id": null}, {"id": 10845935003, "name": "Belmont University", "priority": 113, "external_id": null}, {"id": 10845936003, "name": "Beloit College", "priority": 114, "external_id": null}, {"id": 10845937003, "name": "Bemidji State University", "priority": 115, "external_id": null}, {"id": 10845938003, "name": "Benedict College", "priority": 116, "external_id": null}, {"id": 10845939003, "name": "Benedictine College", "priority": 117, "external_id": null}, {"id": 10845940003, "name": "Benedictine University", "priority": 118, "external_id": null}, {"id": 10845941003, "name": "Benjamin Franklin Institute of Technology", "priority": 119, "external_id": null}, {"id": 10845942003, "name": "Bennett College", "priority": 120, "external_id": null}, {"id": 10845943003, "name": "Bennington College", "priority": 121, "external_id": null}, {"id": 10845944003, "name": "Bentley University", "priority": 122, "external_id": null}, {"id": 10845945003, "name": "Berea College", "priority": 123, "external_id": null}, {"id": 10845946003, "name": "Berkeley College", "priority": 124, "external_id": null}, {"id": 10845947003, "name": "Berklee College of Music", "priority": 125, "external_id": null}, {"id": 10845948003, "name": "Berry College", "priority": 126, "external_id": null}, {"id": 10845949003, "name": "Bethany College", "priority": 127, "external_id": null}, {"id": 10845950003, "name": "Bethany Lutheran College", "priority": 128, "external_id": null}, {"id": 10845951003, "name": "Bethel College", "priority": 129, "external_id": null}, {"id": 10845952003, "name": "Bethel University", "priority": 130, "external_id": null}, {"id": 10845953003, "name": "BI Norwegian Business School", "priority": 131, "external_id": null}, {"id": 10845954003, "name": "Binghamton University - SUNY", "priority": 132, "external_id": null}, {"id": 10845955003, "name": "Biola University", "priority": 133, "external_id": null}, {"id": 10845956003, "name": "Birmingham-Southern College", "priority": 134, "external_id": null}, {"id": 10845957003, "name": "Bismarck State College", "priority": 135, "external_id": null}, {"id": 10845958003, "name": "Black Hills State University", "priority": 136, "external_id": null}, {"id": 10845959003, "name": "Blackburn College", "priority": 137, "external_id": null}, {"id": 10845960003, "name": "Blessing-Rieman College of Nursing", "priority": 138, "external_id": null}, {"id": 10845961003, "name": "Bloomfield College", "priority": 139, "external_id": null}, {"id": 10845962003, "name": "Bloomsburg University of Pennsylvania", "priority": 140, "external_id": null}, {"id": 10845963003, "name": "Blue Mountain College", "priority": 141, "external_id": null}, {"id": 10845964003, "name": "Bluefield College", "priority": 142, "external_id": null}, {"id": 10845965003, "name": "Bluefield State College", "priority": 143, "external_id": null}, {"id": 10845966003, "name": "Bluffton University", "priority": 144, "external_id": null}, {"id": 10845967003, "name": "Boricua College", "priority": 145, "external_id": null}, {"id": 10845968003, "name": "Boston Architectural College", "priority": 146, "external_id": null}, {"id": 10845969003, "name": "Boston Conservatory", "priority": 147, "external_id": null}, {"id": 10845970003, "name": "Boston University", "priority": 148, "external_id": null}, {"id": 10845971003, "name": "Bowdoin College", "priority": 149, "external_id": null}, {"id": 10845972003, "name": "Bowie State University", "priority": 150, "external_id": null}, {"id": 10845973003, "name": "Bradley University", "priority": 151, "external_id": null}, {"id": 10845974003, "name": "Brandeis University", "priority": 152, "external_id": null}, {"id": 10845975003, "name": "Brandman University", "priority": 153, "external_id": null}, {"id": 10845976003, "name": "Brazosport College", "priority": 154, "external_id": null}, {"id": 10845977003, "name": "Brenau University", "priority": 155, "external_id": null}, {"id": 10845978003, "name": "Brescia University", "priority": 156, "external_id": null}, {"id": 10845979003, "name": "Brevard College", "priority": 157, "external_id": null}, {"id": 10845980003, "name": "Brewton-Parker College", "priority": 158, "external_id": null}, {"id": 10845981003, "name": "Briar Cliff University", "priority": 159, "external_id": null}, {"id": 10845982003, "name": "Briarcliffe College", "priority": 160, "external_id": null}, {"id": 10845983003, "name": "Bridgewater College", "priority": 161, "external_id": null}, {"id": 10845984003, "name": "Bridgewater State University", "priority": 162, "external_id": null}, {"id": 10845985003, "name": "Brigham Young University - Hawaii", "priority": 163, "external_id": null}, {"id": 10845986003, "name": "Brigham Young University - Idaho", "priority": 164, "external_id": null}, {"id": 10845987003, "name": "Brock University", "priority": 165, "external_id": null}, {"id": 10845988003, "name": "Bryan College", "priority": 166, "external_id": null}, {"id": 10845989003, "name": "Bryn Athyn College of the New Church", "priority": 167, "external_id": null}, {"id": 10845990003, "name": "Bryn Mawr College", "priority": 168, "external_id": null}, {"id": 10845991003, "name": "Boston College", "priority": 169, "external_id": null}, {"id": 10845992003, "name": "Buena Vista University", "priority": 170, "external_id": null}, {"id": 10845993003, "name": "Burlington College", "priority": 171, "external_id": null}, {"id": 10845994003, "name": "Bowling Green State University", "priority": 172, "external_id": null}, {"id": 10845995003, "name": "Brown University", "priority": 173, "external_id": null}, {"id": 10845996003, "name": "Appalachian State University", "priority": 174, "external_id": null}, {"id": 10845997003, "name": "Brigham Young University - Provo", "priority": 175, "external_id": null}, {"id": 10845998003, "name": "Boise State University", "priority": 176, "external_id": null}, {"id": 10845999003, "name": "Bethune-Cookman University", "priority": 177, "external_id": null}, {"id": 10846000003, "name": "Bryant University", "priority": 178, "external_id": null}, {"id": 10846001003, "name": "Cabarrus College of Health Sciences", "priority": 179, "external_id": null}, {"id": 10846002003, "name": "Cabrini College", "priority": 180, "external_id": null}, {"id": 10846003003, "name": "Cairn University", "priority": 181, "external_id": null}, {"id": 10846004003, "name": "Caldwell College", "priority": 182, "external_id": null}, {"id": 10846005003, "name": "California Baptist University", "priority": 183, "external_id": null}, {"id": 10846006003, "name": "California College of the Arts", "priority": 184, "external_id": null}, {"id": 10846007003, "name": "California Institute of Integral Studies", "priority": 185, "external_id": null}, {"id": 10846008003, "name": "California Institute of Technology", "priority": 186, "external_id": null}, {"id": 10846009003, "name": "California Institute of the Arts", "priority": 187, "external_id": null}, {"id": 10846010003, "name": "California Lutheran University", "priority": 188, "external_id": null}, {"id": 10846011003, "name": "California Maritime Academy", "priority": 189, "external_id": null}, {"id": 10846012003, "name": "California State Polytechnic University - Pomona", "priority": 190, "external_id": null}, {"id": 10846013003, "name": "California State University - Bakersfield", "priority": 191, "external_id": null}, {"id": 10846014003, "name": "California State University - Channel Islands", "priority": 192, "external_id": null}, {"id": 10846015003, "name": "California State University - Chico", "priority": 193, "external_id": null}, {"id": 10846016003, "name": "California State University - Dominguez Hills", "priority": 194, "external_id": null}, {"id": 10846017003, "name": "California State University - East Bay", "priority": 195, "external_id": null}, {"id": 10846018003, "name": "California State University - Fullerton", "priority": 196, "external_id": null}, {"id": 10846019003, "name": "California State University - Los Angeles", "priority": 197, "external_id": null}, {"id": 10846020003, "name": "California State University - Monterey Bay", "priority": 198, "external_id": null}, {"id": 10846021003, "name": "California State University - Northridge", "priority": 199, "external_id": null}, {"id": 10846022003, "name": "California State University - San Bernardino", "priority": 200, "external_id": null}, {"id": 10846023003, "name": "California State University - San Marcos", "priority": 201, "external_id": null}, {"id": 10846024003, "name": "California State University - Stanislaus", "priority": 202, "external_id": null}, {"id": 10846025003, "name": "California University of Pennsylvania", "priority": 203, "external_id": null}, {"id": 10846026003, "name": "Calumet College of St. Joseph", "priority": 204, "external_id": null}, {"id": 10846027003, "name": "Calvary Bible College and Theological Seminary", "priority": 205, "external_id": null}, {"id": 10846028003, "name": "Calvin College", "priority": 206, "external_id": null}, {"id": 10846029003, "name": "Cambridge College", "priority": 207, "external_id": null}, {"id": 10846030003, "name": "Cameron University", "priority": 208, "external_id": null}, {"id": 10846031003, "name": "Campbellsville University", "priority": 209, "external_id": null}, {"id": 10846032003, "name": "Canisius College", "priority": 210, "external_id": null}, {"id": 10846033003, "name": "Capella University", "priority": 211, "external_id": null}, {"id": 10846034003, "name": "Capital University", "priority": 212, "external_id": null}, {"id": 10846035003, "name": "Capitol College", "priority": 213, "external_id": null}, {"id": 10846036003, "name": "Cardinal Stritch University", "priority": 214, "external_id": null}, {"id": 10846037003, "name": "Caribbean University", "priority": 215, "external_id": null}, {"id": 10846038003, "name": "Carleton College", "priority": 216, "external_id": null}, {"id": 10846039003, "name": "Carleton University", "priority": 217, "external_id": null}, {"id": 10846040003, "name": "Carlos Albizu University", "priority": 218, "external_id": null}, {"id": 10846041003, "name": "Carlow University", "priority": 219, "external_id": null}, {"id": 10846042003, "name": "Carnegie Mellon University", "priority": 220, "external_id": null}, {"id": 10846043003, "name": "Carroll College", "priority": 221, "external_id": null}, {"id": 10846044003, "name": "Carroll University", "priority": 222, "external_id": null}, {"id": 10846045003, "name": "Carson-Newman University", "priority": 223, "external_id": null}, {"id": 10846046003, "name": "Carthage College", "priority": 224, "external_id": null}, {"id": 10846047003, "name": "Case Western Reserve University", "priority": 225, "external_id": null}, {"id": 10846048003, "name": "Castleton State College", "priority": 226, "external_id": null}, {"id": 10846049003, "name": "Catawba College", "priority": 227, "external_id": null}, {"id": 10846050003, "name": "Cazenovia College", "priority": 228, "external_id": null}, {"id": 10846051003, "name": "Cedar Crest College", "priority": 229, "external_id": null}, {"id": 10846052003, "name": "Cedarville University", "priority": 230, "external_id": null}, {"id": 10846053003, "name": "Centenary College", "priority": 231, "external_id": null}, {"id": 10846054003, "name": "Centenary College of Louisiana", "priority": 232, "external_id": null}, {"id": 10846055003, "name": "Central Baptist College", "priority": 233, "external_id": null}, {"id": 10846056003, "name": "Central Bible College", "priority": 234, "external_id": null}, {"id": 10846057003, "name": "Central Christian College", "priority": 235, "external_id": null}, {"id": 10846058003, "name": "Central College", "priority": 236, "external_id": null}, {"id": 10846059003, "name": "Central Methodist University", "priority": 237, "external_id": null}, {"id": 10846060003, "name": "Central Penn College", "priority": 238, "external_id": null}, {"id": 10846061003, "name": "Central State University", "priority": 239, "external_id": null}, {"id": 10846062003, "name": "Central Washington University", "priority": 240, "external_id": null}, {"id": 10846063003, "name": "Centre College", "priority": 241, "external_id": null}, {"id": 10846064003, "name": "Chadron State College", "priority": 242, "external_id": null}, {"id": 10846065003, "name": "Chamberlain College of Nursing", "priority": 243, "external_id": null}, {"id": 10846066003, "name": "Chaminade University of Honolulu", "priority": 244, "external_id": null}, {"id": 10846067003, "name": "Champlain College", "priority": 245, "external_id": null}, {"id": 10846068003, "name": "Chancellor University", "priority": 246, "external_id": null}, {"id": 10846069003, "name": "Chapman University", "priority": 247, "external_id": null}, {"id": 10846070003, "name": "Charles R. Drew University of Medicine and Science", "priority": 248, "external_id": null}, {"id": 10846071003, "name": "Charter Oak State College", "priority": 249, "external_id": null}, {"id": 10846072003, "name": "Chatham University", "priority": 250, "external_id": null}, {"id": 10846073003, "name": "Chestnut Hill College", "priority": 251, "external_id": null}, {"id": 10846074003, "name": "Cheyney University of Pennsylvania", "priority": 252, "external_id": null}, {"id": 10846075003, "name": "Chicago State University", "priority": 253, "external_id": null}, {"id": 10846076003, "name": "Chipola College", "priority": 254, "external_id": null}, {"id": 10846077003, "name": "Chowan University", "priority": 255, "external_id": null}, {"id": 10846078003, "name": "Christendom College", "priority": 256, "external_id": null}, {"id": 10846079003, "name": "Baylor University", "priority": 257, "external_id": null}, {"id": 10846080003, "name": "Central Connecticut State University", "priority": 258, "external_id": null}, {"id": 10846081003, "name": "Central Michigan University", "priority": 259, "external_id": null}, {"id": 10846082003, "name": "Charleston Southern University", "priority": 260, "external_id": null}, {"id": 10846083003, "name": "California State University - Sacramento", "priority": 261, "external_id": null}, {"id": 10846084003, "name": "California State University - Fresno", "priority": 262, "external_id": null}, {"id": 10846085003, "name": "Campbell University", "priority": 263, "external_id": null}, {"id": 10846086003, "name": "Christopher Newport University", "priority": 264, "external_id": null}, {"id": 10846087003, "name": "Cincinnati Christian University", "priority": 265, "external_id": null}, {"id": 10846088003, "name": "Cincinnati College of Mortuary Science", "priority": 266, "external_id": null}, {"id": 10846089003, "name": "City University of Seattle", "priority": 267, "external_id": null}, {"id": 10846090003, "name": "Claflin University", "priority": 268, "external_id": null}, {"id": 10846091003, "name": "Claremont McKenna College", "priority": 269, "external_id": null}, {"id": 10846092003, "name": "Clarion University of Pennsylvania", "priority": 270, "external_id": null}, {"id": 10846093003, "name": "Clark Atlanta University", "priority": 271, "external_id": null}, {"id": 10846094003, "name": "Clark University", "priority": 272, "external_id": null}, {"id": 10846095003, "name": "Clarke University", "priority": 273, "external_id": null}, {"id": 10846096003, "name": "Clarkson College", "priority": 274, "external_id": null}, {"id": 10846097003, "name": "Clarkson University", "priority": 275, "external_id": null}, {"id": 10846098003, "name": "Clayton State University", "priority": 276, "external_id": null}, {"id": 10846099003, "name": "Clear Creek Baptist Bible College", "priority": 277, "external_id": null}, {"id": 10846100003, "name": "Clearwater Christian College", "priority": 278, "external_id": null}, {"id": 10846101003, "name": "Cleary University", "priority": 279, "external_id": null}, {"id": 10846102003, "name": "College of William and Mary", "priority": 280, "external_id": null}, {"id": 10846103003, "name": "Cleveland Chiropractic College", "priority": 281, "external_id": null}, {"id": 10846104003, "name": "Cleveland Institute of Art", "priority": 282, "external_id": null}, {"id": 10846105003, "name": "Cleveland Institute of Music", "priority": 283, "external_id": null}, {"id": 10846106003, "name": "Cleveland State University", "priority": 284, "external_id": null}, {"id": 10846107003, "name": "Coe College", "priority": 285, "external_id": null}, {"id": 10846108003, "name": "Cogswell Polytechnical College", "priority": 286, "external_id": null}, {"id": 10846109003, "name": "Coker College", "priority": 287, "external_id": null}, {"id": 10846110003, "name": "Colby College", "priority": 288, "external_id": null}, {"id": 10846111003, "name": "Colby-Sawyer College", "priority": 289, "external_id": null}, {"id": 10846112003, "name": "College at Brockport - SUNY", "priority": 290, "external_id": null}, {"id": 10846113003, "name": "College for Creative Studies", "priority": 291, "external_id": null}, {"id": 10846114003, "name": "College of Charleston", "priority": 292, "external_id": null}, {"id": 10846115003, "name": "College of Idaho", "priority": 293, "external_id": null}, {"id": 10846116003, "name": "College of Mount St. Joseph", "priority": 294, "external_id": null}, {"id": 10846117003, "name": "College of Mount St. Vincent", "priority": 295, "external_id": null}, {"id": 10846118003, "name": "College of New Jersey", "priority": 296, "external_id": null}, {"id": 10846119003, "name": "College of New Rochelle", "priority": 297, "external_id": null}, {"id": 10846120003, "name": "College of Our Lady of the Elms", "priority": 298, "external_id": null}, {"id": 10846121003, "name": "College of Saints John Fisher & Thomas More", "priority": 299, "external_id": null}, {"id": 10846122003, "name": "College of Southern Nevada", "priority": 300, "external_id": null}, {"id": 10846123003, "name": "College of St. Benedict", "priority": 301, "external_id": null}, {"id": 10846124003, "name": "College of St. Elizabeth", "priority": 302, "external_id": null}, {"id": 10846125003, "name": "College of St. Joseph", "priority": 303, "external_id": null}, {"id": 10846126003, "name": "College of St. Mary", "priority": 304, "external_id": null}, {"id": 10846127003, "name": "College of St. Rose", "priority": 305, "external_id": null}, {"id": 10846128003, "name": "College of St. Scholastica", "priority": 306, "external_id": null}, {"id": 10846129003, "name": "College of the Atlantic", "priority": 307, "external_id": null}, {"id": 10846130003, "name": "College of the Holy Cross", "priority": 308, "external_id": null}, {"id": 10846131003, "name": "College of the Ozarks", "priority": 309, "external_id": null}, {"id": 10846132003, "name": "College of Wooster", "priority": 310, "external_id": null}, {"id": 10846133003, "name": "Colorado Christian University", "priority": 311, "external_id": null}, {"id": 10846134003, "name": "Colorado College", "priority": 312, "external_id": null}, {"id": 10846135003, "name": "Colorado Mesa University", "priority": 313, "external_id": null}, {"id": 10846136003, "name": "Colorado School of Mines", "priority": 314, "external_id": null}, {"id": 10846137003, "name": "Colorado State University - Pueblo", "priority": 315, "external_id": null}, {"id": 10846138003, "name": "Colorado Technical University", "priority": 316, "external_id": null}, {"id": 10846139003, "name": "Columbia College", "priority": 317, "external_id": null}, {"id": 10846140003, "name": "Columbia College Chicago", "priority": 318, "external_id": null}, {"id": 10846141003, "name": "Columbia College of Nursing", "priority": 319, "external_id": null}, {"id": 10846142003, "name": "Columbia International University", "priority": 320, "external_id": null}, {"id": 10846143003, "name": "Columbus College of Art and Design", "priority": 321, "external_id": null}, {"id": 10846144003, "name": "Columbus State University", "priority": 322, "external_id": null}, {"id": 10846145003, "name": "Conception Seminary College", "priority": 323, "external_id": null}, {"id": 10846146003, "name": "Concord University", "priority": 324, "external_id": null}, {"id": 10846147003, "name": "Concordia College", "priority": 325, "external_id": null}, {"id": 10846148003, "name": "Concordia College - Moorhead", "priority": 326, "external_id": null}, {"id": 10846149003, "name": "Concordia University", "priority": 327, "external_id": null}, {"id": 10846150003, "name": "Concordia University Chicago", "priority": 328, "external_id": null}, {"id": 10846151003, "name": "Concordia University Texas", "priority": 329, "external_id": null}, {"id": 10846152003, "name": "Concordia University Wisconsin", "priority": 330, "external_id": null}, {"id": 10846153003, "name": "Concordia University - St. Paul", "priority": 331, "external_id": null}, {"id": 10846154003, "name": "Connecticut College", "priority": 332, "external_id": null}, {"id": 10846155003, "name": "Converse College", "priority": 333, "external_id": null}, {"id": 10846156003, "name": "Cooper Union", "priority": 334, "external_id": null}, {"id": 10846157003, "name": "Coppin State University", "priority": 335, "external_id": null}, {"id": 10846158003, "name": "Corban University", "priority": 336, "external_id": null}, {"id": 10846159003, "name": "Corcoran College of Art and Design", "priority": 337, "external_id": null}, {"id": 10846160003, "name": "Cornell College", "priority": 338, "external_id": null}, {"id": 10846161003, "name": "Cornerstone University", "priority": 339, "external_id": null}, {"id": 10846162003, "name": "Cornish College of the Arts", "priority": 340, "external_id": null}, {"id": 10846163003, "name": "Covenant College", "priority": 341, "external_id": null}, {"id": 10846164003, "name": "Cox College", "priority": 342, "external_id": null}, {"id": 10846165003, "name": "Creighton University", "priority": 343, "external_id": null}, {"id": 10846166003, "name": "Criswell College", "priority": 344, "external_id": null}, {"id": 10846167003, "name": "Crown College", "priority": 345, "external_id": null}, {"id": 10846168003, "name": "Culinary Institute of America", "priority": 346, "external_id": null}, {"id": 10846169003, "name": "Culver-Stockton College", "priority": 347, "external_id": null}, {"id": 10846170003, "name": "Cumberland University", "priority": 348, "external_id": null}, {"id": 10846171003, "name": "Columbia University", "priority": 349, "external_id": null}, {"id": 10846172003, "name": "Cornell University", "priority": 350, "external_id": null}, {"id": 10846173003, "name": "Colorado State University", "priority": 351, "external_id": null}, {"id": 10846174003, "name": "University of Virginia", "priority": 352, "external_id": null}, {"id": 10846175003, "name": "Colgate University", "priority": 353, "external_id": null}, {"id": 10846176003, "name": "CUNY - Baruch College", "priority": 354, "external_id": null}, {"id": 10846177003, "name": "CUNY - Brooklyn College", "priority": 355, "external_id": null}, {"id": 10846178003, "name": "CUNY - City College", "priority": 356, "external_id": null}, {"id": 10846179003, "name": "CUNY - College of Staten Island", "priority": 357, "external_id": null}, {"id": 10846180003, "name": "CUNY - Hunter College", "priority": 358, "external_id": null}, {"id": 10846181003, "name": "CUNY - John Jay College of Criminal Justice", "priority": 359, "external_id": null}, {"id": 10846182003, "name": "CUNY - Lehman College", "priority": 360, "external_id": null}, {"id": 10846183003, "name": "CUNY - Medgar Evers College", "priority": 361, "external_id": null}, {"id": 10846184003, "name": "CUNY - New York City College of Technology", "priority": 362, "external_id": null}, {"id": 10846185003, "name": "CUNY - Queens College", "priority": 363, "external_id": null}, {"id": 10846186003, "name": "CUNY - York College", "priority": 364, "external_id": null}, {"id": 10846187003, "name": "Curry College", "priority": 365, "external_id": null}, {"id": 10846188003, "name": "Curtis Institute of Music", "priority": 366, "external_id": null}, {"id": 10846189003, "name": "D'Youville College", "priority": 367, "external_id": null}, {"id": 10846190003, "name": "Daemen College", "priority": 368, "external_id": null}, {"id": 10846191003, "name": "Dakota State University", "priority": 369, "external_id": null}, {"id": 10846192003, "name": "Dakota Wesleyan University", "priority": 370, "external_id": null}, {"id": 10846193003, "name": "Dalhousie University", "priority": 371, "external_id": null}, {"id": 10846194003, "name": "Dallas Baptist University", "priority": 372, "external_id": null}, {"id": 10846195003, "name": "Dallas Christian College", "priority": 373, "external_id": null}, {"id": 10846196003, "name": "Dalton State College", "priority": 374, "external_id": null}, {"id": 10846197003, "name": "Daniel Webster College", "priority": 375, "external_id": null}, {"id": 10846198003, "name": "Davenport University", "priority": 376, "external_id": null}, {"id": 10846199003, "name": "Davis and Elkins College", "priority": 377, "external_id": null}, {"id": 10846200003, "name": "Davis College", "priority": 378, "external_id": null}, {"id": 10846201003, "name": "Daytona State College", "priority": 379, "external_id": null}, {"id": 10846202003, "name": "Dean College", "priority": 380, "external_id": null}, {"id": 10846203003, "name": "Defiance College", "priority": 381, "external_id": null}, {"id": 10846204003, "name": "Delaware Valley College", "priority": 382, "external_id": null}, {"id": 10846205003, "name": "Delta State University", "priority": 383, "external_id": null}, {"id": 10846206003, "name": "Denison University", "priority": 384, "external_id": null}, {"id": 10846207003, "name": "DePaul University", "priority": 385, "external_id": null}, {"id": 10846208003, "name": "DePauw University", "priority": 386, "external_id": null}, {"id": 10846209003, "name": "DEREE - The American College of Greece", "priority": 387, "external_id": null}, {"id": 10846210003, "name": "DeSales University", "priority": 388, "external_id": null}, {"id": 10846211003, "name": "DeVry University", "priority": 389, "external_id": null}, {"id": 10846212003, "name": "Dickinson College", "priority": 390, "external_id": null}, {"id": 10846213003, "name": "Dickinson State University", "priority": 391, "external_id": null}, {"id": 10846214003, "name": "Dillard University", "priority": 392, "external_id": null}, {"id": 10846215003, "name": "Divine Word College", "priority": 393, "external_id": null}, {"id": 10846216003, "name": "Dixie State College of Utah", "priority": 394, "external_id": null}, {"id": 10846217003, "name": "Doane College", "priority": 395, "external_id": null}, {"id": 10846218003, "name": "Dominican College", "priority": 396, "external_id": null}, {"id": 10846219003, "name": "Dominican University", "priority": 397, "external_id": null}, {"id": 10846220003, "name": "Dominican University of California", "priority": 398, "external_id": null}, {"id": 10846221003, "name": "Donnelly College", "priority": 399, "external_id": null}, {"id": 10846222003, "name": "Dordt College", "priority": 400, "external_id": null}, {"id": 10846223003, "name": "Dowling College", "priority": 401, "external_id": null}, {"id": 10846224003, "name": "Drew University", "priority": 402, "external_id": null}, {"id": 10846225003, "name": "Drexel University", "priority": 403, "external_id": null}, {"id": 10846226003, "name": "Drury University", "priority": 404, "external_id": null}, {"id": 10846227003, "name": "Dunwoody College of Technology", "priority": 405, "external_id": null}, {"id": 10846228003, "name": "Earlham College", "priority": 406, "external_id": null}, {"id": 10846229003, "name": "Drake University", "priority": 407, "external_id": null}, {"id": 10846230003, "name": "East Central University", "priority": 408, "external_id": null}, {"id": 10846231003, "name": "East Stroudsburg University of Pennsylvania", "priority": 409, "external_id": null}, {"id": 10846232003, "name": "East Tennessee State University", "priority": 410, "external_id": null}, {"id": 10846233003, "name": "East Texas Baptist University", "priority": 411, "external_id": null}, {"id": 10846234003, "name": "East-West University", "priority": 412, "external_id": null}, {"id": 10846235003, "name": "Eastern Connecticut State University", "priority": 413, "external_id": null}, {"id": 10846236003, "name": "Eastern Mennonite University", "priority": 414, "external_id": null}, {"id": 10846237003, "name": "Eastern Nazarene College", "priority": 415, "external_id": null}, {"id": 10846238003, "name": "Eastern New Mexico University", "priority": 416, "external_id": null}, {"id": 10846239003, "name": "Eastern Oregon University", "priority": 417, "external_id": null}, {"id": 10846240003, "name": "Eastern University", "priority": 418, "external_id": null}, {"id": 10846241003, "name": "Eckerd College", "priority": 419, "external_id": null}, {"id": 10846242003, "name": "ECPI University", "priority": 420, "external_id": null}, {"id": 10846243003, "name": "Edgewood College", "priority": 421, "external_id": null}, {"id": 10846244003, "name": "Edinboro University of Pennsylvania", "priority": 422, "external_id": null}, {"id": 10846245003, "name": "Edison State College", "priority": 423, "external_id": null}, {"id": 10846246003, "name": "Edward Waters College", "priority": 424, "external_id": null}, {"id": 10846247003, "name": "Elizabeth City State University", "priority": 425, "external_id": null}, {"id": 10846248003, "name": "Elizabethtown College", "priority": 426, "external_id": null}, {"id": 10846249003, "name": "Elmhurst College", "priority": 427, "external_id": null}, {"id": 10846250003, "name": "Elmira College", "priority": 428, "external_id": null}, {"id": 10846251003, "name": "Embry-Riddle Aeronautical University", "priority": 429, "external_id": null}, {"id": 10846252003, "name": "Embry-Riddle Aeronautical University - Prescott", "priority": 430, "external_id": null}, {"id": 10846253003, "name": "Emerson College", "priority": 431, "external_id": null}, {"id": 10846254003, "name": "Duquesne University", "priority": 432, "external_id": null}, {"id": 10846255003, "name": "Eastern Washington University", "priority": 433, "external_id": null}, {"id": 10846256003, "name": "Eastern Illinois University", "priority": 434, "external_id": null}, {"id": 10846257003, "name": "Eastern Kentucky University", "priority": 435, "external_id": null}, {"id": 10846258003, "name": "Eastern Michigan University", "priority": 436, "external_id": null}, {"id": 10846259003, "name": "Elon University", "priority": 437, "external_id": null}, {"id": 10846260003, "name": "Delaware State University", "priority": 438, "external_id": null}, {"id": 10846261003, "name": "Duke University", "priority": 439, "external_id": null}, {"id": 10846262003, "name": "California Polytechnic State University - San Luis Obispo", "priority": 440, "external_id": null}, {"id": 10846263003, "name": "Emmanuel College", "priority": 441, "external_id": null}, {"id": 10846264003, "name": "Emmaus Bible College", "priority": 442, "external_id": null}, {"id": 10846265003, "name": "Emory and Henry College", "priority": 443, "external_id": null}, {"id": 10846266003, "name": "Emory University", "priority": 444, "external_id": null}, {"id": 10846267003, "name": "Emporia State University", "priority": 445, "external_id": null}, {"id": 10846268003, "name": "Endicott College", "priority": 446, "external_id": null}, {"id": 10846269003, "name": "Erskine College", "priority": 447, "external_id": null}, {"id": 10846270003, "name": "Escuela de Artes Plasticas de Puerto Rico", "priority": 448, "external_id": null}, {"id": 10846271003, "name": "Eureka College", "priority": 449, "external_id": null}, {"id": 10846272003, "name": "Evangel University", "priority": 450, "external_id": null}, {"id": 10846273003, "name": "Everest College - Phoenix", "priority": 451, "external_id": null}, {"id": 10846274003, "name": "Everglades University", "priority": 452, "external_id": null}, {"id": 10846275003, "name": "Evergreen State College", "priority": 453, "external_id": null}, {"id": 10846276003, "name": "Excelsior College", "priority": 454, "external_id": null}, {"id": 10846277003, "name": "Fairfield University", "priority": 455, "external_id": null}, {"id": 10846278003, "name": "Fairleigh Dickinson University", "priority": 456, "external_id": null}, {"id": 10846279003, "name": "Fairmont State University", "priority": 457, "external_id": null}, {"id": 10846280003, "name": "Faith Baptist Bible College and Theological Seminary", "priority": 458, "external_id": null}, {"id": 10846281003, "name": "Farmingdale State College - SUNY", "priority": 459, "external_id": null}, {"id": 10846282003, "name": "Fashion Institute of Technology", "priority": 460, "external_id": null}, {"id": 10846283003, "name": "Faulkner University", "priority": 461, "external_id": null}, {"id": 10846284003, "name": "Fayetteville State University", "priority": 462, "external_id": null}, {"id": 10846285003, "name": "Felician College", "priority": 463, "external_id": null}, {"id": 10846286003, "name": "Ferris State University", "priority": 464, "external_id": null}, {"id": 10846287003, "name": "Ferrum College", "priority": 465, "external_id": null}, {"id": 10846288003, "name": "Finlandia University", "priority": 466, "external_id": null}, {"id": 10846289003, "name": "Fisher College", "priority": 467, "external_id": null}, {"id": 10846290003, "name": "Fisk University", "priority": 468, "external_id": null}, {"id": 10846291003, "name": "Fitchburg State University", "priority": 469, "external_id": null}, {"id": 10846292003, "name": "Five Towns College", "priority": 470, "external_id": null}, {"id": 10846293003, "name": "Flagler College", "priority": 471, "external_id": null}, {"id": 10846294003, "name": "Florida Christian College", "priority": 472, "external_id": null}, {"id": 10846295003, "name": "Florida College", "priority": 473, "external_id": null}, {"id": 10846296003, "name": "Florida Gulf Coast University", "priority": 474, "external_id": null}, {"id": 10846297003, "name": "Florida Institute of Technology", "priority": 475, "external_id": null}, {"id": 10846298003, "name": "Florida Memorial University", "priority": 476, "external_id": null}, {"id": 10846299003, "name": "Florida Southern College", "priority": 477, "external_id": null}, {"id": 10846300003, "name": "Florida State College - Jacksonville", "priority": 478, "external_id": null}, {"id": 10846301003, "name": "Fontbonne University", "priority": 479, "external_id": null}, {"id": 10846302003, "name": "Fort Hays State University", "priority": 480, "external_id": null}, {"id": 10846303003, "name": "Fort Lewis College", "priority": 481, "external_id": null}, {"id": 10846304003, "name": "Fort Valley State University", "priority": 482, "external_id": null}, {"id": 10846305003, "name": "Framingham State University", "priority": 483, "external_id": null}, {"id": 10846306003, "name": "Francis Marion University", "priority": 484, "external_id": null}, {"id": 10846307003, "name": "Franciscan University of Steubenville", "priority": 485, "external_id": null}, {"id": 10846308003, "name": "Frank Lloyd Wright School of Architecture", "priority": 486, "external_id": null}, {"id": 10846309003, "name": "Franklin and Marshall College", "priority": 487, "external_id": null}, {"id": 10846310003, "name": "Franklin College", "priority": 488, "external_id": null}, {"id": 10846311003, "name": "Franklin College Switzerland", "priority": 489, "external_id": null}, {"id": 10846312003, "name": "Franklin Pierce University", "priority": 490, "external_id": null}, {"id": 10846313003, "name": "Franklin University", "priority": 491, "external_id": null}, {"id": 10846314003, "name": "Franklin W. Olin College of Engineering", "priority": 492, "external_id": null}, {"id": 10846315003, "name": "Freed-Hardeman University", "priority": 493, "external_id": null}, {"id": 10846316003, "name": "Fresno Pacific University", "priority": 494, "external_id": null}, {"id": 10846317003, "name": "Friends University", "priority": 495, "external_id": null}, {"id": 10846318003, "name": "Frostburg State University", "priority": 496, "external_id": null}, {"id": 10846319003, "name": "Gallaudet University", "priority": 497, "external_id": null}, {"id": 10846320003, "name": "Gannon University", "priority": 498, "external_id": null}, {"id": 10846321003, "name": "Geneva College", "priority": 499, "external_id": null}, {"id": 10846322003, "name": "George Fox University", "priority": 500, "external_id": null}, {"id": 10846323003, "name": "George Mason University", "priority": 501, "external_id": null}, {"id": 10846324003, "name": "George Washington University", "priority": 502, "external_id": null}, {"id": 10846325003, "name": "Georgetown College", "priority": 503, "external_id": null}, {"id": 10846326003, "name": "Georgia College & State University", "priority": 504, "external_id": null}, {"id": 10846327003, "name": "Georgia Gwinnett College", "priority": 505, "external_id": null}, {"id": 10846328003, "name": "Georgia Regents University", "priority": 506, "external_id": null}, {"id": 10846329003, "name": "Georgia Southwestern State University", "priority": 507, "external_id": null}, {"id": 10846330003, "name": "Georgian Court University", "priority": 508, "external_id": null}, {"id": 10846331003, "name": "Gettysburg College", "priority": 509, "external_id": null}, {"id": 10846332003, "name": "Glenville State College", "priority": 510, "external_id": null}, {"id": 10846333003, "name": "God's Bible School and College", "priority": 511, "external_id": null}, {"id": 10846334003, "name": "Goddard College", "priority": 512, "external_id": null}, {"id": 10846335003, "name": "Golden Gate University", "priority": 513, "external_id": null}, {"id": 10846336003, "name": "Goldey-Beacom College", "priority": 514, "external_id": null}, {"id": 10846337003, "name": "Goldfarb School of Nursing at Barnes-Jewish College", "priority": 515, "external_id": null}, {"id": 10846338003, "name": "Gonzaga University", "priority": 516, "external_id": null}, {"id": 10846339003, "name": "Gordon College", "priority": 517, "external_id": null}, {"id": 10846340003, "name": "Fordham University", "priority": 518, "external_id": null}, {"id": 10846341003, "name": "Georgia Institute of Technology", "priority": 519, "external_id": null}, {"id": 10846342003, "name": "Gardner-Webb University", "priority": 520, "external_id": null}, {"id": 10846343003, "name": "Georgia Southern University", "priority": 521, "external_id": null}, {"id": 10846344003, "name": "Georgia State University", "priority": 522, "external_id": null}, {"id": 10846345003, "name": "Florida State University", "priority": 523, "external_id": null}, {"id": 10846346003, "name": "Dartmouth College", "priority": 524, "external_id": null}, {"id": 10846347003, "name": "Florida International University", "priority": 525, "external_id": null}, {"id": 10846348003, "name": "Georgetown University", "priority": 526, "external_id": null}, {"id": 10846349003, "name": "Furman University", "priority": 527, "external_id": null}, {"id": 10846350003, "name": "Gordon State College", "priority": 528, "external_id": null}, {"id": 10846351003, "name": "Goshen College", "priority": 529, "external_id": null}, {"id": 10846352003, "name": "Goucher College", "priority": 530, "external_id": null}, {"id": 10846353003, "name": "Governors State University", "priority": 531, "external_id": null}, {"id": 10846354003, "name": "Grace Bible College", "priority": 532, "external_id": null}, {"id": 10846355003, "name": "Grace College and Seminary", "priority": 533, "external_id": null}, {"id": 10846356003, "name": "Grace University", "priority": 534, "external_id": null}, {"id": 10846357003, "name": "Graceland University", "priority": 535, "external_id": null}, {"id": 10846358003, "name": "Grand Canyon University", "priority": 536, "external_id": null}, {"id": 10846359003, "name": "Grand Valley State University", "priority": 537, "external_id": null}, {"id": 10846360003, "name": "Grand View University", "priority": 538, "external_id": null}, {"id": 10846361003, "name": "Granite State College", "priority": 539, "external_id": null}, {"id": 10846362003, "name": "Gratz College", "priority": 540, "external_id": null}, {"id": 10846363003, "name": "Great Basin College", "priority": 541, "external_id": null}, {"id": 10846364003, "name": "Great Lakes Christian College", "priority": 542, "external_id": null}, {"id": 10846365003, "name": "Green Mountain College", "priority": 543, "external_id": null}, {"id": 10846366003, "name": "Greensboro College", "priority": 544, "external_id": null}, {"id": 10846367003, "name": "Greenville College", "priority": 545, "external_id": null}, {"id": 10846368003, "name": "Grinnell College", "priority": 546, "external_id": null}, {"id": 10846369003, "name": "Grove City College", "priority": 547, "external_id": null}, {"id": 10846370003, "name": "Guilford College", "priority": 548, "external_id": null}, {"id": 10846371003, "name": "Gustavus Adolphus College", "priority": 549, "external_id": null}, {"id": 10846372003, "name": "Gwynedd-Mercy College", "priority": 550, "external_id": null}, {"id": 10846373003, "name": "Hamilton College", "priority": 551, "external_id": null}, {"id": 10846374003, "name": "Hamline University", "priority": 552, "external_id": null}, {"id": 10846375003, "name": "Hampden-Sydney College", "priority": 553, "external_id": null}, {"id": 10846376003, "name": "Hampshire College", "priority": 554, "external_id": null}, {"id": 10846377003, "name": "Hannibal-LaGrange University", "priority": 555, "external_id": null}, {"id": 10846378003, "name": "Hanover College", "priority": 556, "external_id": null}, {"id": 10846379003, "name": "Hardin-Simmons University", "priority": 557, "external_id": null}, {"id": 10846380003, "name": "Harding University", "priority": 558, "external_id": null}, {"id": 10846381003, "name": "Harrington College of Design", "priority": 559, "external_id": null}, {"id": 10846382003, "name": "Harris-Stowe State University", "priority": 560, "external_id": null}, {"id": 10846383003, "name": "Harrisburg University of Science and Technology", "priority": 561, "external_id": null}, {"id": 10846384003, "name": "Hartwick College", "priority": 562, "external_id": null}, {"id": 10846385003, "name": "Harvey Mudd College", "priority": 563, "external_id": null}, {"id": 10846386003, "name": "Haskell Indian Nations University", "priority": 564, "external_id": null}, {"id": 10846387003, "name": "Hastings College", "priority": 565, "external_id": null}, {"id": 10846388003, "name": "Haverford College", "priority": 566, "external_id": null}, {"id": 10846389003, "name": "Hawaii Pacific University", "priority": 567, "external_id": null}, {"id": 10846390003, "name": "Hebrew Theological College", "priority": 568, "external_id": null}, {"id": 10846391003, "name": "Heidelberg University", "priority": 569, "external_id": null}, {"id": 10846392003, "name": "Hellenic College", "priority": 570, "external_id": null}, {"id": 10846393003, "name": "Henderson State University", "priority": 571, "external_id": null}, {"id": 10846394003, "name": "Hendrix College", "priority": 572, "external_id": null}, {"id": 10846395003, "name": "Heritage University", "priority": 573, "external_id": null}, {"id": 10846396003, "name": "Herzing University", "priority": 574, "external_id": null}, {"id": 10846397003, "name": "Hesser College", "priority": 575, "external_id": null}, {"id": 10846398003, "name": "High Point University", "priority": 576, "external_id": null}, {"id": 10846399003, "name": "Hilbert College", "priority": 577, "external_id": null}, {"id": 10846400003, "name": "Hillsdale College", "priority": 578, "external_id": null}, {"id": 10846401003, "name": "Hiram College", "priority": 579, "external_id": null}, {"id": 10846402003, "name": "Hobart and William Smith Colleges", "priority": 580, "external_id": null}, {"id": 10846403003, "name": "Hodges University", "priority": 581, "external_id": null}, {"id": 10846404003, "name": "Hofstra University", "priority": 582, "external_id": null}, {"id": 10846405003, "name": "Hollins University", "priority": 583, "external_id": null}, {"id": 10846406003, "name": "Holy Apostles College and Seminary", "priority": 584, "external_id": null}, {"id": 10846407003, "name": "Indiana State University", "priority": 585, "external_id": null}, {"id": 10846408003, "name": "Holy Family University", "priority": 586, "external_id": null}, {"id": 10846409003, "name": "Holy Names University", "priority": 587, "external_id": null}, {"id": 10846410003, "name": "Hood College", "priority": 588, "external_id": null}, {"id": 10846411003, "name": "Hope College", "priority": 589, "external_id": null}, {"id": 10846412003, "name": "Hope International University", "priority": 590, "external_id": null}, {"id": 10846413003, "name": "Houghton College", "priority": 591, "external_id": null}, {"id": 10846414003, "name": "Howard Payne University", "priority": 592, "external_id": null}, {"id": 10846415003, "name": "Hult International Business School", "priority": 593, "external_id": null}, {"id": 10846416003, "name": "Humboldt State University", "priority": 594, "external_id": null}, {"id": 10846417003, "name": "Humphreys College", "priority": 595, "external_id": null}, {"id": 10846418003, "name": "Huntingdon College", "priority": 596, "external_id": null}, {"id": 10846419003, "name": "Huntington University", "priority": 597, "external_id": null}, {"id": 10846420003, "name": "Husson University", "priority": 598, "external_id": null}, {"id": 10846421003, "name": "Huston-Tillotson University", "priority": 599, "external_id": null}, {"id": 10846422003, "name": "Illinois College", "priority": 600, "external_id": null}, {"id": 10846423003, "name": "Illinois Institute of Art at Chicago", "priority": 601, "external_id": null}, {"id": 10846424003, "name": "Illinois Institute of Technology", "priority": 602, "external_id": null}, {"id": 10846425003, "name": "Illinois Wesleyan University", "priority": 603, "external_id": null}, {"id": 10846426003, "name": "Immaculata University", "priority": 604, "external_id": null}, {"id": 10846427003, "name": "Indian River State College", "priority": 605, "external_id": null}, {"id": 10846428003, "name": "Indiana Institute of Technology", "priority": 606, "external_id": null}, {"id": 10846429003, "name": "Indiana University East", "priority": 607, "external_id": null}, {"id": 10846430003, "name": "Indiana University Northwest", "priority": 608, "external_id": null}, {"id": 10846431003, "name": "Indiana University of Pennsylvania", "priority": 609, "external_id": null}, {"id": 10846432003, "name": "Indiana University Southeast", "priority": 610, "external_id": null}, {"id": 10846433003, "name": "Illinois State University", "priority": 611, "external_id": null}, {"id": 10846434003, "name": "Indiana University - Bloomington", "priority": 612, "external_id": null}, {"id": 10846435003, "name": "Davidson College", "priority": 613, "external_id": null}, {"id": 10846436003, "name": "Idaho State University", "priority": 614, "external_id": null}, {"id": 10846437003, "name": "Harvard University", "priority": 615, "external_id": null}, {"id": 10846438003, "name": "Howard University", "priority": 616, "external_id": null}, {"id": 10846439003, "name": "Houston Baptist University", "priority": 617, "external_id": null}, {"id": 10846440003, "name": "Indiana University - Kokomo", "priority": 618, "external_id": null}, {"id": 10846441003, "name": "Indiana University - South Bend", "priority": 619, "external_id": null}, {"id": 10846442003, "name": "Indiana University-Purdue University - Fort Wayne", "priority": 620, "external_id": null}, {"id": 10846443003, "name": "Indiana University-Purdue University - Indianapolis", "priority": 621, "external_id": null}, {"id": 10846444003, "name": "Indiana Wesleyan University", "priority": 622, "external_id": null}, {"id": 10846445003, "name": "Institute of American Indian and Alaska Native Culture and Arts Development", "priority": 623, "external_id": null}, {"id": 10846446003, "name": "Inter American University of Puerto Rico - Aguadilla", "priority": 624, "external_id": null}, {"id": 10846447003, "name": "Inter American University of Puerto Rico - Arecibo", "priority": 625, "external_id": null}, {"id": 10846448003, "name": "Inter American University of Puerto Rico - Barranquitas", "priority": 626, "external_id": null}, {"id": 10846449003, "name": "Inter American University of Puerto Rico - Bayamon", "priority": 627, "external_id": null}, {"id": 10846450003, "name": "Inter American University of Puerto Rico - Fajardo", "priority": 628, "external_id": null}, {"id": 10846451003, "name": "Inter American University of Puerto Rico - Guayama", "priority": 629, "external_id": null}, {"id": 10846452003, "name": "Inter American University of Puerto Rico - Metropolitan Campus", "priority": 630, "external_id": null}, {"id": 10846453003, "name": "Inter American University of Puerto Rico - Ponce", "priority": 631, "external_id": null}, {"id": 10846454003, "name": "Inter American University of Puerto Rico - San German", "priority": 632, "external_id": null}, {"id": 10846455003, "name": "International College of the Cayman Islands", "priority": 633, "external_id": null}, {"id": 10846456003, "name": "Iona College", "priority": 634, "external_id": null}, {"id": 10846457003, "name": "Iowa Wesleyan College", "priority": 635, "external_id": null}, {"id": 10846458003, "name": "Ithaca College", "priority": 636, "external_id": null}, {"id": 10846459003, "name": "Jarvis Christian College", "priority": 637, "external_id": null}, {"id": 10846460003, "name": "Jewish Theological Seminary of America", "priority": 638, "external_id": null}, {"id": 10846461003, "name": "John Brown University", "priority": 639, "external_id": null}, {"id": 10846462003, "name": "John Carroll University", "priority": 640, "external_id": null}, {"id": 10846463003, "name": "John F. Kennedy University", "priority": 641, "external_id": null}, {"id": 10846464003, "name": "Johns Hopkins University", "priority": 642, "external_id": null}, {"id": 10846465003, "name": "Johnson & Wales University", "priority": 643, "external_id": null}, {"id": 10846466003, "name": "Johnson C. Smith University", "priority": 644, "external_id": null}, {"id": 10846467003, "name": "Johnson State College", "priority": 645, "external_id": null}, {"id": 10846468003, "name": "Johnson University", "priority": 646, "external_id": null}, {"id": 10846469003, "name": "Jones International University", "priority": 647, "external_id": null}, {"id": 10846470003, "name": "Judson College", "priority": 648, "external_id": null}, {"id": 10846471003, "name": "Judson University", "priority": 649, "external_id": null}, {"id": 10846472003, "name": "Juilliard School", "priority": 650, "external_id": null}, {"id": 10846473003, "name": "Juniata College", "priority": 651, "external_id": null}, {"id": 10846474003, "name": "Kalamazoo College", "priority": 652, "external_id": null}, {"id": 10846475003, "name": "Kansas City Art Institute", "priority": 653, "external_id": null}, {"id": 10846476003, "name": "Kansas Wesleyan University", "priority": 654, "external_id": null}, {"id": 10846477003, "name": "Kaplan University", "priority": 655, "external_id": null}, {"id": 10846478003, "name": "Kean University", "priority": 656, "external_id": null}, {"id": 10846479003, "name": "Keene State College", "priority": 657, "external_id": null}, {"id": 10846480003, "name": "Keiser University", "priority": 658, "external_id": null}, {"id": 10846481003, "name": "Kendall College", "priority": 659, "external_id": null}, {"id": 10846482003, "name": "Kennesaw State University", "priority": 660, "external_id": null}, {"id": 10846483003, "name": "Kentucky Christian University", "priority": 661, "external_id": null}, {"id": 10846484003, "name": "Kentucky State University", "priority": 662, "external_id": null}, {"id": 10846485003, "name": "Kentucky Wesleyan College", "priority": 663, "external_id": null}, {"id": 10846486003, "name": "Kenyon College", "priority": 664, "external_id": null}, {"id": 10846487003, "name": "Kettering College", "priority": 665, "external_id": null}, {"id": 10846488003, "name": "Kettering University", "priority": 666, "external_id": null}, {"id": 10846489003, "name": "Keuka College", "priority": 667, "external_id": null}, {"id": 10846490003, "name": "Keystone College", "priority": 668, "external_id": null}, {"id": 10846491003, "name": "King University", "priority": 669, "external_id": null}, {"id": 10846492003, "name": "King's College", "priority": 670, "external_id": null}, {"id": 10846493003, "name": "Knox College", "priority": 671, "external_id": null}, {"id": 10846494003, "name": "Kutztown University of Pennsylvania", "priority": 672, "external_id": null}, {"id": 10846495003, "name": "Kuyper College", "priority": 673, "external_id": null}, {"id": 10846496003, "name": "La Roche College", "priority": 674, "external_id": null}, {"id": 10846497003, "name": "La Salle University", "priority": 675, "external_id": null}, {"id": 10846498003, "name": "La Sierra University", "priority": 676, "external_id": null}, {"id": 10846499003, "name": "LaGrange College", "priority": 677, "external_id": null}, {"id": 10846500003, "name": "Laguna College of Art and Design", "priority": 678, "external_id": null}, {"id": 10846501003, "name": "Lake Erie College", "priority": 679, "external_id": null}, {"id": 10846502003, "name": "Lake Forest College", "priority": 680, "external_id": null}, {"id": 10846503003, "name": "Lake Superior State University", "priority": 681, "external_id": null}, {"id": 10846504003, "name": "Lakeland College", "priority": 682, "external_id": null}, {"id": 10846505003, "name": "Lakeview College of Nursing", "priority": 683, "external_id": null}, {"id": 10846506003, "name": "Lancaster Bible College", "priority": 684, "external_id": null}, {"id": 10846507003, "name": "Lander University", "priority": 685, "external_id": null}, {"id": 10846508003, "name": "Lane College", "priority": 686, "external_id": null}, {"id": 10846509003, "name": "Langston University", "priority": 687, "external_id": null}, {"id": 10846510003, "name": "Lasell College", "priority": 688, "external_id": null}, {"id": 10846511003, "name": "Lawrence Technological University", "priority": 689, "external_id": null}, {"id": 10846512003, "name": "Lawrence University", "priority": 690, "external_id": null}, {"id": 10846513003, "name": "Le Moyne College", "priority": 691, "external_id": null}, {"id": 10846514003, "name": "Lebanon Valley College", "priority": 692, "external_id": null}, {"id": 10846515003, "name": "Lee University", "priority": 693, "external_id": null}, {"id": 10846516003, "name": "Lees-McRae College", "priority": 694, "external_id": null}, {"id": 10846517003, "name": "Kansas State University", "priority": 695, "external_id": null}, {"id": 10846518003, "name": "James Madison University", "priority": 696, "external_id": null}, {"id": 10846519003, "name": "Lafayette College", "priority": 697, "external_id": null}, {"id": 10846520003, "name": "Jacksonville University", "priority": 698, "external_id": null}, {"id": 10846521003, "name": "Kent State University", "priority": 699, "external_id": null}, {"id": 10846522003, "name": "Lamar University", "priority": 700, "external_id": null}, {"id": 10846523003, "name": "Jackson State University", "priority": 701, "external_id": null}, {"id": 10846524003, "name": "Lehigh University", "priority": 702, "external_id": null}, {"id": 10846525003, "name": "Jacksonville State University", "priority": 703, "external_id": null}, {"id": 10846526003, "name": "LeMoyne-Owen College", "priority": 704, "external_id": null}, {"id": 10846527003, "name": "Lenoir-Rhyne University", "priority": 705, "external_id": null}, {"id": 10846528003, "name": "Lesley University", "priority": 706, "external_id": null}, {"id": 10846529003, "name": "LeTourneau University", "priority": 707, "external_id": null}, {"id": 10846530003, "name": "Lewis & Clark College", "priority": 708, "external_id": null}, {"id": 10846531003, "name": "Lewis University", "priority": 709, "external_id": null}, {"id": 10846532003, "name": "Lewis-Clark State College", "priority": 710, "external_id": null}, {"id": 10846533003, "name": "Lexington College", "priority": 711, "external_id": null}, {"id": 10846534003, "name": "Life Pacific College", "priority": 712, "external_id": null}, {"id": 10846535003, "name": "Life University", "priority": 713, "external_id": null}, {"id": 10846536003, "name": "LIM College", "priority": 714, "external_id": null}, {"id": 10846537003, "name": "Limestone College", "priority": 715, "external_id": null}, {"id": 10846538003, "name": "Lincoln Christian University", "priority": 716, "external_id": null}, {"id": 10846539003, "name": "Lincoln College", "priority": 717, "external_id": null}, {"id": 10846540003, "name": "Lincoln Memorial University", "priority": 718, "external_id": null}, {"id": 10846541003, "name": "Lincoln University", "priority": 719, "external_id": null}, {"id": 10846542003, "name": "Lindenwood University", "priority": 720, "external_id": null}, {"id": 10846543003, "name": "Lindsey Wilson College", "priority": 721, "external_id": null}, {"id": 10846544003, "name": "Linfield College", "priority": 722, "external_id": null}, {"id": 10846545003, "name": "Lipscomb University", "priority": 723, "external_id": null}, {"id": 10846546003, "name": "LIU Post", "priority": 724, "external_id": null}, {"id": 10846547003, "name": "Livingstone College", "priority": 725, "external_id": null}, {"id": 10846548003, "name": "Lock Haven University of Pennsylvania", "priority": 726, "external_id": null}, {"id": 10846549003, "name": "Loma Linda University", "priority": 727, "external_id": null}, {"id": 10846550003, "name": "Longwood University", "priority": 728, "external_id": null}, {"id": 10846551003, "name": "Loras College", "priority": 729, "external_id": null}, {"id": 10846552003, "name": "Louisiana College", "priority": 730, "external_id": null}, {"id": 10846553003, "name": "Louisiana State University Health Sciences Center", "priority": 731, "external_id": null}, {"id": 10846554003, "name": "Louisiana State University - Alexandria", "priority": 732, "external_id": null}, {"id": 10846555003, "name": "Louisiana State University - Shreveport", "priority": 733, "external_id": null}, {"id": 10846556003, "name": "Lourdes University", "priority": 734, "external_id": null}, {"id": 10846557003, "name": "Loyola Marymount University", "priority": 735, "external_id": null}, {"id": 10846558003, "name": "Loyola University Chicago", "priority": 736, "external_id": null}, {"id": 10846559003, "name": "Loyola University Maryland", "priority": 737, "external_id": null}, {"id": 10846560003, "name": "Loyola University New Orleans", "priority": 738, "external_id": null}, {"id": 10846561003, "name": "Lubbock Christian University", "priority": 739, "external_id": null}, {"id": 10846562003, "name": "Luther College", "priority": 740, "external_id": null}, {"id": 10846563003, "name": "Lycoming College", "priority": 741, "external_id": null}, {"id": 10846564003, "name": "Lyme Academy College of Fine Arts", "priority": 742, "external_id": null}, {"id": 10846565003, "name": "Lynchburg College", "priority": 743, "external_id": null}, {"id": 10846566003, "name": "Lyndon State College", "priority": 744, "external_id": null}, {"id": 10846567003, "name": "Lynn University", "priority": 745, "external_id": null}, {"id": 10846568003, "name": "Lyon College", "priority": 746, "external_id": null}, {"id": 10846569003, "name": "Macalester College", "priority": 747, "external_id": null}, {"id": 10846570003, "name": "MacMurray College", "priority": 748, "external_id": null}, {"id": 10846571003, "name": "Madonna University", "priority": 749, "external_id": null}, {"id": 10846572003, "name": "Maharishi University of Management", "priority": 750, "external_id": null}, {"id": 10846573003, "name": "Maine College of Art", "priority": 751, "external_id": null}, {"id": 10846574003, "name": "Maine Maritime Academy", "priority": 752, "external_id": null}, {"id": 10846575003, "name": "Malone University", "priority": 753, "external_id": null}, {"id": 10846576003, "name": "Manchester University", "priority": 754, "external_id": null}, {"id": 10846577003, "name": "Manhattan Christian College", "priority": 755, "external_id": null}, {"id": 10846578003, "name": "Manhattan College", "priority": 756, "external_id": null}, {"id": 10846579003, "name": "Manhattan School of Music", "priority": 757, "external_id": null}, {"id": 10846580003, "name": "Manhattanville College", "priority": 758, "external_id": null}, {"id": 10846581003, "name": "Mansfield University of Pennsylvania", "priority": 759, "external_id": null}, {"id": 10846582003, "name": "Maranatha Baptist Bible College", "priority": 760, "external_id": null}, {"id": 10846583003, "name": "Marian University", "priority": 761, "external_id": null}, {"id": 10846584003, "name": "Marietta College", "priority": 762, "external_id": null}, {"id": 10846585003, "name": "Marlboro College", "priority": 763, "external_id": null}, {"id": 10846586003, "name": "Marquette University", "priority": 764, "external_id": null}, {"id": 10846587003, "name": "Mars Hill University", "priority": 765, "external_id": null}, {"id": 10846588003, "name": "Martin Luther College", "priority": 766, "external_id": null}, {"id": 10846589003, "name": "Martin Methodist College", "priority": 767, "external_id": null}, {"id": 10846590003, "name": "Martin University", "priority": 768, "external_id": null}, {"id": 10846591003, "name": "Mary Baldwin College", "priority": 769, "external_id": null}, {"id": 10846592003, "name": "Marygrove College", "priority": 770, "external_id": null}, {"id": 10846593003, "name": "Maryland Institute College of Art", "priority": 771, "external_id": null}, {"id": 10846594003, "name": "Marylhurst University", "priority": 772, "external_id": null}, {"id": 10846595003, "name": "Marymount Manhattan College", "priority": 773, "external_id": null}, {"id": 10846596003, "name": "Marymount University", "priority": 774, "external_id": null}, {"id": 10846597003, "name": "Maryville College", "priority": 775, "external_id": null}, {"id": 10846598003, "name": "Maryville University of St. Louis", "priority": 776, "external_id": null}, {"id": 10846599003, "name": "Marywood University", "priority": 777, "external_id": null}, {"id": 10846600003, "name": "Massachusetts College of Art and Design", "priority": 778, "external_id": null}, {"id": 10846601003, "name": "Massachusetts College of Liberal Arts", "priority": 779, "external_id": null}, {"id": 10846602003, "name": "Massachusetts College of Pharmacy and Health Sciences", "priority": 780, "external_id": null}, {"id": 10846603003, "name": "Massachusetts Institute of Technology", "priority": 781, "external_id": null}, {"id": 10846604003, "name": "Massachusetts Maritime Academy", "priority": 782, "external_id": null}, {"id": 10846605003, "name": "Master's College and Seminary", "priority": 783, "external_id": null}, {"id": 10846606003, "name": "Mayville State University", "priority": 784, "external_id": null}, {"id": 10846607003, "name": "McDaniel College", "priority": 785, "external_id": null}, {"id": 10846608003, "name": "McGill University", "priority": 786, "external_id": null}, {"id": 10846609003, "name": "McKendree University", "priority": 787, "external_id": null}, {"id": 10846610003, "name": "McMurry University", "priority": 788, "external_id": null}, {"id": 10846611003, "name": "McPherson College", "priority": 789, "external_id": null}, {"id": 10846612003, "name": "Medaille College", "priority": 790, "external_id": null}, {"id": 10846613003, "name": "Marist College", "priority": 791, "external_id": null}, {"id": 10846614003, "name": "McNeese State University", "priority": 792, "external_id": null}, {"id": 10846615003, "name": "Louisiana Tech University", "priority": 793, "external_id": null}, {"id": 10846616003, "name": "Marshall University", "priority": 794, "external_id": null}, {"id": 10846617003, "name": "Medical University of South Carolina", "priority": 795, "external_id": null}, {"id": 10846618003, "name": "Memorial University of Newfoundland", "priority": 796, "external_id": null}, {"id": 10846619003, "name": "Memphis College of Art", "priority": 797, "external_id": null}, {"id": 10846620003, "name": "Menlo College", "priority": 798, "external_id": null}, {"id": 10846621003, "name": "Mercy College", "priority": 799, "external_id": null}, {"id": 10846622003, "name": "Mercy College of Health Sciences", "priority": 800, "external_id": null}, {"id": 10846623003, "name": "Mercy College of Ohio", "priority": 801, "external_id": null}, {"id": 10846624003, "name": "Mercyhurst University", "priority": 802, "external_id": null}, {"id": 10846625003, "name": "Meredith College", "priority": 803, "external_id": null}, {"id": 10846626003, "name": "Merrimack College", "priority": 804, "external_id": null}, {"id": 10846627003, "name": "Messiah College", "priority": 805, "external_id": null}, {"id": 10846628003, "name": "Methodist University", "priority": 806, "external_id": null}, {"id": 10846629003, "name": "Metropolitan College of New York", "priority": 807, "external_id": null}, {"id": 10846630003, "name": "Metropolitan State University", "priority": 808, "external_id": null}, {"id": 10846631003, "name": "Metropolitan State University of Denver", "priority": 809, "external_id": null}, {"id": 10846632003, "name": "Miami Dade College", "priority": 810, "external_id": null}, {"id": 10846633003, "name": "Miami International University of Art & Design", "priority": 811, "external_id": null}, {"id": 10846634003, "name": "Michigan Technological University", "priority": 812, "external_id": null}, {"id": 10846635003, "name": "Mid-America Christian University", "priority": 813, "external_id": null}, {"id": 10846636003, "name": "Mid-Atlantic Christian University", "priority": 814, "external_id": null}, {"id": 10846637003, "name": "Mid-Continent University", "priority": 815, "external_id": null}, {"id": 10846638003, "name": "MidAmerica Nazarene University", "priority": 816, "external_id": null}, {"id": 10846639003, "name": "Middle Georgia State College", "priority": 817, "external_id": null}, {"id": 10846640003, "name": "Middlebury College", "priority": 818, "external_id": null}, {"id": 10846641003, "name": "Midland College", "priority": 819, "external_id": null}, {"id": 10846642003, "name": "Midland University", "priority": 820, "external_id": null}, {"id": 10846643003, "name": "Midstate College", "priority": 821, "external_id": null}, {"id": 10846644003, "name": "Midway College", "priority": 822, "external_id": null}, {"id": 10846645003, "name": "Midwestern State University", "priority": 823, "external_id": null}, {"id": 10846646003, "name": "Miles College", "priority": 824, "external_id": null}, {"id": 10846647003, "name": "Millersville University of Pennsylvania", "priority": 825, "external_id": null}, {"id": 10846648003, "name": "Milligan College", "priority": 826, "external_id": null}, {"id": 10846649003, "name": "Millikin University", "priority": 827, "external_id": null}, {"id": 10846650003, "name": "Mills College", "priority": 828, "external_id": null}, {"id": 10846651003, "name": "Millsaps College", "priority": 829, "external_id": null}, {"id": 10846652003, "name": "Milwaukee Institute of Art and Design", "priority": 830, "external_id": null}, {"id": 10846653003, "name": "Milwaukee School of Engineering", "priority": 831, "external_id": null}, {"id": 10846654003, "name": "Minneapolis College of Art and Design", "priority": 832, "external_id": null}, {"id": 10846655003, "name": "Minnesota State University - Mankato", "priority": 833, "external_id": null}, {"id": 10846656003, "name": "Minnesota State University - Moorhead", "priority": 834, "external_id": null}, {"id": 10846657003, "name": "Minot State University", "priority": 835, "external_id": null}, {"id": 10846658003, "name": "Misericordia University", "priority": 836, "external_id": null}, {"id": 10846659003, "name": "Mississippi College", "priority": 837, "external_id": null}, {"id": 10846660003, "name": "Mississippi University for Women", "priority": 838, "external_id": null}, {"id": 10846661003, "name": "Missouri Baptist University", "priority": 839, "external_id": null}, {"id": 10846662003, "name": "Missouri Southern State University", "priority": 840, "external_id": null}, {"id": 10846663003, "name": "Missouri University of Science & Technology", "priority": 841, "external_id": null}, {"id": 10846664003, "name": "Missouri Valley College", "priority": 842, "external_id": null}, {"id": 10846665003, "name": "Missouri Western State University", "priority": 843, "external_id": null}, {"id": 10846666003, "name": "Mitchell College", "priority": 844, "external_id": null}, {"id": 10846667003, "name": "Molloy College", "priority": 845, "external_id": null}, {"id": 10846668003, "name": "Monmouth College", "priority": 846, "external_id": null}, {"id": 10846669003, "name": "Monroe College", "priority": 847, "external_id": null}, {"id": 10846670003, "name": "Montana State University - Billings", "priority": 848, "external_id": null}, {"id": 10846671003, "name": "Montana State University - Northern", "priority": 849, "external_id": null}, {"id": 10846672003, "name": "Montana Tech of the University of Montana", "priority": 850, "external_id": null}, {"id": 10846673003, "name": "Montclair State University", "priority": 851, "external_id": null}, {"id": 10846674003, "name": "Monterrey Institute of Technology and Higher Education - Monterrey", "priority": 852, "external_id": null}, {"id": 10846675003, "name": "Montreat College", "priority": 853, "external_id": null}, {"id": 10846676003, "name": "Montserrat College of Art", "priority": 854, "external_id": null}, {"id": 10846677003, "name": "Moody Bible Institute", "priority": 855, "external_id": null}, {"id": 10846678003, "name": "Moore College of Art & Design", "priority": 856, "external_id": null}, {"id": 10846679003, "name": "Moravian College", "priority": 857, "external_id": null}, {"id": 10846680003, "name": "Morehouse College", "priority": 858, "external_id": null}, {"id": 10846681003, "name": "Morningside College", "priority": 859, "external_id": null}, {"id": 10846682003, "name": "Morris College", "priority": 860, "external_id": null}, {"id": 10846683003, "name": "Morrisville State College", "priority": 861, "external_id": null}, {"id": 10846684003, "name": "Mount Aloysius College", "priority": 862, "external_id": null}, {"id": 10846685003, "name": "Mount Angel Seminary", "priority": 863, "external_id": null}, {"id": 10846686003, "name": "Mount Carmel College of Nursing", "priority": 864, "external_id": null}, {"id": 10846687003, "name": "Mount Holyoke College", "priority": 865, "external_id": null}, {"id": 10846688003, "name": "Mount Ida College", "priority": 866, "external_id": null}, {"id": 10846689003, "name": "Mount Marty College", "priority": 867, "external_id": null}, {"id": 10846690003, "name": "Mount Mary University", "priority": 868, "external_id": null}, {"id": 10846691003, "name": "Mount Mercy University", "priority": 869, "external_id": null}, {"id": 10846692003, "name": "Mount Olive College", "priority": 870, "external_id": null}, {"id": 10846693003, "name": "Mississippi State University", "priority": 871, "external_id": null}, {"id": 10846694003, "name": "Montana State University", "priority": 872, "external_id": null}, {"id": 10846695003, "name": "Mississippi Valley State University", "priority": 873, "external_id": null}, {"id": 10846696003, "name": "Monmouth University", "priority": 874, "external_id": null}, {"id": 10846697003, "name": "Morehead State University", "priority": 875, "external_id": null}, {"id": 10846698003, "name": "Miami University - Oxford", "priority": 876, "external_id": null}, {"id": 10846699003, "name": "Morgan State University", "priority": 877, "external_id": null}, {"id": 10846700003, "name": "Missouri State University", "priority": 878, "external_id": null}, {"id": 10846701003, "name": "Michigan State University", "priority": 879, "external_id": null}, {"id": 10846702003, "name": "Mount St. Mary College", "priority": 880, "external_id": null}, {"id": 10846703003, "name": "Mount St. Mary's College", "priority": 881, "external_id": null}, {"id": 10846704003, "name": "Mount St. Mary's University", "priority": 882, "external_id": null}, {"id": 10846705003, "name": "Mount Vernon Nazarene University", "priority": 883, "external_id": null}, {"id": 10846706003, "name": "Muhlenberg College", "priority": 884, "external_id": null}, {"id": 10846707003, "name": "Multnomah University", "priority": 885, "external_id": null}, {"id": 10846708003, "name": "Muskingum University", "priority": 886, "external_id": null}, {"id": 10846709003, "name": "Naropa University", "priority": 887, "external_id": null}, {"id": 10846710003, "name": "National American University", "priority": 888, "external_id": null}, {"id": 10846711003, "name": "National Graduate School of Quality Management", "priority": 889, "external_id": null}, {"id": 10846712003, "name": "National Hispanic University", "priority": 890, "external_id": null}, {"id": 10846713003, "name": "National Labor College", "priority": 891, "external_id": null}, {"id": 10846714003, "name": "National University", "priority": 892, "external_id": null}, {"id": 10846715003, "name": "National-Louis University", "priority": 893, "external_id": null}, {"id": 10846716003, "name": "Nazarene Bible College", "priority": 894, "external_id": null}, {"id": 10846717003, "name": "Nazareth College", "priority": 895, "external_id": null}, {"id": 10846718003, "name": "Nebraska Methodist College", "priority": 896, "external_id": null}, {"id": 10846719003, "name": "Nebraska Wesleyan University", "priority": 897, "external_id": null}, {"id": 10846720003, "name": "Neumann University", "priority": 898, "external_id": null}, {"id": 10846721003, "name": "Nevada State College", "priority": 899, "external_id": null}, {"id": 10846722003, "name": "New College of Florida", "priority": 900, "external_id": null}, {"id": 10846723003, "name": "New England College", "priority": 901, "external_id": null}, {"id": 10846724003, "name": "New England Conservatory of Music", "priority": 902, "external_id": null}, {"id": 10846725003, "name": "New England Institute of Art", "priority": 903, "external_id": null}, {"id": 10846726003, "name": "New England Institute of Technology", "priority": 904, "external_id": null}, {"id": 10846727003, "name": "New Jersey City University", "priority": 905, "external_id": null}, {"id": 10846728003, "name": "New Jersey Institute of Technology", "priority": 906, "external_id": null}, {"id": 10846729003, "name": "New Mexico Highlands University", "priority": 907, "external_id": null}, {"id": 10846730003, "name": "New Mexico Institute of Mining and Technology", "priority": 908, "external_id": null}, {"id": 10846731003, "name": "New Orleans Baptist Theological Seminary", "priority": 909, "external_id": null}, {"id": 10846732003, "name": "New School", "priority": 910, "external_id": null}, {"id": 10846733003, "name": "New York Institute of Technology", "priority": 911, "external_id": null}, {"id": 10846734003, "name": "New York University", "priority": 912, "external_id": null}, {"id": 10846735003, "name": "Newberry College", "priority": 913, "external_id": null}, {"id": 10846736003, "name": "Newbury College", "priority": 914, "external_id": null}, {"id": 10846737003, "name": "Newman University", "priority": 915, "external_id": null}, {"id": 10846738003, "name": "Niagara University", "priority": 916, "external_id": null}, {"id": 10846739003, "name": "Nichols College", "priority": 917, "external_id": null}, {"id": 10846740003, "name": "North Carolina Wesleyan College", "priority": 918, "external_id": null}, {"id": 10846741003, "name": "North Central College", "priority": 919, "external_id": null}, {"id": 10846742003, "name": "North Central University", "priority": 920, "external_id": null}, {"id": 10846743003, "name": "North Greenville University", "priority": 921, "external_id": null}, {"id": 10846744003, "name": "North Park University", "priority": 922, "external_id": null}, {"id": 10846745003, "name": "Northcentral University", "priority": 923, "external_id": null}, {"id": 10846746003, "name": "Northeastern Illinois University", "priority": 924, "external_id": null}, {"id": 10846747003, "name": "Northeastern State University", "priority": 925, "external_id": null}, {"id": 10846748003, "name": "Northeastern University", "priority": 926, "external_id": null}, {"id": 10846749003, "name": "Northern Kentucky University", "priority": 927, "external_id": null}, {"id": 10846750003, "name": "Northern Michigan University", "priority": 928, "external_id": null}, {"id": 10846751003, "name": "Northern New Mexico College", "priority": 929, "external_id": null}, {"id": 10846752003, "name": "Northern State University", "priority": 930, "external_id": null}, {"id": 10846753003, "name": "Northland College", "priority": 931, "external_id": null}, {"id": 10846754003, "name": "Northwest Christian University", "priority": 932, "external_id": null}, {"id": 10846755003, "name": "Northwest Florida State College", "priority": 933, "external_id": null}, {"id": 10846756003, "name": "Northwest Missouri State University", "priority": 934, "external_id": null}, {"id": 10846757003, "name": "Northwest Nazarene University", "priority": 935, "external_id": null}, {"id": 10846758003, "name": "Northwest University", "priority": 936, "external_id": null}, {"id": 10846759003, "name": "Northwestern College", "priority": 937, "external_id": null}, {"id": 10846760003, "name": "Northwestern Health Sciences University", "priority": 938, "external_id": null}, {"id": 10846761003, "name": "Northwestern Oklahoma State University", "priority": 939, "external_id": null}, {"id": 10846762003, "name": "Northwood University", "priority": 940, "external_id": null}, {"id": 10846763003, "name": "Norwich University", "priority": 941, "external_id": null}, {"id": 10846764003, "name": "Notre Dame College of Ohio", "priority": 942, "external_id": null}, {"id": 10846765003, "name": "Notre Dame de Namur University", "priority": 943, "external_id": null}, {"id": 10846766003, "name": "Notre Dame of Maryland University", "priority": 944, "external_id": null}, {"id": 10846767003, "name": "Nova Scotia College of Art and Design", "priority": 945, "external_id": null}, {"id": 10846768003, "name": "Nova Southeastern University", "priority": 946, "external_id": null}, {"id": 10846769003, "name": "Nyack College", "priority": 947, "external_id": null}, {"id": 10846770003, "name": "Oakland City University", "priority": 948, "external_id": null}, {"id": 10846771003, "name": "Oakland University", "priority": 949, "external_id": null}, {"id": 10846772003, "name": "Oakwood University", "priority": 950, "external_id": null}, {"id": 10846773003, "name": "Oberlin College", "priority": 951, "external_id": null}, {"id": 10846774003, "name": "Occidental College", "priority": 952, "external_id": null}, {"id": 10846775003, "name": "Oglala Lakota College", "priority": 953, "external_id": null}, {"id": 10846776003, "name": "North Carolina A&T State University", "priority": 954, "external_id": null}, {"id": 10846777003, "name": "Northern Illinois University", "priority": 955, "external_id": null}, {"id": 10846778003, "name": "North Dakota State University", "priority": 956, "external_id": null}, {"id": 10846779003, "name": "Nicholls State University", "priority": 957, "external_id": null}, {"id": 10846780003, "name": "North Carolina Central University", "priority": 958, "external_id": null}, {"id": 10846781003, "name": "Norfolk State University", "priority": 959, "external_id": null}, {"id": 10846782003, "name": "Northwestern State University of Louisiana", "priority": 960, "external_id": null}, {"id": 10846783003, "name": "Northern Arizona University", "priority": 961, "external_id": null}, {"id": 10846784003, "name": "North Carolina State University - Raleigh", "priority": 962, "external_id": null}, {"id": 10846785003, "name": "Northwestern University", "priority": 963, "external_id": null}, {"id": 10846786003, "name": "Oglethorpe University", "priority": 964, "external_id": null}, {"id": 10846787003, "name": "Ohio Christian University", "priority": 965, "external_id": null}, {"id": 10846788003, "name": "Ohio Dominican University", "priority": 966, "external_id": null}, {"id": 10846789003, "name": "Ohio Northern University", "priority": 967, "external_id": null}, {"id": 10846790003, "name": "Ohio Valley University", "priority": 968, "external_id": null}, {"id": 10846791003, "name": "Ohio Wesleyan University", "priority": 969, "external_id": null}, {"id": 10846792003, "name": "Oklahoma Baptist University", "priority": 970, "external_id": null}, {"id": 10846793003, "name": "Oklahoma Christian University", "priority": 971, "external_id": null}, {"id": 10846794003, "name": "Oklahoma City University", "priority": 972, "external_id": null}, {"id": 10846795003, "name": "Oklahoma Panhandle State University", "priority": 973, "external_id": null}, {"id": 10846796003, "name": "Oklahoma State University Institute of Technology - Okmulgee", "priority": 974, "external_id": null}, {"id": 10846797003, "name": "Oklahoma State University - Oklahoma City", "priority": 975, "external_id": null}, {"id": 10846798003, "name": "Oklahoma Wesleyan University", "priority": 976, "external_id": null}, {"id": 10846799003, "name": "Olivet College", "priority": 977, "external_id": null}, {"id": 10846800003, "name": "Olivet Nazarene University", "priority": 978, "external_id": null}, {"id": 10846801003, "name": "Olympic College", "priority": 979, "external_id": null}, {"id": 10846802003, "name": "Oral Roberts University", "priority": 980, "external_id": null}, {"id": 10846803003, "name": "Oregon College of Art and Craft", "priority": 981, "external_id": null}, {"id": 10846804003, "name": "Oregon Health and Science University", "priority": 982, "external_id": null}, {"id": 10846805003, "name": "Oregon Institute of Technology", "priority": 983, "external_id": null}, {"id": 10846806003, "name": "Otis College of Art and Design", "priority": 984, "external_id": null}, {"id": 10846807003, "name": "Ottawa University", "priority": 985, "external_id": null}, {"id": 10846808003, "name": "Otterbein University", "priority": 986, "external_id": null}, {"id": 10846809003, "name": "Ouachita Baptist University", "priority": 987, "external_id": null}, {"id": 10846810003, "name": "Our Lady of Holy Cross College", "priority": 988, "external_id": null}, {"id": 10846811003, "name": "Our Lady of the Lake College", "priority": 989, "external_id": null}, {"id": 10846812003, "name": "Our Lady of the Lake University", "priority": 990, "external_id": null}, {"id": 10846813003, "name": "Pace University", "priority": 991, "external_id": null}, {"id": 10846814003, "name": "Pacific Lutheran University", "priority": 992, "external_id": null}, {"id": 10846815003, "name": "Pacific Northwest College of Art", "priority": 993, "external_id": null}, {"id": 10846816003, "name": "Pacific Oaks College", "priority": 994, "external_id": null}, {"id": 10846817003, "name": "Pacific Union College", "priority": 995, "external_id": null}, {"id": 10846818003, "name": "Pacific University", "priority": 996, "external_id": null}, {"id": 10846819003, "name": "Paine College", "priority": 997, "external_id": null}, {"id": 10846820003, "name": "Palm Beach Atlantic University", "priority": 998, "external_id": null}, {"id": 10846821003, "name": "Palmer College of Chiropractic", "priority": 999, "external_id": null}, {"id": 10846822003, "name": "Park University", "priority": 1000, "external_id": null}, {"id": 10846823003, "name": "Parker University", "priority": 1001, "external_id": null}, {"id": 10846824003, "name": "Patten University", "priority": 1002, "external_id": null}, {"id": 10846825003, "name": "Paul Smith's College", "priority": 1003, "external_id": null}, {"id": 10846826003, "name": "Peirce College", "priority": 1004, "external_id": null}, {"id": 10846827003, "name": "Peninsula College", "priority": 1005, "external_id": null}, {"id": 10846828003, "name": "Pennsylvania College of Art and Design", "priority": 1006, "external_id": null}, {"id": 10846829003, "name": "Pennsylvania College of Technology", "priority": 1007, "external_id": null}, {"id": 10846830003, "name": "Pennsylvania State University - Erie, The Behrend College", "priority": 1008, "external_id": null}, {"id": 10846831003, "name": "Pennsylvania State University - Harrisburg", "priority": 1009, "external_id": null}, {"id": 10846832003, "name": "Pepperdine University", "priority": 1010, "external_id": null}, {"id": 10846833003, "name": "Peru State College", "priority": 1011, "external_id": null}, {"id": 10846834003, "name": "Pfeiffer University", "priority": 1012, "external_id": null}, {"id": 10846835003, "name": "Philadelphia University", "priority": 1013, "external_id": null}, {"id": 10846836003, "name": "Philander Smith College", "priority": 1014, "external_id": null}, {"id": 10846837003, "name": "Piedmont College", "priority": 1015, "external_id": null}, {"id": 10846838003, "name": "Pine Manor College", "priority": 1016, "external_id": null}, {"id": 10846839003, "name": "Pittsburg State University", "priority": 1017, "external_id": null}, {"id": 10846840003, "name": "Pitzer College", "priority": 1018, "external_id": null}, {"id": 10846841003, "name": "Plaza College", "priority": 1019, "external_id": null}, {"id": 10846842003, "name": "Plymouth State University", "priority": 1020, "external_id": null}, {"id": 10846843003, "name": "Point Loma Nazarene University", "priority": 1021, "external_id": null}, {"id": 10846844003, "name": "Point Park University", "priority": 1022, "external_id": null}, {"id": 10846845003, "name": "Point University", "priority": 1023, "external_id": null}, {"id": 10846846003, "name": "Polytechnic Institute of New York University", "priority": 1024, "external_id": null}, {"id": 10846847003, "name": "Pomona College", "priority": 1025, "external_id": null}, {"id": 10846848003, "name": "Pontifical Catholic University of Puerto Rico", "priority": 1026, "external_id": null}, {"id": 10846849003, "name": "Pontifical College Josephinum", "priority": 1027, "external_id": null}, {"id": 10846850003, "name": "Post University", "priority": 1028, "external_id": null}, {"id": 10846851003, "name": "Potomac College", "priority": 1029, "external_id": null}, {"id": 10846852003, "name": "Pratt Institute", "priority": 1030, "external_id": null}, {"id": 10846853003, "name": "Prescott College", "priority": 1031, "external_id": null}, {"id": 10846854003, "name": "Presentation College", "priority": 1032, "external_id": null}, {"id": 10846855003, "name": "Principia College", "priority": 1033, "external_id": null}, {"id": 10846856003, "name": "Providence College", "priority": 1034, "external_id": null}, {"id": 10846857003, "name": "Puerto Rico Conservatory of Music", "priority": 1035, "external_id": null}, {"id": 10846858003, "name": "Purchase College - SUNY", "priority": 1036, "external_id": null}, {"id": 10846859003, "name": "Purdue University - Calumet", "priority": 1037, "external_id": null}, {"id": 10846860003, "name": "Purdue University - North Central", "priority": 1038, "external_id": null}, {"id": 10846861003, "name": "Queens University of Charlotte", "priority": 1039, "external_id": null}, {"id": 10846862003, "name": "Oklahoma State University", "priority": 1040, "external_id": null}, {"id": 10846863003, "name": "Oregon State University", "priority": 1041, "external_id": null}, {"id": 10846864003, "name": "Portland State University", "priority": 1042, "external_id": null}, {"id": 10846865003, "name": "Old Dominion University", "priority": 1043, "external_id": null}, {"id": 10846866003, "name": "Prairie View A&M University", "priority": 1044, "external_id": null}, {"id": 10846867003, "name": "Presbyterian College", "priority": 1045, "external_id": null}, {"id": 10846868003, "name": "Purdue University - West Lafayette", "priority": 1046, "external_id": null}, {"id": 10846869003, "name": "Ohio University", "priority": 1047, "external_id": null}, {"id": 10846870003, "name": "Princeton University", "priority": 1048, "external_id": null}, {"id": 10846871003, "name": "Quincy University", "priority": 1049, "external_id": null}, {"id": 10846872003, "name": "Quinnipiac University", "priority": 1050, "external_id": null}, {"id": 10846873003, "name": "Radford University", "priority": 1051, "external_id": null}, {"id": 10846874003, "name": "Ramapo College of New Jersey", "priority": 1052, "external_id": null}, {"id": 10846875003, "name": "Randolph College", "priority": 1053, "external_id": null}, {"id": 10846876003, "name": "Randolph-Macon College", "priority": 1054, "external_id": null}, {"id": 10846877003, "name": "Ranken Technical College", "priority": 1055, "external_id": null}, {"id": 10846878003, "name": "Reed College", "priority": 1056, "external_id": null}, {"id": 10846879003, "name": "Regent University", "priority": 1057, "external_id": null}, {"id": 10846880003, "name": "Regent's American College London", "priority": 1058, "external_id": null}, {"id": 10846881003, "name": "Regis College", "priority": 1059, "external_id": null}, {"id": 10846882003, "name": "Regis University", "priority": 1060, "external_id": null}, {"id": 10846883003, "name": "Reinhardt University", "priority": 1061, "external_id": null}, {"id": 10846884003, "name": "Rensselaer Polytechnic Institute", "priority": 1062, "external_id": null}, {"id": 10846885003, "name": "Research College of Nursing", "priority": 1063, "external_id": null}, {"id": 10846886003, "name": "Resurrection University", "priority": 1064, "external_id": null}, {"id": 10846887003, "name": "Rhode Island College", "priority": 1065, "external_id": null}, {"id": 10846888003, "name": "Rhode Island School of Design", "priority": 1066, "external_id": null}, {"id": 10846889003, "name": "Rhodes College", "priority": 1067, "external_id": null}, {"id": 10846890003, "name": "Richard Stockton College of New Jersey", "priority": 1068, "external_id": null}, {"id": 10846891003, "name": "Richmond - The American International University in London", "priority": 1069, "external_id": null}, {"id": 10846892003, "name": "Rider University", "priority": 1070, "external_id": null}, {"id": 10846893003, "name": "Ringling College of Art and Design", "priority": 1071, "external_id": null}, {"id": 10846894003, "name": "Ripon College", "priority": 1072, "external_id": null}, {"id": 10846895003, "name": "Rivier University", "priority": 1073, "external_id": null}, {"id": 10846896003, "name": "Roanoke College", "priority": 1074, "external_id": null}, {"id": 10846897003, "name": "Robert B. Miller College", "priority": 1075, "external_id": null}, {"id": 10846898003, "name": "Roberts Wesleyan College", "priority": 1076, "external_id": null}, {"id": 10846899003, "name": "Rochester College", "priority": 1077, "external_id": null}, {"id": 10846900003, "name": "Rochester Institute of Technology", "priority": 1078, "external_id": null}, {"id": 10846901003, "name": "Rockford University", "priority": 1079, "external_id": null}, {"id": 10846902003, "name": "Rockhurst University", "priority": 1080, "external_id": null}, {"id": 10846903003, "name": "Rocky Mountain College", "priority": 1081, "external_id": null}, {"id": 10846904003, "name": "Rocky Mountain College of Art and Design", "priority": 1082, "external_id": null}, {"id": 10846905003, "name": "Roger Williams University", "priority": 1083, "external_id": null}, {"id": 10846906003, "name": "Rogers State University", "priority": 1084, "external_id": null}, {"id": 10846907003, "name": "Rollins College", "priority": 1085, "external_id": null}, {"id": 10846908003, "name": "Roosevelt University", "priority": 1086, "external_id": null}, {"id": 10846909003, "name": "Rosalind Franklin University of Medicine and Science", "priority": 1087, "external_id": null}, {"id": 10846910003, "name": "Rose-Hulman Institute of Technology", "priority": 1088, "external_id": null}, {"id": 10846911003, "name": "Rosemont College", "priority": 1089, "external_id": null}, {"id": 10846912003, "name": "Rowan University", "priority": 1090, "external_id": null}, {"id": 10846913003, "name": "Rush University", "priority": 1091, "external_id": null}, {"id": 10846914003, "name": "Rust College", "priority": 1092, "external_id": null}, {"id": 10846915003, "name": "Rutgers, the State University of New Jersey - Camden", "priority": 1093, "external_id": null}, {"id": 10846916003, "name": "Rutgers, the State University of New Jersey - Newark", "priority": 1094, "external_id": null}, {"id": 10846917003, "name": "Ryerson University", "priority": 1095, "external_id": null}, {"id": 10846918003, "name": "Sacred Heart Major Seminary", "priority": 1096, "external_id": null}, {"id": 10846919003, "name": "Saginaw Valley State University", "priority": 1097, "external_id": null}, {"id": 10846920003, "name": "Salem College", "priority": 1098, "external_id": null}, {"id": 10846921003, "name": "Salem International University", "priority": 1099, "external_id": null}, {"id": 10846922003, "name": "Salem State University", "priority": 1100, "external_id": null}, {"id": 10846923003, "name": "Salisbury University", "priority": 1101, "external_id": null}, {"id": 10846924003, "name": "Salish Kootenai College", "priority": 1102, "external_id": null}, {"id": 10846925003, "name": "Salve Regina University", "priority": 1103, "external_id": null}, {"id": 10846926003, "name": "Samuel Merritt University", "priority": 1104, "external_id": null}, {"id": 10846927003, "name": "San Diego Christian College", "priority": 1105, "external_id": null}, {"id": 10846928003, "name": "San Francisco Art Institute", "priority": 1106, "external_id": null}, {"id": 10846929003, "name": "San Francisco Conservatory of Music", "priority": 1107, "external_id": null}, {"id": 10846930003, "name": "San Francisco State University", "priority": 1108, "external_id": null}, {"id": 10846931003, "name": "Sanford College of Nursing", "priority": 1109, "external_id": null}, {"id": 10846932003, "name": "Santa Clara University", "priority": 1110, "external_id": null}, {"id": 10846933003, "name": "Santa Fe University of Art and Design", "priority": 1111, "external_id": null}, {"id": 10846934003, "name": "Sarah Lawrence College", "priority": 1112, "external_id": null}, {"id": 10846935003, "name": "Savannah College of Art and Design", "priority": 1113, "external_id": null}, {"id": 10846936003, "name": "School of the Art Institute of Chicago", "priority": 1114, "external_id": null}, {"id": 10846937003, "name": "School of Visual Arts", "priority": 1115, "external_id": null}, {"id": 10846938003, "name": "Schreiner University", "priority": 1116, "external_id": null}, {"id": 10846939003, "name": "Scripps College", "priority": 1117, "external_id": null}, {"id": 10846940003, "name": "Seattle Pacific University", "priority": 1118, "external_id": null}, {"id": 10846941003, "name": "Seattle University", "priority": 1119, "external_id": null}, {"id": 10846942003, "name": "Seton Hall University", "priority": 1120, "external_id": null}, {"id": 10846943003, "name": "Seton Hill University", "priority": 1121, "external_id": null}, {"id": 10846944003, "name": "Sewanee - University of the South", "priority": 1122, "external_id": null}, {"id": 10846945003, "name": "Shaw University", "priority": 1123, "external_id": null}, {"id": 10846946003, "name": "Shawnee State University", "priority": 1124, "external_id": null}, {"id": 10846947003, "name": "Shenandoah University", "priority": 1125, "external_id": null}, {"id": 10846948003, "name": "Shepherd University", "priority": 1126, "external_id": null}, {"id": 10846949003, "name": "Shimer College", "priority": 1127, "external_id": null}, {"id": 10846950003, "name": "Sacred Heart University", "priority": 1128, "external_id": null}, {"id": 10846951003, "name": "Robert Morris University", "priority": 1129, "external_id": null}, {"id": 10846952003, "name": "Sam Houston State University", "priority": 1130, "external_id": null}, {"id": 10846953003, "name": "Samford University", "priority": 1131, "external_id": null}, {"id": 10846954003, "name": "Savannah State University", "priority": 1132, "external_id": null}, {"id": 10846955003, "name": "San Jose State University", "priority": 1133, "external_id": null}, {"id": 10846956003, "name": "Rutgers, the State University of New Jersey - New Brunswick", "priority": 1134, "external_id": null}, {"id": 10846957003, "name": "San Diego State University", "priority": 1135, "external_id": null}, {"id": 10846958003, "name": "Shippensburg University of Pennsylvania", "priority": 1136, "external_id": null}, {"id": 10846959003, "name": "Shorter University", "priority": 1137, "external_id": null}, {"id": 10846960003, "name": "Siena College", "priority": 1138, "external_id": null}, {"id": 10846961003, "name": "Siena Heights University", "priority": 1139, "external_id": null}, {"id": 10846962003, "name": "Sierra Nevada College", "priority": 1140, "external_id": null}, {"id": 10846963003, "name": "Silver Lake College", "priority": 1141, "external_id": null}, {"id": 10846964003, "name": "Simmons College", "priority": 1142, "external_id": null}, {"id": 10846965003, "name": "Simon Fraser University", "priority": 1143, "external_id": null}, {"id": 10846966003, "name": "Simpson College", "priority": 1144, "external_id": null}, {"id": 10846967003, "name": "Simpson University", "priority": 1145, "external_id": null}, {"id": 10846968003, "name": "Sinte Gleska University", "priority": 1146, "external_id": null}, {"id": 10846969003, "name": "Sitting Bull College", "priority": 1147, "external_id": null}, {"id": 10846970003, "name": "Skidmore College", "priority": 1148, "external_id": null}, {"id": 10846971003, "name": "Slippery Rock University of Pennsylvania", "priority": 1149, "external_id": null}, {"id": 10846972003, "name": "Smith College", "priority": 1150, "external_id": null}, {"id": 10846973003, "name": "Sojourner-Douglass College", "priority": 1151, "external_id": null}, {"id": 10846974003, "name": "Soka University of America", "priority": 1152, "external_id": null}, {"id": 10846975003, "name": "Sonoma State University", "priority": 1153, "external_id": null}, {"id": 10846976003, "name": "South College", "priority": 1154, "external_id": null}, {"id": 10846977003, "name": "South Dakota School of Mines and Technology", "priority": 1155, "external_id": null}, {"id": 10846978003, "name": "South Seattle Community College", "priority": 1156, "external_id": null}, {"id": 10846979003, "name": "South Texas College", "priority": 1157, "external_id": null}, {"id": 10846980003, "name": "South University", "priority": 1158, "external_id": null}, {"id": 10846981003, "name": "Southeastern Oklahoma State University", "priority": 1159, "external_id": null}, {"id": 10846982003, "name": "Southeastern University", "priority": 1160, "external_id": null}, {"id": 10846983003, "name": "Southern Adventist University", "priority": 1161, "external_id": null}, {"id": 10846984003, "name": "Southern Arkansas University", "priority": 1162, "external_id": null}, {"id": 10846985003, "name": "Southern Baptist Theological Seminary", "priority": 1163, "external_id": null}, {"id": 10846986003, "name": "Southern California Institute of Architecture", "priority": 1164, "external_id": null}, {"id": 10846987003, "name": "Southern Connecticut State University", "priority": 1165, "external_id": null}, {"id": 10846988003, "name": "Southern Illinois University - Edwardsville", "priority": 1166, "external_id": null}, {"id": 10846989003, "name": "Southern Nazarene University", "priority": 1167, "external_id": null}, {"id": 10846990003, "name": "Southern New Hampshire University", "priority": 1168, "external_id": null}, {"id": 10846991003, "name": "Southern Oregon University", "priority": 1169, "external_id": null}, {"id": 10846992003, "name": "Southern Polytechnic State University", "priority": 1170, "external_id": null}, {"id": 10846993003, "name": "Southern University - New Orleans", "priority": 1171, "external_id": null}, {"id": 10846994003, "name": "Southern Vermont College", "priority": 1172, "external_id": null}, {"id": 10846995003, "name": "Southern Wesleyan University", "priority": 1173, "external_id": null}, {"id": 10846996003, "name": "Southwest Baptist University", "priority": 1174, "external_id": null}, {"id": 10846997003, "name": "Southwest Minnesota State University", "priority": 1175, "external_id": null}, {"id": 10846998003, "name": "Southwest University of Visual Arts", "priority": 1176, "external_id": null}, {"id": 10846999003, "name": "Southwestern Adventist University", "priority": 1177, "external_id": null}, {"id": 10847000003, "name": "Southwestern Assemblies of God University", "priority": 1178, "external_id": null}, {"id": 10847001003, "name": "Southwestern Christian College", "priority": 1179, "external_id": null}, {"id": 10847002003, "name": "Southwestern Christian University", "priority": 1180, "external_id": null}, {"id": 10847003003, "name": "Southwestern College", "priority": 1181, "external_id": null}, {"id": 10847004003, "name": "Southwestern Oklahoma State University", "priority": 1182, "external_id": null}, {"id": 10847005003, "name": "Southwestern University", "priority": 1183, "external_id": null}, {"id": 10847006003, "name": "Spalding University", "priority": 1184, "external_id": null}, {"id": 10847007003, "name": "Spelman College", "priority": 1185, "external_id": null}, {"id": 10847008003, "name": "Spring Arbor University", "priority": 1186, "external_id": null}, {"id": 10847009003, "name": "Spring Hill College", "priority": 1187, "external_id": null}, {"id": 10847010003, "name": "Springfield College", "priority": 1188, "external_id": null}, {"id": 10847011003, "name": "St. Ambrose University", "priority": 1189, "external_id": null}, {"id": 10847012003, "name": "St. Anselm College", "priority": 1190, "external_id": null}, {"id": 10847013003, "name": "St. Anthony College of Nursing", "priority": 1191, "external_id": null}, {"id": 10847014003, "name": "St. Augustine College", "priority": 1192, "external_id": null}, {"id": 10847015003, "name": "St. Augustine's University", "priority": 1193, "external_id": null}, {"id": 10847016003, "name": "St. Bonaventure University", "priority": 1194, "external_id": null}, {"id": 10847017003, "name": "St. Catharine College", "priority": 1195, "external_id": null}, {"id": 10847018003, "name": "St. Catherine University", "priority": 1196, "external_id": null}, {"id": 10847019003, "name": "St. Charles Borromeo Seminary", "priority": 1197, "external_id": null}, {"id": 10847020003, "name": "St. Cloud State University", "priority": 1198, "external_id": null}, {"id": 10847021003, "name": "St. Edward's University", "priority": 1199, "external_id": null}, {"id": 10847022003, "name": "St. Francis College", "priority": 1200, "external_id": null}, {"id": 10847023003, "name": "St. Francis Medical Center College of Nursing", "priority": 1201, "external_id": null}, {"id": 10847024003, "name": "St. Gregory's University", "priority": 1202, "external_id": null}, {"id": 10847025003, "name": "St. John Fisher College", "priority": 1203, "external_id": null}, {"id": 10847026003, "name": "St. John Vianney College Seminary", "priority": 1204, "external_id": null}, {"id": 10847027003, "name": "St. John's College", "priority": 1205, "external_id": null}, {"id": 10847028003, "name": "St. John's University", "priority": 1206, "external_id": null}, {"id": 10847029003, "name": "St. Joseph Seminary College", "priority": 1207, "external_id": null}, {"id": 10847030003, "name": "St. Joseph's College", "priority": 1208, "external_id": null}, {"id": 10847031003, "name": "St. Joseph's College New York", "priority": 1209, "external_id": null}, {"id": 10847032003, "name": "St. Joseph's University", "priority": 1210, "external_id": null}, {"id": 10847033003, "name": "St. Lawrence University", "priority": 1211, "external_id": null}, {"id": 10847034003, "name": "St. Leo University", "priority": 1212, "external_id": null}, {"id": 10847035003, "name": "Southern University and A&M College", "priority": 1213, "external_id": null}, {"id": 10847036003, "name": "Southern Methodist University", "priority": 1214, "external_id": null}, {"id": 10847037003, "name": "Southeast Missouri State University", "priority": 1215, "external_id": null}, {"id": 10847038003, "name": "Southern Utah University", "priority": 1216, "external_id": null}, {"id": 10847039003, "name": "South Dakota State University", "priority": 1217, "external_id": null}, {"id": 10847040003, "name": "St. Francis University", "priority": 1218, "external_id": null}, {"id": 10847041003, "name": "Southeastern Louisiana University", "priority": 1219, "external_id": null}, {"id": 10847042003, "name": "Southern Illinois University - Carbondale", "priority": 1220, "external_id": null}, {"id": 10847043003, "name": "St. Louis College of Pharmacy", "priority": 1221, "external_id": null}, {"id": 10847044003, "name": "St. Louis University", "priority": 1222, "external_id": null}, {"id": 10847045003, "name": "St. Luke's College of Health Sciences", "priority": 1223, "external_id": null}, {"id": 10847046003, "name": "St. Martin's University", "priority": 1224, "external_id": null}, {"id": 10847047003, "name": "St. Mary's College", "priority": 1225, "external_id": null}, {"id": 10847048003, "name": "St. Mary's College of California", "priority": 1226, "external_id": null}, {"id": 10847049003, "name": "St. Mary's College of Maryland", "priority": 1227, "external_id": null}, {"id": 10847050003, "name": "St. Mary's Seminary and University", "priority": 1228, "external_id": null}, {"id": 10847051003, "name": "St. Mary's University of Minnesota", "priority": 1229, "external_id": null}, {"id": 10847052003, "name": "St. Mary's University of San Antonio", "priority": 1230, "external_id": null}, {"id": 10847053003, "name": "St. Mary-of-the-Woods College", "priority": 1231, "external_id": null}, {"id": 10847054003, "name": "St. Michael's College", "priority": 1232, "external_id": null}, {"id": 10847055003, "name": "St. Norbert College", "priority": 1233, "external_id": null}, {"id": 10847056003, "name": "St. Olaf College", "priority": 1234, "external_id": null}, {"id": 10847057003, "name": "St. Paul's College", "priority": 1235, "external_id": null}, {"id": 10847058003, "name": "St. Peter's University", "priority": 1236, "external_id": null}, {"id": 10847059003, "name": "St. Petersburg College", "priority": 1237, "external_id": null}, {"id": 10847060003, "name": "St. Thomas Aquinas College", "priority": 1238, "external_id": null}, {"id": 10847061003, "name": "St. Thomas University", "priority": 1239, "external_id": null}, {"id": 10847062003, "name": "St. Vincent College", "priority": 1240, "external_id": null}, {"id": 10847063003, "name": "St. Xavier University", "priority": 1241, "external_id": null}, {"id": 10847064003, "name": "Stephens College", "priority": 1242, "external_id": null}, {"id": 10847065003, "name": "Sterling College", "priority": 1243, "external_id": null}, {"id": 10847066003, "name": "Stevens Institute of Technology", "priority": 1244, "external_id": null}, {"id": 10847067003, "name": "Stevenson University", "priority": 1245, "external_id": null}, {"id": 10847068003, "name": "Stillman College", "priority": 1246, "external_id": null}, {"id": 10847069003, "name": "Stonehill College", "priority": 1247, "external_id": null}, {"id": 10847070003, "name": "Strayer University", "priority": 1248, "external_id": null}, {"id": 10847071003, "name": "Suffolk University", "priority": 1249, "external_id": null}, {"id": 10847072003, "name": "Sul Ross State University", "priority": 1250, "external_id": null}, {"id": 10847073003, "name": "Sullivan University", "priority": 1251, "external_id": null}, {"id": 10847074003, "name": "SUNY Buffalo State", "priority": 1252, "external_id": null}, {"id": 10847075003, "name": "SUNY College of Agriculture and Technology - Cobleskill", "priority": 1253, "external_id": null}, {"id": 10847076003, "name": "SUNY College of Environmental Science and Forestry", "priority": 1254, "external_id": null}, {"id": 10847077003, "name": "SUNY College of Technology - Alfred", "priority": 1255, "external_id": null}, {"id": 10847078003, "name": "SUNY College of Technology - Canton", "priority": 1256, "external_id": null}, {"id": 10847079003, "name": "SUNY College of Technology - Delhi", "priority": 1257, "external_id": null}, {"id": 10847080003, "name": "SUNY College - Cortland", "priority": 1258, "external_id": null}, {"id": 10847081003, "name": "SUNY College - Old Westbury", "priority": 1259, "external_id": null}, {"id": 10847082003, "name": "SUNY College - Oneonta", "priority": 1260, "external_id": null}, {"id": 10847083003, "name": "SUNY College - Potsdam", "priority": 1261, "external_id": null}, {"id": 10847084003, "name": "SUNY Downstate Medical Center", "priority": 1262, "external_id": null}, {"id": 10847085003, "name": "SUNY Empire State College", "priority": 1263, "external_id": null}, {"id": 10847086003, "name": "SUNY Institute of Technology - Utica/Rome", "priority": 1264, "external_id": null}, {"id": 10847087003, "name": "SUNY Maritime College", "priority": 1265, "external_id": null}, {"id": 10847088003, "name": "SUNY Upstate Medical University", "priority": 1266, "external_id": null}, {"id": 10847089003, "name": "SUNY - Fredonia", "priority": 1267, "external_id": null}, {"id": 10847090003, "name": "SUNY - Geneseo", "priority": 1268, "external_id": null}, {"id": 10847091003, "name": "SUNY - New Paltz", "priority": 1269, "external_id": null}, {"id": 10847092003, "name": "SUNY - Oswego", "priority": 1270, "external_id": null}, {"id": 10847093003, "name": "SUNY - Plattsburgh", "priority": 1271, "external_id": null}, {"id": 10847094003, "name": "Swarthmore College", "priority": 1272, "external_id": null}, {"id": 10847095003, "name": "Sweet Briar College", "priority": 1273, "external_id": null}, {"id": 10847096003, "name": "Tabor College", "priority": 1274, "external_id": null}, {"id": 10847097003, "name": "Talladega College", "priority": 1275, "external_id": null}, {"id": 10847098003, "name": "Tarleton State University", "priority": 1276, "external_id": null}, {"id": 10847099003, "name": "Taylor University", "priority": 1277, "external_id": null}, {"id": 10847100003, "name": "Tennessee Wesleyan College", "priority": 1278, "external_id": null}, {"id": 10847101003, "name": "Texas A&M International University", "priority": 1279, "external_id": null}, {"id": 10847102003, "name": "Texas A&M University - Commerce", "priority": 1280, "external_id": null}, {"id": 10847103003, "name": "Texas A&M University - Corpus Christi", "priority": 1281, "external_id": null}, {"id": 10847104003, "name": "Texas A&M University - Galveston", "priority": 1282, "external_id": null}, {"id": 10847105003, "name": "Texas A&M University - Kingsville", "priority": 1283, "external_id": null}, {"id": 10847106003, "name": "Texas A&M University - Texarkana", "priority": 1284, "external_id": null}, {"id": 10847107003, "name": "Texas College", "priority": 1285, "external_id": null}, {"id": 10847108003, "name": "Texas Lutheran University", "priority": 1286, "external_id": null}, {"id": 10847109003, "name": "Bucknell University", "priority": 1287, "external_id": null}, {"id": 10847110003, "name": "Butler University", "priority": 1288, "external_id": null}, {"id": 10847111003, "name": "Stephen F. Austin State University", "priority": 1289, "external_id": null}, {"id": 10847112003, "name": "Texas A&M University - College Station", "priority": 1290, "external_id": null}, {"id": 10847113003, "name": "Stanford University", "priority": 1291, "external_id": null}, {"id": 10847114003, "name": "Stetson University", "priority": 1292, "external_id": null}, {"id": 10847115003, "name": "Stony Brook University - SUNY", "priority": 1293, "external_id": null}, {"id": 10847116003, "name": "Syracuse University", "priority": 1294, "external_id": null}, {"id": 10847117003, "name": "Texas Christian University", "priority": 1295, "external_id": null}, {"id": 10847118003, "name": "Temple University", "priority": 1296, "external_id": null}, {"id": 10847119003, "name": "Clemson University", "priority": 1297, "external_id": null}, {"id": 10847120003, "name": "Texas Southern University", "priority": 1298, "external_id": null}, {"id": 10847121003, "name": "Austin Peay State University", "priority": 1299, "external_id": null}, {"id": 10847122003, "name": "Tennessee State University", "priority": 1300, "external_id": null}, {"id": 10847123003, "name": "Ball State University", "priority": 1301, "external_id": null}, {"id": 10847124003, "name": "Texas Tech University Health Sciences Center", "priority": 1302, "external_id": null}, {"id": 10847125003, "name": "Texas Wesleyan University", "priority": 1303, "external_id": null}, {"id": 10847126003, "name": "Texas Woman's University", "priority": 1304, "external_id": null}, {"id": 10847127003, "name": "The Catholic University of America", "priority": 1305, "external_id": null}, {"id": 10847128003, "name": "The Sage Colleges", "priority": 1306, "external_id": null}, {"id": 10847129003, "name": "Thiel College", "priority": 1307, "external_id": null}, {"id": 10847130003, "name": "Thomas Aquinas College", "priority": 1308, "external_id": null}, {"id": 10847131003, "name": "Thomas College", "priority": 1309, "external_id": null}, {"id": 10847132003, "name": "Thomas Edison State College", "priority": 1310, "external_id": null}, {"id": 10847133003, "name": "Thomas Jefferson University", "priority": 1311, "external_id": null}, {"id": 10847134003, "name": "Thomas More College", "priority": 1312, "external_id": null}, {"id": 10847135003, "name": "Thomas More College of Liberal Arts", "priority": 1313, "external_id": null}, {"id": 10847136003, "name": "Thomas University", "priority": 1314, "external_id": null}, {"id": 10847137003, "name": "Tiffin University", "priority": 1315, "external_id": null}, {"id": 10847138003, "name": "Tilburg University", "priority": 1316, "external_id": null}, {"id": 10847139003, "name": "Toccoa Falls College", "priority": 1317, "external_id": null}, {"id": 10847140003, "name": "Tougaloo College", "priority": 1318, "external_id": null}, {"id": 10847141003, "name": "Touro College", "priority": 1319, "external_id": null}, {"id": 10847142003, "name": "Transylvania University", "priority": 1320, "external_id": null}, {"id": 10847143003, "name": "Trent University", "priority": 1321, "external_id": null}, {"id": 10847144003, "name": "Trevecca Nazarene University", "priority": 1322, "external_id": null}, {"id": 10847145003, "name": "Trident University International", "priority": 1323, "external_id": null}, {"id": 10847146003, "name": "Trine University", "priority": 1324, "external_id": null}, {"id": 10847147003, "name": "Trinity Christian College", "priority": 1325, "external_id": null}, {"id": 10847148003, "name": "Trinity College", "priority": 1326, "external_id": null}, {"id": 10847149003, "name": "Trinity College of Nursing & Health Sciences", "priority": 1327, "external_id": null}, {"id": 10847150003, "name": "Trinity International University", "priority": 1328, "external_id": null}, {"id": 10847151003, "name": "Trinity Lutheran College", "priority": 1329, "external_id": null}, {"id": 10847152003, "name": "Trinity University", "priority": 1330, "external_id": null}, {"id": 10847153003, "name": "Trinity Western University", "priority": 1331, "external_id": null}, {"id": 10847154003, "name": "Truett McConnell College", "priority": 1332, "external_id": null}, {"id": 10847155003, "name": "Truman State University", "priority": 1333, "external_id": null}, {"id": 10847156003, "name": "Tufts University", "priority": 1334, "external_id": null}, {"id": 10847157003, "name": "Tusculum College", "priority": 1335, "external_id": null}, {"id": 10847158003, "name": "Tuskegee University", "priority": 1336, "external_id": null}, {"id": 10847159003, "name": "Union College", "priority": 1337, "external_id": null}, {"id": 10847160003, "name": "Union Institute and University", "priority": 1338, "external_id": null}, {"id": 10847161003, "name": "Union University", "priority": 1339, "external_id": null}, {"id": 10847162003, "name": "United States Coast Guard Academy", "priority": 1340, "external_id": null}, {"id": 10847163003, "name": "United States International University - Kenya", "priority": 1341, "external_id": null}, {"id": 10847164003, "name": "United States Merchant Marine Academy", "priority": 1342, "external_id": null}, {"id": 10847165003, "name": "United States Sports Academy", "priority": 1343, "external_id": null}, {"id": 10847166003, "name": "Unity College", "priority": 1344, "external_id": null}, {"id": 10847167003, "name": "Universidad Adventista de las Antillas", "priority": 1345, "external_id": null}, {"id": 10847168003, "name": "Universidad del Este", "priority": 1346, "external_id": null}, {"id": 10847169003, "name": "Universidad del Turabo", "priority": 1347, "external_id": null}, {"id": 10847170003, "name": "Universidad Metropolitana", "priority": 1348, "external_id": null}, {"id": 10847171003, "name": "Universidad Politecnica De Puerto Rico", "priority": 1349, "external_id": null}, {"id": 10847172003, "name": "University of Advancing Technology", "priority": 1350, "external_id": null}, {"id": 10847173003, "name": "University of Alabama - Huntsville", "priority": 1351, "external_id": null}, {"id": 10847174003, "name": "University of Alaska - Anchorage", "priority": 1352, "external_id": null}, {"id": 10847175003, "name": "University of Alaska - Fairbanks", "priority": 1353, "external_id": null}, {"id": 10847176003, "name": "University of Alaska - Southeast", "priority": 1354, "external_id": null}, {"id": 10847177003, "name": "University of Alberta", "priority": 1355, "external_id": null}, {"id": 10847178003, "name": "University of Arkansas for Medical Sciences", "priority": 1356, "external_id": null}, {"id": 10847179003, "name": "University of Arkansas - Fort Smith", "priority": 1357, "external_id": null}, {"id": 10847180003, "name": "University of Arkansas - Little Rock", "priority": 1358, "external_id": null}, {"id": 10847181003, "name": "University of Arkansas - Monticello", "priority": 1359, "external_id": null}, {"id": 10847182003, "name": "University of Baltimore", "priority": 1360, "external_id": null}, {"id": 10847183003, "name": "University of Bridgeport", "priority": 1361, "external_id": null}, {"id": 10847184003, "name": "University of British Columbia", "priority": 1362, "external_id": null}, {"id": 10847185003, "name": "University of Calgary", "priority": 1363, "external_id": null}, {"id": 10847186003, "name": "University of California - Riverside", "priority": 1364, "external_id": null}, {"id": 10847187003, "name": "Holy Cross College", "priority": 1365, "external_id": null}, {"id": 10847188003, "name": "Towson University", "priority": 1366, "external_id": null}, {"id": 10847189003, "name": "United States Military Academy", "priority": 1367, "external_id": null}, {"id": 10847190003, "name": "The Citadel", "priority": 1368, "external_id": null}, {"id": 10847191003, "name": "Troy University", "priority": 1369, "external_id": null}, {"id": 10847192003, "name": "University of California - Davis", "priority": 1370, "external_id": null}, {"id": 10847193003, "name": "Grambling State University", "priority": 1371, "external_id": null}, {"id": 10847194003, "name": "University at Albany - SUNY", "priority": 1372, "external_id": null}, {"id": 10847195003, "name": "University at Buffalo - SUNY", "priority": 1373, "external_id": null}, {"id": 10847196003, "name": "United States Naval Academy", "priority": 1374, "external_id": null}, {"id": 10847197003, "name": "University of Arizona", "priority": 1375, "external_id": null}, {"id": 10847198003, "name": "University of California - Los Angeles", "priority": 1376, "external_id": null}, {"id": 10847199003, "name": "Florida A&M University", "priority": 1377, "external_id": null}, {"id": 10847200003, "name": "Texas State University", "priority": 1378, "external_id": null}, {"id": 10847201003, "name": "University of Alabama - Birmingham", "priority": 1379, "external_id": null}, {"id": 10847202003, "name": "University of California - Santa Cruz", "priority": 1380, "external_id": null}, {"id": 10847203003, "name": "University of Central Missouri", "priority": 1381, "external_id": null}, {"id": 10847204003, "name": "University of Central Oklahoma", "priority": 1382, "external_id": null}, {"id": 10847205003, "name": "University of Charleston", "priority": 1383, "external_id": null}, {"id": 10847206003, "name": "University of Chicago", "priority": 1384, "external_id": null}, {"id": 10847207003, "name": "University of Cincinnati - UC Blue Ash College", "priority": 1385, "external_id": null}, {"id": 10847208003, "name": "University of Colorado - Colorado Springs", "priority": 1386, "external_id": null}, {"id": 10847209003, "name": "University of Colorado - Denver", "priority": 1387, "external_id": null}, {"id": 10847210003, "name": "University of Dallas", "priority": 1388, "external_id": null}, {"id": 10847211003, "name": "University of Denver", "priority": 1389, "external_id": null}, {"id": 10847212003, "name": "University of Detroit Mercy", "priority": 1390, "external_id": null}, {"id": 10847213003, "name": "University of Dubuque", "priority": 1391, "external_id": null}, {"id": 10847214003, "name": "University of Evansville", "priority": 1392, "external_id": null}, {"id": 10847215003, "name": "University of Findlay", "priority": 1393, "external_id": null}, {"id": 10847216003, "name": "University of Great Falls", "priority": 1394, "external_id": null}, {"id": 10847217003, "name": "University of Guam", "priority": 1395, "external_id": null}, {"id": 10847218003, "name": "University of Guelph", "priority": 1396, "external_id": null}, {"id": 10847219003, "name": "University of Hartford", "priority": 1397, "external_id": null}, {"id": 10847220003, "name": "University of Hawaii - Hilo", "priority": 1398, "external_id": null}, {"id": 10847221003, "name": "University of Hawaii - Maui College", "priority": 1399, "external_id": null}, {"id": 10847222003, "name": "University of Hawaii - West Oahu", "priority": 1400, "external_id": null}, {"id": 10847223003, "name": "University of Houston - Clear Lake", "priority": 1401, "external_id": null}, {"id": 10847224003, "name": "University of Houston - Downtown", "priority": 1402, "external_id": null}, {"id": 10847225003, "name": "University of Houston - Victoria", "priority": 1403, "external_id": null}, {"id": 10847226003, "name": "University of Illinois - Chicago", "priority": 1404, "external_id": null}, {"id": 10847227003, "name": "University of Illinois - Springfield", "priority": 1405, "external_id": null}, {"id": 10847228003, "name": "University of Indianapolis", "priority": 1406, "external_id": null}, {"id": 10847229003, "name": "University of Jamestown", "priority": 1407, "external_id": null}, {"id": 10847230003, "name": "University of La Verne", "priority": 1408, "external_id": null}, {"id": 10847231003, "name": "University of Maine - Augusta", "priority": 1409, "external_id": null}, {"id": 10847232003, "name": "University of Maine - Farmington", "priority": 1410, "external_id": null}, {"id": 10847233003, "name": "University of Maine - Fort Kent", "priority": 1411, "external_id": null}, {"id": 10847234003, "name": "University of Maine - Machias", "priority": 1412, "external_id": null}, {"id": 10847235003, "name": "University of Maine - Presque Isle", "priority": 1413, "external_id": null}, {"id": 10847236003, "name": "University of Mary", "priority": 1414, "external_id": null}, {"id": 10847237003, "name": "University of Mary Hardin-Baylor", "priority": 1415, "external_id": null}, {"id": 10847238003, "name": "University of Mary Washington", "priority": 1416, "external_id": null}, {"id": 10847239003, "name": "University of Maryland - Baltimore", "priority": 1417, "external_id": null}, {"id": 10847240003, "name": "University of Maryland - Baltimore County", "priority": 1418, "external_id": null}, {"id": 10847241003, "name": "University of Maryland - Eastern Shore", "priority": 1419, "external_id": null}, {"id": 10847242003, "name": "University of Maryland - University College", "priority": 1420, "external_id": null}, {"id": 10847243003, "name": "University of Massachusetts - Boston", "priority": 1421, "external_id": null}, {"id": 10847244003, "name": "University of Massachusetts - Dartmouth", "priority": 1422, "external_id": null}, {"id": 10847245003, "name": "University of Massachusetts - Lowell", "priority": 1423, "external_id": null}, {"id": 10847246003, "name": "University of Medicine and Dentistry of New Jersey", "priority": 1424, "external_id": null}, {"id": 10847247003, "name": "University of Michigan - Dearborn", "priority": 1425, "external_id": null}, {"id": 10847248003, "name": "University of Michigan - Flint", "priority": 1426, "external_id": null}, {"id": 10847249003, "name": "University of Minnesota - Crookston", "priority": 1427, "external_id": null}, {"id": 10847250003, "name": "University of Minnesota - Duluth", "priority": 1428, "external_id": null}, {"id": 10847251003, "name": "University of Minnesota - Morris", "priority": 1429, "external_id": null}, {"id": 10847252003, "name": "University of Mississippi Medical Center", "priority": 1430, "external_id": null}, {"id": 10847253003, "name": "University of Missouri - Kansas City", "priority": 1431, "external_id": null}, {"id": 10847254003, "name": "University of Missouri - St. Louis", "priority": 1432, "external_id": null}, {"id": 10847255003, "name": "University of Mobile", "priority": 1433, "external_id": null}, {"id": 10847256003, "name": "University of Montana - Western", "priority": 1434, "external_id": null}, {"id": 10847257003, "name": "University of Montevallo", "priority": 1435, "external_id": null}, {"id": 10847258003, "name": "University of Mount Union", "priority": 1436, "external_id": null}, {"id": 10847259003, "name": "University of Nebraska Medical Center", "priority": 1437, "external_id": null}, {"id": 10847260003, "name": "University of Nebraska - Kearney", "priority": 1438, "external_id": null}, {"id": 10847261003, "name": "University of Dayton", "priority": 1439, "external_id": null}, {"id": 10847262003, "name": "University of Delaware", "priority": 1440, "external_id": null}, {"id": 10847263003, "name": "University of Florida", "priority": 1441, "external_id": null}, {"id": 10847264003, "name": "University of Iowa", "priority": 1442, "external_id": null}, {"id": 10847265003, "name": "University of Idaho", "priority": 1443, "external_id": null}, {"id": 10847266003, "name": "University of Kentucky", "priority": 1444, "external_id": null}, {"id": 10847267003, "name": "University of Massachusetts - Amherst", "priority": 1445, "external_id": null}, {"id": 10847268003, "name": "University of Maine", "priority": 1446, "external_id": null}, {"id": 10847269003, "name": "University of Michigan - Ann Arbor", "priority": 1447, "external_id": null}, {"id": 10847270003, "name": "University of Cincinnati", "priority": 1448, "external_id": null}, {"id": 10847271003, "name": "University of Miami", "priority": 1449, "external_id": null}, {"id": 10847272003, "name": "University of Louisiana - Monroe", "priority": 1450, "external_id": null}, {"id": 10847273003, "name": "University of Missouri", "priority": 1451, "external_id": null}, {"id": 10847274003, "name": "University of Mississippi", "priority": 1452, "external_id": null}, {"id": 10847275003, "name": "University of Memphis", "priority": 1453, "external_id": null}, {"id": 10847276003, "name": "University of Houston", "priority": 1454, "external_id": null}, {"id": 10847277003, "name": "University of Colorado - Boulder", "priority": 1455, "external_id": null}, {"id": 10847278003, "name": "University of Nebraska - Omaha", "priority": 1456, "external_id": null}, {"id": 10847279003, "name": "University of New Brunswick", "priority": 1457, "external_id": null}, {"id": 10847280003, "name": "University of New England", "priority": 1458, "external_id": null}, {"id": 10847281003, "name": "University of New Haven", "priority": 1459, "external_id": null}, {"id": 10847282003, "name": "University of New Orleans", "priority": 1460, "external_id": null}, {"id": 10847283003, "name": "University of North Alabama", "priority": 1461, "external_id": null}, {"id": 10847284003, "name": "University of North Carolina School of the Arts", "priority": 1462, "external_id": null}, {"id": 10847285003, "name": "University of North Carolina - Asheville", "priority": 1463, "external_id": null}, {"id": 10847286003, "name": "University of North Carolina - Greensboro", "priority": 1464, "external_id": null}, {"id": 10847287003, "name": "University of North Carolina - Pembroke", "priority": 1465, "external_id": null}, {"id": 10847288003, "name": "University of North Carolina - Wilmington", "priority": 1466, "external_id": null}, {"id": 10847289003, "name": "University of North Florida", "priority": 1467, "external_id": null}, {"id": 10847290003, "name": "University of North Georgia", "priority": 1468, "external_id": null}, {"id": 10847291003, "name": "University of Northwestern Ohio", "priority": 1469, "external_id": null}, {"id": 10847292003, "name": "University of Northwestern - St. Paul", "priority": 1470, "external_id": null}, {"id": 10847293003, "name": "University of Ottawa", "priority": 1471, "external_id": null}, {"id": 10847294003, "name": "University of Phoenix", "priority": 1472, "external_id": null}, {"id": 10847295003, "name": "University of Pikeville", "priority": 1473, "external_id": null}, {"id": 10847296003, "name": "University of Portland", "priority": 1474, "external_id": null}, {"id": 10847297003, "name": "University of Prince Edward Island", "priority": 1475, "external_id": null}, {"id": 10847298003, "name": "University of Puerto Rico - Aguadilla", "priority": 1476, "external_id": null}, {"id": 10847299003, "name": "University of Puerto Rico - Arecibo", "priority": 1477, "external_id": null}, {"id": 10847300003, "name": "University of Puerto Rico - Bayamon", "priority": 1478, "external_id": null}, {"id": 10847301003, "name": "University of Puerto Rico - Cayey", "priority": 1479, "external_id": null}, {"id": 10847302003, "name": "University of Puerto Rico - Humacao", "priority": 1480, "external_id": null}, {"id": 10847303003, "name": "University of Puerto Rico - Mayaguez", "priority": 1481, "external_id": null}, {"id": 10847304003, "name": "University of Puerto Rico - Medical Sciences Campus", "priority": 1482, "external_id": null}, {"id": 10847305003, "name": "University of Puerto Rico - Ponce", "priority": 1483, "external_id": null}, {"id": 10847306003, "name": "University of Puerto Rico - Rio Piedras", "priority": 1484, "external_id": null}, {"id": 10847307003, "name": "University of Puget Sound", "priority": 1485, "external_id": null}, {"id": 10847308003, "name": "University of Redlands", "priority": 1486, "external_id": null}, {"id": 10847309003, "name": "University of Regina", "priority": 1487, "external_id": null}, {"id": 10847310003, "name": "University of Rio Grande", "priority": 1488, "external_id": null}, {"id": 10847311003, "name": "University of Rochester", "priority": 1489, "external_id": null}, {"id": 10847312003, "name": "University of San Francisco", "priority": 1490, "external_id": null}, {"id": 10847313003, "name": "University of Saskatchewan", "priority": 1491, "external_id": null}, {"id": 10847314003, "name": "University of Science and Arts of Oklahoma", "priority": 1492, "external_id": null}, {"id": 10847315003, "name": "University of Scranton", "priority": 1493, "external_id": null}, {"id": 10847316003, "name": "University of Sioux Falls", "priority": 1494, "external_id": null}, {"id": 10847317003, "name": "University of South Carolina - Aiken", "priority": 1495, "external_id": null}, {"id": 10847318003, "name": "University of South Carolina - Beaufort", "priority": 1496, "external_id": null}, {"id": 10847319003, "name": "University of South Carolina - Upstate", "priority": 1497, "external_id": null}, {"id": 10847320003, "name": "University of South Florida - St. Petersburg", "priority": 1498, "external_id": null}, {"id": 10847321003, "name": "University of Southern Indiana", "priority": 1499, "external_id": null}, {"id": 10847322003, "name": "University of Southern Maine", "priority": 1500, "external_id": null}, {"id": 10847323003, "name": "University of St. Francis", "priority": 1501, "external_id": null}, {"id": 10847324003, "name": "University of St. Joseph", "priority": 1502, "external_id": null}, {"id": 10847325003, "name": "University of St. Mary", "priority": 1503, "external_id": null}, {"id": 10847326003, "name": "University of St. Thomas", "priority": 1504, "external_id": null}, {"id": 10847327003, "name": "University of Tampa", "priority": 1505, "external_id": null}, {"id": 10847328003, "name": "University of Texas Health Science Center - Houston", "priority": 1506, "external_id": null}, {"id": 10847329003, "name": "University of Texas Health Science Center - San Antonio", "priority": 1507, "external_id": null}, {"id": 10847330003, "name": "University of Texas Medical Branch - Galveston", "priority": 1508, "external_id": null}, {"id": 10847331003, "name": "University of Texas of the Permian Basin", "priority": 1509, "external_id": null}, {"id": 10847332003, "name": "University of Texas - Arlington", "priority": 1510, "external_id": null}, {"id": 10847333003, "name": "University of Texas - Brownsville", "priority": 1511, "external_id": null}, {"id": 10847334003, "name": "University of Texas - Pan American", "priority": 1512, "external_id": null}, {"id": 10847335003, "name": "University of Oregon", "priority": 1513, "external_id": null}, {"id": 10847336003, "name": "University of New Mexico", "priority": 1514, "external_id": null}, {"id": 10847337003, "name": "University of Pennsylvania", "priority": 1515, "external_id": null}, {"id": 10847338003, "name": "University of North Dakota", "priority": 1516, "external_id": null}, {"id": 10847339003, "name": "University of Nevada - Reno", "priority": 1517, "external_id": null}, {"id": 10847340003, "name": "University of New Hampshire", "priority": 1518, "external_id": null}, {"id": 10847341003, "name": "University of Texas - Austin", "priority": 1519, "external_id": null}, {"id": 10847342003, "name": "University of Southern Mississippi", "priority": 1520, "external_id": null}, {"id": 10847343003, "name": "University of Rhode Island", "priority": 1521, "external_id": null}, {"id": 10847344003, "name": "University of South Dakota", "priority": 1522, "external_id": null}, {"id": 10847345003, "name": "University of Tennessee", "priority": 1523, "external_id": null}, {"id": 10847346003, "name": "University of North Texas", "priority": 1524, "external_id": null}, {"id": 10847347003, "name": "University of North Carolina - Charlotte", "priority": 1525, "external_id": null}, {"id": 10847348003, "name": "University of Texas - San Antonio", "priority": 1526, "external_id": null}, {"id": 10847349003, "name": "University of Notre Dame", "priority": 1527, "external_id": null}, {"id": 10847350003, "name": "University of Southern California", "priority": 1528, "external_id": null}, {"id": 10847351003, "name": "University of Texas - Tyler", "priority": 1529, "external_id": null}, {"id": 10847352003, "name": "University of the Arts", "priority": 1530, "external_id": null}, {"id": 10847353003, "name": "University of the Cumberlands", "priority": 1531, "external_id": null}, {"id": 10847354003, "name": "University of the District of Columbia", "priority": 1532, "external_id": null}, {"id": 10847355003, "name": "University of the Ozarks", "priority": 1533, "external_id": null}, {"id": 10847356003, "name": "University of the Pacific", "priority": 1534, "external_id": null}, {"id": 10847357003, "name": "University of the Sacred Heart", "priority": 1535, "external_id": null}, {"id": 10847358003, "name": "University of the Sciences", "priority": 1536, "external_id": null}, {"id": 10847359003, "name": "University of the Southwest", "priority": 1537, "external_id": null}, {"id": 10847360003, "name": "University of the Virgin Islands", "priority": 1538, "external_id": null}, {"id": 10847361003, "name": "University of the West", "priority": 1539, "external_id": null}, {"id": 10847362003, "name": "University of Toronto", "priority": 1540, "external_id": null}, {"id": 10847363003, "name": "University of Vermont", "priority": 1541, "external_id": null}, {"id": 10847364003, "name": "University of Victoria", "priority": 1542, "external_id": null}, {"id": 10847365003, "name": "University of Virginia - Wise", "priority": 1543, "external_id": null}, {"id": 10847366003, "name": "University of Waterloo", "priority": 1544, "external_id": null}, {"id": 10847367003, "name": "University of West Alabama", "priority": 1545, "external_id": null}, {"id": 10847368003, "name": "University of West Florida", "priority": 1546, "external_id": null}, {"id": 10847369003, "name": "University of West Georgia", "priority": 1547, "external_id": null}, {"id": 10847370003, "name": "University of Windsor", "priority": 1548, "external_id": null}, {"id": 10847371003, "name": "University of Winnipeg", "priority": 1549, "external_id": null}, {"id": 10847372003, "name": "University of Wisconsin - Eau Claire", "priority": 1550, "external_id": null}, {"id": 10847373003, "name": "University of Wisconsin - Green Bay", "priority": 1551, "external_id": null}, {"id": 10847374003, "name": "University of Wisconsin - La Crosse", "priority": 1552, "external_id": null}, {"id": 10847375003, "name": "University of Wisconsin - Milwaukee", "priority": 1553, "external_id": null}, {"id": 10847376003, "name": "University of Wisconsin - Oshkosh", "priority": 1554, "external_id": null}, {"id": 10847377003, "name": "University of Wisconsin - Parkside", "priority": 1555, "external_id": null}, {"id": 10847378003, "name": "University of Wisconsin - Platteville", "priority": 1556, "external_id": null}, {"id": 10847379003, "name": "University of Wisconsin - River Falls", "priority": 1557, "external_id": null}, {"id": 10847380003, "name": "University of Wisconsin - Stevens Point", "priority": 1558, "external_id": null}, {"id": 10847381003, "name": "University of Wisconsin - Stout", "priority": 1559, "external_id": null}, {"id": 10847382003, "name": "University of Wisconsin - Superior", "priority": 1560, "external_id": null}, {"id": 10847383003, "name": "University of Wisconsin - Whitewater", "priority": 1561, "external_id": null}, {"id": 10847384003, "name": "Upper Iowa University", "priority": 1562, "external_id": null}, {"id": 10847385003, "name": "Urbana University", "priority": 1563, "external_id": null}, {"id": 10847386003, "name": "Ursinus College", "priority": 1564, "external_id": null}, {"id": 10847387003, "name": "Ursuline College", "priority": 1565, "external_id": null}, {"id": 10847388003, "name": "Utah Valley University", "priority": 1566, "external_id": null}, {"id": 10847389003, "name": "Utica College", "priority": 1567, "external_id": null}, {"id": 10847390003, "name": "Valdosta State University", "priority": 1568, "external_id": null}, {"id": 10847391003, "name": "Valley City State University", "priority": 1569, "external_id": null}, {"id": 10847392003, "name": "Valley Forge Christian College", "priority": 1570, "external_id": null}, {"id": 10847393003, "name": "VanderCook College of Music", "priority": 1571, "external_id": null}, {"id": 10847394003, "name": "Vanguard University of Southern California", "priority": 1572, "external_id": null}, {"id": 10847395003, "name": "Vassar College", "priority": 1573, "external_id": null}, {"id": 10847396003, "name": "Vaughn College of Aeronautics and Technology", "priority": 1574, "external_id": null}, {"id": 10847397003, "name": "Vermont Technical College", "priority": 1575, "external_id": null}, {"id": 10847398003, "name": "Victory University", "priority": 1576, "external_id": null}, {"id": 10847399003, "name": "Vincennes University", "priority": 1577, "external_id": null}, {"id": 10847400003, "name": "Virginia Commonwealth University", "priority": 1578, "external_id": null}, {"id": 10847401003, "name": "Virginia Intermont College", "priority": 1579, "external_id": null}, {"id": 10847402003, "name": "Virginia State University", "priority": 1580, "external_id": null}, {"id": 10847403003, "name": "Virginia Union University", "priority": 1581, "external_id": null}, {"id": 10847404003, "name": "Virginia Wesleyan College", "priority": 1582, "external_id": null}, {"id": 10847405003, "name": "Viterbo University", "priority": 1583, "external_id": null}, {"id": 10847406003, "name": "Voorhees College", "priority": 1584, "external_id": null}, {"id": 10847407003, "name": "Wabash College", "priority": 1585, "external_id": null}, {"id": 10847408003, "name": "Walden University", "priority": 1586, "external_id": null}, {"id": 10847409003, "name": "Waldorf College", "priority": 1587, "external_id": null}, {"id": 10847410003, "name": "Walla Walla University", "priority": 1588, "external_id": null}, {"id": 10847411003, "name": "Walsh College of Accountancy and Business Administration", "priority": 1589, "external_id": null}, {"id": 10847412003, "name": "Walsh University", "priority": 1590, "external_id": null}, {"id": 10847413003, "name": "Warner Pacific College", "priority": 1591, "external_id": null}, {"id": 10847414003, "name": "Warner University", "priority": 1592, "external_id": null}, {"id": 10847415003, "name": "Warren Wilson College", "priority": 1593, "external_id": null}, {"id": 10847416003, "name": "Wartburg College", "priority": 1594, "external_id": null}, {"id": 10847417003, "name": "Washburn University", "priority": 1595, "external_id": null}, {"id": 10847418003, "name": "Washington Adventist University", "priority": 1596, "external_id": null}, {"id": 10847419003, "name": "Washington and Jefferson College", "priority": 1597, "external_id": null}, {"id": 10847420003, "name": "Washington and Lee University", "priority": 1598, "external_id": null}, {"id": 10847421003, "name": "Washington College", "priority": 1599, "external_id": null}, {"id": 10847422003, "name": "Washington University in St. Louis", "priority": 1600, "external_id": null}, {"id": 10847423003, "name": "Watkins College of Art, Design & Film", "priority": 1601, "external_id": null}, {"id": 10847424003, "name": "Wayland Baptist University", "priority": 1602, "external_id": null}, {"id": 10847425003, "name": "Wayne State College", "priority": 1603, "external_id": null}, {"id": 10847426003, "name": "Wayne State University", "priority": 1604, "external_id": null}, {"id": 10847427003, "name": "Waynesburg University", "priority": 1605, "external_id": null}, {"id": 10847428003, "name": "Valparaiso University", "priority": 1606, "external_id": null}, {"id": 10847429003, "name": "Villanova University", "priority": 1607, "external_id": null}, {"id": 10847430003, "name": "Virginia Tech", "priority": 1608, "external_id": null}, {"id": 10847431003, "name": "Washington State University", "priority": 1609, "external_id": null}, {"id": 10847432003, "name": "University of Toledo", "priority": 1610, "external_id": null}, {"id": 10847433003, "name": "Wagner College", "priority": 1611, "external_id": null}, {"id": 10847434003, "name": "University of Wyoming", "priority": 1612, "external_id": null}, {"id": 10847435003, "name": "University of Wisconsin - Madison", "priority": 1613, "external_id": null}, {"id": 10847436003, "name": "University of Tulsa", "priority": 1614, "external_id": null}, {"id": 10847437003, "name": "Webb Institute", "priority": 1615, "external_id": null}, {"id": 10847438003, "name": "Webber International University", "priority": 1616, "external_id": null}, {"id": 10847439003, "name": "Webster University", "priority": 1617, "external_id": null}, {"id": 10847440003, "name": "Welch College", "priority": 1618, "external_id": null}, {"id": 10847441003, "name": "Wellesley College", "priority": 1619, "external_id": null}, {"id": 10847442003, "name": "Wells College", "priority": 1620, "external_id": null}, {"id": 10847443003, "name": "Wentworth Institute of Technology", "priority": 1621, "external_id": null}, {"id": 10847444003, "name": "Wesley College", "priority": 1622, "external_id": null}, {"id": 10847445003, "name": "Wesleyan College", "priority": 1623, "external_id": null}, {"id": 10847446003, "name": "Wesleyan University", "priority": 1624, "external_id": null}, {"id": 10847447003, "name": "West Chester University of Pennsylvania", "priority": 1625, "external_id": null}, {"id": 10847448003, "name": "West Liberty University", "priority": 1626, "external_id": null}, {"id": 10847449003, "name": "West Texas A&M University", "priority": 1627, "external_id": null}, {"id": 10847450003, "name": "West Virginia State University", "priority": 1628, "external_id": null}, {"id": 10847451003, "name": "West Virginia University Institute of Technology", "priority": 1629, "external_id": null}, {"id": 10847452003, "name": "West Virginia University - Parkersburg", "priority": 1630, "external_id": null}, {"id": 10847453003, "name": "West Virginia Wesleyan College", "priority": 1631, "external_id": null}, {"id": 10847454003, "name": "Western Connecticut State University", "priority": 1632, "external_id": null}, {"id": 10847455003, "name": "Western Governors University", "priority": 1633, "external_id": null}, {"id": 10847456003, "name": "Western International University", "priority": 1634, "external_id": null}, {"id": 10847457003, "name": "Western Nevada College", "priority": 1635, "external_id": null}, {"id": 10847458003, "name": "Western New England University", "priority": 1636, "external_id": null}, {"id": 10847459003, "name": "Western New Mexico University", "priority": 1637, "external_id": null}, {"id": 10847460003, "name": "Western Oregon University", "priority": 1638, "external_id": null}, {"id": 10847461003, "name": "Western State Colorado University", "priority": 1639, "external_id": null}, {"id": 10847462003, "name": "Western University", "priority": 1640, "external_id": null}, {"id": 10847463003, "name": "Western Washington University", "priority": 1641, "external_id": null}, {"id": 10847464003, "name": "Westfield State University", "priority": 1642, "external_id": null}, {"id": 10847465003, "name": "Westminster College", "priority": 1643, "external_id": null}, {"id": 10847466003, "name": "Westmont College", "priority": 1644, "external_id": null}, {"id": 10847467003, "name": "Wheaton College", "priority": 1645, "external_id": null}, {"id": 10847468003, "name": "Wheeling Jesuit University", "priority": 1646, "external_id": null}, {"id": 10847469003, "name": "Wheelock College", "priority": 1647, "external_id": null}, {"id": 10847470003, "name": "Whitman College", "priority": 1648, "external_id": null}, {"id": 10847471003, "name": "Whittier College", "priority": 1649, "external_id": null}, {"id": 10847472003, "name": "Whitworth University", "priority": 1650, "external_id": null}, {"id": 10847473003, "name": "Wichita State University", "priority": 1651, "external_id": null}, {"id": 10847474003, "name": "Widener University", "priority": 1652, "external_id": null}, {"id": 10847475003, "name": "Wilberforce University", "priority": 1653, "external_id": null}, {"id": 10847476003, "name": "Wiley College", "priority": 1654, "external_id": null}, {"id": 10847477003, "name": "Wilkes University", "priority": 1655, "external_id": null}, {"id": 10847478003, "name": "Willamette University", "priority": 1656, "external_id": null}, {"id": 10847479003, "name": "William Carey University", "priority": 1657, "external_id": null}, {"id": 10847480003, "name": "William Jessup University", "priority": 1658, "external_id": null}, {"id": 10847481003, "name": "William Jewell College", "priority": 1659, "external_id": null}, {"id": 10847482003, "name": "William Paterson University of New Jersey", "priority": 1660, "external_id": null}, {"id": 10847483003, "name": "William Peace University", "priority": 1661, "external_id": null}, {"id": 10847484003, "name": "William Penn University", "priority": 1662, "external_id": null}, {"id": 10847485003, "name": "William Woods University", "priority": 1663, "external_id": null}, {"id": 10847486003, "name": "Williams Baptist College", "priority": 1664, "external_id": null}, {"id": 10847487003, "name": "Williams College", "priority": 1665, "external_id": null}, {"id": 10847488003, "name": "Wilmington College", "priority": 1666, "external_id": null}, {"id": 10847489003, "name": "Wilmington University", "priority": 1667, "external_id": null}, {"id": 10847490003, "name": "Wilson College", "priority": 1668, "external_id": null}, {"id": 10847491003, "name": "Wingate University", "priority": 1669, "external_id": null}, {"id": 10847492003, "name": "Winona State University", "priority": 1670, "external_id": null}, {"id": 10847493003, "name": "Winston-Salem State University", "priority": 1671, "external_id": null}, {"id": 10847494003, "name": "Winthrop University", "priority": 1672, "external_id": null}, {"id": 10847495003, "name": "Wisconsin Lutheran College", "priority": 1673, "external_id": null}, {"id": 10847496003, "name": "Wittenberg University", "priority": 1674, "external_id": null}, {"id": 10847497003, "name": "Woodbury University", "priority": 1675, "external_id": null}, {"id": 10847498003, "name": "Worcester Polytechnic Institute", "priority": 1676, "external_id": null}, {"id": 10847499003, "name": "Worcester State University", "priority": 1677, "external_id": null}, {"id": 10847500003, "name": "Wright State University", "priority": 1678, "external_id": null}, {"id": 10847501003, "name": "Xavier University", "priority": 1679, "external_id": null}, {"id": 10847502003, "name": "Xavier University of Louisiana", "priority": 1680, "external_id": null}, {"id": 10847503003, "name": "Yeshiva University", "priority": 1681, "external_id": null}, {"id": 10847504003, "name": "York College", "priority": 1682, "external_id": null}, {"id": 10847505003, "name": "York College of Pennsylvania", "priority": 1683, "external_id": null}, {"id": 10847506003, "name": "York University", "priority": 1684, "external_id": null}, {"id": 10847507003, "name": "University of Cambridge", "priority": 1685, "external_id": null}, {"id": 10847508003, "name": "UCL (University College London)", "priority": 1686, "external_id": null}, {"id": 10847509003, "name": "Imperial College London", "priority": 1687, "external_id": null}, {"id": 10847510003, "name": "University of Oxford", "priority": 1688, "external_id": null}, {"id": 10847511003, "name": "ETH Zurich (Swiss Federal Institute of Technology)", "priority": 1689, "external_id": null}, {"id": 10847512003, "name": "University of Edinburgh", "priority": 1690, "external_id": null}, {"id": 10847513003, "name": "Ecole Polytechnique F\u00e9d\u00e9rale de Lausanne", "priority": 1691, "external_id": null}, {"id": 10847514003, "name": "King's College London (KCL)", "priority": 1692, "external_id": null}, {"id": 10847515003, "name": "National University of Singapore (NUS)", "priority": 1693, "external_id": null}, {"id": 10847516003, "name": "University of Hong Kong", "priority": 1694, "external_id": null}, {"id": 10847517003, "name": "Australian National University", "priority": 1695, "external_id": null}, {"id": 10847518003, "name": "Ecole normale sup\u00e9rieure, Paris", "priority": 1696, "external_id": null}, {"id": 10847519003, "name": "University of Bristol", "priority": 1697, "external_id": null}, {"id": 10847520003, "name": "The University of Melbourne", "priority": 1698, "external_id": null}, {"id": 10847521003, "name": "The University of Tokyo", "priority": 1699, "external_id": null}, {"id": 10847522003, "name": "The University of Manchester", "priority": 1700, "external_id": null}, {"id": 10847523003, "name": "Western Illinois University", "priority": 1701, "external_id": null}, {"id": 10847524003, "name": "Wofford College", "priority": 1702, "external_id": null}, {"id": 10847525003, "name": "Western Carolina University", "priority": 1703, "external_id": null}, {"id": 10847526003, "name": "West Virginia University", "priority": 1704, "external_id": null}, {"id": 10847527003, "name": "Yale University", "priority": 1705, "external_id": null}, {"id": 10847528003, "name": "The Hong Kong University of Science and Technology", "priority": 1706, "external_id": null}, {"id": 10847529003, "name": "Kyoto University", "priority": 1707, "external_id": null}, {"id": 10847530003, "name": "Seoul National University", "priority": 1708, "external_id": null}, {"id": 10847531003, "name": "The University of Sydney", "priority": 1709, "external_id": null}, {"id": 10847532003, "name": "The Chinese University of Hong Kong", "priority": 1710, "external_id": null}, {"id": 10847533003, "name": "Ecole Polytechnique", "priority": 1711, "external_id": null}, {"id": 10847534003, "name": "Nanyang Technological University (NTU)", "priority": 1712, "external_id": null}, {"id": 10847535003, "name": "The University of Queensland", "priority": 1713, "external_id": null}, {"id": 10847536003, "name": "University of Copenhagen", "priority": 1714, "external_id": null}, {"id": 10847537003, "name": "Peking University", "priority": 1715, "external_id": null}, {"id": 10847538003, "name": "Tsinghua University", "priority": 1716, "external_id": null}, {"id": 10847539003, "name": "Ruprecht-Karls-Universit\u00e4t Heidelberg", "priority": 1717, "external_id": null}, {"id": 10847540003, "name": "University of Glasgow", "priority": 1718, "external_id": null}, {"id": 10847541003, "name": "The University of New South Wales", "priority": 1719, "external_id": null}, {"id": 10847542003, "name": "Technische Universit\u00e4t M\u00fcnchen", "priority": 1720, "external_id": null}, {"id": 10847543003, "name": "Osaka University", "priority": 1721, "external_id": null}, {"id": 10847544003, "name": "University of Amsterdam", "priority": 1722, "external_id": null}, {"id": 10847545003, "name": "KAIST - Korea Advanced Institute of Science & Technology", "priority": 1723, "external_id": null}, {"id": 10847546003, "name": "Trinity College Dublin", "priority": 1724, "external_id": null}, {"id": 10847547003, "name": "University of Birmingham", "priority": 1725, "external_id": null}, {"id": 10847548003, "name": "The University of Warwick", "priority": 1726, "external_id": null}, {"id": 10847549003, "name": "Ludwig-Maximilians-Universit\u00e4t M\u00fcnchen", "priority": 1727, "external_id": null}, {"id": 10847550003, "name": "Tokyo Institute of Technology", "priority": 1728, "external_id": null}, {"id": 10847551003, "name": "Lund University", "priority": 1729, "external_id": null}, {"id": 10847552003, "name": "London School of Economics and Political Science (LSE)", "priority": 1730, "external_id": null}, {"id": 10847553003, "name": "Monash University", "priority": 1731, "external_id": null}, {"id": 10847554003, "name": "University of Helsinki", "priority": 1732, "external_id": null}, {"id": 10847555003, "name": "The University of Sheffield", "priority": 1733, "external_id": null}, {"id": 10847556003, "name": "University of Geneva", "priority": 1734, "external_id": null}, {"id": 10847557003, "name": "Leiden University", "priority": 1735, "external_id": null}, {"id": 10847558003, "name": "The University of Nottingham", "priority": 1736, "external_id": null}, {"id": 10847559003, "name": "Tohoku University", "priority": 1737, "external_id": null}, {"id": 10847560003, "name": "KU Leuven", "priority": 1738, "external_id": null}, {"id": 10847561003, "name": "University of Zurich", "priority": 1739, "external_id": null}, {"id": 10847562003, "name": "Uppsala University", "priority": 1740, "external_id": null}, {"id": 10847563003, "name": "Utrecht University", "priority": 1741, "external_id": null}, {"id": 10847564003, "name": "National Taiwan University (NTU)", "priority": 1742, "external_id": null}, {"id": 10847565003, "name": "University of St Andrews", "priority": 1743, "external_id": null}, {"id": 10847566003, "name": "The University of Western Australia", "priority": 1744, "external_id": null}, {"id": 10847567003, "name": "University of Southampton", "priority": 1745, "external_id": null}, {"id": 10847568003, "name": "Fudan University", "priority": 1746, "external_id": null}, {"id": 10847569003, "name": "University of Oslo", "priority": 1747, "external_id": null}, {"id": 10847570003, "name": "Durham University", "priority": 1748, "external_id": null}, {"id": 10847571003, "name": "Aarhus University", "priority": 1749, "external_id": null}, {"id": 10847572003, "name": "Erasmus University Rotterdam", "priority": 1750, "external_id": null}, {"id": 10847573003, "name": "Universit\u00e9 de Montr\u00e9al", "priority": 1751, "external_id": null}, {"id": 10847574003, "name": "The University of Auckland", "priority": 1752, "external_id": null}, {"id": 10847575003, "name": "Delft University of Technology", "priority": 1753, "external_id": null}, {"id": 10847576003, "name": "University of Groningen", "priority": 1754, "external_id": null}, {"id": 10847577003, "name": "University of Leeds", "priority": 1755, "external_id": null}, {"id": 10847578003, "name": "Nagoya University", "priority": 1756, "external_id": null}, {"id": 10847579003, "name": "Universit\u00e4t Freiburg", "priority": 1757, "external_id": null}, {"id": 10847580003, "name": "City University of Hong Kong", "priority": 1758, "external_id": null}, {"id": 10847581003, "name": "The University of Adelaide", "priority": 1759, "external_id": null}, {"id": 10847582003, "name": "Pohang University of Science And Technology (POSTECH)", "priority": 1760, "external_id": null}, {"id": 10847583003, "name": "Freie Universit\u00e4t Berlin", "priority": 1761, "external_id": null}, {"id": 10847584003, "name": "University of Basel", "priority": 1762, "external_id": null}, {"id": 10847585003, "name": "University of Lausanne", "priority": 1763, "external_id": null}, {"id": 10847586003, "name": "Universit\u00e9 Pierre et Marie Curie (UPMC)", "priority": 1764, "external_id": null}, {"id": 10847587003, "name": "Yonsei University", "priority": 1765, "external_id": null}, {"id": 10847588003, "name": "University of York", "priority": 1766, "external_id": null}, {"id": 10847589003, "name": "Queen Mary, University of London (QMUL)", "priority": 1767, "external_id": null}, {"id": 10847590003, "name": "Karlsruhe Institute of Technology (KIT)", "priority": 1768, "external_id": null}, {"id": 10847591003, "name": "KTH, Royal Institute of Technology", "priority": 1769, "external_id": null}, {"id": 10847592003, "name": "Lomonosov Moscow State University", "priority": 1770, "external_id": null}, {"id": 10847593003, "name": "Maastricht University", "priority": 1771, "external_id": null}, {"id": 10847594003, "name": "University of Ghent", "priority": 1772, "external_id": null}, {"id": 10847595003, "name": "Shanghai Jiao Tong University", "priority": 1773, "external_id": null}, {"id": 10847596003, "name": "Humboldt-Universit\u00e4t zu Berlin", "priority": 1774, "external_id": null}, {"id": 10847597003, "name": "Universidade de S\u00e3o Paulo (USP)", "priority": 1775, "external_id": null}, {"id": 10847598003, "name": "Georg-August-Universit\u00e4t G\u00f6ttingen", "priority": 1776, "external_id": null}, {"id": 10847599003, "name": "Newcastle University", "priority": 1777, "external_id": null}, {"id": 10847600003, "name": "University of Liverpool", "priority": 1778, "external_id": null}, {"id": 10847601003, "name": "Kyushu University", "priority": 1779, "external_id": null}, {"id": 10847602003, "name": "Eberhard Karls Universit\u00e4t T\u00fcbingen", "priority": 1780, "external_id": null}, {"id": 10847603003, "name": "Technical University of Denmark", "priority": 1781, "external_id": null}, {"id": 10847604003, "name": "Cardiff University", "priority": 1782, "external_id": null}, {"id": 10847605003, "name": "Universit\u00e9 Catholique de Louvain (UCL)", "priority": 1783, "external_id": null}, {"id": 10847606003, "name": "University College Dublin", "priority": 1784, "external_id": null}, {"id": 10847607003, "name": "McMaster University", "priority": 1785, "external_id": null}, {"id": 10847608003, "name": "Hebrew University of Jerusalem", "priority": 1786, "external_id": null}, {"id": 10847609003, "name": "Radboud University Nijmegen", "priority": 1787, "external_id": null}, {"id": 10847610003, "name": "Hokkaido University", "priority": 1788, "external_id": null}, {"id": 10847611003, "name": "Korea University", "priority": 1789, "external_id": null}, {"id": 10847612003, "name": "University of Cape Town", "priority": 1790, "external_id": null}, {"id": 10847613003, "name": "Rheinisch-Westf\u00e4lische Technische Hochschule Aachen", "priority": 1791, "external_id": null}, {"id": 10847614003, "name": "University of Aberdeen", "priority": 1792, "external_id": null}, {"id": 10847615003, "name": "Wageningen University", "priority": 1793, "external_id": null}, {"id": 10847616003, "name": "University of Bergen", "priority": 1794, "external_id": null}, {"id": 10847617003, "name": "University of Bern", "priority": 1795, "external_id": null}, {"id": 10847618003, "name": "University of Otago", "priority": 1796, "external_id": null}, {"id": 10847619003, "name": "Lancaster University", "priority": 1797, "external_id": null}, {"id": 10847620003, "name": "Eindhoven University of Technology", "priority": 1798, "external_id": null}, {"id": 10847621003, "name": "Ecole Normale Sup\u00e9rieure de Lyon", "priority": 1799, "external_id": null}, {"id": 10847622003, "name": "University of Vienna", "priority": 1800, "external_id": null}, {"id": 10847623003, "name": "The Hong Kong Polytechnic University", "priority": 1801, "external_id": null}, {"id": 10847624003, "name": "Sungkyunkwan University", "priority": 1802, "external_id": null}, {"id": 10847625003, "name": "Rheinische Friedrich-Wilhelms-Universit\u00e4t Bonn", "priority": 1803, "external_id": null}, {"id": 10847626003, "name": "Universidad Nacional Aut\u00f3noma de M\u00e9xico (UNAM)", "priority": 1804, "external_id": null}, {"id": 10847627003, "name": "Zhejiang University", "priority": 1805, "external_id": null}, {"id": 10847628003, "name": "Pontificia Universidad Cat\u00f3lica de Chile", "priority": 1806, "external_id": null}, {"id": 10847629003, "name": "Universiti Malaya (UM)", "priority": 1807, "external_id": null}, {"id": 10847630003, "name": "Universit\u00e9 Libre de Bruxelles (ULB)", "priority": 1808, "external_id": null}, {"id": 10847631003, "name": "University of Exeter", "priority": 1809, "external_id": null}, {"id": 10847632003, "name": "Stockholm University", "priority": 1810, "external_id": null}, {"id": 10847633003, "name": "Queen's University of Belfast", "priority": 1811, "external_id": null}, {"id": 10847634003, "name": "Vrije Universiteit Brussel (VUB)", "priority": 1812, "external_id": null}, {"id": 10847635003, "name": "University of Science and Technology of China", "priority": 1813, "external_id": null}, {"id": 10847636003, "name": "Nanjing University", "priority": 1814, "external_id": null}, {"id": 10847637003, "name": "Universitat Aut\u00f3noma de Barcelona", "priority": 1815, "external_id": null}, {"id": 10847638003, "name": "University of Barcelona", "priority": 1816, "external_id": null}, {"id": 10847639003, "name": "VU University Amsterdam", "priority": 1817, "external_id": null}, {"id": 10847640003, "name": "Technion - Israel Institute of Technology", "priority": 1818, "external_id": null}, {"id": 10847641003, "name": "Technische Universit\u00e4t Berlin", "priority": 1819, "external_id": null}, {"id": 10847642003, "name": "University of Antwerp", "priority": 1820, "external_id": null}, {"id": 10847643003, "name": "Universit\u00e4t Hamburg", "priority": 1821, "external_id": null}, {"id": 10847644003, "name": "University of Bath", "priority": 1822, "external_id": null}, {"id": 10847645003, "name": "University of Bologna", "priority": 1823, "external_id": null}, {"id": 10847646003, "name": "Queen's University, Ontario", "priority": 1824, "external_id": null}, {"id": 10847647003, "name": "Universit\u00e9 Paris-Sud 11", "priority": 1825, "external_id": null}, {"id": 10847648003, "name": "Keio University", "priority": 1826, "external_id": null}, {"id": 10847649003, "name": "University of Sussex", "priority": 1827, "external_id": null}, {"id": 10847650003, "name": "Universidad Aut\u00f3noma de Madrid", "priority": 1828, "external_id": null}, {"id": 10847651003, "name": "Aalto University", "priority": 1829, "external_id": null}, {"id": 10847652003, "name": "Sapienza University of Rome", "priority": 1830, "external_id": null}, {"id": 10847653003, "name": "Tel Aviv University", "priority": 1831, "external_id": null}, {"id": 10847654003, "name": "National Tsing Hua University", "priority": 1832, "external_id": null}, {"id": 10847655003, "name": "Chalmers University of Technology", "priority": 1833, "external_id": null}, {"id": 10847656003, "name": "University of Leicester", "priority": 1834, "external_id": null}, {"id": 10847657003, "name": "Universit\u00e9 Paris Diderot - Paris 7", "priority": 1835, "external_id": null}, {"id": 10847658003, "name": "University of Gothenburg", "priority": 1836, "external_id": null}, {"id": 10847659003, "name": "University of Turku", "priority": 1837, "external_id": null}, {"id": 10847660003, "name": "Universit\u00e4t Frankfurt am Main", "priority": 1838, "external_id": null}, {"id": 10847661003, "name": "Universidad de Buenos Aires", "priority": 1839, "external_id": null}, {"id": 10847662003, "name": "University College Cork", "priority": 1840, "external_id": null}, {"id": 10847663003, "name": "University of Tsukuba", "priority": 1841, "external_id": null}, {"id": 10847664003, "name": "University of Reading", "priority": 1842, "external_id": null}, {"id": 10847665003, "name": "Sciences Po Paris", "priority": 1843, "external_id": null}, {"id": 10847666003, "name": "Universidade Estadual de Campinas", "priority": 1844, "external_id": null}, {"id": 10847667003, "name": "King Fahd University of Petroleum & Minerals", "priority": 1845, "external_id": null}, {"id": 10847668003, "name": "University Complutense Madrid", "priority": 1846, "external_id": null}, {"id": 10847669003, "name": "Universit\u00e9 Paris-Sorbonne (Paris IV)", "priority": 1847, "external_id": null}, {"id": 10847670003, "name": "University of Dundee", "priority": 1848, "external_id": null}, {"id": 10847671003, "name": "Universit\u00e9 Joseph Fourier - Grenoble 1", "priority": 1849, "external_id": null}, {"id": 10847672003, "name": "Waseda University", "priority": 1850, "external_id": null}, {"id": 10847673003, "name": "Indian Institute of Technology Delhi (IITD)", "priority": 1851, "external_id": null}, {"id": 10847674003, "name": "Universidad de Chile", "priority": 1852, "external_id": null}, {"id": 10847675003, "name": "Universit\u00e9 Paris 1 Panth\u00e9on-Sorbonne", "priority": 1853, "external_id": null}, {"id": 10847676003, "name": "Universit\u00e9 de Strasbourg", "priority": 1854, "external_id": null}, {"id": 10847677003, "name": "University of Twente", "priority": 1855, "external_id": null}, {"id": 10847678003, "name": "University of East Anglia (UEA)", "priority": 1856, "external_id": null}, {"id": 10847679003, "name": "National Chiao Tung University", "priority": 1857, "external_id": null}, {"id": 10847680003, "name": "Politecnico di Milano", "priority": 1858, "external_id": null}, {"id": 10847681003, "name": "Charles University", "priority": 1859, "external_id": null}, {"id": 10847682003, "name": "Indian Institute of Technology Bombay (IITB)", "priority": 1860, "external_id": null}, {"id": 10847683003, "name": "University of Milano", "priority": 1861, "external_id": null}, {"id": 10847684003, "name": "Westf\u00e4lische Wilhelms-Universit\u00e4t M\u00fcnster", "priority": 1862, "external_id": null}, {"id": 10847685003, "name": "University of Canterbury", "priority": 1863, "external_id": null}, {"id": 10847686003, "name": "Chulalongkorn University", "priority": 1864, "external_id": null}, {"id": 10847687003, "name": "Saint-Petersburg State University", "priority": 1865, "external_id": null}, {"id": 10847688003, "name": "University of Liege", "priority": 1866, "external_id": null}, {"id": 10847689003, "name": "Universit\u00e4t zu K\u00f6ln", "priority": 1867, "external_id": null}, {"id": 10847690003, "name": "Loughborough University", "priority": 1868, "external_id": null}, {"id": 10847691003, "name": "National Cheng Kung University", "priority": 1869, "external_id": null}, {"id": 10847692003, "name": "Universit\u00e4t Stuttgart", "priority": 1870, "external_id": null}, {"id": 10847693003, "name": "Hanyang University", "priority": 1871, "external_id": null}, {"id": 10847694003, "name": "American University of Beirut (AUB)", "priority": 1872, "external_id": null}, {"id": 10847695003, "name": "Norwegian University of Science And Technology", "priority": 1873, "external_id": null}, {"id": 10847696003, "name": "Beijing Normal University", "priority": 1874, "external_id": null}, {"id": 10847697003, "name": "King Saud University", "priority": 1875, "external_id": null}, {"id": 10847698003, "name": "University of Oulu", "priority": 1876, "external_id": null}, {"id": 10847699003, "name": "Kyung Hee University", "priority": 1877, "external_id": null}, {"id": 10847700003, "name": "University of Strathclyde", "priority": 1878, "external_id": null}, {"id": 10847701003, "name": "Universit\u00e4t Ulm", "priority": 1879, "external_id": null}, {"id": 10847702003, "name": "University of Pisa", "priority": 1880, "external_id": null}, {"id": 10847703003, "name": "Technische Universit\u00e4t Darmstadt", "priority": 1881, "external_id": null}, {"id": 10847704003, "name": "Technische Universit\u00e4t Dresden", "priority": 1882, "external_id": null}, {"id": 10847705003, "name": "Macquarie University", "priority": 1883, "external_id": null}, {"id": 10847706003, "name": "Vienna University of Technology", "priority": 1884, "external_id": null}, {"id": 10847707003, "name": "Royal Holloway University of London", "priority": 1885, "external_id": null}, {"id": 10847708003, "name": "Victoria University of Wellington", "priority": 1886, "external_id": null}, {"id": 10847709003, "name": "University of Padua", "priority": 1887, "external_id": null}, {"id": 10847710003, "name": "Universiti Kebangsaan Malaysia (UKM)", "priority": 1888, "external_id": null}, {"id": 10847711003, "name": "University of Technology, Sydney", "priority": 1889, "external_id": null}, {"id": 10847712003, "name": "Universit\u00e4t Konstanz", "priority": 1890, "external_id": null}, {"id": 10847713003, "name": "Universidad de Los Andes Colombia", "priority": 1891, "external_id": null}, {"id": 10847714003, "name": "Universit\u00e9 Paris Descartes", "priority": 1892, "external_id": null}, {"id": 10847715003, "name": "Tokyo Medical and Dental University", "priority": 1893, "external_id": null}, {"id": 10847716003, "name": "University of Wollongong", "priority": 1894, "external_id": null}, {"id": 10847717003, "name": "Universit\u00e4t Erlangen-N\u00fcrnberg", "priority": 1895, "external_id": null}, {"id": 10847718003, "name": "Queensland University of Technology", "priority": 1896, "external_id": null}, {"id": 10847719003, "name": "Tecnol\u00f3gico de Monterrey (ITESM)", "priority": 1897, "external_id": null}, {"id": 10847720003, "name": "Universit\u00e4t Mannheim", "priority": 1898, "external_id": null}, {"id": 10847721003, "name": "Universitat Pompeu Fabra", "priority": 1899, "external_id": null}, {"id": 10847722003, "name": "Mahidol University", "priority": 1900, "external_id": null}, {"id": 10847723003, "name": "Curtin University", "priority": 1901, "external_id": null}, {"id": 10847724003, "name": "National University of Ireland, Galway", "priority": 1902, "external_id": null}, {"id": 10847725003, "name": "Universidade Federal do Rio de Janeiro", "priority": 1903, "external_id": null}, {"id": 10847726003, "name": "University of Surrey", "priority": 1904, "external_id": null}, {"id": 10847727003, "name": "Hong Kong Baptist University", "priority": 1905, "external_id": null}, {"id": 10847728003, "name": "Ume\u00e5 University", "priority": 1906, "external_id": null}, {"id": 10847729003, "name": "Universit\u00e4t Innsbruck", "priority": 1907, "external_id": null}, {"id": 10847730003, "name": "RMIT University", "priority": 1908, "external_id": null}, {"id": 10847731003, "name": "University of Eastern Finland", "priority": 1909, "external_id": null}, {"id": 10847732003, "name": "Christian-Albrechts-Universit\u00e4t zu Kiel", "priority": 1910, "external_id": null}, {"id": 10847733003, "name": "Indian Institute of Technology Kanpur (IITK)", "priority": 1911, "external_id": null}, {"id": 10847734003, "name": "National Yang Ming University", "priority": 1912, "external_id": null}, {"id": 10847735003, "name": "Johannes Gutenberg Universit\u00e4t Mainz", "priority": 1913, "external_id": null}, {"id": 10847736003, "name": "The University of Newcastle", "priority": 1914, "external_id": null}, {"id": 10847737003, "name": "Al-Farabi Kazakh National University", "priority": 1915, "external_id": null}, {"id": 10847738003, "name": "\u00c9cole des Ponts ParisTech", "priority": 1916, "external_id": null}, {"id": 10847739003, "name": "University of Jyv\u00e4skyl\u00e4", "priority": 1917, "external_id": null}, {"id": 10847740003, "name": "L.N. Gumilyov Eurasian National University", "priority": 1918, "external_id": null}, {"id": 10847741003, "name": "Kobe University", "priority": 1919, "external_id": null}, {"id": 10847742003, "name": "University of Tromso", "priority": 1920, "external_id": null}, {"id": 10847743003, "name": "Hiroshima University", "priority": 1921, "external_id": null}, {"id": 10847744003, "name": "Universit\u00e9 Bordeaux 1, Sciences Technologies", "priority": 1922, "external_id": null}, {"id": 10847745003, "name": "University of Indonesia", "priority": 1923, "external_id": null}, {"id": 10847746003, "name": "Universit\u00e4t Leipzig", "priority": 1924, "external_id": null}, {"id": 10847747003, "name": "University of Southern Denmark", "priority": 1925, "external_id": null}, {"id": 10847748003, "name": "Indian Institute of Technology Madras (IITM)", "priority": 1926, "external_id": null}, {"id": 10847749003, "name": "University of The Witwatersrand", "priority": 1927, "external_id": null}, {"id": 10847750003, "name": "University of Navarra", "priority": 1928, "external_id": null}, {"id": 10847751003, "name": "Universidad Austral - Argentina", "priority": 1929, "external_id": null}, {"id": 10847752003, "name": "Universidad Carlos III de Madrid", "priority": 1930, "external_id": null}, {"id": 10847753003, "name": "Universit\u00e0\u00a1 degli Studi di Roma - Tor Vergata", "priority": 1931, "external_id": null}, {"id": 10847754003, "name": "Pontificia Universidad Cat\u00f3lica Argentina Santa Mar\u00eda de los Buenos Aires", "priority": 1932, "external_id": null}, {"id": 10847755003, "name": "UCA", "priority": 1933, "external_id": null}, {"id": 10847756003, "name": "Julius-Maximilians-Universit\u00e4t W\u00fcrzburg", "priority": 1934, "external_id": null}, {"id": 10847757003, "name": "Universidad Nacional de Colombia", "priority": 1935, "external_id": null}, {"id": 10847758003, "name": "Laval University", "priority": 1936, "external_id": null}, {"id": 10847759003, "name": "Ben Gurion University of The Negev", "priority": 1937, "external_id": null}, {"id": 10847760003, "name": "Link\u00f6ping University", "priority": 1938, "external_id": null}, {"id": 10847761003, "name": "Aalborg University", "priority": 1939, "external_id": null}, {"id": 10847762003, "name": "Bauman Moscow State Technical University", "priority": 1940, "external_id": null}, {"id": 10847763003, "name": "Ecole Normale Sup\u00e9rieure de Cachan", "priority": 1941, "external_id": null}, {"id": 10847764003, "name": "SOAS - School of Oriental and African Studies, University of London", "priority": 1942, "external_id": null}, {"id": 10847765003, "name": "University of Essex", "priority": 1943, "external_id": null}, {"id": 10847766003, "name": "University of Warsaw", "priority": 1944, "external_id": null}, {"id": 10847767003, "name": "Griffith University", "priority": 1945, "external_id": null}, {"id": 10847768003, "name": "University of South Australia", "priority": 1946, "external_id": null}, {"id": 10847769003, "name": "Massey University", "priority": 1947, "external_id": null}, {"id": 10847770003, "name": "University of Porto", "priority": 1948, "external_id": null}, {"id": 10847771003, "name": "Universitat Polit\u00e8cnica de Catalunya", "priority": 1949, "external_id": null}, {"id": 10847772003, "name": "Indian Institute of Technology Kharagpur (IITKGP)", "priority": 1950, "external_id": null}, {"id": 10847773003, "name": "City University London", "priority": 1951, "external_id": null}, {"id": 10847774003, "name": "Dublin City University", "priority": 1952, "external_id": null}, {"id": 10847775003, "name": "Pontificia Universidad Javeriana", "priority": 1953, "external_id": null}, {"id": 10847776003, "name": "James Cook University", "priority": 1954, "external_id": null}, {"id": 10847777003, "name": "Novosibirsk State University", "priority": 1955, "external_id": null}, {"id": 10847778003, "name": "Universidade Nova de Lisboa", "priority": 1956, "external_id": null}, {"id": 10847779003, "name": "Universit\u00e9 Aix-Marseille", "priority": 1957, "external_id": null}, {"id": 10847780003, "name": "Universiti Sains Malaysia (USM)", "priority": 1958, "external_id": null}, {"id": 10847781003, "name": "Universiti Teknologi Malaysia (UTM)", "priority": 1959, "external_id": null}, {"id": 10847782003, "name": "Universit\u00e9 Paris Dauphine", "priority": 1960, "external_id": null}, {"id": 10847783003, "name": "University of Coimbra", "priority": 1961, "external_id": null}, {"id": 10847784003, "name": "Brunel University", "priority": 1962, "external_id": null}, {"id": 10847785003, "name": "King Abdul Aziz University (KAU)", "priority": 1963, "external_id": null}, {"id": 10847786003, "name": "Ewha Womans University", "priority": 1964, "external_id": null}, {"id": 10847787003, "name": "Nankai University", "priority": 1965, "external_id": null}, {"id": 10847788003, "name": "Taipei Medical University", "priority": 1966, "external_id": null}, {"id": 10847789003, "name": "Universit\u00e4t Jena", "priority": 1967, "external_id": null}, {"id": 10847790003, "name": "Ruhr-Universit\u00e4t Bochum", "priority": 1968, "external_id": null}, {"id": 10847791003, "name": "Heriot-Watt University", "priority": 1969, "external_id": null}, {"id": 10847792003, "name": "Politecnico di Torino", "priority": 1970, "external_id": null}, {"id": 10847793003, "name": "Universit\u00e4t Bremen", "priority": 1971, "external_id": null}, {"id": 10847794003, "name": "Xi'an Jiaotong University", "priority": 1972, "external_id": null}, {"id": 10847795003, "name": "Birkbeck College, University of London", "priority": 1973, "external_id": null}, {"id": 10847796003, "name": "Oxford Brookes University", "priority": 1974, "external_id": null}, {"id": 10847797003, "name": "Jagiellonian University", "priority": 1975, "external_id": null}, {"id": 10847798003, "name": "University of Tampere", "priority": 1976, "external_id": null}, {"id": 10847799003, "name": "University of Florence", "priority": 1977, "external_id": null}, {"id": 10847800003, "name": "Deakin University", "priority": 1978, "external_id": null}, {"id": 10847801003, "name": "University of the Philippines", "priority": 1979, "external_id": null}, {"id": 10847802003, "name": "Universitat Polit\u00e8cnica de Val\u00e8ncia", "priority": 1980, "external_id": null}, {"id": 10847803003, "name": "Sun Yat-sen University", "priority": 1981, "external_id": null}, {"id": 10847804003, "name": "Universit\u00e9 Montpellier 2, Sciences et Techniques du Languedoc", "priority": 1982, "external_id": null}, {"id": 10847805003, "name": "Moscow State Institute of International Relations (MGIMO-University)", "priority": 1983, "external_id": null}, {"id": 10847806003, "name": "Stellenbosch University", "priority": 1984, "external_id": null}, {"id": 10847807003, "name": "Polit\u00e9cnica de Madrid", "priority": 1985, "external_id": null}, {"id": 10847808003, "name": "Instituto Tecnol\u00f3gico de Buenos Aires (ITBA)", "priority": 1986, "external_id": null}, {"id": 10847809003, "name": "La Trobe University", "priority": 1987, "external_id": null}, {"id": 10847810003, "name": "Universit\u00e9 Paul Sabatier Toulouse III", "priority": 1988, "external_id": null}, {"id": 10847811003, "name": "Karl-Franzens-Universit\u00e4t Graz", "priority": 1989, "external_id": null}, {"id": 10847812003, "name": "Universit\u00e4t D\u00fcsseldorf", "priority": 1990, "external_id": null}, {"id": 10847813003, "name": "University of Naples - Federico Ii", "priority": 1991, "external_id": null}, {"id": 10847814003, "name": "Aston University", "priority": 1992, "external_id": null}, {"id": 10847815003, "name": "University of Turin", "priority": 1993, "external_id": null}, {"id": 10847816003, "name": "Beihang University (former BUAA)", "priority": 1994, "external_id": null}, {"id": 10847817003, "name": "Indian Institute of Technology Roorkee (IITR)", "priority": 1995, "external_id": null}, {"id": 10847818003, "name": "National Central University", "priority": 1996, "external_id": null}, {"id": 10847819003, "name": "Sogang University", "priority": 1997, "external_id": null}, {"id": 10847820003, "name": "Universit\u00e4t Regensburg", "priority": 1998, "external_id": null}, {"id": 10847821003, "name": "Universit\u00e9 Lille 1, Sciences et Technologie", "priority": 1999, "external_id": null}, {"id": 10847822003, "name": "University of Tasmania", "priority": 2000, "external_id": null}, {"id": 10847823003, "name": "University of Waikato", "priority": 2001, "external_id": null}, {"id": 10847824003, "name": "Wuhan University", "priority": 2002, "external_id": null}, {"id": 10847825003, "name": "National Taiwan University of Science And Technology", "priority": 2003, "external_id": null}, {"id": 10847826003, "name": "Universidade Federal de S\u00e3o Paulo (UNIFESP)", "priority": 2004, "external_id": null}, {"id": 10847827003, "name": "Universit\u00e0 degli Studi di Pavia", "priority": 2005, "external_id": null}, {"id": 10847828003, "name": "Universit\u00e4t Bayreuth", "priority": 2006, "external_id": null}, {"id": 10847829003, "name": "Universit\u00e9 Claude Bernard Lyon 1", "priority": 2007, "external_id": null}, {"id": 10847830003, "name": "Universit\u00e9 du Qu\u00e9bec", "priority": 2008, "external_id": null}, {"id": 10847831003, "name": "Universiti Putra Malaysia (UPM)", "priority": 2009, "external_id": null}, {"id": 10847832003, "name": "University of Kent", "priority": 2010, "external_id": null}, {"id": 10847833003, "name": "University of St Gallen (HSG)", "priority": 2011, "external_id": null}, {"id": 10847834003, "name": "Bond University", "priority": 2012, "external_id": null}, {"id": 10847835003, "name": "United Arab Emirates University", "priority": 2013, "external_id": null}, {"id": 10847836003, "name": "Universidad de San Andr\u00c3\u00a9s", "priority": 2014, "external_id": null}, {"id": 10847837003, "name": "Universidad Nacional de La Plata", "priority": 2015, "external_id": null}, {"id": 10847838003, "name": "Universit\u00e4t des Saarlandes", "priority": 2016, "external_id": null}, {"id": 10847839003, "name": "American University of Sharjah (AUS)", "priority": 2017, "external_id": null}, {"id": 10847840003, "name": "Bilkent University", "priority": 2018, "external_id": null}, {"id": 10847841003, "name": "Flinders University", "priority": 2019, "external_id": null}, {"id": 10847842003, "name": "Hankuk (Korea) University of Foreign Studies", "priority": 2020, "external_id": null}, {"id": 10847843003, "name": "Middle East Technical University", "priority": 2021, "external_id": null}, {"id": 10847844003, "name": "Philipps-Universit\u00e4t Marburg", "priority": 2022, "external_id": null}, {"id": 10847845003, "name": "Swansea University", "priority": 2023, "external_id": null}, {"id": 10847846003, "name": "Tampere University of Technology", "priority": 2024, "external_id": null}, {"id": 10847847003, "name": "Universit\u00e4t Bielefeld", "priority": 2025, "external_id": null}, {"id": 10847848003, "name": "University of Manitoba", "priority": 2026, "external_id": null}, {"id": 10847849003, "name": "Chiba University", "priority": 2027, "external_id": null}, {"id": 10847850003, "name": "Moscow Institute of Physics and Technology State University", "priority": 2028, "external_id": null}, {"id": 10847851003, "name": "Tallinn University of Technology", "priority": 2029, "external_id": null}, {"id": 10847852003, "name": "Taras Shevchenko National University of Kyiv", "priority": 2030, "external_id": null}, {"id": 10847853003, "name": "Tokyo University of Science", "priority": 2031, "external_id": null}, {"id": 10847854003, "name": "University of Salamanca", "priority": 2032, "external_id": null}, {"id": 10847855003, "name": "University of Trento", "priority": 2033, "external_id": null}, {"id": 10847856003, "name": "Universit\u00e9 de Sherbrooke", "priority": 2034, "external_id": null}, {"id": 10847857003, "name": "Universit\u00e9 Panth\u00e9on-Assas (Paris 2)", "priority": 2035, "external_id": null}, {"id": 10847858003, "name": "University of Delhi", "priority": 2036, "external_id": null}, {"id": 10847859003, "name": "Abo Akademi University", "priority": 2037, "external_id": null}, {"id": 10847860003, "name": "Czech Technical University In Prague", "priority": 2038, "external_id": null}, {"id": 10847861003, "name": "Leibniz Universit\u00e4t Hannover", "priority": 2039, "external_id": null}, {"id": 10847862003, "name": "Pusan National University", "priority": 2040, "external_id": null}, {"id": 10847863003, "name": "Shanghai University", "priority": 2041, "external_id": null}, {"id": 10847864003, "name": "St. Petersburg State Politechnical University", "priority": 2042, "external_id": null}, {"id": 10847865003, "name": "Universit\u00e0 Cattolica del Sacro Cuore", "priority": 2043, "external_id": null}, {"id": 10847866003, "name": "University of Genoa", "priority": 2044, "external_id": null}, {"id": 10847867003, "name": "Bandung Institute of Technology (ITB)", "priority": 2045, "external_id": null}, {"id": 10847868003, "name": "Bogazici University", "priority": 2046, "external_id": null}, {"id": 10847869003, "name": "Goldsmiths, University of London", "priority": 2047, "external_id": null}, {"id": 10847870003, "name": "National Sun Yat-sen University", "priority": 2048, "external_id": null}, {"id": 10847871003, "name": "Renmin (People\u2019s) University of China", "priority": 2049, "external_id": null}, {"id": 10847872003, "name": "Universidad de Costa Rica", "priority": 2050, "external_id": null}, {"id": 10847873003, "name": "Universidad de Santiago de Chile - USACH", "priority": 2051, "external_id": null}, {"id": 10847874003, "name": "University of Tartu", "priority": 2052, "external_id": null}, {"id": 10847875003, "name": "Aristotle University of Thessaloniki", "priority": 2053, "external_id": null}, {"id": 10847876003, "name": "Auckland University of Technology", "priority": 2054, "external_id": null}, {"id": 10847877003, "name": "Bangor University", "priority": 2055, "external_id": null}, {"id": 10847878003, "name": "Charles Darwin University", "priority": 2056, "external_id": null}, {"id": 10847879003, "name": "Kingston University, London", "priority": 2057, "external_id": null}, {"id": 10847880003, "name": "Universitat de Valencia", "priority": 2058, "external_id": null}, {"id": 10847881003, "name": "Universit\u00e9 Montpellier 1", "priority": 2059, "external_id": null}, {"id": 10847882003, "name": "University of Pretoria", "priority": 2060, "external_id": null}, {"id": 10847883003, "name": "Lincoln University", "priority": 2061, "external_id": null}, {"id": 10847884003, "name": "National Taiwan Normal University", "priority": 2062, "external_id": null}, {"id": 10847885003, "name": "National University of Sciences And Technology (NUST) Islamabad", "priority": 2063, "external_id": null}, {"id": 10847886003, "name": "Swinburne University of Technology", "priority": 2064, "external_id": null}, {"id": 10847887003, "name": "Tongji University", "priority": 2065, "external_id": null}, {"id": 10847888003, "name": "Universidad de Zaragoza", "priority": 2066, "external_id": null}, {"id": 10847889003, "name": "Universidade Federal de Minas Gerais", "priority": 2067, "external_id": null}, {"id": 10847890003, "name": "Universit\u00e4t Duisburg-Essen", "priority": 2068, "external_id": null}, {"id": 10847891003, "name": "Al-Imam Mohamed Ibn Saud Islamic University", "priority": 2069, "external_id": null}, {"id": 10847892003, "name": "Harbin Institute of Technology", "priority": 2070, "external_id": null}, {"id": 10847893003, "name": "People's Friendship University of Russia", "priority": 2071, "external_id": null}, {"id": 10847894003, "name": "Universidade Estadual PaulistaJ\u00falio de Mesquita Filho' (UNESP)", "priority": 2072, "external_id": null}, {"id": 10847895003, "name": "Universit\u00e9 Nice Sophia-Antipolis", "priority": 2073, "external_id": null}, {"id": 10847896003, "name": "University of Crete", "priority": 2074, "external_id": null}, {"id": 10847897003, "name": "University of Milano-Bicocca", "priority": 2075, "external_id": null}, {"id": 10847898003, "name": "Ateneo de Manila University", "priority": 2076, "external_id": null}, {"id": 10847899003, "name": "Beijing Institute of Technology", "priority": 2077, "external_id": null}, {"id": 10847900003, "name": "Chang Gung University", "priority": 2078, "external_id": null}, {"id": 10847901003, "name": "hung-Ang University", "priority": 2079, "external_id": null}, {"id": 10847902003, "name": "Dublin Institute of Technology", "priority": 2080, "external_id": null}, {"id": 10847903003, "name": "Huazhong University of Science and Technology", "priority": 2081, "external_id": null}, {"id": 10847904003, "name": "International Islamic University Malaysia (IIUM)", "priority": 2082, "external_id": null}, {"id": 10847905003, "name": "Johannes Kepler University Linz", "priority": 2083, "external_id": null}, {"id": 10847906003, "name": "Justus-Liebig-Universit\u00e4t Gie\u00dfen", "priority": 2084, "external_id": null}, {"id": 10847907003, "name": "Kanazawa University", "priority": 2085, "external_id": null}, {"id": 10847908003, "name": "Keele University", "priority": 2086, "external_id": null}, {"id": 10847909003, "name": "Koc University", "priority": 2087, "external_id": null}, {"id": 10847910003, "name": "National and Kapodistrian University of Athens", "priority": 2088, "external_id": null}, {"id": 10847911003, "name": "National Research University \u2013 Higher School of Economics (HSE)", "priority": 2089, "external_id": null}, {"id": 10847912003, "name": "National Technical University of Athens", "priority": 2090, "external_id": null}, {"id": 10847913003, "name": "Okayama University", "priority": 2091, "external_id": null}, {"id": 10847914003, "name": "Sabanci University", "priority": 2092, "external_id": null}, {"id": 10847915003, "name": "Southeast University", "priority": 2093, "external_id": null}, {"id": 10847916003, "name": "Sultan Qaboos University", "priority": 2094, "external_id": null}, {"id": 10847917003, "name": "Technische Universit\u00e4t Braunschweig", "priority": 2095, "external_id": null}, {"id": 10847918003, "name": "Technische Universit\u00e4t Dortmund", "priority": 2096, "external_id": null}, {"id": 10847919003, "name": "The Catholic University of Korea", "priority": 2097, "external_id": null}, {"id": 10847920003, "name": "Tianjin University", "priority": 2098, "external_id": null}, {"id": 10847921003, "name": "Tokyo Metropolitan University", "priority": 2099, "external_id": null}, {"id": 10847922003, "name": "Universidad de Antioquia", "priority": 2100, "external_id": null}, {"id": 10847923003, "name": "University of Granada", "priority": 2101, "external_id": null}, {"id": 10847924003, "name": "Universidad de Palermo", "priority": 2102, "external_id": null}, {"id": 10847925003, "name": "Universidad Nacional de C\u00f3rdoba", "priority": 2103, "external_id": null}, {"id": 10847926003, "name": "Universidade de Santiago de Compostela", "priority": 2104, "external_id": null}, {"id": 10847927003, "name": "Universidade Federal do Rio Grande Do Sul", "priority": 2105, "external_id": null}, {"id": 10847928003, "name": "University of Siena", "priority": 2106, "external_id": null}, {"id": 10847929003, "name": "University of Trieste", "priority": 2107, "external_id": null}, {"id": 10847930003, "name": "Universitas Gadjah Mada", "priority": 2108, "external_id": null}, {"id": 10847931003, "name": "Universit\u00e9 de Lorraine", "priority": 2109, "external_id": null}, {"id": 10847932003, "name": "Universit\u00e9 de Rennes 1", "priority": 2110, "external_id": null}, {"id": 10847933003, "name": "University of Bradford", "priority": 2111, "external_id": null}, {"id": 10847934003, "name": "University of Hull", "priority": 2112, "external_id": null}, {"id": 10847935003, "name": "University of Kwazulu-Natal", "priority": 2113, "external_id": null}, {"id": 10847936003, "name": "University of Limerick", "priority": 2114, "external_id": null}, {"id": 10847937003, "name": "University of Stirling", "priority": 2115, "external_id": null}, {"id": 10847938003, "name": "University of Szeged", "priority": 2116, "external_id": null}, {"id": 10847939003, "name": "Ural Federal University", "priority": 2117, "external_id": null}, {"id": 10847940003, "name": "Xiamen University", "priority": 2118, "external_id": null}, {"id": 10847941003, "name": "Yokohama City University", "priority": 2119, "external_id": null}, {"id": 10847942003, "name": "Aberystwyth University", "priority": 2120, "external_id": null}, {"id": 10847943003, "name": "Belarus State University", "priority": 2121, "external_id": null}, {"id": 10847944003, "name": "Cairo University", "priority": 2122, "external_id": null}, {"id": 10847945003, "name": "Chiang Mai University", "priority": 2123, "external_id": null}, {"id": 10847946003, "name": "Chonbuk National University", "priority": 2124, "external_id": null}, {"id": 10847947003, "name": "E\u00f6tv\u00f6s Lor\u00e1nd University", "priority": 2125, "external_id": null}, {"id": 10847948003, "name": "Inha University", "priority": 2126, "external_id": null}, {"id": 10847949003, "name": "Instituto Polit\u00e9cnico Nacional (IPN)", "priority": 2127, "external_id": null}, {"id": 10847950003, "name": "Istanbul Technical University", "priority": 2128, "external_id": null}, {"id": 10847951003, "name": "Kumamoto University", "priority": 2129, "external_id": null}, {"id": 10847952003, "name": "Kyungpook National University", "priority": 2130, "external_id": null}, {"id": 10847953003, "name": "Lingnan University (Hong Kong)", "priority": 2131, "external_id": null}, {"id": 10847954003, "name": "Masaryk University", "priority": 2132, "external_id": null}, {"id": 10847955003, "name": "Murdoch University", "priority": 2133, "external_id": null}, {"id": 10847956003, "name": "Nagasaki University", "priority": 2134, "external_id": null}, {"id": 10847957003, "name": "National Chung Hsing University", "priority": 2135, "external_id": null}, {"id": 10847958003, "name": "National Taipei University of Technology", "priority": 2136, "external_id": null}, {"id": 10847959003, "name": "National University of Ireland Maynooth", "priority": 2137, "external_id": null}, {"id": 10847960003, "name": "Osaka City University", "priority": 2138, "external_id": null}, {"id": 10847961003, "name": "Pontificia Universidad Cat\u00f3lica del Per\u00fa", "priority": 2139, "external_id": null}, {"id": 10847962003, "name": "Pontificia Universidade Cat\u00f3lica de S\u00e3o Paulo (PUC -SP)", "priority": 2140, "external_id": null}, {"id": 10847963003, "name": "Pontificia Universidade Cat\u00f3lica do Rio de Janeiro (PUC - Rio)", "priority": 2141, "external_id": null}, {"id": 10847964003, "name": "Qatar University", "priority": 2142, "external_id": null}, {"id": 10847965003, "name": "Rhodes University", "priority": 2143, "external_id": null}, {"id": 10847966003, "name": "Tokyo University of Agriculture and Technology", "priority": 2144, "external_id": null}, {"id": 10847967003, "name": "Tomsk Polytechnic University", "priority": 2145, "external_id": null}, {"id": 10847968003, "name": "Tomsk State University", "priority": 2146, "external_id": null}, {"id": 10847969003, "name": "Umm Al-Qura University", "priority": 2147, "external_id": null}, {"id": 10847970003, "name": "Universidad Cat\u00f3lica Andr\u00e9s Bello - UCAB", "priority": 2148, "external_id": null}, {"id": 10847971003, "name": "Universidad Central de Venezuela - UCV", "priority": 2149, "external_id": null}, {"id": 10847972003, "name": "Universidad de Belgrano", "priority": 2150, "external_id": null}, {"id": 10847973003, "name": "Universidad de Concepci\u00f3n", "priority": 2151, "external_id": null}, {"id": 10847974003, "name": "Universidad de Sevilla", "priority": 2152, "external_id": null}, {"id": 10847975003, "name": "Universidade Catolica Portuguesa, Lisboa", "priority": 2153, "external_id": null}, {"id": 10847976003, "name": "Universidade de Brasilia (UnB)", "priority": 2154, "external_id": null}, {"id": 10847977003, "name": "University of Lisbon", "priority": 2155, "external_id": null}, {"id": 10847978003, "name": "University of Ljubljana", "priority": 2156, "external_id": null}, {"id": 10847979003, "name": "University of Seoul", "priority": 2157, "external_id": null}, {"id": 10847980003, "name": "Abu Dhabi University", "priority": 2158, "external_id": null}, {"id": 10847981003, "name": "Ain Shams University", "priority": 2159, "external_id": null}, {"id": 10847982003, "name": "Ajou University", "priority": 2160, "external_id": null}, {"id": 10847983003, "name": "De La Salle University", "priority": 2161, "external_id": null}, {"id": 10847984003, "name": "Dongguk University", "priority": 2162, "external_id": null}, {"id": 10847985003, "name": "Gifu University", "priority": 2163, "external_id": null}, {"id": 10847986003, "name": "Hacettepe University", "priority": 2164, "external_id": null}, {"id": 10847987003, "name": "Indian Institute of Technology Guwahati (IITG)", "priority": 2165, "external_id": null}, {"id": 10847988003, "name": "Jilin University", "priority": 2166, "external_id": null}, {"id": 10847989003, "name": "Kazan Federal University", "priority": 2167, "external_id": null}, {"id": 10847990003, "name": "King Khalid University", "priority": 2168, "external_id": null}, {"id": 10847991003, "name": "Martin-Luther-Universit\u00e4t Halle-Wittenberg", "priority": 2169, "external_id": null}, {"id": 10847992003, "name": "National Chengchi University", "priority": 2170, "external_id": null}, {"id": 10847993003, "name": "National Technical University of UkraineKyiv Polytechnic Institute'", "priority": 2171, "external_id": null}, {"id": 10847994003, "name": "Niigata University", "priority": 2172, "external_id": null}, {"id": 10847995003, "name": "Osaka Prefecture University", "priority": 2173, "external_id": null}, {"id": 10847996003, "name": "Paris Lodron University of Salzburg", "priority": 2174, "external_id": null}, {"id": 10847997003, "name": "Sharif University of Technology", "priority": 2175, "external_id": null}, {"id": 10847998003, "name": "Southern Federal University", "priority": 2176, "external_id": null}, {"id": 10847999003, "name": "Thammasat University", "priority": 2177, "external_id": null}, {"id": 10848000003, "name": "Universidad de Guadalajara (UDG)", "priority": 2178, "external_id": null}, {"id": 10848001003, "name": "Universidad de la Rep\u00fablica (UdelaR)", "priority": 2179, "external_id": null}, {"id": 10848002003, "name": "Universidad Iberoamericana (UIA)", "priority": 2180, "external_id": null}, {"id": 10848003003, "name": "Universidad Torcuato Di Tella", "priority": 2181, "external_id": null}, {"id": 10848004003, "name": "Universidade Federal da Bahia", "priority": 2182, "external_id": null}, {"id": 10848005003, "name": "Universidade Federal de S\u00e3o Carlos", "priority": 2183, "external_id": null}, {"id": 10848006003, "name": "Universidade Federal de Vi\u00e7osa", "priority": 2184, "external_id": null}, {"id": 10848007003, "name": "Perugia University", "priority": 2185, "external_id": null}, {"id": 10848008003, "name": "Universit\u00e9 de Nantes", "priority": 2186, "external_id": null}, {"id": 10848009003, "name": "Universit\u00e9 Saint-Joseph de Beyrouth", "priority": 2187, "external_id": null}, {"id": 10848010003, "name": "University of Canberra", "priority": 2188, "external_id": null}, {"id": 10848011003, "name": "University of Debrecen", "priority": 2189, "external_id": null}, {"id": 10848012003, "name": "University of Johannesburg", "priority": 2190, "external_id": null}, {"id": 10848013003, "name": "University of Mumbai", "priority": 2191, "external_id": null}, {"id": 10848014003, "name": "University of Patras", "priority": 2192, "external_id": null}, {"id": 10848015003, "name": "University of Tehran", "priority": 2193, "external_id": null}, {"id": 10848016003, "name": "University of Ulsan", "priority": 2194, "external_id": null}, {"id": 10848017003, "name": "University of Ulster", "priority": 2195, "external_id": null}, {"id": 10848018003, "name": "University of Zagreb", "priority": 2196, "external_id": null}, {"id": 10848019003, "name": "Vilnius University", "priority": 2197, "external_id": null}, {"id": 10848020003, "name": "Warsaw University of Technology", "priority": 2198, "external_id": null}, {"id": 10848021003, "name": "Al Azhar University", "priority": 2199, "external_id": null}, {"id": 10848022003, "name": "Bar-Ilan University", "priority": 2200, "external_id": null}, {"id": 10848023003, "name": "Brno University of Technology", "priority": 2201, "external_id": null}, {"id": 10848024003, "name": "Chonnam National University", "priority": 2202, "external_id": null}, {"id": 10848025003, "name": "Chungnam National University", "priority": 2203, "external_id": null}, {"id": 10848026003, "name": "Corvinus University of Budapest", "priority": 2204, "external_id": null}, {"id": 10848027003, "name": "Gunma University", "priority": 2205, "external_id": null}, {"id": 10848028003, "name": "Hallym University", "priority": 2206, "external_id": null}, {"id": 10848029003, "name": "Instituto Tecnol\u00f3gico Autonomo de M\u00e9xico (ITAM)", "priority": 2207, "external_id": null}, {"id": 10848030003, "name": "Istanbul University", "priority": 2208, "external_id": null}, {"id": 10848031003, "name": "Jordan University of Science & Technology", "priority": 2209, "external_id": null}, {"id": 10848032003, "name": "Kasetsart University", "priority": 2210, "external_id": null}, {"id": 10848033003, "name": "Kazakh-British Technical University", "priority": 2211, "external_id": null}, {"id": 10848034003, "name": "Khazar University", "priority": 2212, "external_id": null}, {"id": 10848035003, "name": "London Metropolitan University", "priority": 2213, "external_id": null}, {"id": 10848036003, "name": "Middlesex University", "priority": 2214, "external_id": null}, {"id": 10848037003, "name": "Universidad Industrial de Santander", "priority": 2215, "external_id": null}, {"id": 10848038003, "name": "Pontificia Universidad Cat\u00f3lica de Valpara\u00edso", "priority": 2216, "external_id": null}, {"id": 10848039003, "name": "Pontificia Universidade Cat\u00f3lica do Rio Grande do Sul", "priority": 2217, "external_id": null}, {"id": 10848040003, "name": "Qafqaz University", "priority": 2218, "external_id": null}, {"id": 10848041003, "name": "Ritsumeikan University", "priority": 2219, "external_id": null}, {"id": 10848042003, "name": "Shandong University", "priority": 2220, "external_id": null}, {"id": 10848043003, "name": "University of St. Kliment Ohridski", "priority": 2221, "external_id": null}, {"id": 10848044003, "name": "South Kazakhstan State University (SKSU)", "priority": 2222, "external_id": null}, {"id": 10848045003, "name": "Universidad Adolfo Ib\u00e1\u00f1ez", "priority": 2223, "external_id": null}, {"id": 10848046003, "name": "Universidad Aut\u00f3noma del Estado de M\u00e9xico", "priority": 2224, "external_id": null}, {"id": 10848047003, "name": "Universidad Aut\u00f3noma Metropolitana (UAM)", "priority": 2225, "external_id": null}, {"id": 10848048003, "name": "Universidad de Alcal\u00e1", "priority": 2226, "external_id": null}, {"id": 10848049003, "name": "Universidad Nacional Costa Rica", "priority": 2227, "external_id": null}, {"id": 10848050003, "name": "Universidad Nacional de Mar del Plata", "priority": 2228, "external_id": null}, {"id": 10848051003, "name": "Universidad Peruana Cayetano Heredia", "priority": 2229, "external_id": null}, {"id": 10848052003, "name": "Universidad Sim\u00f3n Bol\u00edvar Venezuela", "priority": 2230, "external_id": null}, {"id": 10848053003, "name": "Universidade Federal de Santa Catarina", "priority": 2231, "external_id": null}, {"id": 10848054003, "name": "Universidade Federal do Paran\u00e1 (UFPR)", "priority": 2232, "external_id": null}, {"id": 10848055003, "name": "Universidade Federal Fluminense", "priority": 2233, "external_id": null}, {"id": 10848056003, "name": "University of Modena", "priority": 2234, "external_id": null}, {"id": 10848057003, "name": "Universit\u00e9 Lumi\u00e8re Lyon 2", "priority": 2235, "external_id": null}, {"id": 10848058003, "name": "Universit\u00e9 Toulouse 1, Capitole", "priority": 2236, "external_id": null}, {"id": 10848059003, "name": "University of Economics Prague", "priority": 2237, "external_id": null}, {"id": 10848060003, "name": "University of Hertfordshire", "priority": 2238, "external_id": null}, {"id": 10848061003, "name": "University of Plymouth", "priority": 2239, "external_id": null}, {"id": 10848062003, "name": "University of Salford", "priority": 2240, "external_id": null}, {"id": 10848063003, "name": "University of Science and Technology Beijing", "priority": 2241, "external_id": null}, {"id": 10848064003, "name": "University of Western Sydney", "priority": 2242, "external_id": null}, {"id": 10848065003, "name": "Yamaguchi University", "priority": 2243, "external_id": null}, {"id": 10848066003, "name": "Yokohama National University", "priority": 2244, "external_id": null}, {"id": 10848067003, "name": "Airlangga University", "priority": 2245, "external_id": null}, {"id": 10848068003, "name": "Alexandria University", "priority": 2246, "external_id": null}, {"id": 10848069003, "name": "Alexandru Ioan Cuza University", "priority": 2247, "external_id": null}, {"id": 10848070003, "name": "Alpen-Adria-Universit\u00e4t Klagenfurt", "priority": 2248, "external_id": null}, {"id": 10848071003, "name": "Aoyama Gakuin University", "priority": 2249, "external_id": null}, {"id": 10848072003, "name": "Athens University of Economy And Business", "priority": 2250, "external_id": null}, {"id": 10848073003, "name": "Babes-Bolyai University", "priority": 2251, "external_id": null}, {"id": 10848074003, "name": "Baku State University", "priority": 2252, "external_id": null}, {"id": 10848075003, "name": "Belarusian National Technical University", "priority": 2253, "external_id": null}, {"id": 10848076003, "name": "Benem\u00e9rita Universidad Aut\u00f3noma de Puebla", "priority": 2254, "external_id": null}, {"id": 10848077003, "name": "Bogor Agricultural University", "priority": 2255, "external_id": null}, {"id": 10848078003, "name": "Coventry University", "priority": 2256, "external_id": null}, {"id": 10848079003, "name": "Cukurova University", "priority": 2257, "external_id": null}, {"id": 10848080003, "name": "Diponegoro University", "priority": 2258, "external_id": null}, {"id": 10848081003, "name": "Donetsk National University", "priority": 2259, "external_id": null}, {"id": 10848082003, "name": "Doshisha University", "priority": 2260, "external_id": null}, {"id": 10848083003, "name": "E.A.Buketov Karaganda State University", "priority": 2261, "external_id": null}, {"id": 10848084003, "name": "Far Eastern Federal University", "priority": 2262, "external_id": null}, {"id": 10848085003, "name": "Fu Jen Catholic University", "priority": 2263, "external_id": null}, {"id": 10848086003, "name": "Kagoshima University", "priority": 2264, "external_id": null}, {"id": 10848087003, "name": "Kaunas University of Technology", "priority": 2265, "external_id": null}, {"id": 10848088003, "name": "Kazakh Ablai khan University of International Relations and World Languages", "priority": 2266, "external_id": null}, {"id": 10848089003, "name": "Kazakh National Pedagogical University Abai", "priority": 2267, "external_id": null}, {"id": 10848090003, "name": "Kazakh National Technical University", "priority": 2268, "external_id": null}, {"id": 10848091003, "name": "Khon Kaen University", "priority": 2269, "external_id": null}, {"id": 10848092003, "name": "King Faisal University", "priority": 2270, "external_id": null}, {"id": 10848093003, "name": "King Mongkut''s University of Technology Thonburi", "priority": 2271, "external_id": null}, {"id": 10848094003, "name": "Kuwait University", "priority": 2272, "external_id": null}, {"id": 10848095003, "name": "Lodz University", "priority": 2273, "external_id": null}, {"id": 10848096003, "name": "Manchester Metropolitan University", "priority": 2274, "external_id": null}, {"id": 10848097003, "name": "Lobachevsky State University of Nizhni Novgorod", "priority": 2275, "external_id": null}, {"id": 10848098003, "name": "National Technical UniversityKharkiv Polytechnic Institute'", "priority": 2276, "external_id": null}, {"id": 10848099003, "name": "Nicolaus Copernicus University", "priority": 2277, "external_id": null}, {"id": 10848100003, "name": "Northumbria University at Newcastle", "priority": 2278, "external_id": null}, {"id": 10848101003, "name": "Nottingham Trent University", "priority": 2279, "external_id": null}, {"id": 10848102003, "name": "Ochanomizu University", "priority": 2280, "external_id": null}, {"id": 10848103003, "name": "Plekhanov Russian University of Economics", "priority": 2281, "external_id": null}, {"id": 10848104003, "name": "Pontificia Universidad Catolica del Ecuador", "priority": 2282, "external_id": null}, {"id": 10848105003, "name": "Prince of Songkla University", "priority": 2283, "external_id": null}, {"id": 10848106003, "name": "S.Seifullin Kazakh Agro Technical University", "priority": 2284, "external_id": null}, {"id": 10848107003, "name": "Saitama University", "priority": 2285, "external_id": null}, {"id": 10848108003, "name": "Sepuluh Nopember Institute of Technology", "priority": 2286, "external_id": null}, {"id": 10848109003, "name": "Shinshu University", "priority": 2287, "external_id": null}, {"id": 10848110003, "name": "The Robert Gordon University", "priority": 2288, "external_id": null}, {"id": 10848111003, "name": "Tokai University", "priority": 2289, "external_id": null}, {"id": 10848112003, "name": "Universidad ANAHUAC", "priority": 2290, "external_id": null}, {"id": 10848113003, "name": "Universidad Austral de Chile", "priority": 2291, "external_id": null}, {"id": 10848114003, "name": "University Aut\u00f3noma de Nuevo Le\u00f3n (UANL)", "priority": 2292, "external_id": null}, {"id": 10848115003, "name": "Universidad de la Habana", "priority": 2293, "external_id": null}, {"id": 10848116003, "name": "Universidad de La Sabana", "priority": 2294, "external_id": null}, {"id": 10848117003, "name": "Universidad de las Am\u00e9ricas Puebla (UDLAP)", "priority": 2295, "external_id": null}, {"id": 10848118003, "name": "Universidad de los Andes M\u00e9rida", "priority": 2296, "external_id": null}, {"id": 10848119003, "name": "University of Murcia", "priority": 2297, "external_id": null}, {"id": 10848120003, "name": "Universidad de Puerto Rico", "priority": 2298, "external_id": null}, {"id": 10848121003, "name": "Universidad de San Francisco de Quito", "priority": 2299, "external_id": null}, {"id": 10848122003, "name": "Universidad de Talca", "priority": 2300, "external_id": null}, {"id": 10848123003, "name": "Universidad del Norte", "priority": 2301, "external_id": null}, {"id": 10848124003, "name": "Universidad del Rosario", "priority": 2302, "external_id": null}, {"id": 10848125003, "name": "Universidad del Valle", "priority": 2303, "external_id": null}, {"id": 10848126003, "name": "Universidad Nacional de Cuyo", "priority": 2304, "external_id": null}, {"id": 10848127003, "name": "Universidad Nacional de Rosario", "priority": 2305, "external_id": null}, {"id": 10848128003, "name": "Universidad Nacional de Tucum\u00e1n", "priority": 2306, "external_id": null}, {"id": 10848129003, "name": "Universidad Nacional del Sur", "priority": 2307, "external_id": null}, {"id": 10848130003, "name": "Universidad Nacional Mayor de San Marcos", "priority": 2308, "external_id": null}, {"id": 10848131003, "name": "Universidad T\u00e9cnica Federico Santa Mar\u00eda", "priority": 2309, "external_id": null}, {"id": 10848132003, "name": "Universidad Tecnol\u00f3gica Nacional (UTN)", "priority": 2310, "external_id": null}, {"id": 10848133003, "name": "Universidade do Estado do Rio de Janeiro (UERJ)", "priority": 2311, "external_id": null}, {"id": 10848134003, "name": "Universidade Estadual de Londrina (UEL)", "priority": 2312, "external_id": null}, {"id": 10848135003, "name": "Universidade Federal de Santa Maria", "priority": 2313, "external_id": null}, {"id": 10848136003, "name": "Universidade Federal do Cear\u00e1 (UFC)", "priority": 2314, "external_id": null}, {"id": 10848137003, "name": "Universidade Federal do Pernambuco", "priority": 2315, "external_id": null}, {"id": 10848138003, "name": "Universit\u00e0 Ca'' Foscari Venezia", "priority": 2316, "external_id": null}, {"id": 10848139003, "name": "Catania University", "priority": 2317, "external_id": null}, {"id": 10848140003, "name": "Universit\u00e0 degli Studi Roma Tre", "priority": 2318, "external_id": null}, {"id": 10848141003, "name": "Universit\u00e9 Charles-de-Gaulle Lille 3", "priority": 2319, "external_id": null}, {"id": 10848142003, "name": "Universit\u00e9 de Caen Basse-Normandie", "priority": 2320, "external_id": null}, {"id": 10848143003, "name": "Universit\u00e9 de Cergy-Pontoise", "priority": 2321, "external_id": null}, {"id": 10848144003, "name": "Universit\u00e9 de Poitiers", "priority": 2322, "external_id": null}, {"id": 10848145003, "name": "Universit\u00e9 Jean Moulin Lyon 3", "priority": 2323, "external_id": null}, {"id": 10848146003, "name": "Universit\u00e9 Lille 2 Droit et Sant\u00e9", "priority": 2324, "external_id": null}, {"id": 10848147003, "name": "Universit\u00e9 Paris Ouest Nanterre La D\u00e9fense", "priority": 2325, "external_id": null}, {"id": 10848148003, "name": "Universit\u00e9 Paul-Val\u00e9ry Montpellier 3", "priority": 2326, "external_id": null}, {"id": 10848149003, "name": "Universit\u00e9 Pierre Mend\u00e8s France - Grenoble 2", "priority": 2327, "external_id": null}, {"id": 10848150003, "name": "Universit\u00e9 Stendhal Grenoble 3", "priority": 2328, "external_id": null}, {"id": 10848151003, "name": "Universit\u00e9 Toulouse II, Le Mirail", "priority": 2329, "external_id": null}, {"id": 10848152003, "name": "Universiti Teknologi MARA - UiTM", "priority": 2330, "external_id": null}, {"id": 10848153003, "name": "University of Baghdad", "priority": 2331, "external_id": null}, {"id": 10848154003, "name": "University of Bahrain", "priority": 2332, "external_id": null}, {"id": 10848155003, "name": "University of Bari", "priority": 2333, "external_id": null}, {"id": 10848156003, "name": "University of Belgrade", "priority": 2334, "external_id": null}, {"id": 10848157003, "name": "University of Brawijaya", "priority": 2335, "external_id": null}, {"id": 10848158003, "name": "University of Brescia", "priority": 2336, "external_id": null}, {"id": 10848159003, "name": "University of Bucharest", "priority": 2337, "external_id": null}, {"id": 10848160003, "name": "University of Calcutta", "priority": 2338, "external_id": null}, {"id": 10848161003, "name": "University of Central Lancashire", "priority": 2339, "external_id": null}, {"id": 10848162003, "name": "University of Colombo", "priority": 2340, "external_id": null}, {"id": 10848163003, "name": "University of Dhaka", "priority": 2341, "external_id": null}, {"id": 10848164003, "name": "University of East London", "priority": 2342, "external_id": null}, {"id": 10848165003, "name": "University of Engineering & Technology (UET) Lahore", "priority": 2343, "external_id": null}, {"id": 10848166003, "name": "University of Greenwich", "priority": 2344, "external_id": null}, {"id": 10848167003, "name": "University of Jordan", "priority": 2345, "external_id": null}, {"id": 10848168003, "name": "University of Karachi", "priority": 2346, "external_id": null}, {"id": 10848169003, "name": "University of Lahore", "priority": 2347, "external_id": null}, {"id": 10848170003, "name": "University of Latvia", "priority": 2348, "external_id": null}, {"id": 10848171003, "name": "University of New England", "priority": 2349, "external_id": null}, {"id": 10848172003, "name": "University of Pune", "priority": 2350, "external_id": null}, {"id": 10848173003, "name": "University of Santo Tomas", "priority": 2351, "external_id": null}, {"id": 10848174003, "name": "University of Southern Queensland", "priority": 2352, "external_id": null}, {"id": 10848175003, "name": "University of Wroclaw", "priority": 2353, "external_id": null}, {"id": 10848176003, "name": "Verona University", "priority": 2354, "external_id": null}, {"id": 10848177003, "name": "Victoria University", "priority": 2355, "external_id": null}, {"id": 10848178003, "name": "Vilnius Gediminas Technical University", "priority": 2356, "external_id": null}, {"id": 10848179003, "name": "Voronezh State University", "priority": 2357, "external_id": null}, {"id": 10848180003, "name": "Vytautas Magnus University", "priority": 2358, "external_id": null}, {"id": 10848181003, "name": "West University of Timisoara", "priority": 2359, "external_id": null}, {"id": 10848182003, "name": "University of South Alabama", "priority": 2360, "external_id": null}, {"id": 10848183003, "name": "University of Arkansas", "priority": 2361, "external_id": null}, {"id": 10848184003, "name": "University of California - Berkeley", "priority": 2362, "external_id": null}, {"id": 10848185003, "name": "University of Connecticut", "priority": 2363, "external_id": null}, {"id": 10848186003, "name": "University of South Florida", "priority": 2364, "external_id": null}, {"id": 10848187003, "name": "University of Georgia", "priority": 2365, "external_id": null}, {"id": 10848188003, "name": "University of Hawaii - Manoa", "priority": 2366, "external_id": null}, {"id": 10848189003, "name": "Iowa State University", "priority": 2367, "external_id": null}, {"id": 10848190003, "name": "Murray State University", "priority": 2368, "external_id": null}, {"id": 10848191003, "name": "University of Louisville", "priority": 2369, "external_id": null}, {"id": 10848192003, "name": "Western Kentucky University", "priority": 2370, "external_id": null}, {"id": 10848193003, "name": "Louisiana State University - Baton Rouge", "priority": 2371, "external_id": null}, {"id": 10848194003, "name": "University of Maryland - College Park", "priority": 2372, "external_id": null}, {"id": 10848195003, "name": "University of Minnesota - Twin Cities", "priority": 2373, "external_id": null}, {"id": 10848196003, "name": "University of Montana", "priority": 2374, "external_id": null}, {"id": 10848197003, "name": "East Carolina University", "priority": 2375, "external_id": null}, {"id": 10848198003, "name": "University of North Carolina - Chapel Hill", "priority": 2376, "external_id": null}, {"id": 10848199003, "name": "Wake Forest University", "priority": 2377, "external_id": null}, {"id": 10848200003, "name": "University of Nebraska - Lincoln", "priority": 2378, "external_id": null}, {"id": 10848201003, "name": "New Mexico State University", "priority": 2379, "external_id": null}, {"id": 10848202003, "name": "Ohio State University - Columbus", "priority": 2380, "external_id": null}, {"id": 10848203003, "name": "University of Oklahoma", "priority": 2381, "external_id": null}, {"id": 10848204003, "name": "Pennsylvania State University - University Park", "priority": 2382, "external_id": null}, {"id": 10848205003, "name": "University of Pittsburgh", "priority": 2383, "external_id": null}, {"id": 10848206003, "name": "University of Tennessee - Chattanooga", "priority": 2384, "external_id": null}, {"id": 10848207003, "name": "Vanderbilt University", "priority": 2385, "external_id": null}, {"id": 10848208003, "name": "Rice University", "priority": 2386, "external_id": null}, {"id": 10848209003, "name": "University of Utah", "priority": 2387, "external_id": null}, {"id": 10848210003, "name": "University of Richmond", "priority": 2388, "external_id": null}, {"id": 10848211003, "name": "University of Arkansas - Pine Bluff", "priority": 2389, "external_id": null}, {"id": 10848212003, "name": "University of Central Florida", "priority": 2390, "external_id": null}, {"id": 10848213003, "name": "Florida Atlantic University", "priority": 2391, "external_id": null}, {"id": 10848214003, "name": "Hampton University", "priority": 2392, "external_id": null}, {"id": 10848215003, "name": "Liberty University", "priority": 2393, "external_id": null}, {"id": 10848216003, "name": "Mercer University", "priority": 2394, "external_id": null}, {"id": 10848217003, "name": "Middle Tennessee State University", "priority": 2395, "external_id": null}, {"id": 10848218003, "name": "University of Nevada - Las Vegas", "priority": 2396, "external_id": null}, {"id": 10848219003, "name": "South Carolina State University", "priority": 2397, "external_id": null}, {"id": 10848220003, "name": "University of Tennessee - Martin", "priority": 2398, "external_id": null}, {"id": 10848221003, "name": "Weber State University", "priority": 2399, "external_id": null}, {"id": 10848222003, "name": "Youngstown State University", "priority": 2400, "external_id": null}, {"id": 10848223003, "name": "University of the Incarnate Word", "priority": 2401, "external_id": null}, {"id": 10848224003, "name": "University of Washington", "priority": 2402, "external_id": null}, {"id": 10848225003, "name": "University of Louisiana - Lafayette", "priority": 2403, "external_id": null}, {"id": 10848226003, "name": "Coastal Carolina University", "priority": 2404, "external_id": null}, {"id": 10848227003, "name": "Utah State University", "priority": 2405, "external_id": null}, {"id": 10848228003, "name": "University of Alabama", "priority": 2406, "external_id": null}, {"id": 10848229003, "name": "University of Illinois - Urbana-Champaign", "priority": 2407, "external_id": null}, {"id": 10848230003, "name": "United States Air Force Academy", "priority": 2408, "external_id": null}, {"id": 10848231003, "name": "University of Akron", "priority": 2409, "external_id": null}, {"id": 10848232003, "name": "University of Central Arkansas", "priority": 2410, "external_id": null}, {"id": 10848233003, "name": "University of Kansas", "priority": 2411, "external_id": null}, {"id": 10848234003, "name": "University of Northern Colorado", "priority": 2412, "external_id": null}, {"id": 10848235003, "name": "University of Northern Iowa", "priority": 2413, "external_id": null}, {"id": 10848236003, "name": "University of South Carolina", "priority": 2414, "external_id": null}, {"id": 10848237003, "name": "Tennessee Technological University", "priority": 2415, "external_id": null}, {"id": 10848238003, "name": "University of Texas - El Paso", "priority": 2416, "external_id": null}, {"id": 10848239003, "name": "Texas Tech University", "priority": 2417, "external_id": null}, {"id": 10848240003, "name": "Tulane University", "priority": 2418, "external_id": null}, {"id": 10848241003, "name": "Virginia Military Institute", "priority": 2419, "external_id": null}, {"id": 10848242003, "name": "Western Michigan University", "priority": 2420, "external_id": null}, {"id": 10848243003, "name": "Wilfrid Laurier University", "priority": 2421, "external_id": null}, {"id": 10848244003, "name": "University of San Diego", "priority": 2422, "external_id": null}, {"id": 10848245003, "name": "University of California - San Diego", "priority": 2423, "external_id": null}, {"id": 10848246003, "name": "Brooks Institute of Photography", "priority": 2424, "external_id": null}, {"id": 10848247003, "name": "Acupuncture and Integrative Medicine College - Berkeley", "priority": 2425, "external_id": null}, {"id": 10848248003, "name": "Southern Alberta Institute of Technology", "priority": 2426, "external_id": null}, {"id": 10848249003, "name": "Susquehanna University", "priority": 2427, "external_id": null}, {"id": 10848250003, "name": "University of Texas - Dallas", "priority": 2428, "external_id": null}, {"id": 10848251003, "name": "Thunderbird School of Global Management", "priority": 2429, "external_id": null}, {"id": 10848252003, "name": "Presidio Graduate School", "priority": 2430, "external_id": null}, {"id": 10848253003, "name": "\u00c9cole sup\u00e9rieure de commerce de Dijon", "priority": 2431, "external_id": null}, {"id": 10848254003, "name": "University of California - San Francisco", "priority": 2432, "external_id": null}, {"id": 10848255003, "name": "Hack Reactor", "priority": 2433, "external_id": null}, {"id": 10848256003, "name": "St. Mary''s College of California", "priority": 2434, "external_id": null}, {"id": 10848257003, "name": "New England Law", "priority": 2435, "external_id": null}, {"id": 10848258003, "name": "University of California, Merced", "priority": 2436, "external_id": null}, {"id": 10848259003, "name": "University of California, Hastings College of the Law", "priority": 2437, "external_id": null}, {"id": 10848260003, "name": "V.N. Karazin Kharkiv National University", "priority": 2438, "external_id": null}, {"id": 10848261003, "name": "SIM University (UniSIM)", "priority": 2439, "external_id": null}, {"id": 10848262003, "name": "Singapore Management University (SMU)", "priority": 2440, "external_id": null}, {"id": 10848263003, "name": "Singapore University of Technology and Design (SUTD)", "priority": 2441, "external_id": null}, {"id": 10848264003, "name": "Singapore Institute of Technology (SIT)", "priority": 2442, "external_id": null}, {"id": 10848265003, "name": "Nanyang Polytechnic (NYP)", "priority": 2443, "external_id": null}, {"id": 10848266003, "name": "Ngee Ann Polytechnic (NP)", "priority": 2444, "external_id": null}, {"id": 10848267003, "name": "Republic Polytechnic (RP)", "priority": 2445, "external_id": null}, {"id": 10848268003, "name": "Singapore Polytechnic (SP)", "priority": 2446, "external_id": null}, {"id": 10848269003, "name": "Temasek Polytechnic (TP)", "priority": 2447, "external_id": null}, {"id": 10848270003, "name": "INSEAD", "priority": 2448, "external_id": null}, {"id": 10848271003, "name": "Funda\u00e7\u00e3o Get\u00falio Vargas", "priority": 2449, "external_id": null}, {"id": 10848272003, "name": "Acharya Nagarjuna University", "priority": 2450, "external_id": null}, {"id": 10848273003, "name": "University of California - Santa Barbara", "priority": 2451, "external_id": null}, {"id": 10848274003, "name": "University of California - Irvine", "priority": 2452, "external_id": null}, {"id": 10848275003, "name": "California State University - Long Beach", "priority": 2453, "external_id": null}, {"id": 10848276003, "name": "Robert Morris University Illinois", "priority": 2454, "external_id": null}, {"id": 10848277003, "name": "Harold Washington College - City Colleges of Chicago", "priority": 2455, "external_id": null}, {"id": 10848278003, "name": "Harry S Truman College - City Colleges of Chicago", "priority": 2456, "external_id": null}, {"id": 10848279003, "name": "Kennedy-King College - City Colleges of Chicago", "priority": 2457, "external_id": null}, {"id": 10848280003, "name": "Malcolm X College - City Colleges of Chicago", "priority": 2458, "external_id": null}, {"id": 10848281003, "name": "Olive-Harvey College - City Colleges of Chicago", "priority": 2459, "external_id": null}, {"id": 10848282003, "name": "Richard J Daley College - City Colleges of Chicago", "priority": 2460, "external_id": null}, {"id": 10848283003, "name": "Wilbur Wright College - City Colleges of Chicago", "priority": 2461, "external_id": null}, {"id": 10848284003, "name": "Abertay University", "priority": 2462, "external_id": null}, {"id": 10848285003, "name": "Pontif\u00edcia Universidade Cat\u00f3lica de Minas Gerais", "priority": 2463, "external_id": null}, {"id": 10848286003, "name": "Other", "priority": 2464, "external_id": null}, {"id": 19126655003, "name": "Atlanta College of Arts", "priority": 2465, "external_id": null}]}, "emitted_at": 1664285620787} {"stream": "custom_fields", "data": {"id": 4680899003, "name": "Degree", "active": true, "field_type": "candidate", "priority": 1, "value_type": "single_select", "private": false, "required": false, "require_approval": false, "trigger_new_version": false, "name_key": "degree", "description": null, "expose_in_job_board_api": false, "api_only": false, "offices": [], "departments": [], "template_token_string": null, "custom_field_options": [{"id": 10848287003, "name": "High School", "priority": 0, "external_id": null}, {"id": 10848288003, "name": "Associate's Degree", "priority": 1, "external_id": null}, {"id": 10848289003, "name": "Bachelor's Degree", "priority": 2, "external_id": null}, {"id": 10848290003, "name": "Master's Degree", "priority": 3, "external_id": null}, {"id": 10848291003, "name": "Master of Business Administration (M.B.A.)", "priority": 4, "external_id": null}, {"id": 10848292003, "name": "Juris Doctor (J.D.)", "priority": 5, "external_id": null}, {"id": 10848293003, "name": "Doctor of Medicine (M.D.)", "priority": 6, "external_id": null}, {"id": 10848294003, "name": "Doctor of Philosophy (Ph.D.)", "priority": 7, "external_id": null}, {"id": 10848295003, "name": "Engineer's Degree", "priority": 8, "external_id": null}, {"id": 10848296003, "name": "Other", "priority": 9, "external_id": null}]}, "emitted_at": 1664285620804} {"stream": "custom_fields", "data": {"id": 4680900003, "name": "Discipline", "active": true, "field_type": "candidate", "priority": 2, "value_type": "single_select", "private": false, "required": false, "require_approval": false, "trigger_new_version": false, "name_key": "discipline", "description": null, "expose_in_job_board_api": false, "api_only": false, "offices": [], "departments": [], "template_token_string": null, "custom_field_options": [{"id": 10848297003, "name": "Accounting", "priority": 0, "external_id": null}, {"id": 10848298003, "name": "African Studies", "priority": 1, "external_id": null}, {"id": 10848299003, "name": "Agriculture", "priority": 2, "external_id": null}, {"id": 10848300003, "name": "Anthropology", "priority": 3, "external_id": null}, {"id": 10848301003, "name": "Applied Health Services", "priority": 4, "external_id": null}, {"id": 10848302003, "name": "Architecture", "priority": 5, "external_id": null}, {"id": 10848303003, "name": "Art", "priority": 6, "external_id": null}, {"id": 10848304003, "name": "Asian Studies", "priority": 7, "external_id": null}, {"id": 10848305003, "name": "Biology", "priority": 8, "external_id": null}, {"id": 10848306003, "name": "Business", "priority": 9, "external_id": null}, {"id": 10848307003, "name": "Business Administration", "priority": 10, "external_id": null}, {"id": 10848308003, "name": "Chemistry", "priority": 11, "external_id": null}, {"id": 10848309003, "name": "Classical Languages", "priority": 12, "external_id": null}, {"id": 10848310003, "name": "Communications & Film", "priority": 13, "external_id": null}, {"id": 10848311003, "name": "Computer Science", "priority": 14, "external_id": null}, {"id": 10848312003, "name": "Dentistry", "priority": 15, "external_id": null}, {"id": 10848313003, "name": "Developing Nations", "priority": 16, "external_id": null}, {"id": 10848314003, "name": "Discipline Unknown", "priority": 17, "external_id": null}, {"id": 10848315003, "name": "Earth Sciences", "priority": 18, "external_id": null}, {"id": 10848316003, "name": "Economics", "priority": 19, "external_id": null}, {"id": 10848317003, "name": "Education", "priority": 20, "external_id": null}, {"id": 10848318003, "name": "Electronics", "priority": 21, "external_id": null}, {"id": 10848319003, "name": "Engineering", "priority": 22, "external_id": null}, {"id": 10848320003, "name": "English Studies", "priority": 23, "external_id": null}, {"id": 10848321003, "name": "Environmental Studies", "priority": 24, "external_id": null}, {"id": 10848322003, "name": "European Studies", "priority": 25, "external_id": null}, {"id": 10848323003, "name": "Fashion", "priority": 26, "external_id": null}, {"id": 10848324003, "name": "Finance", "priority": 27, "external_id": null}, {"id": 10848325003, "name": "Fine Arts", "priority": 28, "external_id": null}, {"id": 10848326003, "name": "General Studies", "priority": 29, "external_id": null}, {"id": 10848327003, "name": "Health Services", "priority": 30, "external_id": null}, {"id": 10848328003, "name": "History", "priority": 31, "external_id": null}, {"id": 10848329003, "name": "Human Resources Management", "priority": 32, "external_id": null}, {"id": 10848330003, "name": "Humanities", "priority": 33, "external_id": null}, {"id": 10848331003, "name": "Industrial Arts & Carpentry", "priority": 34, "external_id": null}, {"id": 10848332003, "name": "Information Systems", "priority": 35, "external_id": null}, {"id": 10848333003, "name": "International Relations", "priority": 36, "external_id": null}, {"id": 10848334003, "name": "Journalism", "priority": 37, "external_id": null}, {"id": 10848335003, "name": "Languages", "priority": 38, "external_id": null}, {"id": 10848336003, "name": "Latin American Studies", "priority": 39, "external_id": null}, {"id": 10848337003, "name": "Law", "priority": 40, "external_id": null}, {"id": 10848338003, "name": "Linguistics", "priority": 41, "external_id": null}, {"id": 10848339003, "name": "Manufacturing & Mechanics", "priority": 42, "external_id": null}, {"id": 10848340003, "name": "Mathematics", "priority": 43, "external_id": null}, {"id": 10848341003, "name": "Medicine", "priority": 44, "external_id": null}, {"id": 10848342003, "name": "Middle Eastern Studies", "priority": 45, "external_id": null}, {"id": 10848343003, "name": "Naval Science", "priority": 46, "external_id": null}, {"id": 10848344003, "name": "North American Studies", "priority": 47, "external_id": null}, {"id": 10848345003, "name": "Nuclear Technics", "priority": 48, "external_id": null}, {"id": 10848346003, "name": "Operations Research & Strategy", "priority": 49, "external_id": null}, {"id": 10848347003, "name": "Organizational Theory", "priority": 50, "external_id": null}, {"id": 10848348003, "name": "Philosophy", "priority": 51, "external_id": null}, {"id": 10848349003, "name": "Physical Education", "priority": 52, "external_id": null}, {"id": 10848350003, "name": "Physical Sciences", "priority": 53, "external_id": null}, {"id": 10848351003, "name": "Physics", "priority": 54, "external_id": null}, {"id": 10848352003, "name": "Political Science", "priority": 55, "external_id": null}, {"id": 10848353003, "name": "Psychology", "priority": 56, "external_id": null}, {"id": 10848354003, "name": "Public Policy", "priority": 57, "external_id": null}, {"id": 10848355003, "name": "Public Service", "priority": 58, "external_id": null}, {"id": 10848356003, "name": "Religious Studies", "priority": 59, "external_id": null}, {"id": 10848357003, "name": "Russian & Soviet Studies", "priority": 60, "external_id": null}, {"id": 10848358003, "name": "Scandinavian Studies", "priority": 61, "external_id": null}, {"id": 10848359003, "name": "Science", "priority": 62, "external_id": null}, {"id": 10848360003, "name": "Slavic Studies", "priority": 63, "external_id": null}, {"id": 10848361003, "name": "Social Science", "priority": 64, "external_id": null}, {"id": 10848362003, "name": "Social Sciences", "priority": 65, "external_id": null}, {"id": 10848363003, "name": "Sociology", "priority": 66, "external_id": null}, {"id": 10848364003, "name": "Speech", "priority": 67, "external_id": null}, {"id": 10848365003, "name": "Statistics & Decision Theory", "priority": 68, "external_id": null}, {"id": 10848366003, "name": "Urban Studies", "priority": 69, "external_id": null}, {"id": 10848367003, "name": "Veterinary Medicine", "priority": 70, "external_id": null}, {"id": 10848368003, "name": "Other", "priority": 71, "external_id": null}]}, "emitted_at": 1664285620804} diff --git a/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/schemas/users.json b/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/schemas/users.json index 6e0e7a4a788a..57aa80f97ead 100644 --- a/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/schemas/users.json +++ b/airbyte-integrations/connectors/source-greenhouse/source_greenhouse/schemas/users.json @@ -40,6 +40,75 @@ }, "linked_candidate_ids": { "type": ["null", "array"] + }, + "departments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "parent_id": { + "type": ["null", "integer"] + }, + "parent_department_external_id": { + "type": ["null", "string"] + }, + "child_ids": { + "type": "array" + }, + "child_department_external_ids": { + "type": "array" + }, + "external_id": { + "type": ["null", "string"] + } + } + } + }, + "offices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "location": { + "type": "object", + "properties": { + "name": { + "type": ["null", "string"] + } + } + }, + "primary_contact_user_id": { + "type": "integer" + }, + "parent_id": { + "type": ["null", "integer"] + }, + "parent_office_external_id": { + "type": ["null", "string"] + }, + "child_ids": { + "type": "array" + }, + "child_office_external_ids": { + "type": "array" + }, + "external_id": { + "type": ["null", "string"] + } + } + } } } } diff --git a/docs/integrations/sources/greenhouse.md b/docs/integrations/sources/greenhouse.md index 0d8b0d4d32a4..81a0da3a46e4 100644 --- a/docs/integrations/sources/greenhouse.md +++ b/docs/integrations/sources/greenhouse.md @@ -73,6 +73,7 @@ Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------------------------------------| +| 0.3.0 | 2022-10-19 | [18154](https://github.com/airbytehq/airbyte/pull/18154) | Extend `Users` stream schema | | 0.2.11 | 2022-09-27 | [17239](https://github.com/airbytehq/airbyte/pull/17239) | Always install the latest version of Airbyte CDK | | 0.2.10 | 2022-09-05 | [16338](https://github.com/airbytehq/airbyte/pull/16338) | Implement incremental syncs & fix SATs | | 0.2.9 | 2022-08-22 | [15800](https://github.com/airbytehq/airbyte/pull/15800) | Bugfix to allow reading sentry.yaml and schemas at runtime | From 86cb666b93466af8b32bcf984f285d3d08d6b1fc Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:46:56 +0200 Subject: [PATCH 303/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Hubspot:=20upda?= =?UTF-8?q?te=20CDK;=20fix=20expected=20test=20result=20according=20to=200?= =?UTF-8?q?.2.2=20update=20(#18424)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../source-hubspot/acceptance-test-config.yml | 12 ++++++++++-- .../connectors/source-hubspot/setup.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/airbyte-integrations/connectors/source-hubspot/acceptance-test-config.yml b/airbyte-integrations/connectors/source-hubspot/acceptance-test-config.yml index ac1d4738c9e2..767a95927cc9 100644 --- a/airbyte-integrations/connectors/source-hubspot/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-hubspot/acceptance-test-config.yml @@ -1,8 +1,12 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests connector_image: airbyte/source-hubspot:dev -tests: +acceptance_tests: spec: + tests: - spec_path: "source_hubspot/spec.yaml" connection: + tests: - config_path: "secrets/config.json" status: "succeed" - config_path: "secrets/config_oauth.json" @@ -12,12 +16,14 @@ tests: - config_path: "integration_tests/invalid_config_oauth.json" status: "failed" - config_path: "integration_tests/invalid_config_wrong_title.json" - status: "exception" + status: "failed" discovery: + tests: - config_path: "secrets/config.json" backward_compatibility_tests_config: disable_for_version: "0.1.83" basic_read: + tests: - config_path: "secrets/config.json" timeout_seconds: 600 configured_catalog_path: "sample_files/basic_read_catalog.json" @@ -33,10 +39,12 @@ tests: # expect_records: # path: "integration_tests/expected_records.txt" incremental: + tests: - config_path: "secrets/config.json" configured_catalog_path: "sample_files/incremental_catalog.json" future_state_path: "integration_tests/abnormal_state.json" full_refresh: + tests: - config_path: "secrets/config.json" configured_catalog_path: "sample_files/full_refresh_catalog.json" ignored_fields: diff --git a/airbyte-integrations/connectors/source-hubspot/setup.py b/airbyte-integrations/connectors/source-hubspot/setup.py index 0ebfed398b9c..a4c05e9a795b 100644 --- a/airbyte-integrations/connectors/source-hubspot/setup.py +++ b/airbyte-integrations/connectors/source-hubspot/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1", + "airbyte-cdk~=0.2", "backoff==1.11.1", "pendulum==2.1.2", "requests==2.26.0", From b323ccd66addd73b0a8f78599dd8dd56b33a58f1 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 25 Oct 2022 11:45:12 -0400 Subject: [PATCH 304/498] Additional worker APM tracing (#18427) --- .../workers/internal/AirbyteMessageTracker.java | 5 +++++ .../internal/AirbyteProtocolPredicate.java | 4 ++++ .../internal/DefaultAirbyteDestination.java | 11 +++++++++++ .../workers/internal/DefaultAirbyteSource.java | 9 +++++++++ .../internal/DefaultAirbyteStreamFactory.java | 4 ++++ .../workers/internal/EmptyAirbyteSource.java | 9 +++++++++ .../workers/internal/StateDeltaTracker.java | 6 ++++++ .../internal/VersionedAirbyteStreamFactory.java | 8 ++++++-- .../state_aggregator/SingleStateAggregator.java | 5 +++++ .../state_aggregator/StreamStateAggregator.java | 5 +++++ .../process/AirbyteIntegrationLauncher.java | 17 +++++++++++++++++ 11 files changed, 81 insertions(+), 2 deletions(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java index 259d8b3f192c..b6a03567c017 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteMessageTracker.java @@ -4,12 +4,15 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; +import datadog.trace.api.Trace; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.json.Jsons; import io.airbyte.config.FailureReason; @@ -98,6 +101,7 @@ protected AirbyteMessageTracker(final StateDeltaTracker stateDeltaTracker, this.stateAggregator = stateAggregator; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void acceptFromSource(final AirbyteMessage message) { logMessageAsJSON("source", message); @@ -110,6 +114,7 @@ public void acceptFromSource(final AirbyteMessage message) { } } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void acceptFromDestination(final AirbyteMessage message) { logMessageAsJSON("destination", message); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java index bd61e714b670..d07104b88994 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/AirbyteProtocolPredicate.java @@ -4,7 +4,10 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.fasterxml.jackson.databind.JsonNode; +import datadog.trace.api.Trace; import io.airbyte.protocol.models.AirbyteProtocolSchema; import io.airbyte.validation.json.JsonSchemaValidator; import java.util.function.Predicate; @@ -23,6 +26,7 @@ public AirbyteProtocolPredicate() { schema = JsonSchemaValidator.getSchema(AirbyteProtocolSchema.PROTOCOL.getFile(), "AirbyteMessage"); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public boolean test(final JsonNode s) { return jsonSchemaValidator.test(schema, s); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java index b866d9dbf740..8520fbbfa4ca 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteDestination.java @@ -4,8 +4,11 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.google.common.base.Charsets; import com.google.common.base.Preconditions; +import datadog.trace.api.Trace; import io.airbyte.commons.io.IOs; import io.airbyte.commons.io.LineGobbler; import io.airbyte.commons.json.Jsons; @@ -58,6 +61,7 @@ public DefaultAirbyteDestination(final IntegrationLauncher integrationLauncher, this.streamFactory = streamFactory; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void start(final WorkerDestinationConfig destinationConfig, final Path jobRoot) throws IOException, WorkerException { Preconditions.checkState(destinationProcess == null); @@ -79,6 +83,7 @@ public void start(final WorkerDestinationConfig destinationConfig, final Path jo .iterator(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void accept(final AirbyteMessage message) throws IOException { Preconditions.checkState(destinationProcess != null && !inputHasEnded.get()); @@ -87,6 +92,7 @@ public void accept(final AirbyteMessage message) throws IOException { writer.newLine(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void notifyEndOfInput() throws IOException { Preconditions.checkState(destinationProcess != null && !inputHasEnded.get()); @@ -96,6 +102,7 @@ public void notifyEndOfInput() throws IOException { inputHasEnded.set(true); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void close() throws Exception { if (destinationProcess == null) { @@ -116,6 +123,7 @@ public void close() throws Exception { } } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() throws Exception { LOGGER.info("Attempting to cancel destination process..."); @@ -129,6 +137,7 @@ public void cancel() throws Exception { } } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public boolean isFinished() { Preconditions.checkState(destinationProcess != null); @@ -139,6 +148,7 @@ public boolean isFinished() { return !messageIterator.hasNext() && !destinationProcess.isAlive(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public int getExitValue() { Preconditions.checkState(destinationProcess != null, "Destination process is null, cannot retrieve exit value."); @@ -151,6 +161,7 @@ public int getExitValue() { return exitValue; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Optional attemptRead() { Preconditions.checkState(destinationProcess != null); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java index 8a94d3a05599..1f7767a0ae97 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteSource.java @@ -4,8 +4,11 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import datadog.trace.api.Trace; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.io.IOs; import io.airbyte.commons.io.LineGobbler; @@ -63,6 +66,7 @@ public DefaultAirbyteSource(final IntegrationLauncher integrationLauncher) { this.heartbeatMonitor = heartbeatMonitor; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void start(final WorkerSourceConfig sourceConfig, final Path jobRoot) throws Exception { Preconditions.checkState(sourceProcess == null); @@ -85,6 +89,7 @@ public void start(final WorkerSourceConfig sourceConfig, final Path jobRoot) thr .iterator(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public boolean isFinished() { Preconditions.checkState(sourceProcess != null); @@ -96,6 +101,7 @@ public boolean isFinished() { return !messageIterator.hasNext() && !sourceProcess.isAlive(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public int getExitValue() throws IllegalStateException { Preconditions.checkState(sourceProcess != null, "Source process is null, cannot retrieve exit value."); @@ -108,6 +114,7 @@ public int getExitValue() throws IllegalStateException { return exitValue; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Optional attemptRead() { Preconditions.checkState(sourceProcess != null); @@ -115,6 +122,7 @@ public Optional attemptRead() { return Optional.ofNullable(messageIterator.hasNext() ? messageIterator.next() : null); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void close() throws Exception { if (sourceProcess == null) { @@ -134,6 +142,7 @@ public void close() throws Exception { } } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() throws Exception { LOGGER.info("Attempting to cancel source process..."); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java index c8bd56efa0ab..21ae3f239313 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/DefaultAirbyteStreamFactory.java @@ -4,7 +4,10 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.fasterxml.jackson.databind.JsonNode; +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.logging.MdcScope; import io.airbyte.metrics.lib.MetricClientFactory; @@ -52,6 +55,7 @@ public DefaultAirbyteStreamFactory(final MdcScope.Builder containerLogMdcBuilder this.containerLogMdcBuilder = containerLogMdcBuilder; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Stream create(final BufferedReader bufferedReader) { final var metricClient = MetricClientFactory.getMetricClient(); diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java index 9bbeb99be12a..ad46f140cf75 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/EmptyAirbyteSource.java @@ -4,6 +4,9 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.config.ResetSourceConfiguration; import io.airbyte.config.StateType; @@ -50,6 +53,7 @@ public EmptyAirbyteSource(final boolean useStreamCapableState) { this.useStreamCapableState = useStreamCapableState; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void start(final WorkerSourceConfig workerSourceConfig, final Path jobRoot) throws Exception { @@ -105,16 +109,19 @@ public void start(final WorkerSourceConfig workerSourceConfig, final Path jobRoo } // always finished. it has no data to send. + @Trace(operationName = WORKER_OPERATION_NAME) @Override public boolean isFinished() { return hasEmittedState.get(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public int getExitValue() { return 0; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Optional attemptRead() { if (!isStarted) { @@ -134,11 +141,13 @@ public Optional attemptRead() { } } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void close() throws Exception { // no op. } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void cancel() throws Exception { // no op. diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java index 480b4678f076..beab1e0b63e1 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/StateDeltaTracker.java @@ -4,7 +4,10 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.google.common.annotations.VisibleForTesting; +import datadog.trace.api.Trace; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -72,6 +75,7 @@ public StateDeltaTracker(final long memoryLimitBytes) { * @throws StateDeltaTrackerException thrown when the memory footprint of stateDeltas exceeds * available capacity. */ + @Trace(operationName = WORKER_OPERATION_NAME) public void addState(final int stateHash, final Map streamIndexToRecordCount) throws StateDeltaTrackerException { synchronized (this) { final int size = STATE_HASH_BYTES + (streamIndexToRecordCount.size() * BYTES_PER_STREAM); @@ -104,6 +108,7 @@ public void addState(final int stateHash, final Map streamIndexToRe * @throws StateDeltaTrackerException thrown when committed counts can no longer be reliably * computed. */ + @Trace(operationName = WORKER_OPERATION_NAME) public void commitStateHash(final int stateHash) throws StateDeltaTrackerException { synchronized (this) { if (capacityExceeded) { @@ -139,6 +144,7 @@ public void commitStateHash(final int stateHash) throws StateDeltaTrackerExcepti } } + @Trace(operationName = WORKER_OPERATION_NAME) public Map getStreamToCommittedRecords() { return streamToCommittedRecords; } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java index b49190dc8658..65a2cd461fe1 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/VersionedAirbyteStreamFactory.java @@ -4,8 +4,11 @@ package io.airbyte.workers.internal; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Preconditions; +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.logging.MdcScope; import io.airbyte.commons.protocol.AirbyteMessageSerDeProvider; @@ -74,6 +77,7 @@ public VersionedAirbyteStreamFactory(final AirbyteMessageSerDeProvider serDeProv * If detectVersion is set to true, it will decide which protocol version to use from the content of * the stream rather than the one passed from the constructor. */ + @Trace(operationName = WORKER_OPERATION_NAME) @SneakyThrows @Override public Stream create(final BufferedReader bufferedReader) { @@ -123,7 +127,7 @@ private Optional detectVersion(final BufferedReader bufferedReader) thr } bufferedReader.reset(); return Optional.empty(); - } catch (IOException e) { + } catch (final IOException e) { logger.warn( "Protocol version detection failed, it is likely than the connector sent more than {}B without an complete SPEC message." + " A SPEC message that is too long could be the root cause here.", @@ -156,7 +160,7 @@ protected Stream toAirbyteMessage(final JsonNode json) { try { final io.airbyte.protocol.models.v0.AirbyteMessage message = migrator.upgrade(deserializer.deserialize(json)); return Stream.of(convert(message)); - } catch (RuntimeException e) { + } catch (final RuntimeException e) { logger.warn("Failed to upgrade a message from version {}: {}", protocolVersion, Jsons.serialize(json), e); return Stream.empty(); } diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java index 0cfe422ea1f7..7eaca78a662a 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/SingleStateAggregator.java @@ -4,6 +4,9 @@ package io.airbyte.workers.internal.state_aggregator; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.config.State; import io.airbyte.protocol.models.AirbyteStateMessage; @@ -14,11 +17,13 @@ class SingleStateAggregator implements StateAggregator { AirbyteStateMessage state; + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void ingest(final AirbyteStateMessage stateMessage) { state = stateMessage; } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public State getAggregated() { if (state.getType() == null || state.getType() == AirbyteStateType.LEGACY) { diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java index 4d3247b2549d..dfe046eaf2c9 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/internal/state_aggregator/StreamStateAggregator.java @@ -4,6 +4,9 @@ package io.airbyte.workers.internal.state_aggregator; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + +import datadog.trace.api.Trace; import io.airbyte.commons.json.Jsons; import io.airbyte.config.State; import io.airbyte.protocol.models.AirbyteStateMessage; @@ -15,6 +18,7 @@ class StreamStateAggregator implements StateAggregator { Map aggregatedState = new HashMap<>(); + @Trace(operationName = WORKER_OPERATION_NAME) @Override public void ingest(final AirbyteStateMessage stateMessage) { /** @@ -27,6 +31,7 @@ public void ingest(final AirbyteStateMessage stateMessage) { aggregatedState.put(stateMessage.getStream().getStreamDescriptor(), stateMessage); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public State getAggregated() { diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java index 405293663946..7f8542f9b116 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/process/AirbyteIntegrationLauncher.java @@ -4,13 +4,20 @@ package io.airbyte.workers.process; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.DOCKER_IMAGE_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ROOT_KEY; +import static io.airbyte.metrics.lib.ApmTraceConstants.WORKER_OPERATION_NAME; + import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; +import datadog.trace.api.Trace; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.features.FeatureFlags; import io.airbyte.config.ResourceRequirements; import io.airbyte.config.WorkerEnvConstants; +import io.airbyte.metrics.lib.ApmTraceUtils; import io.airbyte.workers.exception.WorkerException; import java.nio.file.Path; import java.util.Collections; @@ -63,8 +70,10 @@ public AirbyteIntegrationLauncher(final String jobId, this.featureFlags = new EnvVariableFeatureFlags(); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Process spec(final Path jobRoot) throws WorkerException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot, DOCKER_IMAGE_KEY, imageName)); return processFactory.create( SPEC_JOB, jobId, @@ -81,8 +90,10 @@ public Process spec(final Path jobRoot) throws WorkerException { "spec"); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Process check(final Path jobRoot, final String configFilename, final String configContents) throws WorkerException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot, DOCKER_IMAGE_KEY, imageName)); return processFactory.create( CHECK_JOB, jobId, @@ -100,8 +111,10 @@ public Process check(final Path jobRoot, final String configFilename, final Stri CONFIG, configFilename); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Process discover(final Path jobRoot, final String configFilename, final String configContents) throws WorkerException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot, DOCKER_IMAGE_KEY, imageName)); return processFactory.create( DISCOVER_JOB, jobId, @@ -119,6 +132,7 @@ public Process discover(final Path jobRoot, final String configFilename, final S CONFIG, configFilename); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Process read(final Path jobRoot, final String configFilename, @@ -128,6 +142,7 @@ public Process read(final Path jobRoot, final String stateFilename, final String stateContents) throws WorkerException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot, DOCKER_IMAGE_KEY, imageName)); final List arguments = Lists.newArrayList( "read", CONFIG, configFilename, @@ -161,6 +176,7 @@ public Process read(final Path jobRoot, arguments.toArray(new String[arguments.size()])); } + @Trace(operationName = WORKER_OPERATION_NAME) @Override public Process write(final Path jobRoot, final String configFilename, @@ -168,6 +184,7 @@ public Process write(final Path jobRoot, final String catalogFilename, final String catalogContents) throws WorkerException { + ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId, JOB_ROOT_KEY, jobRoot, DOCKER_IMAGE_KEY, imageName)); final Map files = ImmutableMap.of( configFilename, configContents, catalogFilename, catalogContents); From 25e7a37de6dd609fa9555886bcb66ef898cfbe52 Mon Sep 17 00:00:00 2001 From: "Sherif A. Nada" Date: Tue, 25 Oct 2022 09:02:06 -0700 Subject: [PATCH 305/498] Publish new CDK version with typehints exported (#18398) --- airbyte-cdk/python/CHANGELOG.md | 3 +++ airbyte-cdk/python/setup.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/airbyte-cdk/python/CHANGELOG.md b/airbyte-cdk/python/CHANGELOG.md index edd080f4400a..7971c272e615 100644 --- a/airbyte-cdk/python/CHANGELOG.md +++ b/airbyte-cdk/python/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.3.0 +Publish python typehints via `py.typed` file. + ## 0.2.3 - Propagate options to InterpolatedRequestInputProvider diff --git a/airbyte-cdk/python/setup.py b/airbyte-cdk/python/setup.py index 5bc6577ecf4a..fb2385a1ee9a 100644 --- a/airbyte-cdk/python/setup.py +++ b/airbyte-cdk/python/setup.py @@ -15,7 +15,7 @@ setup( name="airbyte-cdk", - version="0.2.3", + version="0.3.0", description="A framework for writing Airbyte Connectors.", long_description=README, long_description_content_type="text/markdown", From 62eb6b6623e70dc131787d4ec7409ef1033fb3b6 Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Tue, 25 Oct 2022 09:04:29 -0700 Subject: [PATCH 306/498] Extract the AttemptApi out of the ConfigurationApi (#18406) * Tmp * Extract the Attempt API from the V1 API * Add comments * format * Rename to Controller --- .../java/io/airbyte/server/ServerFactory.java | 9 +++-- .../server/apis/AttemptApiController.java | 28 +++++++++++++++ .../airbyte/server/apis/ConfigurationApi.java | 23 ++++++------ .../server/apis/binders/AttemptApiBinder.java | 21 +++++++++++ .../apis/factories/AttemptApiFactory.java | 35 +++++++++++++++++++ 5 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java create mode 100644 airbyte-server/src/main/java/io/airbyte/server/apis/binders/AttemptApiBinder.java create mode 100644 airbyte-server/src/main/java/io/airbyte/server/apis/factories/AttemptApiFactory.java diff --git a/airbyte-server/src/main/java/io/airbyte/server/ServerFactory.java b/airbyte-server/src/main/java/io/airbyte/server/ServerFactory.java index a86486d53961..f943a52419d4 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/ServerFactory.java +++ b/airbyte-server/src/main/java/io/airbyte/server/ServerFactory.java @@ -14,7 +14,10 @@ import io.airbyte.config.persistence.StatePersistence; import io.airbyte.db.Database; import io.airbyte.persistence.job.JobPersistence; +import io.airbyte.server.apis.AttemptApiController; import io.airbyte.server.apis.ConfigurationApi; +import io.airbyte.server.apis.binders.AttemptApiBinder; +import io.airbyte.server.apis.factories.AttemptApiFactory; import io.airbyte.server.scheduler.EventRunner; import io.airbyte.server.scheduler.SynchronousSchedulerClient; import java.net.http.HttpClient; @@ -82,9 +85,11 @@ public ServerRunnable create(final SynchronousSchedulerClient synchronousSchedul configsFlyway, jobsFlyway); + AttemptApiFactory.setValues(jobPersistence, MDC.getCopyOfContextMap()); + // server configurations - final Set> componentClasses = Set.of(ConfigurationApi.class); - final Set components = Set.of(new CorsFilter(), new ConfigurationApiBinder()); + final Set> componentClasses = Set.of(ConfigurationApi.class, AttemptApiController.class); + final Set components = Set.of(new CorsFilter(), new ConfigurationApiBinder(), new AttemptApiBinder()); // construct server return new ServerApp(airbyteVersion, componentClasses, components); diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java b/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java new file mode 100644 index 000000000000..4aa363a9a22c --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/AttemptApiController.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.apis; + +import io.airbyte.api.generated.AttemptApi; +import io.airbyte.api.model.generated.InternalOperationResult; +import io.airbyte.api.model.generated.SetWorkflowInAttemptRequestBody; +import io.airbyte.persistence.job.JobPersistence; +import io.airbyte.server.handlers.AttemptHandler; +import javax.ws.rs.Path; + +@Path("/v1/attempt/set_workflow_in_attempt") +public class AttemptApiController implements AttemptApi { + + private final AttemptHandler attemptHandler; + + public AttemptApiController(final JobPersistence jobPersistence) { + attemptHandler = new AttemptHandler(jobPersistence); + } + + @Override + public InternalOperationResult setWorkflowInAttempt(final SetWorkflowInAttemptRequestBody requestBody) { + return ConfigurationApi.execute(() -> attemptHandler.setWorkflowInAttempt(requestBody)); + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index ae575aaa2572..81fc1daff33b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -113,7 +113,6 @@ import io.airbyte.persistence.job.WorkspaceHelper; import io.airbyte.server.errors.BadObjectSchemaKnownException; import io.airbyte.server.errors.IdNotFoundKnownException; -import io.airbyte.server.handlers.AttemptHandler; import io.airbyte.server.handlers.ConnectionsHandler; import io.airbyte.server.handlers.DbMigrationHandler; import io.airbyte.server.handlers.DestinationDefinitionsHandler; @@ -141,6 +140,7 @@ import java.nio.file.Path; import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.NotImplementedException; import org.flywaydb.core.Flyway; @javax.ws.rs.Path("/v1") @@ -164,7 +164,6 @@ public class ConfigurationApi implements io.airbyte.api.generated.V1Api { private final OpenApiConfigHandler openApiConfigHandler; private final DbMigrationHandler dbMigrationHandler; private final OAuthHandler oAuthHandler; - private final AttemptHandler attemptHandler; private final WorkerEnvironment workerEnvironment; private final LogConfigs logConfigs; private final Path workspaceRoot; @@ -249,7 +248,6 @@ public ConfigurationApi(final ConfigRepository configRepository, logsHandler = new LogsHandler(); openApiConfigHandler = new OpenApiConfigHandler(); dbMigrationHandler = new DbMigrationHandler(configsDatabase, configsFlyway, jobsDatabase, jobsFlyway); - attemptHandler = new AttemptHandler(jobPersistence); } // WORKSPACE @@ -433,6 +431,15 @@ public void setInstancewideSourceOauthParams(final SetInstancewideSourceOauthPar }); } + /** + * This implementation has been moved to {@link AttemptApiController}. Since the path of + * {@link AttemptApiController} is more granular, it will override this implementation + */ + @Override + public InternalOperationResult setWorkflowInAttempt(final SetWorkflowInAttemptRequestBody setWorkflowInAttemptRequestBody) { + throw new NotImplementedException(); + } + // SOURCE IMPLEMENTATION @Override @@ -835,12 +842,8 @@ public WebBackendWorkspaceStateResult webBackendGetWorkspaceState(final WebBacke return execute(() -> webBackendConnectionsHandler.getWorkspaceState(webBackendWorkspaceState)); } - @Override - public InternalOperationResult setWorkflowInAttempt(final SetWorkflowInAttemptRequestBody requestBody) { - return execute(() -> attemptHandler.setWorkflowInAttempt(requestBody)); - } - - private static T execute(final HandlerCall call) { + // TODO: Move to common when all the api are moved + static T execute(final HandlerCall call) { try { return call.call(); } catch (final ConfigNotFoundException e) { @@ -854,7 +857,7 @@ private static T execute(final HandlerCall call) { } } - private interface HandlerCall { + interface HandlerCall { T call() throws ConfigNotFoundException, IOException, JsonValidationException; diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/binders/AttemptApiBinder.java b/airbyte-server/src/main/java/io/airbyte/server/apis/binders/AttemptApiBinder.java new file mode 100644 index 000000000000..2eb09dddaf02 --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/binders/AttemptApiBinder.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.apis.binders; + +import io.airbyte.server.apis.AttemptApiController; +import io.airbyte.server.apis.factories.AttemptApiFactory; +import org.glassfish.hk2.utilities.binding.AbstractBinder; +import org.glassfish.jersey.process.internal.RequestScoped; + +public class AttemptApiBinder extends AbstractBinder { + + @Override + protected void configure() { + bindFactory(AttemptApiFactory.class) + .to(AttemptApiController.class) + .in(RequestScoped.class); + } + +} diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/factories/AttemptApiFactory.java b/airbyte-server/src/main/java/io/airbyte/server/apis/factories/AttemptApiFactory.java new file mode 100644 index 000000000000..9fea2dda023c --- /dev/null +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/factories/AttemptApiFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.server.apis.factories; + +import io.airbyte.persistence.job.JobPersistence; +import io.airbyte.server.apis.AttemptApiController; +import java.util.Map; +import org.glassfish.hk2.api.Factory; +import org.slf4j.MDC; + +public class AttemptApiFactory implements Factory { + + private static JobPersistence jobPersistence; + private static Map mdc; + + public static void setValues(final JobPersistence jobPersistence, final Map mdc) { + AttemptApiFactory.jobPersistence = jobPersistence; + AttemptApiFactory.mdc = mdc; + } + + @Override + public AttemptApiController provide() { + MDC.setContextMap(AttemptApiFactory.mdc); + + return new AttemptApiController(jobPersistence); + } + + @Override + public void dispose(final AttemptApiController instance) { + /* no op */ + } + +} From b050e7b7bf36359294737b075744e3c8732c4544 Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 25 Oct 2022 18:25:04 +0200 Subject: [PATCH 307/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Salesforce:=20u?= =?UTF-8?q?pdate=20CDK;=20fix=20test=20supported=5Fsync=5Fmodes=20is=20req?= =?UTF-8?q?uired=20for=20AirbyteStream=20(#18429)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- airbyte-integrations/connectors/source-salesforce/setup.py | 2 +- .../connectors/source-salesforce/unit_tests/api_test.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/airbyte-integrations/connectors/source-salesforce/setup.py b/airbyte-integrations/connectors/source-salesforce/setup.py index beabe0dd8bf3..517429e3d7d1 100644 --- a/airbyte-integrations/connectors/source-salesforce/setup.py +++ b/airbyte-integrations/connectors/source-salesforce/setup.py @@ -5,7 +5,7 @@ from setuptools import find_packages, setup -MAIN_REQUIREMENTS = ["airbyte-cdk~=0.1", "vcrpy==4.1.1", "pandas"] +MAIN_REQUIREMENTS = ["airbyte-cdk~=0.2", "vcrpy==4.1.1", "pandas"] TEST_REQUIREMENTS = ["pytest~=6.1", "pytest-mock~=3.6", "requests_mock", "source-acceptance-test", "pytest-timeout"] diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py index aadaef4cae19..38144ceae4c7 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py @@ -460,7 +460,9 @@ def test_forwarding_sobject_options(stream_config, stream_names, catalog_stream_ catalog = ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( - stream=AirbyteStream(name=catalog_stream_name, json_schema={"type": "object"}), + stream=AirbyteStream(name=catalog_stream_name, + supported_sync_modes=[SyncMode.full_refresh], + json_schema={"type": "object"}), sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.overwrite, ) From 316cdd88d6cf99500331f8ea26909ac4c9bc4cee Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 25 Oct 2022 09:58:10 -0700 Subject: [PATCH 308/498] :window: :wrench: Introduce useAuthentication hook (#17965) * Introduce useAuthentication hook * Work for both auth mechanism * Finalize implementation * Add documentation * Fix unit tests * Add unit tests * Remove getValues call * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx Co-authored-by: Lake Mossman * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx Co-authored-by: Lake Mossman * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx Co-authored-by: Lake Mossman * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx Co-authored-by: Lake Mossman * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx Co-authored-by: Lake Mossman * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx Co-authored-by: Lake Mossman * Update airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.test.tsx Co-authored-by: Lake Mossman * Change variable name Co-authored-by: Lake Mossman --- airbyte-webapp/src/core/form/types.ts | 1 - airbyte-webapp/src/core/jsonSchema/index.ts | 1 - .../src/core/jsonSchema/schemaToUiWidget.ts | 1 - airbyte-webapp/src/core/jsonSchema/types.ts | 1 - .../src/core/jsonSchema/utils.test.ts | 222 ------------------ airbyte-webapp/src/core/jsonSchema/utils.ts | 98 -------- .../components/Sections/FormSection.tsx | 39 ++- .../Sections/auth/AuthButton.test.tsx | 25 +- .../components/Sections/auth/AuthButton.tsx | 12 +- .../ServiceForm/serviceFormContext.tsx | 46 +--- .../ServiceForm/useAuthentication.mocks.ts | 78 ++++++ .../ServiceForm/useAuthentication.test.tsx | 195 +++++++++++++++ .../ServiceForm/useAuthentication.tsx | 210 +++++++++++++++++ .../Connector/ServiceForm/useBuildForm.tsx | 42 +--- .../src/views/Connector/ServiceForm/utils.ts | 4 +- 15 files changed, 527 insertions(+), 448 deletions(-) delete mode 100644 airbyte-webapp/src/core/jsonSchema/utils.test.ts delete mode 100644 airbyte-webapp/src/core/jsonSchema/utils.ts create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.mocks.ts create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.test.tsx create mode 100644 airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx diff --git a/airbyte-webapp/src/core/form/types.ts b/airbyte-webapp/src/core/form/types.ts index 30b9baf6b46c..658d5e267884 100644 --- a/airbyte-webapp/src/core/form/types.ts +++ b/airbyte-webapp/src/core/form/types.ts @@ -25,7 +25,6 @@ export interface FormGroupItem extends FormItem { jsonSchema: AirbyteJSONSchema; properties: FormBlock[]; isLoading?: boolean; - hasOauth?: boolean; examples?: JSONSchema7Type; } diff --git a/airbyte-webapp/src/core/jsonSchema/index.ts b/airbyte-webapp/src/core/jsonSchema/index.ts index 1fe6d2c44ae9..abfe80b16dd1 100644 --- a/airbyte-webapp/src/core/jsonSchema/index.ts +++ b/airbyte-webapp/src/core/jsonSchema/index.ts @@ -1,4 +1,3 @@ export * from "./types"; export * from "./schemaToUiWidget"; export * from "./schemaToYup"; -export * from "./utils"; diff --git a/airbyte-webapp/src/core/jsonSchema/schemaToUiWidget.ts b/airbyte-webapp/src/core/jsonSchema/schemaToUiWidget.ts index c34c6e7f647b..67ba062d869d 100644 --- a/airbyte-webapp/src/core/jsonSchema/schemaToUiWidget.ts +++ b/airbyte-webapp/src/core/jsonSchema/schemaToUiWidget.ts @@ -81,7 +81,6 @@ export const jsonSchemaToUiWidget = ( jsonSchema, path: path || key, fieldKey: key, - hasOauth: jsonSchema.is_auth, properties, isRequired, }; diff --git a/airbyte-webapp/src/core/jsonSchema/types.ts b/airbyte-webapp/src/core/jsonSchema/types.ts index b2bbedba5328..8370d02364fa 100644 --- a/airbyte-webapp/src/core/jsonSchema/types.ts +++ b/airbyte-webapp/src/core/jsonSchema/types.ts @@ -2,7 +2,6 @@ import { JSONSchema7, JSONSchema7Definition } from "json-schema"; interface AirbyteJSONSchemaProps { airbyte_secret?: boolean; - is_auth?: boolean; airbyte_hidden?: boolean; multiline?: boolean; order?: number; diff --git a/airbyte-webapp/src/core/jsonSchema/utils.test.ts b/airbyte-webapp/src/core/jsonSchema/utils.test.ts deleted file mode 100644 index 23a84e32efef..000000000000 --- a/airbyte-webapp/src/core/jsonSchema/utils.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { AirbyteJSONSchema } from "./types"; -import { applyFuncAt, removeNestedPaths } from "./utils"; - -it("should filter paths", () => { - const schema: AirbyteJSONSchema = { - type: "object", - required: ["host", "name"], - properties: { - host: { type: "string" }, - port: { type: "string" }, - name: { type: "string" }, - db: { - type: "object", - properties: { - url: { type: "string" }, - }, - }, - ssl: { - type: "object", - properties: { - ssl_done: { type: "string" }, - ssl_string: { type: "string" }, - }, - }, - }, - }; - - const filtered = removeNestedPaths(schema, [["host"], ["ssl"]]); - - expect(filtered).toEqual({ - required: ["name"], - properties: { - db: { - properties: { - url: { - type: "string", - }, - }, - type: "object", - }, - name: { - type: "string", - }, - port: { - type: "string", - }, - }, - type: "object", - }); -}); - -it("should exclude nested paths", () => { - const schema: AirbyteJSONSchema = { - type: "object", - properties: { - ssl: { - type: "object", - properties: { - ssl_done: { type: "string" }, - ssl_port: { type: "string" }, - ssl_object: { - type: "object", - properties: { - ssl_object_ref: { - type: "string", - }, - ssl_object_ref2: { - type: "string", - }, - }, - }, - }, - }, - }, - }; - - const filtered = removeNestedPaths(schema, [ - ["ssl", "ssl_object", "ssl_object_ref2"], - ["ssl", "ssl_done"], - ]); - - expect(filtered).toEqual({ - properties: { - ssl: { - properties: { - ssl_port: { - type: "string", - }, - ssl_object: { - properties: { - ssl_object_ref: { - type: "string", - }, - }, - type: "object", - }, - }, - type: "object", - }, - }, - type: "object", - }); -}); - -it("should exclude paths in oneOf", () => { - const schema: AirbyteJSONSchema = { - type: "object", - properties: { - ssl: { - type: "object", - oneOf: [ - { - properties: { - ssl_string: { - type: "string", - }, - }, - }, - { - properties: { - ssl_path: { - type: "string", - }, - }, - }, - ], - }, - }, - }; - - const filtered = removeNestedPaths(schema, [["ssl", "ssl_string"]]); - - expect(filtered).toEqual({ - properties: { - ssl: { - oneOf: [ - { - properties: {}, - }, - { - properties: { - ssl_path: { - type: "string", - }, - }, - }, - ], - type: "object", - }, - }, - type: "object", - }); -}); - -it("apply func at", () => { - const schema: AirbyteJSONSchema = { - type: "object", - properties: { - ssl: { - type: "object", - oneOf: [ - { - properties: { - ssl_string: { - type: "string", - }, - }, - }, - { - properties: { - ssl_path: { - type: "string", - }, - }, - }, - ], - }, - }, - }; - - const applied = applyFuncAt(schema, ["ssl", 0], (sch) => { - if (typeof sch === "boolean") { - return {}; - } - - sch.properties = sch.properties ?? {}; - - sch.properties.marked = { - type: "string", - }; - - return sch; - }); - - expect(applied).toEqual({ - type: "object", - properties: { - ssl: { - type: "object", - oneOf: [ - { - properties: { - ssl_string: { - type: "string", - }, - marked: { - type: "string", - }, - }, - }, - { - properties: { - ssl_path: { - type: "string", - }, - }, - }, - ], - }, - }, - }); -}); diff --git a/airbyte-webapp/src/core/jsonSchema/utils.ts b/airbyte-webapp/src/core/jsonSchema/utils.ts deleted file mode 100644 index 99d8d36e52ff..000000000000 --- a/airbyte-webapp/src/core/jsonSchema/utils.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { JSONSchema7, JSONSchema7Definition } from "json-schema"; - -import { isDefined } from "utils/common"; - -import { AirbyteJSONSchema, AirbyteJSONSchemaDefinition } from "./types"; - -function removeNestedPaths( - schema: AirbyteJSONSchemaDefinition, - pathList: string[][], - ignoreProp = true -): AirbyteJSONSchema { - if (typeof schema === "boolean") { - // TODO: Types need to be corrected here - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return null as any; - } - - if (pathList.length === 0) { - return schema; - } - - const resultSchema: JSONSchema7 = schema; - - if (schema.oneOf) { - resultSchema.oneOf = schema.oneOf.map((o) => removeNestedPaths(o, pathList, ignoreProp)); - } - - if (schema.properties) { - const { properties } = schema; - const filteredProperties: Record = {}; - - for (const propertiesKey in properties) { - const matchingPaths = pathList.filter(([p]) => p === propertiesKey); - - if (matchingPaths.length === 0) { - filteredProperties[propertiesKey] = properties[propertiesKey]; - } - - if (matchingPaths.some((p) => p.length === 1)) { - if (schema.required) { - resultSchema.required = schema.required?.filter((requiredFiled) => requiredFiled !== propertiesKey); - } - - if (!ignoreProp) { - const prop = properties[propertiesKey]; - if (typeof prop !== "boolean") { - prop.airbyte_hidden = true; - } - filteredProperties[propertiesKey] = prop; - } - } else { - const innerPath = matchingPaths.map(([, ...rest]) => rest); - - filteredProperties[propertiesKey] = removeNestedPaths(properties[propertiesKey], innerPath, ignoreProp); - } - } - - resultSchema.properties = filteredProperties; - } - - return resultSchema; -} - -function applyFuncAt( - schema: JSONSchema7Definition, - path: Array, - f: (schema: JSONSchema7Definition) => JSONSchema7 -): JSONSchema7Definition { - if (typeof schema === "boolean") { - return schema; - } - - const [pathElem, ...restPath] = path; - - if (!isDefined(pathElem)) { - return f(schema); - } - - const resultSchema: JSONSchema7 = schema; - - if (schema.oneOf) { - const idx = typeof pathElem === "number" ? pathElem : parseInt(pathElem); - resultSchema.oneOf = schema.oneOf.map((o, index) => (index === idx ? applyFuncAt(o, restPath, f) : o)); - } - - if (schema.properties && typeof pathElem === "string") { - resultSchema.properties = Object.fromEntries( - Object.entries(schema.properties).map(([key, value]) => [ - key, - key === pathElem ? applyFuncAt(value, restPath, f) : value, - ]) - ); - } - - return resultSchema; -} - -export { applyFuncAt, removeNestedPaths }; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/FormSection.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/FormSection.tsx index faed191650da..8396b9c92447 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/FormSection.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/FormSection.tsx @@ -2,8 +2,8 @@ import React, { useMemo } from "react"; import { FormBlock } from "core/form/types"; -import { useServiceForm } from "../../serviceFormContext"; -import { makeConnectionConfigurationPath, OrderComparator } from "../../utils"; +import { useAuthentication } from "../../useAuthentication"; +import { OrderComparator } from "../../utils"; import { ArraySection } from "./ArraySection"; import { AuthSection } from "./auth/AuthSection"; import { ConditionSection } from "./ConditionSection"; @@ -18,9 +18,7 @@ interface FormNodeProps { const FormNode: React.FC = ({ sectionPath, formField, disabled }) => { if (formField._type === "formGroup") { - return ( - - ); + return ; } else if (formField._type === "formCondition") { return ; } else if (formField._type === "objectArray") { @@ -39,11 +37,12 @@ interface FormSectionProps { blocks: FormBlock[] | FormBlock; path?: string; skipAppend?: boolean; - hasOauth?: boolean; disabled?: boolean; } -export const FormSection: React.FC = ({ blocks = [], path, skipAppend, hasOauth, disabled }) => { +export const FormSection: React.FC = ({ blocks = [], path, skipAppend, disabled }) => { + const { isHiddenAuthField, shouldShowAuthButton } = useAuthentication(); + const sections = useMemo(() => { const flattenedBlocks = [blocks].flat(); @@ -54,29 +53,25 @@ export const FormSection: React.FC = ({ blocks = [], path, ski return flattenedBlocks; }, [blocks]); - const { selectedConnector, isAuthFlowSelected, authFieldsToHide } = useServiceForm(); - return ( <> - {hasOauth && } {sections - .filter( - (formField) => - !formField.airbyte_hidden && - // TODO: check that it is a good idea to add authFieldsToHide - (!isAuthFlowSelected || (isAuthFlowSelected && !authFieldsToHide.includes(formField.path))) - ) + .filter((formField) => !formField.airbyte_hidden && !isHiddenAuthField(formField.path)) .map((formField) => { const sectionPath = path ? (skipAppend ? path : `${path}.${formField.fieldKey}`) : formField.fieldKey; - const isAuthSection = - isAuthFlowSelected && - selectedConnector?.advancedAuth?.predicateKey && - sectionPath === makeConnectionConfigurationPath(selectedConnector?.advancedAuth?.predicateKey); - return ( - {isAuthSection && } + {/* + If the auth button should be rendered here, do so. In addition to the check useAuthentication does + we also need to check if the formField type is not a `formCondition`. We render a lot of OAuth buttons + in conditional fields in which case the path they should be rendered is the path of the conditional itself. + For conditional fields we're rendering this component twice, once "outside" of the conditional, which causes + the actual conditional frame to be rendered and once inside the conditional to render the actual content. + Since we want to only render the auth button inside the conditional and not above it, we filter out the cases + where the formField._type is formCondition, which will be the "outside rendering". + */} + {shouldShowAuthButton(sectionPath) && formField._type !== "formCondition" && } ); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.test.tsx index 28a96d6ab612..ae3166b7daf5 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.test.tsx @@ -3,6 +3,7 @@ import { TestWrapper } from "test-utils/testutils"; import { useFormikOauthAdapter } from "views/Connector/ServiceForm/components/Sections/auth/useOauthFlowAdapter"; import { useServiceForm } from "views/Connector/ServiceForm/serviceFormContext"; +import { useAuthentication } from "views/Connector/ServiceForm/useAuthentication"; import { AuthButton } from "./AuthButton"; jest.setTimeout(10000); @@ -41,18 +42,22 @@ const baseUseServiceFormValues = { selectedService: undefined, }; +jest.mock("views/Connector/ServiceForm/useAuthentication"); +const mockUseAuthentication = useAuthentication as unknown as jest.Mock>; + describe("auth button", () => { beforeEach(() => { jest.clearAllMocks(); + + mockUseAuthentication.mockReturnValue({ hiddenAuthFieldErrors: {} }); }); it("initially renders with correct message and no status message", () => { // no auth errors mockUseServiceForm.mockImplementationOnce(() => { - const authErrors = {}; - const { selectedConnector, allowOAuthConnector, selectedService } = baseUseServiceFormValues; + const { selectedConnector, selectedService } = baseUseServiceFormValues; - return { authErrors, selectedConnector, allowOAuthConnector, selectedService }; + return { selectedConnector, selectedService }; }); // not done @@ -85,10 +90,9 @@ describe("auth button", () => { it("after successful authentication, it renders with correct message and success message", () => { // no auth errors mockUseServiceForm.mockImplementationOnce(() => { - const authErrors = {}; - const { selectedConnector, allowOAuthConnector, selectedService } = baseUseServiceFormValues; + const { selectedConnector, selectedService } = baseUseServiceFormValues; - return { authErrors, selectedConnector, allowOAuthConnector, selectedService }; + return { selectedConnector, selectedService }; }); // done @@ -114,13 +118,14 @@ describe("auth button", () => { expect(successMessage).toBeInTheDocument(); }); - it("if authError is true, it renders the correct message", () => { + it("renders an error if there are any auth fields with empty values", () => { // auth errors + mockUseAuthentication.mockReturnValue({ hiddenAuthFieldErrors: { field: "form.empty.error" } }); + mockUseServiceForm.mockImplementationOnce(() => { - const authErrors = { field: "form.empty.error" }; - const { selectedConnector, allowOAuthConnector, selectedService } = baseUseServiceFormValues; + const { selectedConnector, selectedService } = baseUseServiceFormValues; - return { authErrors, selectedConnector, allowOAuthConnector, selectedService }; + return { selectedConnector, selectedService }; }); // not done diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx index 9ff34bfd9c61..b87f9480241e 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx @@ -8,6 +8,7 @@ import { Text } from "components/ui/Text"; import { ConnectorSpecification } from "core/domain/connector"; import { useServiceForm } from "../../../serviceFormContext"; +import { useAuthentication } from "../../../useAuthentication"; import styles from "./AuthButton.module.scss"; import GoogleAuthButton from "./GoogleAuthButton"; import { useFormikOauthAdapter } from "./useOauthFlowAdapter"; @@ -39,8 +40,9 @@ function getAuthenticateMessageId(connectorDefinitionId: string): string { } export const AuthButton: React.FC = () => { - const { selectedService, authErrors, selectedConnector } = useServiceForm(); - const hasAuthError = Object.values(authErrors).includes("form.empty.error"); + const { selectedService, selectedConnector } = useServiceForm(); + const { hiddenAuthFieldErrors } = useAuthentication(); + const authRequiredError = Object.values(hiddenAuthFieldErrors).includes("form.empty.error"); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const { loading, done, run, hasRun } = useFormikOauthAdapter(selectedConnector!); @@ -54,8 +56,8 @@ export const AuthButton: React.FC = () => { const Component = getButtonComponent(definitionId); const messageStyle = classnames(styles.message, { - [styles.error]: hasAuthError, - [styles.success]: !hasAuthError, + [styles.error]: authRequiredError, + [styles.success]: !authRequiredError, }); const buttonLabel = done ? ( @@ -72,7 +74,7 @@ export const AuthButton: React.FC = () => { )} - {hasAuthError && ( + {authRequiredError && ( diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/serviceFormContext.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/serviceFormContext.tsx index 820be4fc64e8..decf91db9bbf 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/serviceFormContext.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/serviceFormContext.tsx @@ -1,13 +1,11 @@ -import { getIn, useFormikContext } from "formik"; +import { useFormikContext } from "formik"; import React, { useContext, useMemo } from "react"; import { AnySchema } from "yup"; import { Connector, ConnectorDefinition, ConnectorDefinitionSpecification } from "core/domain/connector"; import { WidgetConfigMap } from "core/form/types"; -import { FeatureItem, useFeature } from "hooks/services/Feature"; import { ServiceFormValues } from "./types"; -import { makeConnectionConfigurationPath, serverProvidedOauthPaths } from "./utils"; interface ServiceFormContext { formType: "source" | "destination"; @@ -22,10 +20,7 @@ interface ServiceFormContext { selectedConnector?: ConnectorDefinitionSpecification; isLoadingSchema?: boolean; isEditMode?: boolean; - isAuthFlowSelected?: boolean; - authFieldsToHide: string[]; validationSchema: AnySchema; - authErrors: Record; } const serviceFormContext = React.createContext(null); @@ -64,9 +59,7 @@ export const ServiceFormContextProvider: React.FC { - const { values, resetForm, getFieldMeta, submitCount } = useFormikContext(); - - const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector); + const { values, resetForm } = useFormikContext(); const { serviceType } = values; const selectedService = useMemo( @@ -74,42 +67,10 @@ export const ServiceFormContextProvider: React.FC - allowOAuthConnector && - selectedConnector?.advancedAuth && - selectedConnector?.advancedAuth.predicateValue === - getIn(getValues(values), makeConnectionConfigurationPath(selectedConnector?.advancedAuth.predicateKey ?? [])), - [selectedConnector, allowOAuthConnector, values, getValues] - ); - - const authFieldsToHide = useMemo( - () => - isAuthFlowSelected - ? Object.values(serverProvidedOauthPaths(selectedConnector)).map((f) => - makeConnectionConfigurationPath(f.path_in_connector_config) - ) - : [], - [selectedConnector, isAuthFlowSelected] - ); - - const authErrors = useMemo(() => { - // key of field path, value of error code - return authFieldsToHide.reduce>((authErrors, fieldName) => { - const { error } = getFieldMeta(fieldName); - if (submitCount > 0 && error) { - authErrors[fieldName] = error; - } - return authErrors; - }, {}); - }, [authFieldsToHide, getFieldMeta, submitCount]); const ctx = useMemo(() => { const unfinishedFlows = widgetsInfo["_common.unfinishedFlows"] ?? {}; return { - authErrors, widgetsInfo, - isAuthFlowSelected, - authFieldsToHide, getValues, setUiWidgetsInfo, selectedService, @@ -135,10 +96,7 @@ export const ServiceFormContextProvider: React.FC ({ + ...jest.requireActual("formik"), + useFormikContext: jest.fn(), +})); + +const mockServiceForm = useServiceForm as unknown as jest.Mock>>; +const mockFormikContext = useFormikContext as unknown as jest.Mock>>; + +interface MockParams { + connector: Pick; + values: unknown; + submitCount?: number; + fieldMeta?: Record; +} + +const mockContext = ({ connector, values, submitCount, fieldMeta = {} }: MockParams) => { + mockFormikContext.mockReturnValue({ + values, + submitCount: submitCount ?? 0, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + getFieldMeta: (field) => (fieldMeta[field] ?? {}) as any, + }); + mockServiceForm.mockReturnValue({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + selectedConnector: { ...connector, sourceDefinitionId: "12345", jobInfo: {} as any }, + }); +}; + +const useAuthentication = (withOauthFeature = true) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const { result } = renderHook(() => useAuthenticationHook(), { + wrapper: ({ children }) => ( + {children} + ), + }); + return result.current; +}; + +describe("useAuthentication", () => { + it("should return empty results for non OAuth connectors", () => { + mockContext({ connector: {}, values: {} }); + const result = useAuthentication(); + expect(result.hiddenAuthFieldErrors).toEqual({}); + expect(result.shouldShowAuthButton("field")).toBe(false); + expect(result.isHiddenAuthField("field")).toBe(false); + }); + + it("should not handle auth specifically if OAuth feature is disabled", () => { + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "oauth2.0" } } }, + }); + const result = useAuthentication(false); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "access_token"]))).toBe(false); + expect(result.shouldShowAuthButton(makeConnectionConfigurationPath(["credentials", "auth_type"]))).toBe(false); + }); + + describe("for advancedAuth connectors", () => { + describe("without a predicateKey", () => { + it("should calculate hiddenAuthFields correctly", () => { + mockContext({ connector: { advancedAuth: noPredicateAdvancedAuth }, values: {} }); + const result = useAuthentication(); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["access_token"]))).toBe(true); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["client_id"]))).toBe(false); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["client_secret"]))).toBe(false); + }); + + it("should show the auth button on the root level", () => { + mockContext({ connector: { advancedAuth: noPredicateAdvancedAuth }, values: {} }); + const result = useAuthentication(); + expect(result.shouldShowAuthButton(makeConnectionConfigurationPath())).toBe(true); + }); + + it("should not return authErrors before submitting", () => { + const accessTokenField = makeConnectionConfigurationPath(["access_token"]); + mockContext({ + connector: { advancedAuth: noPredicateAdvancedAuth }, + values: {}, + fieldMeta: { [accessTokenField]: { error: "form.empty.error" } }, + submitCount: 0, + }); + const result = useAuthentication(); + expect(result.hiddenAuthFieldErrors).toEqual({}); + }); + + it("should return existing authErrors if submitted once", () => { + const accessTokenField = makeConnectionConfigurationPath(["access_token"]); + mockContext({ + connector: { advancedAuth: noPredicateAdvancedAuth }, + values: {}, + fieldMeta: { [accessTokenField]: { error: "form.empty.error" } }, + submitCount: 1, + }); + const result = useAuthentication(); + expect(result.hiddenAuthFieldErrors).toEqual({ [accessTokenField]: "form.empty.error" }); + }); + }); + + describe("with predicateKey inside conditional", () => { + it("should hide auth fields when predicate value matches", () => { + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "oauth2.0" } } }, + }); + const result = useAuthentication(); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "access_token"]))).toBe(true); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "client_id"]))).toBe(true); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "client_secret"]))).toBe(true); + }); + + it("should not hide auth fields when predicate value is a mismatch", () => { + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "token" } } }, + }); + const result = useAuthentication(); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "access_token"]))).toBe(false); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "client_id"]))).toBe(false); + expect(result.isHiddenAuthField(makeConnectionConfigurationPath(["credentials", "client_secret"]))).toBe(false); + }); + + it("should show the auth button inside the conditional if right option is selected", () => { + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "oauth2.0" } } }, + }); + const result = useAuthentication(); + expect(result.shouldShowAuthButton(makeConnectionConfigurationPath(["credentials", "auth_type"]))).toBe(true); + }); + + it("shouldn't show the auth button if the wrong conditional option is selected", () => { + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "token" } } }, + }); + const result = useAuthentication(); + expect(result.shouldShowAuthButton(makeConnectionConfigurationPath(["credentials", "auth_type"]))).toBe(false); + }); + + it("should not return authErrors before submitting", () => { + const accessTokenField = makeConnectionConfigurationPath(["credentials", "access_token"]); + const clientIdField = makeConnectionConfigurationPath(["credentials", "client_id"]); + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "oauth2.0" } } }, + fieldMeta: { [accessTokenField]: { error: "form.empty.error" }, [clientIdField]: { error: "another.error" } }, + submitCount: 0, + }); + const result = useAuthentication(); + expect(result.hiddenAuthFieldErrors).toEqual({}); + }); + + it("should return authErrors when conditional has correct option selected", () => { + const accessTokenField = makeConnectionConfigurationPath(["credentials", "access_token"]); + const clientIdField = makeConnectionConfigurationPath(["credentials", "client_id"]); + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "oauth2.0" } } }, + fieldMeta: { [accessTokenField]: { error: "form.empty.error" }, [clientIdField]: { error: "another.error" } }, + submitCount: 1, + }); + const result = useAuthentication(); + expect(result.hiddenAuthFieldErrors).toEqual({ + [accessTokenField]: "form.empty.error", + [clientIdField]: "another.error", + }); + }); + + it("should not return authErrors when conditional has the incorrect option selected", () => { + const accessTokenField = makeConnectionConfigurationPath(["credentials", "access_token"]); + const clientIdField = makeConnectionConfigurationPath(["credentials", "client_id"]); + mockContext({ + connector: { advancedAuth: predicateInsideConditional }, + values: { connectionConfiguration: { credentials: { auth_type: "token" } } }, + fieldMeta: { [accessTokenField]: { error: "form.empty.error" }, [clientIdField]: { error: "another.error" } }, + submitCount: 1, + }); + const result = useAuthentication(); + expect(result.hiddenAuthFieldErrors).toEqual({}); + }); + }); + }); +}); diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx new file mode 100644 index 000000000000..b863bc4b5ead --- /dev/null +++ b/airbyte-webapp/src/views/Connector/ServiceForm/useAuthentication.tsx @@ -0,0 +1,210 @@ +import { getIn, useFormikContext } from "formik"; +import { JSONSchema7 } from "json-schema"; +import { useCallback, useMemo } from "react"; + +import { FeatureItem, useFeature } from "hooks/services/Feature"; + +import { useServiceForm } from "./serviceFormContext"; +import { ServiceFormValues } from "./types"; +import { makeConnectionConfigurationPath, serverProvidedOauthPaths } from "./utils"; + +type Path = Array; + +const isNumerical = (input: string | number): boolean => { + return typeof input === "number" || /^\d+$/.test(input); +}; + +/** + * Takes an array of strings or numbers and remove all elements from it that are either + * a number or a string that just contains a number. This will be used to remove index + * accessors into oneOf from paths, since they are not part of the field path later. + */ +const stripNumericalEntries = (paths: Path): string[] => { + return paths.filter((p): p is string => !isNumerical(p)); +}; + +/** + * Takes a list of paths in an array representation as well as a root path in array representation, concats + * them as well as prefix them with the `connectionConfiguration` prefix that Formik uses for all connector + * parameter values, and joins them to string paths. + */ +const convertAndPrefixPaths = (paths?: Path[], rootPath: Path = []): string[] => { + return ( + paths?.map((pathParts) => { + return makeConnectionConfigurationPath([...stripNumericalEntries(rootPath), ...stripNumericalEntries(pathParts)]); + }) ?? [] + ); +}; + +/** + * Returns true if the auth button should be shown for an advancedAuth specification. + * This will check if the connector has a predicateKey, and if so, check if the current form value + * of the corresponding field matches the predicateValue from the specification. + */ +const shouldShowButtonForAdvancedAuth = ( + predicateKey: string[] | undefined, + predicateValue: string | undefined, + values: ServiceFormValues +): boolean => { + return ( + !predicateKey || + predicateKey.length === 0 || + predicateValue === getIn(values, makeConnectionConfigurationPath(predicateKey)) + ); +}; + +/** + * Returns true if the auth button should be shown for an authSpecification connector. + */ +const shouldShowButtonForLegacyAuth = ( + spec: JSONSchema7, + rootPath: Path, + values: ServiceFormValues +): boolean => { + if (!rootPath.some((p) => isNumerical(p))) { + // If the root path of the auth parameters (which is also the place the button will be rendered) + // is not inside a conditional, i.e. none of the root path is a numerical value, we will always + // show the button. + return true; + } + + // If the spec had a root path inside a conditional, e.g. `credentials.0`, we need to figure + // out if that conditional is currently on the correct selected option. Unlike `advancedAuth` + // which has a `predicateValue`, the legacy auth configuration doesn't have the value for the conditional, + // so we need to find that ourselves first. + + // To find the path inside the connector spec that matches the `rootPath` we'll need to insert `properties` + // and `oneOf`, since they'll appear in the JSONSchema, e.g. this turns `credentials.0` to `properties.credentials.oneOf.0` + const specPath = rootPath.flatMap((path) => + isNumerical(path) ? ["oneOf", String(path)] : ["properties", String(path)] + ); + // Get the part of the spec that `rootPath` point to + const credentialsSpecRoot = getIn(spec, specPath) as JSONSchema7 | undefined; + + if (!credentialsSpecRoot?.properties) { + // if the path doesn't exist in the spec (which should not happen) we just show the auth button always. + return true; + } + + // To find the value we're expecting, we run through all properties inside that matching spec inside the conditional + // to find the one that has a `const` value in it, since this is the actual value that will be written into the conditional + // field itself once it's selected. + const constProperty = Object.entries(credentialsSpecRoot.properties) + .map(([key, prop]) => [key, typeof prop !== "boolean" ? prop.const : undefined] as const) + .find(([, constValue]) => !!constValue); + + // If none of the conditional properties is a const value, we'll also show the auth button always (should not happen) + if (!constProperty) { + return true; + } + + // Check if the value in the form matches the found `const` value from the spec. If so we know the conditional + // is on the right option. + const [key, constValue] = constProperty; + const value = getIn(values, makeConnectionConfigurationPath(stripNumericalEntries([...rootPath, key]))); + return value === constValue; +}; + +interface AuthenticationHook { + /** + * Returns whether a given field path should be hidden, because it's part of the + * OAuth flow and will be filled in by that. + */ + isHiddenAuthField: (fieldPath: string) => boolean; + /** + * A record of all formik errors in hidden authentication fields. The key will be the + * name of the field and the value an error code. If no error is present in a field + * it will be missing from this object. + */ + hiddenAuthFieldErrors: Record; + /** + * This will return true if the auth button should be visible and rendered in the place of + * the passed in field, and false otherwise. + */ + shouldShowAuthButton: (fieldPath: string) => boolean; +} + +export const useAuthentication = (): AuthenticationHook => { + const { values, getFieldMeta, submitCount } = useFormikContext(); + const { selectedConnector } = useServiceForm(); + + const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector); + + const advancedAuth = selectedConnector?.advancedAuth; + const legacyOauthSpec = selectedConnector?.authSpecification?.oauth2Specification; + + const spec = selectedConnector?.connectionSpecification as JSONSchema7; + + const isAuthButtonVisible = useMemo(() => { + const shouldShowAdvancedAuth = + advancedAuth && shouldShowButtonForAdvancedAuth(advancedAuth.predicateKey, advancedAuth.predicateValue, values); + const shouldShowLegacyAuth = + legacyOauthSpec && shouldShowButtonForLegacyAuth(spec, legacyOauthSpec.rootObject as Path, values); + return Boolean(allowOAuthConnector && (shouldShowAdvancedAuth || shouldShowLegacyAuth)); + }, [values, advancedAuth, legacyOauthSpec, spec, allowOAuthConnector]); + + // Fields that are filled by the OAuth flow and thus won't need to be shown in the UI if OAuth is available + const implicitAuthFieldPaths = useMemo( + () => [ + // Fields from `advancedAuth` connectors + ...(advancedAuth + ? Object.values(serverProvidedOauthPaths(selectedConnector)).map((f) => + makeConnectionConfigurationPath(f.path_in_connector_config) + ) + : []), + // Fields from legacy `authSpecification` connectors + ...(legacyOauthSpec + ? [ + ...convertAndPrefixPaths(legacyOauthSpec.oauthFlowInitParameters, legacyOauthSpec.rootObject as Path), + ...convertAndPrefixPaths(legacyOauthSpec.oauthFlowOutputParameters, legacyOauthSpec.rootObject as Path), + ] + : []), + ], + [advancedAuth, legacyOauthSpec, selectedConnector] + ); + + const isHiddenAuthField = useCallback( + (fieldPath: string) => { + // A field should be hidden due to OAuth if we have OAuth enabled and selected (in case it's inside a oneOf) + // and the field is part of the OAuth flow parameters. + return isAuthButtonVisible && implicitAuthFieldPaths.includes(fieldPath); + }, + [implicitAuthFieldPaths, isAuthButtonVisible] + ); + + const hiddenAuthFieldErrors = useMemo(() => { + if (!isAuthButtonVisible) { + // We don't want to return the errors if the auth button isn't visible. + return {}; + } + return implicitAuthFieldPaths.reduce>((authErrors, fieldName) => { + const { error } = getFieldMeta(fieldName); + if (submitCount > 0 && error) { + authErrors[fieldName] = error; + } + return authErrors; + }, {}); + }, [getFieldMeta, implicitAuthFieldPaths, isAuthButtonVisible, submitCount]); + + const shouldShowAuthButton = useCallback( + (fieldPath: string) => { + if (!isAuthButtonVisible) { + // Never show the auth button anywhere if its not enabled or visible (inside a conditional that's not selected) + return false; + } + + const path = advancedAuth + ? advancedAuth.predicateKey && makeConnectionConfigurationPath(advancedAuth.predicateKey) + : legacyOauthSpec && makeConnectionConfigurationPath(stripNumericalEntries(legacyOauthSpec.rootObject as Path)); + + return fieldPath === (path ?? makeConnectionConfigurationPath()); + }, + [advancedAuth, isAuthButtonVisible, legacyOauthSpec] + ); + + return { + isHiddenAuthField, + hiddenAuthFieldErrors, + shouldShowAuthButton, + }; +}; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/useBuildForm.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/useBuildForm.tsx index 2bcb99677ea5..d3d0bef42c3f 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/useBuildForm.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/useBuildForm.tsx @@ -8,57 +8,17 @@ import { AnySchema } from "yup"; import { ConnectorDefinitionSpecification } from "core/domain/connector"; import { FormBlock, WidgetConfig, WidgetConfigMap } from "core/form/types"; import { buildPathInitialState } from "core/form/uiWidget"; -import { applyFuncAt, removeNestedPaths } from "core/jsonSchema"; import { jsonSchemaToUiWidget } from "core/jsonSchema/schemaToUiWidget"; import { buildYupFormForJsonSchema } from "core/jsonSchema/schemaToYup"; -import { FeatureItem, useFeature } from "hooks/services/Feature"; -import { DestinationDefinitionSpecificationRead } from "../../../core/request/AirbyteClient"; import { ServiceFormValues } from "./types"; -function upgradeSchemaLegacyAuth( - connectorSpecification: Required< - Pick - > -) { - const spec = connectorSpecification.authSpecification.oauth2Specification; - return applyFuncAt( - connectorSpecification.connectionSpecification as JSONSchema7Definition, - (spec?.rootObject ?? []) as Array, - (schema) => { - // Very hacky way to allow placing button within section - // @ts-expect-error json schema - schema.is_auth = true; - const schemaWithoutPaths = removeNestedPaths(schema, spec?.oauthFlowInitParameters ?? [], false); - - const schemaWithoutOutputPats = removeNestedPaths( - schemaWithoutPaths, - spec?.oauthFlowOutputParameters ?? [], - false - ); - - return schemaWithoutOutputPats; - } - ); -} - export function useBuildInitialSchema( connectorSpecification?: ConnectorDefinitionSpecification ): JSONSchema7Definition | undefined { - const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector); - return useMemo(() => { - if (allowOAuthConnector) { - if (connectorSpecification?.authSpecification && !connectorSpecification?.advancedAuth) { - return upgradeSchemaLegacyAuth({ - connectionSpecification: connectorSpecification?.connectionSpecification, - authSpecification: connectorSpecification.authSpecification, - }); - } - } - return connectorSpecification?.connectionSpecification as JSONSchema7Definition | undefined; - }, [allowOAuthConnector, connectorSpecification]); + }, [connectorSpecification]); } export interface BuildFormHook { diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/utils.ts b/airbyte-webapp/src/views/Connector/ServiceForm/utils.ts index e3645eabbe66..c9dd2ac272d2 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/utils.ts +++ b/airbyte-webapp/src/views/Connector/ServiceForm/utils.ts @@ -3,8 +3,8 @@ import { naturalComparator } from "utils/objects"; import { ConnectorDefinitionSpecification } from "../../../core/domain/connector"; -export function makeConnectionConfigurationPath(path: string[]): string { - return `connectionConfiguration.${path.join(".")}`; +export function makeConnectionConfigurationPath(path: string[] = []): string { + return ["connectionConfiguration", ...path].join("."); } type OAuthOutputSpec = { properties: Record } | undefined; From fc2cebc9b2cfa184f36c2ba7d693368c4cbe5b71 Mon Sep 17 00:00:00 2001 From: Teri Eyenike Date: Tue, 25 Oct 2022 18:07:03 +0100 Subject: [PATCH 309/498] Update: aws ec2 deployment guide (#18360) --- docs/deploying-airbyte/on-aws-ec2.md | 132 +++++++++------------------ 1 file changed, 45 insertions(+), 87 deletions(-) diff --git a/docs/deploying-airbyte/on-aws-ec2.md b/docs/deploying-airbyte/on-aws-ec2.md index ded1b5757b3c..78395dab2df3 100644 --- a/docs/deploying-airbyte/on-aws-ec2.md +++ b/docs/deploying-airbyte/on-aws-ec2.md @@ -1,140 +1,98 @@ -# On AWS (EC2) +# Deploy on AWS (Amazon EC2) -:::info +This page guides you through deploying Airbyte Open Source on an Amazon EC2 instance by setting up the deployment environment, installing and starting Airbyte, and connecting it to the Amazon EC2 instance. -The instructions have been tested on `Amazon Linux 2 AMI (HVM)` +> INFO +> +> The instructions have been tested on Amazon Linux 2 AMI (HVM) -::: +## Requirements -## Create a new instance +- To test Airbyte, we recommend a `t2.medium` instance +- To deploy Airbyte in a production environment, we recommend a `t2.large` instance +- Make sure your Docker Desktop app is running to ensure all services run smoothly -- Launch a new instance +[Create and download an SSH key to connect to the instance](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/create-key-pairs.html) -![](../.gitbook/assets/aws_ec2_launch.png) +## Set up the environment -- Select instance AMI +1. Create an Amazon EC2 instance. -![](../.gitbook/assets/aws_ec2_ami.png) +To connect to your instance, run the following command on your local terminal: -- Select instance type - - For testing out Airbyte, a `t2.medium` instance is likely sufficient. Airbyte uses a lot of disk space with images and logs, so make sure to provision at least 30GBs of disk per node. - - For long-running Airbyte installations, we recommend a `t2.large` instance. - -![](../.gitbook/assets/aws_ec2_instance_type.png) - -- `Next: Configure Instance Details` - - You can tune parameters or keep the defaults -- `Next: Add Storage` - - You can tune parameters or keep the defaults -- `Next: Add Tags` - - You can tune parameters or keep the defaults -- `Next: Configure Security Groups` - - We are going to allow network for `ssh` - -![](../.gitbook/assets/aws_ec2_security_group.png) - -- `Review and Launch` -- `Launch` -- Create a ssh key so you can connect to the instance - - Download the key \(and don't lose it or you won't be able to connect to the instance\) - -![](../.gitbook/assets/aws_ec2_ssh_key.png) - -- `Launch Instances` - -![](../.gitbook/assets/aws_ec2_instance_view.png) - -- Wait for the instance to become `Running` - -## Install environment - -:::info - -Note: The following commands will be entered either on your local terminal or in your ssh session on the instance terminal. The comments above each command block will indicate where to enter the commands. - -::: - -- Connect to your instance - -```bash -# In your workstation terminal -SSH_KEY=~/Downloads/airbyte-key.pem # or wherever you've downloaded the key -INSTANCE_IP=REPLACE_WITH_YOUR_INSTANCE_IP +``` +SSH_KEY=~/Downloads/dataline-key-airbyte.pem # the file path you downloaded the key +INSTANCE_IP=REPLACE_WITH_YOUR_INSTANCE_IP # you can find your IP address in the EC2 console under the Instances tab chmod 400 $SSH_KEY # or ssh will complain that the key has the wrong permissions -ssh -i $SSH_KEY ec2-user@$INSTANCE_IP +ssh -i $SSH_KEY ec2-user@$INSTANCE_IP # connect to the aws ec2 instance AMI and the your private IP address ``` -- Install `docker` +2. To install Docker, run the following command in your SSH session on the instance terminal: -```bash -# In your ssh session on the instance terminal +``` sudo yum update -y sudo yum install -y docker sudo service docker start sudo usermod -a -G docker $USER ``` -- Install `docker-compose` +3. To install `docker-compose`, run the following command in your ssh session on the instance terminal: -```bash -# In your ssh session on the instance terminal +``` sudo wget https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m) -O /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose docker-compose --version ``` -- Close the ssh connection to ensure the group modification is taken into account +4. To close the SSH connection, run the following command in your SSH session on the instance terminal: -```bash -# In your ssh session on the instance terminal +``` logout ``` -## Install & start Airbyte +## Install and start Airbyte -- Connect to your instance +In your local terminal, run the following commands: -```bash -# In your workstation terminal +1. Connect to your instance + +``` ssh -i $SSH_KEY ec2-user@$INSTANCE_IP ``` -- Install Airbyte +2. Install Airbyte -```bash -# In your ssh session on the instance terminal +``` mkdir airbyte && cd airbyte wget https://raw.githubusercontent.com/airbytehq/airbyte/master/{.env,docker-compose.yaml} -docker-compose up -d +docker-compose up -d # run the Docker container ``` ## Connect to Airbyte -:::danger - -For security reasons, we strongly recommend to not expose Airbyte on Internet available ports. Future versions will add support for SSL & Authentication. - -::: +> DANGER +> +> We strongly recommend not exposing Airbyte on Internet available ports for security reasons. -- Create ssh tunnel for port 8000 +1. Create an SSH tunnel for port 8000 -:::info +> INFO +> +> If you want to use different ports, modify `API_URL` in your .env file and restart Airbyte. +> Run the following commands in your workstation terminal from the downloaded key folder: -If you want to use different ports or change the HTTP basic auth username and password, you will need to modify `API_URL` in your `.env` file and restart Airbyte. - -::: - -```bash +``` # In your workstation terminal +SSH_KEY=~/Downloads/dataline-key-airbyte.pem ssh -i $SSH_KEY -L 8000:localhost:8000 -N -f ec2-user@$INSTANCE_IP ``` -- Just visit [http://localhost:8000](http://localhost:8000) in your browser and start moving some data! +2. Visit `http://localhost:8000` to verify the deployment. -## Pushing Airbyte logs to CloudWatch +## Get Airbyte logs in CloudWatch -If you want to get your logs from your Airbyte Docker containers in CloudWatch, simply follow [this](https://aws.amazon.com/pt/premiumsupport/knowledge-center/cloudwatch-docker-container-logs-proxy/) guide to do so. +Follow this [guide](https://aws.amazon.com/pt/premiumsupport/knowledge-center/cloudwatch-docker-container-logs-proxy/) to get your logs from your Airbyte Docker containers in CloudWatch. ## Troubleshooting -If you encounter any issues, just connect to our [Slack](https://slack.airbyte.io). Our community will help! We also have a [FAQ](../troubleshooting/on-deploying.md) section in our docs for common problems. +If you encounter any issues, reach out to our community on [Slack](https://slack.airbyte.com/). From c8943f2a75e42fdb75a858340b409941e737136a Mon Sep 17 00:00:00 2001 From: Pragati Verma Date: Tue, 25 Oct 2022 22:39:20 +0530 Subject: [PATCH 310/498] Updated Deploy on GCP Doc (#18286) * Updated Deploy on GCP Doc * Added formatting changes --- .../on-gcp-compute-engine.md | 98 ++++++------------- 1 file changed, 30 insertions(+), 68 deletions(-) diff --git a/docs/deploying-airbyte/on-gcp-compute-engine.md b/docs/deploying-airbyte/on-gcp-compute-engine.md index f82df901476a..cb6e0bbcb78a 100644 --- a/docs/deploying-airbyte/on-gcp-compute-engine.md +++ b/docs/deploying-airbyte/on-gcp-compute-engine.md @@ -3,67 +3,37 @@ import TabItem from '@theme/TabItem'; # On GCP (Compute Engine) -:::info - -The instructions have been tested on `Debian GNU/Linux 10 (buster)` - -::: - -## Create a new instance - -* Launch a new instance - -![](../.gitbook/assets/gcp_ce_launch.png) - -* Configure new instance - * For testing out Airbyte, an `e2.medium` instance is likely sufficient. Airbyte uses a lot of disk space with images and logs, so make sure to provision at least 30GBs of disk per node. - * For long-running Airbyte installations, we recommend a `n1-standard-2` instance. - -![](../.gitbook/assets/gcp_ce_configure.png) - -* `Create` - -## Install environment +This page guides you through deploying Airbyte Open Source on a [Google Cloud Platform (GCP) Compute Engine instance](https://cloud.google.com/compute/docs/instances) by setting up the deployment environment, installing and starting Airbyte, and connecting it to the GCP instance. :::info -Note: The following commands will be entered either on your local terminal or in your ssh session on the instance terminal. The comments above each command block will indicate where to enter the commands. +The instructions have been tested on a `Debian GNU/Linux 10` VM instance. ::: -* Set variables in your terminal +## Requirements -```bash -# In your workstation terminal -PROJECT_ID=PROJECT_ID_WHERE_YOU_CREATED_YOUR_INSTANCE -INSTANCE_NAME=airbyte # or anyother name that you've used -``` +- To test Airbyte, we recommend an `e2.medium` instance and provision at least 30GBs of disk per node. +- To deploy Airbyte in a production environment, we recommend a `n1-standard-2` instance. -* Install `gcloud` +## Set up the environment - - +1. Create a [new GCP instance](https://cloud.google.com/compute/docs/instances/create-start-instance). +2. Set variables in your local terminal: ```bash -echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list -sudo apt-get install apt-transport-https ca-certificates gnupg -curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - -sudo apt-get update && sudo apt-get install google-cloud-sdk +PROJECT_ID=PROJECT_ID_WHERE_YOU_CREATED_YOUR_INSTANCE +INSTANCE_NAME=airbyte # or any other name that you've used ``` - - +3. Install Google Cloud SDK and initialize the gcloud command-line tool using the following commands in your local terminal: ```bash -# In your workstation terminal brew install --cask google-cloud-sdk -gcloud init # Follow instructions``` +gcloud init ``` - - - -* List all instances in your project +4. List all instances in your project and verify that you can see the Airbyte instance you created in step 1 in your local terminal: ```bash # Verify you can see your instance @@ -71,17 +41,15 @@ gcloud --project $PROJECT_ID compute instances list [...] # You should see the airbyte instance you just created ``` -* Connect to your instance +5. Connect to your instance in your local terminal: ```bash -# In your workstation terminal -gcloud --project=$PROJECT_ID beta compute ssh $INSTANCE_NAME +gcloud --project=$PROJECT_ID beta compute SSH $INSTANCE_NAME ``` -* Install `docker` +6. Install Docker on your VM instance by following the below commands in your VM terminal: ```bash -# In your ssh session on the instance terminal sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -- @@ -91,36 +59,34 @@ sudo apt-get install -y docker-ce docker-ce-cli containerd.io sudo usermod -a -G docker $USER ``` -* Install `docker-compose` +7. Install `docker-compose` on your VM instance by following the below commands in your VM terminal: ```bash -# In your ssh session on the instance terminal sudo apt-get -y install wget sudo wget https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m) -O /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose docker-compose --version ``` -* Close the ssh connection to ensure the group modification is taken into account +8. Close the SSH connection on your VM instance to ensure the group modification is taken into account by following the below command in your VM terminal: ```bash -# In your ssh session on the instance terminal logout ``` -## Install & start Airbyte +## Install and launch Airbyte + +To install and launch Airbyte: -* Connect to your instance +1. In your local terminal, connect to your Google Cloud instance: ```bash -# In your workstation terminal -gcloud --project=$PROJECT_ID beta compute ssh $INSTANCE_NAME +gcloud --project=$PROJECT_ID beta compute SSH $INSTANCE_NAME ``` -* Install Airbyte +2. In your VM terminal, install Airbyte: ```bash -# In your ssh session on the instance terminal mkdir airbyte && cd airbyte wget https://raw.githubusercontent.com/airbytehq/airbyte/master/{.env,docker-compose.yaml} docker-compose up -d @@ -128,22 +94,18 @@ docker-compose up -d ## Connect to Airbyte -:::danger - -For security reasons, we strongly recommend to not expose Airbyte publicly. Future versions will add support for SSL & Authentication. - +:::caution +Warning: For security reasons, we strongly recommended not exposing Airbyte publicly. ::: -* Create ssh tunnel. +1. In your local terminal, create an SSH tunnel to connect the GCP instance to Airbyte: ```bash -# In your workstation terminal -gcloud --project=$PROJECT_ID beta compute ssh $INSTANCE_NAME -- -L 8000:localhost:8000 -N -f +gcloud --project=$PROJECT_ID beta compute SSH $INSTANCE_NAME -- -L 8000:localhost:8000 -N -f ``` -* Just visit [http://localhost:8000](http://localhost:8000) in your browser and start moving some data! +2. Verify the connection by visiting [http://localhost:8000](http://localhost:8000) in your browser. ## Troubleshooting -If you encounter any issues, just connect to our [Slack](https://slack.airbyte.io). Our community will help! We also have a [FAQ](../troubleshooting/on-deploying.md) section in our docs for common problems. - +If you encounter any issues, reach out to our community on [Slack](https://slack.airbyte.com/). From 0c37753213007db02ed9ba9f99a7a2a152ec2541 Mon Sep 17 00:00:00 2001 From: Salomon Marquez <56137084+sblaizerwize@users.noreply.github.com> Date: Tue, 25 Oct 2022 19:10:16 +0200 Subject: [PATCH 311/498] Updated deploying Airbyte on Azure section (#18331) --- .../on-azure-vm-cloud-shell.md | 176 +++++++----------- 1 file changed, 71 insertions(+), 105 deletions(-) diff --git a/docs/deploying-airbyte/on-azure-vm-cloud-shell.md b/docs/deploying-airbyte/on-azure-vm-cloud-shell.md index fbb3c450f863..c32efcc9afda 100644 --- a/docs/deploying-airbyte/on-azure-vm-cloud-shell.md +++ b/docs/deploying-airbyte/on-azure-vm-cloud-shell.md @@ -1,149 +1,115 @@ -# On Azure (VM) +# Deploy on Azure + +This page guides you through deploying Airbyte Open Source on a Microsoft Azure VM by setting up the deployment environment, installing and starting Airbyte, and connecting it to the VM. :::info -The instructions have been tested on `Azure VM Linux (ubuntu 18.04)` +The instructions have been tested on a standard DS1 v2 (1 vcpu, 3.5 GiB memory) Microsoft Azure VM with Ubuntu 18.04. ::: -## Launch Azure Cloud Shell - -Launch cloud shell by going to [https://shell.azure.com/bash](https://shell.azure.com/bash) +## Set up the environment -![](../.gitbook/assets/azure_shell_launch.png) +Install Docker and Docker Compose in the VM: -## Create a new virtual machine +1. [Create a new VM](https://learn.microsoft.com/en-us/azure/virtual-machines/) and [generate the SSH keys](https://learn.microsoft.com/en-us/azure/virtual-machines/ssh-keys-portal) to connect to the VM. You’ll need the SSH keys to connect to the VM remotely later. -### Create resource group +2. To connect to the VM, run the following command in the Azure Cloud Shell: -```bash -# Inside Azure cloud shell -rgName=airbyte-demo -rgLocation=eastus -az group create --name $rgName --location $rgLocation -``` + ```bash + ssh @ + ``` + If successfully connected to the VM, the working directory of Cloud Shell should look like this: `@:~$` -![](../.gitbook/assets/azure_shell_create_rg.png) +3. To install Docker, run the following commands: -### Create virtual machine + ```bash + sudo apt-get update -y + sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release -y + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install docker-ce docker-ce-cli -y + sudo usermod -a -G docker $USER + ``` -```bash -# Inside Azure cloud shell -userName=byteuser -vmName=airbyte -dnsName=$(head -3 /dev/urandom | tr -dc a-z | cut -c -16) -publicIp=$(az vm create --resource-group $rgName \ - --name $vmName --image UbuntuLTS \ - --admin-username $userName \ - --public-ip-address-dns-name $dnsName \ - --generate-ssh-keys --query 'publicIpAddress' -o json) -echo $publicIp -``` +4. To install Docker Compose, run the following command: -This step will create a virtual machine and add a user account named `byteuser`. The `--generate-ssh-keys` option will generate a new ssh key and put it to the default key location \(~/.ssh\) + ```bash + sudo wget https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m) -O /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + ``` -**Note: Copy the `publicIpAddress` output, you will need this address later to connect from your workstation.** +5. Check Docker Compose version: -![](../.gitbook/assets/azure_shell_create_vm.png) + ```bash + docker-compose --version + ``` -### Download SSH key +6. Close the SSH connection to ensure that the group modification is considered: -```bash -# Inside Azure cloud shell -download ~/.ssh/id_rsa -``` + ```bash + logout + ``` -Make sure to update the permissions on the private key, or you'll get an error telling you that permissions for this file are too open. -```bash -chmod 600 ./$YOUR_PATH_TO_DOWNLOADS/id_rsa -``` +7. Reconnect to the VM: -Above command will generate download link and give you pop-up on right bottom side, click on `Click here to download your file.` to download private key. Note: Save this file, you will need it to connect to your VM in [Connect to Airbyte](on-azure-vm-cloud-shell.md#connect-to-airbyte) step. + ```bash + ssh @ + ``` -![](../.gitbook/assets/azure_shell_download_ssh_key.png) +## Install and start Airbyte -### Connect to virtual machine +Download Airbyte and deploy it in the VM using Docker Compose: -If you get this error: `Could not resolve hostname "XX.XXX.X.XXX": Name or service not known`, just manually enter the publicIp host name when running the ssh command. +1. Ensure that you are connected to the VM: -```bash -# Inside Azure cloud shell -ssh $userName@$publicIp -``` + ```bash + ssh @ + ``` -## Install environment +2. Create and use a new directory: -* Install `docker` + ```bash + mkdir airbyte + cd airbyte + ``` -```bash -# Inside Azure cloud shell -sudo apt-get update -y -sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release -y -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg -echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null -sudo apt-get update -sudo apt-get install docker-ce docker-ce-cli -y -sudo usermod -a -G docker $USER -``` +3. Download Airbyte from GitHub: -* Install `docker-compose` + ```bash + wget https://raw.githubusercontent.com/airbytehq/airbyte/master/{.env,docker-compose.yaml} + ``` -```bash -# Inside Azure cloud shell -sudo wget https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m) -O /usr/local/bin/docker-compose -sudo chmod +x /usr/local/bin/docker-compose -docker-compose --version -``` +4. Start Airbyte by running the following command: -* Close the ssh connection to ensure the group modification is taken into account + ```bash + sudo docker-compose up -d + ``` + This step takes about two minutes to complete. When done, you will see the cursor prompt. -```bash -# Inside Azure cloud shell -logout -``` -* Reconnect to virtual machine +## Connect to Airbyte -```bash -# Inside Azure cloud shell -ssh $userName@$publicIp -``` +Test a remote connection to your VM locally and verify that Airbyte is up and running. -## Install and Start Airbyte +1. In your local machine, open a terminal. +2. Go to the folder where you stored the SSH key. +3. Create a SSH tunnel for `port 8000` by typing the following command: -```bash -# Inside Azure cloud shell -mkdir airbyte && cd airbyte -wget https://raw.githubusercontent.com/airbytehq/airbyte/master/{.env,docker-compose.yaml} -sudo docker-compose up -d -``` + ```bash + ssh -N -L 8000:localhost:8000 -i @ + ``` + As a result, nothing happens. The cursor prompt keeps blinking. -## Connect to Airbyte +4. Open a web browser and navigate to `http://localhost:8000`. You will see Airbyte’s landing page. :::danger -For security reasons, we strongly recommend to not expose Airbyte on Internet available ports. Future versions will add support for SSL & Authentication. +For security reasons, it is strongly recommended not to expose Airbyte on Internet available ports. Future versions will add support for SSL and authentication. ::: -:::info - -This part assumes that you have access to a terminal on your workstation - -::: - -* Create ssh tunnel for port 8000 - - ```bash - # Inside your workstation terminal - # 1. Replace $SSH_KEY with private key path downloaded from earlier steps - # 2. Replace $INSTANCE_IP with publicIpAddress noted from earlier steps - ssh -N -L 8000:localhost:8000 -i $SSH_KEY byteuser@$INSTANCE_IP - ``` - -* Just visit [http://localhost:8000](http://localhost:8000) in your browser and start moving some data! - ## Troubleshooting -If you encounter any issues, just connect to our [Slack](https://slack.airbyte.io). Our community will help! We also have a [FAQ](../troubleshooting/on-deploying.md) section in our docs for common problems. - +If you encounter any issues, reach out to our community on [Slack](https://slack.airbyte.com/). \ No newline at end of file From 2aa3322f2a9aff159600d51c5f7195a2775f8acf Mon Sep 17 00:00:00 2001 From: Jagruti Tiwari Date: Tue, 25 Oct 2022 22:41:46 +0530 Subject: [PATCH 312/498] feat: deploy airbyte on kubernetes doc (#18339) --- docs/deploying-airbyte/on-kubernetes.md | 257 +++++++++++------------- 1 file changed, 119 insertions(+), 138 deletions(-) diff --git a/docs/deploying-airbyte/on-kubernetes.md b/docs/deploying-airbyte/on-kubernetes.md index 4325e4db3eaf..a17246c948eb 100644 --- a/docs/deploying-airbyte/on-kubernetes.md +++ b/docs/deploying-airbyte/on-kubernetes.md @@ -1,84 +1,75 @@ -# On Kubernetes (Beta) +# Deploy Airbyte on Kubernetes -## Overview +This page guides you through deploying Airbyte Open Source on Kubernetes. -Airbyte allows scaling sync workloads horizontally using Kubernetes. The core components \(api server, scheduler, etc\) run as deployments while the scheduler launches connector-related pods on different nodes. +## Requirements -## Quickstart +To test locally, you can use one of the following: -If you don't want to configure your own K8s cluster and Airbyte instance, you can use the free, open-source project [Plural](https://www.plural.sh/) to bring up a K8s cluster and Airbyte for you. Use [this guide](on-plural.md) to get started. - -## Getting Started - -### Cluster Setup - -For local testing we recommend following one of the following setup guides: - -* [Docker Desktop \(Mac\)](https://docs.docker.com/desktop/kubernetes) -* [Minikube](https://minikube.sigs.k8s.io/docs/start) - * NOTE: Start Minikube with at least 4gb RAM with `minikube start --memory=4000` +* [Docker Desktop](https://docs.docker.com/desktop/) with [Kubernetes](https://docs.docker.com/desktop/kubernetes/#enable-kubernetes) enabled +* [Minikube](https://docs.docker.com/desktop/kubernetes/#enable-kubernetes) with at least 4GB RAM * [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) -For testing on GKE you can [create a cluster with the command line or the Cloud Console UI](https://cloud.google.com/kubernetes-engine/docs/how-to/creating-a-zonal-cluster). - -For testing on EKS you can [install eksctl](https://eksctl.io/introduction/) and run `eksctl create cluster` to create an EKS cluster/VPC/subnets/etc. This process should take 10-15 minutes. -For production, Airbyte should function on most clusters v1.19 and above. We have tested support on GKE and EKS. If you run into a problem starting Airbyte, please reach out on the `#troubleshooting` channel on our [Slack](https://slack.airbyte.io/) or [create an issue on GitHub](https://github.com/airbytehq/airbyte/issues/new?assignees=&labels=type%2Fbug&template=bug-report.md&title=). +To test on Google Kubernetes Engine(GKE) create a standard zonal cluster. -### Install `kubectl` +To test on Amazon Elastic Kubernetes Service (Amazon EKS), install eksctl and then create a cluster. -If you do not already have the CLI tool `kubectl` installed, please follow [these instructions to install](https://kubernetes.io/docs/tasks/tools/). +**_Note:_** Airbyte deployment is tested on GKE and EKS with version v1.19 and above. If you run into problems, reach out on the `#airbyte-help` channel in our Slack or create an issue on GitHub. -### Configure `kubectl` +## Install and configure `kubectl ` -Configure `kubectl` to connect to your cluster by using `kubectl use-context my-cluster-name`. - -* For GKE - * Configure `gcloud` with `gcloud auth login`. - * On the Google Cloud Console, the cluster page will have a `Connect` button, which will give a command to run locally that looks like - - `gcloud container clusters get-credentials CLUSTER_NAME --zone ZONE_NAME --project PROJECT_NAME`. +Install `kubectl` and run the following command to configure it and connect to your cluster: +``` +kubectl use-context +``` +* To configure `kubectl` in `GKE`: + * Initialize the `gcloud` cli. + * To view cluster details, go to the `cluster` page in the Google Cloud Console and click `connect`. Run the following command to test cluster details: +`gcloud container clusters get-credentials --zone --project `. + * To view contexts, run: `kubectl config get-contexts`. + * To access the cluster from `kubectl` run : `kubectl config use-context `. - * Use `kubectl config get-contexts` to show the contexts available. - * Run `kubectl config use-context ` to access the cluster from `kubectl`. -* For EKS - * [Configure your AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) to connect to your project. - * Install [eksctl](https://eksctl.io/introduction/) - * Run `eksctl utils write-kubeconfig --cluster=` to make the context available to `kubectl` - * Use `kubectl config get-contexts` to show the contexts available. - * Run `kubectl config use-context ` to access the cluster with `kubectl`. +* To configure `kubectl` in `EKS`: + * [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) to connect to your project. + * Install [`eksctl`](https://eksctl.io/introduction/). + * To Make contexts available to `kubectl`, run `eksctl utils write-kubeconfig --cluster=` + * To view available contexts, run `kubectl config get-contexts`. + * To Access the cluster run `kubectl config use-context `. -### Configure Logs +## Configure Logs -#### Default Configuration -Both `dev` and `stable` versions of Airbyte include a stand-alone `Minio` deployment. Airbyte publishes logs to this `Minio` deployment by default. This means Airbyte comes as a **self-contained Kubernetes deployment - no other configuration is required**. +### Default configuration -So if you just want logs to be sent to the local `Minio` deployment, you do not need to change the values of any environment variables from what is currently on master. +Airbyte comes with a self-contained Kubernetes deployment and uses a stand-alone `Minio` deployment in both the `dev` and `stable` versions. Logs are published to the `Minio` deployment by default. -#### Custom Configuration +To send the logs to the local `Minio` deployment, make sure the specified credentials have both read and write permissions. -Alternatively, if you want logs to be sent to a custom location, Airbyte currently supports logging to `Minio`, `S3` or `GCS`. The following instructions are for users wishing to log to their own `Minio` layer, `S3` bucket or `GCS` bucket. +### Custom configuration -The provided credentials require both read and write permissions. The logger attempts to create the log bucket if it does not exist. +Airbyte supports logging to the `Minio` layer, `S3` bucket, and `GCS` bucket. -##### Configuring Custom Minio Log Location +### Customize the `Minio` log location -To write to a custom minio log location, replace the following variables in the `.env` file in the `kube/overlays/stable` directory: +To write to a custom location, update the following `.env` variable in the `kube/overlays/stable` directory (you will find this directory at the location you launched Airbyte) -```text +``` S3_LOG_BUCKET= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= S3_MINIO_ENDPOINT= +S3_LOG_BUCKET_REGION= ``` +Set the` S3_PATH_STYLE_ACCESS variable to `true`. +Let the `S3_LOG_BUCKET_REGION` variable remain empty. -The `S3_PATH_STYLE_ACCESS` variable should remain `true`. The `S3_LOG_BUCKET_REGION` variable should remain empty. +### Configure the Custom `S3` Log Location​ -##### Configuring Custom S3 Log Location +For the `S3` log location, create an S3 bucket with your AWS credentials. -To write to a custom S3 log location, replace the following variables in the `.env` file in the `kube/overlays/stable` directory: +To write to a custom location, update the following `.env` variable in the `kube/overlays/stable` directory (you can find this directory at the location you launched Airbyte) -```text +``` S3_LOG_BUCKET= S3_LOG_BUCKET_REGION= # Set this to empty. @@ -86,177 +77,171 @@ S3_MINIO_ENDPOINT= # Set this to empty. S3_PATH_STYLE_ACCESS= ``` -Additionally, replace the following variables in the `.secrets` file in the `kube/overlays/stable` directory: -```text +Replace the following variable in `.secrets` file in the `kube/overlays/stable` directory: +``` AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= ``` -See [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/create-bucket-overview.html) for instructions on creating an S3 bucket and [here](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) for instructions on creating AWS credentials. -##### Configuring Custom GCS Log Location +### Configure the Custom GCS Log Location​ -Create the GCP service account with read/write permission to the GCS log bucket. +Create a GCS bucket and GCP credentials if you haven’t already. Make sure your GCS log bucket has read/write permission. -1\) Base64 encode the GCP json secret. +To configure the custom log location: -```text +Base encode the GCP JSON secret with the following command: +``` # The output of this command will be a Base64 string. $ cat gcp.json | base64 ``` +To populate the `gcs-log-creds` secrets with the Base64-encoded credential, take the encoded GCP JSON secret from the previous step and add it to `secret-gcs-log-creds.yaml` file as the value for `gcp.json` key. -2\) Populate the gcs-log-creds secrets with the Base64-encoded credential. This is as simple as taking the encoded credential from the previous step and adding it to the `secret-gcs-log-creds.yaml` file. - -```text +``` apiVersion: v1 kind: Secret metadata: - name: gcs-log-creds - namespace: default + name: gcs-log-creds + namespace: default data: - gcp.json: + gcp.json: ``` -3\) Replace the following variables in the `.env` file in the `kube/overlays/stable` directory: +In the `kube/overlays/stable` directory, update the `GCS_LOG_BUCKET` with your GCS log bucket credentials: -```text +``` GCS_LOG_BUCKET= ``` -4\) Modify the `.secrets` file in the `kube/overlays/stable` directory -```text +Modify `GOOGLE_APPLICATION_CREDENTIALS` to the path to `gcp.json` in the `.secrets` file at `kube/overlays/stable` directory. + +``` # The path the GCS creds are written to. Unless you know what you are doing, use the below default value. + GOOGLE_APPLICATION_CREDENTIALS=/secrets/gcs-log-creds/gcp.json ``` -See [here](https://cloud.google.com/storage/docs/creating-buckets) for instruction on creating a GCS bucket and [here](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#iam-service-account-keys-create-console) for instruction on creating GCP credentials. -### Launch Airbyte +## Launch Airbyte -Run the following commands to launch Airbyte: +The following commands will help you launch Airbyte: -```text +``` git clone https://github.com/airbytehq/airbyte.git cd airbyte kubectl apply -k kube/overlays/stable ``` -After 2-5 minutes, `kubectl get pods | grep airbyte` should show `Running` as the status for all the core Airbyte pods. This may take longer on Kubernetes clusters with slow internet connections. +It might take 2 to 5 minutes for the setup depending on your network and system configuration. -Run `kubectl port-forward svc/airbyte-webapp-svc 8000:80` to allow access to the UI/API. +To check the pod status, run `kubectl get pods | grep airbyte`. -Now visit [http://localhost:8000](http://localhost:8000) in your browser and start moving some data! +If you are on Windows, run `kubectl get pods` to the list of pods. -## Production Airbyte on Kubernetes +Run `kubectl port-forward svc/airbyte-webapp-svc 8000:80` to allow access to the UI/API. +Navigate to http://localhost:8000 in your browser to verify the deployment. -### Setting resource limits +## Deploying Airbyte on Kubernetes in production -* Core container pods - * Instead of launching Airbyte with `kubectl apply -k kube/overlays/stable`, you can run with `kubectl apply -k kube/overlays/stable-with-resource-limits`. - * The `kube/overlays/stable-with-resource-limits/set-resource-limits.yaml` file can be modified to provide different resource requirements for core pods. -* Connector pods - * By default, connector pods launch without resource limits. - * To add resource limits, configure the "Docker Resource Limits" section of the `.env` file in the overlay folder you're using. -* Volume sizes - * You can modify `kube/resources/volume-*` files to specify different volume sizes for the persistent volumes backing Airbyte. +### Setting resource limits -### Increasing job parallelism +* Core container pods -The number of simultaneous jobs \(getting specs, checking connections, discovering schemas, and performing syncs\) is limited by a few factors. First of all, jobs are picked up and executed by airbyte-worker pods, so increasing the number of workers will allow more jobs to be processed in parallel. + * To provide different resource requirements for core pods, set resource limits in the `kube/overlays/stable-with-resource-limits/set-resource-limits.yaml` file. -The number of worker pods can be changed by increasing the number of replicas for the `airbyte-worker` deployment. An example of a Kustomization patch that increases this number can be seen in `airbyte/kube/overlays/dev-integration-test/kustomization.yaml` and `airbyte/kube/overlays/dev-integration-test/parallelize-worker.yaml`. The number of simultaneous jobs on a specific worker pod is also limited by the number of ports exposed by the worker deployment and set by `TEMPORAL_WORKER_PORTS` in your `.env` file. Without additional ports used to communicate to connector pods, jobs will start to run but will hang until ports become available. + * To launch Airbyte with new resource limits, use the `kubectl apply -k kube/overlays/stable-with-resource-limits command. -You can also tune environment variables for the max simultaneous job types that can run on the worker pod by setting `MAX_SPEC_WORKERS`, `MAX_CHECK_WORKERS`, `MAX_DISCOVER_WORKERS`, `MAX_SYNC_WORKERS` for the worker pod deployment \(not in the `.env` file\). These values can be used if you want to create separate worker deployments for separate types of workers with different resource allocations. +* Connector pods + + * By default, connector pods launch without resource limits. To add resource limit, configure the `Docker resource limits` section of the `.env` file in the `kube/overlays` directory. -### Cloud logging +* Volume sizes -Airbyte writes logs to two directories. App logs, including server and scheduler logs, are written to the `app-logging` directory. Job logs are written to the `job-logging` directory. Both directories live at the top-level e.g., the `app-logging` directory lives at `s3://log-bucket/app-logging` etc. These paths can change, so we recommend having a dedicated log bucket, and to not use this bucket for other purposes. + * To specify different volume sizes for the persistent volume backing Airbyte, modify `kube/resources/volume-*` files. -Airbyte publishes logs every minute. This means it is normal to see minute-long log delays. Each publish creates it's own log file, since Cloud Storages do not support append operations. This also mean it is normal to see hundreds of files in your log bucket. -Each log file is named `{yyyyMMddHH24mmss}_{podname}_{UUID}` and is not compressed. Users can view logs simply by navigating to the relevant folder and downloading the file for the time period in question. +### Increasing job parallelism -See the [Known Issues](on-kubernetes.md#known-issues) section for planned logging improvements. +The ability to run parallel jobs like getting specs, checking connections, discovering schemas and performing syncs is limited by a few factors. `Airbyte-worker-pods` picks and executes the job. Increasing the number of workers will allow more jobs to be processed. -### Using an external DB +To create more worker pods, increase the number of replicas for the `airbyte-worker` deployment. Refer to examples of increasing worker pods in a Kustomization patch in `airbyte/kube/overlays/dev-integration-test/kustomization.yaml` and `airbyte/kube/overlays/dev-integration-test/parallelize-worker.yaml` + +To limit the exposed ports in `.env` file, set the value to `TEMPORAL_WORKER_PORTS`. You can run jobs parallely at each exposed port. +If you do not have enough ports to communicate, the jobs might not complete or halt until ports become available. -After [Issue \#3605](https://github.com/airbytehq/airbyte/issues/3605) is completed, users will be able to configure custom dbs instead of a simple `postgres` container running directly in Kubernetes. This separate instance \(preferable on a system like AWS RDS or Google Cloud SQL\) should be easier and safer to maintain than Postgres on your cluster. +You can set a limit for the maximum parallel jobs that run on the pod. Set the value to `MAX_SPEC_WORKERS`, `MAX_CHECK_WORKERS`, `MAX_DISCOVER_WORKERS`, and `MAX_SYNC_WORKERS` variables in the worker pod deployment and not in `.env` file. You can use these values to create separate worker deployments for each type of worker with different resource allocations. -## Known Issues -As we improve our Kubernetes offering, we would like to point out some common pain points. We are working on improving these. Please let us know if there are any other issues blocking your adoption of Airbyte or if you would like to contribute fixes to address any of these issues. +### Cloud Logging -* Some UI operations have higher latency on Kubernetes than Docker-Compose. \([\#4233](https://github.com/airbytehq/airbyte/issues/4233)\) -* Logging to Azure Storage is not supported. \([\#4200](https://github.com/airbytehq/airbyte/issues/4200)\) -* Large log files might take a while to load. \([\#4201](https://github.com/airbytehq/airbyte/issues/4201)\) -* UI does not include configured buckets in the displayed log path. \([\#4204](https://github.com/airbytehq/airbyte/issues/4204)\) -* Logs are not reset when Airbyte is re-deployed. \([\#4235](https://github.com/airbytehq/airbyte/issues/4235)\) -* File sources reading from and file destinations writing to local mounts are not supported on Kubernetes. -* Cannot run custom DBT transformation. \([\#5091](https://github.com/airbytehq/airbyte/issues/5091)\) +Airbyte writes logs to two different directories: The `App-logging` directory and the `job-logging` directory. App logs, server logs, and scheduler logs are written to the `app-logging` directory. Job logs are written to the `job-logging` directory. Both directories live at the top level. For example, the app logging directory may live at `s3://log-bucket/app-logging`. We recommend having a dedicated logging bucket and not using it for other purposes. -## Customizing Airbyte Manifests +Airbyte publishes logs every minute, so it’s normal to have minute-long log delays. Cloud Storages do not support append operations. Each publisher creates its own log files, which means you will have hundreds of files in your log bucket. -We use [Kustomize](https://kustomize.io/) to allow overrides for different environments. Our shared resources are in the `kube/resources` directory, and we define overlays for each environment. We recommend creating your own overlay if you want to customize your deployments. This overlay can live in your own VCS. +Each log file is uncompressed and named `{yyyyMMddHH24mmss}_{podname}_{UUID}`. +To view logs, navigate to the relevant folder and download the file for the time period you want. -Example `kustomization.yaml` file: +### Using external databases +You can configure a custom database instead of a simple `postgres` container in Kubernetes. This separate instance (AWS RDS or Google Cloud SQL) should be easier and safer to maintain than Postgres on your cluster. -```yaml +## Customizing Airbytes Manifests +We use Kustomize to allow configuration for different environments. Our shared resources are in the `kube/resources` directory. We recommend defining overlays for each environment and creating your own overlay to customize your deployments. The overlay can live in your own version control system. +An example of `kustomization.yaml` file: +``` apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization -bases: - - https://github.com/airbytehq/airbyte.git/kube/overlays/stable?ref=master +bases: https://github.com/airbytehq/airbyte.git/kube/overlays/stable?ref=master ``` ### View Raw Manifests -For a specific overlay, you can run `kubectl kustomize kube/overlays/stable` to view the manifests that Kustomize will apply to your Kubernetes cluster. This is useful for debugging because it will show the exact resources you are defining. +To view manifests for a specific overlay that Kustomize applies to your Kubernetes cluster, run `kubectl kustomize kube/overlays/stable`. + ### Helm Charts -Check out the [Helm Chart Readme](https://github.com/airbytehq/airbyte/tree/master/charts/airbyte) +For detailed information about Helm Charts, refer to the charts [readme](https://github.com/airbytehq/airbyte/tree/master/charts/airbyte) file. -## Operator Guide -### View API Server Logs +## Operator Guide -`kubectl logs deployments/airbyte-server` to view real-time logs. Logs can also be downloaded as a text file via the Admin tab in the UI. +### View API server logs -### Connector Container Logs +You can view real-time logs in `kubectl logs deployments/airbyte-server` directory and download them from the Admin Tab. -Although all logs can be accessed by viewing the scheduler logs, connector container logs may be easier to understand when isolated by accessing from the Airbyte UI or the [Airbyte API](../api-documentation.md) for a specific job attempt. Connector pods launched by Airbyte will not relay logs directly to Kubernetes logging. You must access these logs through Airbyte. +### Connector Container Logs​ -### Upgrading Airbyte Kube +All logs can be accessed by viewing the scheduler logs. As for connector container logs, use Airbyte UI or Airbyte API to isolate them for a specific job attempt and for easier understanding. Connector pods launched by Airbyte will not relay logs directly to Kubernetes logging. You must access these logs through Airbyte. -See [Upgrading K8s](../operator-guides/upgrading-airbyte.md). ### Resizing Volumes -To resize a volume, change the `.spec.resources.requests.storage` value. After re-applying, the mount should be extended if that operation is supported for your type of mount. For a production deployment, it's useful to track the usage of volumes to ensure they don't run out of space. +To resize a volume, change the `.spec.resources.requests.storage` value. After re-applying, extend the mount(if that operation is supported for your mount type). For a production deployment, track the usage of volumes to ensure they don't run out of space. -### Copy Files To/From Volumes +### Copy Files in Volumes -See the documentation for [`kubectl cp`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#cp). +To copy files, use the [`cp` command in kubectl](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#cp). ### Listing Files -```bash -kubectl exec -it airbyte-server-6b5747df5c-bj4fx ls /tmp/workspace/8 -``` +To list files, run: + +`kubectl exec -it airbyte-server-6b5747df5c-bj4fx ls /tmp/workspace/8` ### Reading Files -```bash -kubectl exec -it airbyte-server-6b5747df5c-bj4fx cat /tmp/workspace/8/0/logs.log -``` +To read files, run: -### Persistent storage on GKE regional cluster +`kubectl exec -it airbyte-server-6b5747df5c-bj4fx cat /tmp/workspace/8/0/logs.log` -Running Airbyte on GKE regional cluster requires enabling persistent regional storage. To do so, enable [CSI driver](https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/gce-pd-csi-driver) on GKE. After enabling, add `storageClassName: standard-rwo` to the [volume-configs](https://github.com/airbytehq/airbyte/tree/86ee2ad05bccb4aca91df2fb07c412efde5ba71c/kube/resources/volume-configs.yaml) yaml. +### Persistent storage on Google Kubernetes Engine(GKE) regional cluster -`volume-configs.yaml` example: +Running Airbyte on a GKE regional cluster requires enabling persistent regional storage. Start with [enabling CSE driver](https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/gce-pd-csi-driver#enabling_the_on_an_existing_cluster) on GKE and add `storageClassName: standard-rwo` to the [volume-configs.yamll](https://github.com/airbytehq/airbyte/blob/86ee2ad05bccb4aca91df2fb07c412efde5ba71c/kube/resources/volume-configs.yaml). -```yaml +Sample `volume-configs.yaml` file: + +``` apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -271,12 +256,8 @@ spec: storage: 500Mi storageClassName: standard-rwo ``` - ## Troubleshooting +If you encounter any issues, reach out to our community on [Slack](https://slack.airbyte.com/). -If you run into any problems operating Airbyte on Kubernetes, please reach out on the `#issues` channel on our [Slack](https://slack.airbyte.io/) or [create an issue on GitHub](https://github.com/airbytehq/airbyte/issues/new?assignees=&labels=type%2Fbug&template=bug-report.md&title=). - -## Developing Airbyte on Kubernetes -[Read about the Kubernetes dev cycle!](https://docs.airbyte.io/contributing-to-airbyte/developing-on-kubernetes) From 9e666e7f7c6bb32217a759c571d5130750a2c45d Mon Sep 17 00:00:00 2001 From: Subham Sahu Date: Tue, 25 Oct 2022 22:42:34 +0530 Subject: [PATCH 313/498] New Source: Zoom [low-code CDK] (#18179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement meetings & users API in zoom connector * feat: add support fot all zoom GET APIs * fix: unhandled error cases by adding default handler and minor refactor * feat: sync the catalog with the latest zoom API responses * docs: add the new zoom connector built with low-code CDK * chore: remove unnecessary files, and rename connector in definitions * fix: forgot to add source_definitions.yaml earlier 😅 * remove some empty streams * add eof * correct spec yaml * docs: update docs/integrations and replace zoom with low code CDK zoom * fix: definition generation err * udpate seed file and remove zoom-singer * remove zoom singer spec * add icon Co-authored-by: marcosmarxm --- .../resources/seed/source_definitions.yaml | 16 +- .../src/main/resources/seed/source_specs.yaml | 38 +- .../source-zoom-singer/.dockerignore | 3 - .../connectors/source-zoom-singer/.gitignore | 1 - .../connectors/source-zoom-singer/README.md | 113 -- .../source-zoom-singer/build.gradle | 21 - .../source-zoom-singer/requirements.txt | 2 - .../sample_files/configured_catalog.json | 71 - .../sample_files/full_configured_catalog.json | 1462 ----------------- .../sample_files/sample_config.json | 3 - .../connectors/source-zoom-singer/setup.py | 16 - .../source_zoom_singer/__init__.py | 3 - .../source_zoom_singer/source.py | 24 - .../source_zoom_singer/spec.json | 18 - .../connectors/source-zoom/.dockerignore | 6 + .../Dockerfile | 12 +- .../unit_test.py => source-zoom/__init__.py} | 4 - .../source-zoom/acceptance-test-config.yml | 44 + .../source-zoom/acceptance-test-docker.sh | 16 + .../connectors/source-zoom/build.gradle | 9 + .../source-zoom/integration_tests/__init__.py | 3 + .../integration_tests/abnormal_state.json | 5 + .../integration_tests/acceptance.py | 14 + .../integration_tests/configured_catalog.json | 213 +++ .../integration_tests/invalid_config.json | 3 + .../integration_tests/sample_config.json | 3 + .../integration_tests/sample_state.json | 3 + .../main.py | 4 +- .../connectors/source-zoom/requirements.txt | 2 + .../connectors/source-zoom/setup.py | 29 + .../source-zoom/source_zoom/__init__.py | 8 + .../schemas/meeting_poll_results.json | 37 + .../source_zoom/schemas/meeting_polls.json | 96 ++ .../schemas/meeting_registrants.json | 86 + .../meeting_registration_questions.json | 50 + .../source_zoom/schemas/meetings.json | 408 +++++ .../schemas/report_meeting_participants.json | 46 + .../source_zoom/schemas/report_meetings.json | 76 + .../schemas/report_webinar_participants.json | 55 + .../source_zoom/schemas/report_webinars.json | 76 + .../source_zoom/schemas/users.json | 86 + .../schemas/webinar_absentees.json | 85 + .../schemas/webinar_panelists.json | 37 + .../schemas/webinar_poll_results.json | 37 + .../source_zoom/schemas/webinar_polls.json | 97 ++ .../schemas/webinar_qna_results.json | 31 + .../schemas/webinar_registrants.json | 85 + .../webinar_registration_questions.json | 49 + .../schemas/webinar_tracking_sources.json | 25 + .../source_zoom/schemas/webinars.json | 322 ++++ .../source-zoom/source_zoom/source.py | 18 + .../source-zoom/source_zoom/spec.yaml | 13 + .../source-zoom/source_zoom/zoom.yaml | 815 +++++++++ docs/integrations/sources/zoom.md | 17 +- 54 files changed, 3028 insertions(+), 1788 deletions(-) delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/.dockerignore delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/.gitignore delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/README.md delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/build.gradle delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/requirements.txt delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/sample_files/configured_catalog.json delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/sample_files/full_configured_catalog.json delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/sample_files/sample_config.json delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/setup.py delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/__init__.py delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/source.py delete mode 100644 airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json create mode 100644 airbyte-integrations/connectors/source-zoom/.dockerignore rename airbyte-integrations/connectors/{source-zoom-singer => source-zoom}/Dockerfile (79%) rename airbyte-integrations/connectors/{source-zoom-singer/unit_tests/unit_test.py => source-zoom/__init__.py} (57%) create mode 100644 airbyte-integrations/connectors/source-zoom/acceptance-test-config.yml create mode 100755 airbyte-integrations/connectors/source-zoom/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-zoom/build.gradle create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/abnormal_state.json create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json rename airbyte-integrations/connectors/{source-zoom-singer => source-zoom}/main.py (68%) create mode 100644 airbyte-integrations/connectors/source-zoom/requirements.txt create mode 100644 airbyte-integrations/connectors/source-zoom/setup.py create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/__init__.py create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_poll_results.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meeting_participants.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meetings.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinar_participants.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinars.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_absentees.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_panelists.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_poll_results.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_polls.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_qna_results.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registrants.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registration_questions.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_tracking_sources.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinars.json create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/source.py create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/spec.yaml create mode 100644 airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index cac80536a205..3a8b6f2bbb13 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1241,14 +1241,6 @@ icon: sentry.svg sourceType: api releaseStage: generally_available -- name: Zoom - sourceDefinitionId: aea2fd0d-377d-465e-86c0-4fdc4f688e51 - dockerRepository: airbyte/source-zoom-singer - dockerImageTag: 0.2.4 - documentationUrl: https://docs.airbyte.com/integrations/sources/zoom - icon: zoom.svg - sourceType: api - releaseStage: alpha - name: Zuora sourceDefinitionId: 3dc3037c-5ce8-4661-adc2-f7a9e3c5ece5 dockerRepository: airbyte/source-zuora @@ -1299,3 +1291,11 @@ documentationUrl: https://docs.airbyte.com/integrations/sources/yandex-metrica sourceType: api releaseStage: alpha +- name: Zoom + sourceDefinitionId: cbfd9856-1322-44fb-bcf1-0b39b7a8e92e + dockerRepository: airbyte/source-zoom + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.io/integrations/sources/zoom + sourceType: api + icon: zoom.svg + releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index ca8f093ffcd2..9c48061ef3bb 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -12545,26 +12545,6 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-zoom-singer:0.2.4" - spec: - documentationUrl: "https://docs.airbyte.com/integrations/sources/zoom" - connectionSpecification: - $schema: "http://json-schema.org/draft-07/schema#" - title: "Source Zoom Singer Spec" - type: "object" - required: - - "jwt" - additionalProperties: false - properties: - jwt: - title: "JWT Token" - type: "string" - description: "Zoom JWT Token. See the docs for more information on how to obtain this key." - airbyte_secret: true - supportsNormalization: false - supportsDBT: false - supported_destination_sync_modes: [] - dockerImage: "airbyte/source-zuora:0.1.3" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/zuora" @@ -13016,3 +12996,21 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-zoom:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/sources/zoom" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Zoom Spec" + type: "object" + required: + - "jwt_token" + additionalProperties: true + properties: + jwt_token: + type: "string" + description: "JWT Token" + airbyte_secret: true + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] diff --git a/airbyte-integrations/connectors/source-zoom-singer/.dockerignore b/airbyte-integrations/connectors/source-zoom-singer/.dockerignore deleted file mode 100644 index 540fc080488d..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -build -.venv - diff --git a/airbyte-integrations/connectors/source-zoom-singer/.gitignore b/airbyte-integrations/connectors/source-zoom-singer/.gitignore deleted file mode 100644 index 29fffc6a50cc..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/.gitignore +++ /dev/null @@ -1 +0,0 @@ -NEW_SOURCE_CHECKLIST.md diff --git a/airbyte-integrations/connectors/source-zoom-singer/README.md b/airbyte-integrations/connectors/source-zoom-singer/README.md deleted file mode 100644 index 0177fc2c5f6c..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Source Zoom Singer - -This is the repository for the Zoom source connector, based on a Singer tap. -For information about how to use this connector within Airbyte, see [the User Documentation](https://docs.airbyte.io/integrations/sources/zoom). - -## Local development - -### Prerequisites -**To iterate on this connector, make sure to complete this prerequisites section.** - -#### Minimum Python version required `= 3.7.0` - -#### Build & Activate Virtual Environment and install dependencies -From this connector directory, create a virtual environment: -``` -python -m venv .venv -``` - -This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your -development environment of choice. To activate it from the terminal, run: -``` -source .venv/bin/activate -pip install -r requirements.txt -``` -If you are in an IDE, follow your IDE's instructions to activate the virtualenv. - -Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is -used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. -If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything -should work as you expect. - -#### Building via Gradle -From the Airbyte repository root, run: -``` -./gradlew :airbyte-integrations:connectors:source-zoom:build -``` - -#### Create credentials -**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/zoom) -to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_zoom_singer/spec.json` file. -Note that the `secrets` directory is gitignored by default, so there is no danger of accidentally checking in sensitive information. -See `sample_files/sample_config.json` for a sample config file. - -**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source zoom test creds` -and place them into `secrets/config.json`. - -### Locally running the connector -``` -python main_dev.py spec -python main_dev.py check --config secrets/config.json -python main_dev.py discover --config secrets/config.json -python main_dev.py read --config secrets/config.json --catalog sample_files/configured_catalog.json -``` - -### Unit Tests -To run unit tests locally, from the connector root run: -``` -pytest unit_tests -``` - -### Locally running the connector -``` -python main_dev.py spec -python main_dev.py check --config secrets/config.json -python main_dev.py discover --config secrets/config.json -python main_dev.py read --config secrets/config.json --catalog sample_files/configured_catalog.json -``` - -### Unit Tests -To run unit tests locally, from the connector directory run: -``` -python -m pytest unit_tests -``` - -### Locally running the connector docker image - -#### Build -First, make sure you build the latest Docker image: -``` -docker build . -t airbyte/source-zoom-singer:dev -``` - -You can also build the connector image via Gradle: -``` -./gradlew :airbyte-integrations:connectors:source-zoom:airbyteDocker -``` -When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in -the Dockerfile. - -#### Run -Then run any of the connector commands as follows: -``` -docker run --rm airbyte/source-zoom-singer:dev spec -docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-zoom-singer:dev check --config /secrets/config.json -docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-zoom-singer:dev discover --config /secrets/config.json -docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/sample_files:/sample_files airbyte/source-zoom-singer:dev read --config /secrets/config.json --catalog /sample_files/configured_catalog.json -``` - -### Integration Tests -1. From the airbyte project root, run `./gradlew :airbyte-integrations:connectors:source-zoom-singer:integrationTest` to run the standard integration test suite. -1. To run additional integration tests, create a directory `integration_tests` which contain your tests and run them with `pytest integration_tests`. - Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. - -## Dependency Management -All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. - -### Publishing a new version of the connector -You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? -1. Make sure your changes are passing unit and integration tests -1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use SemVer). -1. Create a Pull Request -1. Pat yourself on the back for being an awesome contributor -1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master diff --git a/airbyte-integrations/connectors/source-zoom-singer/build.gradle b/airbyte-integrations/connectors/source-zoom-singer/build.gradle deleted file mode 100644 index 408979685851..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id 'airbyte-python' - id 'airbyte-docker' - id 'airbyte-standard-source-test-file' -} - -airbytePython { - moduleDirectory 'source_zoom_singer' -} - -airbyteStandardSourceTestFile { - specPath = "source_zoom_singer/spec.json" - configPath = "secrets/config.json" - configuredCatalogPath = "sample_files/configured_catalog.json" -} - - - -dependencies { - implementation files(project(':airbyte-integrations:bases:base-standard-source-test-file').airbyteDocker.outputs) -} diff --git a/airbyte-integrations/connectors/source-zoom-singer/requirements.txt b/airbyte-integrations/connectors/source-zoom-singer/requirements.txt deleted file mode 100644 index 7b9114ed5867..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# This file is autogenerated -- only edit if you know what you are doing. Use setup.py for declaring dependencies. --e . diff --git a/airbyte-integrations/connectors/source-zoom-singer/sample_files/configured_catalog.json b/airbyte-integrations/connectors/source-zoom-singer/sample_files/configured_catalog.json deleted file mode 100644 index 0d10357b06a4..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/sample_files/configured_catalog.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "streams": [ - { - "stream": { - "name": "users", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "email": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "integer"] - }, - "status": { - "type": ["null", "string"] - }, - "pmi": { - "type": ["null", "integer"] - }, - "timezone": { - "type": ["null", "string"] - }, - "dept": { - "type": ["null", "string"] - }, - "created_at": { - "format": "date-time", - "type": ["null", "string"] - }, - "last_login_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "last_client_version": { - "type": ["null", "string"] - }, - "group_ids": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - }, - "im_group_ids": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - }, - "verified": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - } - ] -} diff --git a/airbyte-integrations/connectors/source-zoom-singer/sample_files/full_configured_catalog.json b/airbyte-integrations/connectors/source-zoom-singer/sample_files/full_configured_catalog.json deleted file mode 100644 index 6859a4e1e9e0..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/sample_files/full_configured_catalog.json +++ /dev/null @@ -1,1462 +0,0 @@ -{ - "streams": [ - { - "stream": { - "name": "meeting_polls", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "meeting_id": { - "type": ["string"] - }, - "status": { - "type": ["null", "string"] - }, - "title": { - "type": ["null", "string"] - }, - "questions": { - "items": { - "properties": { - "name": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "answers": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "report_webinar_participants", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "user_id": { - "type": ["string"] - }, - "webinar_id": { - "type": ["string"] - }, - "name": { - "type": ["null", "string"] - }, - "user_email": { - "type": ["null", "string"] - }, - "join_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "leave_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "string"] - }, - "attentiveness_score": { - "type": ["null", "string"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "meeting_files", - "json_schema": { - "properties": { - "meeting_uuid": { - "type": ["string"] - }, - "file_name": { - "type": ["null", "string"] - }, - "download_url": { - "type": ["null", "string"] - }, - "file_size": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "meeting_registrants", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "meeting_id": { - "type": ["string"] - }, - "email": { - "type": ["null", "string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "county": { - "type": ["null", "string"] - }, - "zip": { - "type": ["null", "string"] - }, - "state": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "industry": { - "type": ["null", "string"] - }, - "org": { - "type": ["null", "string"] - }, - "job_title": { - "type": ["null", "string"] - }, - "purchasing_time_frame": { - "type": ["null", "string"] - }, - "role_in_purchase_process": { - "type": ["null", "string"] - }, - "no_of_employees": { - "type": ["null", "string"] - }, - "comments": { - "type": ["null", "string"] - }, - "custom_questions": { - "items": { - "properties": { - "title": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "status": { - "type": ["null", "string"] - }, - "create_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "join_url": { - "type": ["null", "string"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "users", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "email": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "integer"] - }, - "status": { - "type": ["null", "string"] - }, - "pmi": { - "type": ["null", "integer"] - }, - "timezone": { - "type": ["null", "string"] - }, - "dept": { - "type": ["null", "string"] - }, - "created_at": { - "format": "date-time", - "type": ["null", "string"] - }, - "last_login_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "last_client_version": { - "type": ["null", "string"] - }, - "group_ids": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - }, - "im_group_ids": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - }, - "verified": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_poll_results", - "json_schema": { - "properties": { - "webinar_uuid": { - "type": ["string"] - }, - "email": { - "type": ["string"] - }, - "name": { - "type": ["null", "string"] - }, - "question_details": { - "items": { - "properties": { - "question": { - "type": ["null", "string"] - }, - "answer": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_files", - "json_schema": { - "properties": { - "webinar_uuid": { - "type": ["string"] - }, - "file_name": { - "type": ["null", "string"] - }, - "download_url": { - "type": ["null", "string"] - }, - "file_size": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_polls", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "webinar_id": { - "type": ["string"] - }, - "status": { - "type": ["null", "string"] - }, - "title": { - "type": ["null", "string"] - }, - "questions": { - "items": { - "properties": { - "name": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "answers": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "meetings", - "json_schema": { - "properties": { - "id": { - "type": ["integer"] - }, - "meeting_id": { - "type": ["string"] - }, - "uuid": { - "type": ["string"] - }, - "host_id": { - "type": ["null", "string"] - }, - "topic": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "integer"] - }, - "status": { - "type": ["null", "string"] - }, - "start_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "integer"] - }, - "timezone": { - "type": ["null", "string"] - }, - "created_at": { - "format": "date-time", - "type": ["null", "string"] - }, - "agenda": { - "type": ["null", "string"] - }, - "start_url": { - "type": ["null", "string"] - }, - "join_url": { - "type": ["null", "string"] - }, - "password": { - "type": ["null", "string"] - }, - "h323_password": { - "type": ["null", "string"] - }, - "encrypted_Password": { - "type": ["null", "string"] - }, - "pmi": { - "type": ["null", "integer"] - }, - "tracking_fields": { - "items": { - "properties": { - "field": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "occurences": { - "items": { - "properties": { - "occurence_id": { - "type": ["null", "string"] - }, - "start_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "integer"] - }, - "status": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "settings": { - "properties": { - "host_video": { - "type": ["null", "boolean"] - }, - "participant_video": { - "type": ["null", "boolean"] - }, - "cn_meeting": { - "type": ["null", "boolean"] - }, - "in_meeting": { - "type": ["null", "boolean"] - }, - "join_before_host": { - "type": ["null", "boolean"] - }, - "mute_upon_entry": { - "type": ["null", "boolean"] - }, - "watermark": { - "type": ["null", "boolean"] - }, - "use_pmi": { - "type": ["null", "boolean"] - }, - "approval_type": { - "type": ["null", "integer"] - }, - "registration_type": { - "type": ["null", "integer"] - }, - "audio": { - "type": ["null", "string"] - }, - "auto_recording": { - "type": ["null", "string"] - }, - "enforce_login": { - "type": ["null", "boolean"] - }, - "enforce_login_domains": { - "type": ["null", "string"] - }, - "alternative_hosts": { - "type": ["null", "string"] - }, - "close_registration": { - "type": ["null", "boolean"] - }, - "waiting_room": { - "type": ["null", "boolean"] - }, - "global_dial_in_countries": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - }, - "global_dial_in_numbers": { - "items": { - "properties": { - "country": { - "type": ["null", "string"] - }, - "country_name": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "number": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "contact_name": { - "type": ["null", "boolean"] - }, - "contact_email": { - "type": ["null", "boolean"] - }, - "registrants_confirmation_email": { - "type": ["null", "boolean"] - }, - "registrants_email_notification": { - "type": ["null", "boolean"] - }, - "meeting_authentication": { - "type": ["null", "boolean"] - }, - "authentication_option": { - "type": ["null", "string"] - }, - "authentication_domains": { - "type": ["null", "string"] - } - }, - "type": ["null", "object"], - "additionalProperties": false - }, - "recurrence": { - "properties": { - "type": { - "type": ["null", "integer"] - }, - "repeat_interval": { - "type": ["null", "integer"] - }, - "weekly_days": { - "type": ["null", "integer"] - }, - "monthly_day": { - "type": ["null", "integer"] - }, - "monthly_week": { - "type": ["null", "integer"] - }, - "monthly_week_day": { - "type": ["null", "integer"] - }, - "end_times": { - "type": ["null", "integer"] - }, - "end_date_time": { - "format": "date-time", - "type": ["null", "string"] - } - }, - "type": ["null", "object"], - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "meeting_questions", - "json_schema": { - "properties": { - "meeting_id": { - "type": ["string"] - }, - "questions": { - "items": { - "properties": { - "field_name": { - "type": ["null", "string"] - }, - "required": { - "type": ["null", "boolean"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "custom_questions": { - "items": { - "properties": { - "title": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "required": { - "type": ["null", "boolean"] - }, - "answers": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_registrants", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "webinar_id": { - "type": ["string"] - }, - "email": { - "type": ["null", "string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "county": { - "type": ["null", "string"] - }, - "zip": { - "type": ["null", "string"] - }, - "state": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "industry": { - "type": ["null", "string"] - }, - "org": { - "type": ["null", "string"] - }, - "job_title": { - "type": ["null", "string"] - }, - "purchasing_time_frame": { - "type": ["null", "string"] - }, - "role_in_purchase_process": { - "type": ["null", "string"] - }, - "no_of_employees": { - "type": ["null", "string"] - }, - "comments": { - "type": ["null", "string"] - }, - "custom_questions": { - "items": { - "properties": { - "title": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "status": { - "type": ["null", "string"] - }, - "create_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "join_url": { - "type": ["null", "string"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_panelists", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "webinar_id": { - "type": ["string"] - }, - "name": { - "type": ["null", "string"] - }, - "email": { - "type": ["null", "string"] - }, - "join_url": { - "type": ["null", "string"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "meeting_poll_results", - "json_schema": { - "properties": { - "meeting_uuid": { - "type": ["string"] - }, - "email": { - "type": ["string"] - }, - "name": { - "type": ["null", "string"] - }, - "question_details": { - "items": { - "properties": { - "question": { - "type": ["null", "string"] - }, - "answer": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_questions", - "json_schema": { - "properties": { - "webinar_id": { - "type": ["string"] - }, - "questions": { - "items": { - "properties": { - "field_name": { - "type": ["null", "string"] - }, - "required": { - "type": ["null", "boolean"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "custom_questions": { - "items": { - "properties": { - "title": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "string"] - }, - "required": { - "type": ["null", "boolean"] - }, - "answers": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "report_meeting_participants", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "user_id": { - "type": ["string"] - }, - "meeting_id": { - "type": ["string"] - }, - "name": { - "type": ["null", "string"] - }, - "user_email": { - "type": ["null", "string"] - }, - "join_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "leave_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "string"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinars", - "json_schema": { - "properties": { - "uuid": { - "type": ["string"] - }, - "id": { - "type": ["string"] - }, - "host_id": { - "type": ["null", "string"] - }, - "topic": { - "type": ["null", "string"] - }, - "type": { - "type": ["null", "integer"] - }, - "start_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "integer"] - }, - "timezone": { - "type": ["null", "string"] - }, - "agenda": { - "type": ["null", "string"] - }, - "created_at": { - "format": "date-time", - "type": ["null", "string"] - }, - "start_url": { - "type": ["null", "string"] - }, - "join_url": { - "type": ["null", "string"] - }, - "tracking_fields": { - "items": { - "properties": { - "field": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "occurences": { - "items": { - "properties": { - "occurence_id": { - "type": ["null", "string"] - }, - "start_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "integer"] - }, - "status": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "settings": { - "properties": { - "host_video": { - "type": ["null", "boolean"] - }, - "panelists_video": { - "type": ["null", "boolean"] - }, - "practice_session": { - "type": ["null", "boolean"] - }, - "hd_video": { - "type": ["null", "boolean"] - }, - "approval_type": { - "type": ["null", "integer"] - }, - "registration_type": { - "type": ["null", "integer"] - }, - "audio": { - "type": ["null", "string"] - }, - "auto_recording": { - "type": ["null", "string"] - }, - "enforce_login": { - "type": ["null", "boolean"] - }, - "enforce_login_domains": { - "type": ["null", "string"] - }, - "alternative_hosts": { - "type": ["null", "string"] - }, - "close_registration": { - "type": ["null", "boolean"] - }, - "show_share_button": { - "type": ["null", "boolean"] - }, - "allow_multiple_devices": { - "type": ["null", "boolean"] - }, - "on_demand": { - "type": ["null", "boolean"] - }, - "global_dial_in_countries": { - "items": { - "type": ["string"] - }, - "type": ["null", "array"] - }, - "contact_name": { - "type": ["null", "boolean"] - }, - "contact_email": { - "type": ["null", "boolean"] - }, - "registrants_confirmation_email": { - "type": ["null", "boolean"] - }, - "registrants_restrict_number": { - "type": ["null", "integer"] - }, - "notify_registrants": { - "type": ["null", "boolean"] - }, - "post_webinar_survey": { - "type": ["null", "boolean"] - }, - "survey_url": { - "type": ["null", "string"] - }, - "registrants_email_notification": { - "type": ["null", "boolean"] - }, - "meeting_authentication": { - "type": ["null", "boolean"] - }, - "authentication_option": { - "type": ["null", "string"] - }, - "authentication_domains": { - "type": ["null", "string"] - } - }, - "type": ["null", "object"], - "additionalProperties": false - }, - "recurrence": { - "properties": { - "type": { - "type": ["null", "integer"] - }, - "repeat_interval": { - "type": ["null", "integer"] - }, - "weekly_days": { - "type": ["null", "integer"] - }, - "monthly_day": { - "type": ["null", "integer"] - }, - "monthly_week": { - "type": ["null", "integer"] - }, - "monthly_week_day": { - "type": ["null", "integer"] - }, - "end_times": { - "type": ["null", "integer"] - }, - "end_date_time": { - "format": "date-time", - "type": ["null", "string"] - } - }, - "type": ["null", "object"], - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_qna_results", - "json_schema": { - "properties": { - "webinar_uuid": { - "type": ["string"] - }, - "email": { - "type": ["string"] - }, - "name": { - "type": ["null", "string"] - }, - "question_details": { - "items": { - "properties": { - "question": { - "type": ["null", "string"] - }, - "answer": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_absentees", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "webinar_uuid": { - "type": ["string"] - }, - "email": { - "type": ["null", "string"] - }, - "first_name": { - "type": ["null", "string"] - }, - "last_name": { - "type": ["null", "string"] - }, - "address": { - "type": ["null", "string"] - }, - "city": { - "type": ["null", "string"] - }, - "county": { - "type": ["null", "string"] - }, - "zip": { - "type": ["null", "string"] - }, - "state": { - "type": ["null", "string"] - }, - "phone": { - "type": ["null", "string"] - }, - "industry": { - "type": ["null", "string"] - }, - "org": { - "type": ["null", "string"] - }, - "job_title": { - "type": ["null", "string"] - }, - "purchasing_time_frame": { - "type": ["null", "string"] - }, - "role_in_purchase_process": { - "type": ["null", "string"] - }, - "no_of_employees": { - "type": ["null", "string"] - }, - "comments": { - "type": ["null", "string"] - }, - "custom_questions": { - "items": { - "properties": { - "title": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "status": { - "type": ["null", "string"] - }, - "create_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "join_url": { - "type": ["null", "string"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "webinar_tracking_sources", - "json_schema": { - "properties": { - "id": { - "type": ["string"] - }, - "webinar_id": { - "type": ["string"] - }, - "source_name": { - "type": ["null", "string"] - }, - "tracking_url": { - "type": ["null", "string"] - }, - "registration_count": { - "type": ["null", "integer"] - }, - "visitor_count": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "report_meetings", - "json_schema": { - "properties": { - "meeting_id": { - "type": ["string"] - }, - "uuid": { - "type": ["string"] - }, - "id": { - "type": ["integer"] - }, - "type": { - "type": ["null", "integer"] - }, - "topic": { - "type": ["null", "string"] - }, - "user_name": { - "type": ["null", "string"] - }, - "user_email": { - "type": ["null", "string"] - }, - "start_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "end_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "integer"] - }, - "total_minutes": { - "type": ["null", "integer"] - }, - "participants_count": { - "type": ["null", "integer"] - }, - "tracking_fields": { - "items": { - "properties": { - "field": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "dept": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - }, - { - "stream": { - "name": "report_webinars", - "json_schema": { - "properties": { - "webinar_id": { - "type": ["string"] - }, - "uuid": { - "type": ["string"] - }, - "id": { - "type": ["integer"] - }, - "type": { - "type": ["null", "integer"] - }, - "topic": { - "type": ["null", "string"] - }, - "user_name": { - "type": ["null", "string"] - }, - "user_email": { - "type": ["null", "string"] - }, - "start_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "end_time": { - "format": "date-time", - "type": ["null", "string"] - }, - "duration": { - "type": ["null", "integer"] - }, - "total_minutes": { - "type": ["null", "integer"] - }, - "participants_count": { - "type": ["null", "integer"] - }, - "tracking_fields": { - "items": { - "properties": { - "field": { - "type": ["null", "string"] - }, - "value": { - "type": ["null", "string"] - } - }, - "type": ["object"], - "additionalProperties": false - }, - "type": ["null", "array"] - }, - "dept": { - "type": ["null", "integer"] - } - }, - "type": "object", - "additionalProperties": false - }, - "supported_sync_modes": ["full_refresh"] - }, - "sync_mode": "full_refresh", - "destination_sync_mode": "overwrite" - } - ] -} diff --git a/airbyte-integrations/connectors/source-zoom-singer/sample_files/sample_config.json b/airbyte-integrations/connectors/source-zoom-singer/sample_files/sample_config.json deleted file mode 100644 index 2975582744b1..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/sample_files/sample_config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "jwt": "" -} diff --git a/airbyte-integrations/connectors/source-zoom-singer/setup.py b/airbyte-integrations/connectors/source-zoom-singer/setup.py deleted file mode 100644 index 6387b921e852..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/setup.py +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright (c) 2022 Airbyte, Inc., all rights reserved. -# - - -from setuptools import find_packages, setup - -setup( - name="source_zoom_singer", - description="Source implementation for Zoom, built on the Singer tap implementation.", - author="Airbyte", - author_email="contact@airbyte.io", - packages=find_packages(), - install_requires=["airbyte-cdk", "tap-zoom==1.0.0", "pytest==6.1.2"], - package_data={"": ["*.json"]}, -) diff --git a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/__init__.py b/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/__init__.py deleted file mode 100644 index dee834d0c068..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .source import SourceZoomSinger - -__all__ = ["SourceZoomSinger"] diff --git a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/source.py b/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/source.py deleted file mode 100644 index 3dac31a63e8f..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/source.py +++ /dev/null @@ -1,24 +0,0 @@ -# -# Copyright (c) 2022 Airbyte, Inc., all rights reserved. -# - - -from airbyte_cdk import AirbyteLogger -from airbyte_cdk.sources.singer.source import BaseSingerSource -from requests import HTTPError -from tap_zoom.client import ZoomClient - - -class SourceZoomSinger(BaseSingerSource): - """ - Zoom API Reference: https://marketplace.zoom.us/docs/api-reference/zoom-api - """ - - tap_cmd = "tap-zoom" - tap_name = "Zoom API" - api_error = HTTPError - force_full_refresh = True - - def try_connect(self, logger: AirbyteLogger, config: dict): - client = ZoomClient(config=config, config_path="") - client.get(path="users") diff --git a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json b/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json deleted file mode 100644 index ccd2b3111b35..000000000000 --- a/airbyte-integrations/connectors/source-zoom-singer/source_zoom_singer/spec.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "documentationUrl": "https://docs.airbyte.com/integrations/sources/zoom", - "connectionSpecification": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Source Zoom Singer Spec", - "type": "object", - "required": ["jwt"], - "additionalProperties": false, - "properties": { - "jwt": { - "title": "JWT Token", - "type": "string", - "description": "Zoom JWT Token. See the docs for more information on how to obtain this key.", - "airbyte_secret": true - } - } - } -} diff --git a/airbyte-integrations/connectors/source-zoom/.dockerignore b/airbyte-integrations/connectors/source-zoom/.dockerignore new file mode 100644 index 000000000000..0804d05bd68e --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_zoom +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-zoom-singer/Dockerfile b/airbyte-integrations/connectors/source-zoom/Dockerfile similarity index 79% rename from airbyte-integrations/connectors/source-zoom-singer/Dockerfile rename to airbyte-integrations/connectors/source-zoom/Dockerfile index 3b56601b682c..9ad7060d0685 100644 --- a/airbyte-integrations/connectors/source-zoom-singer/Dockerfile +++ b/airbyte-integrations/connectors/source-zoom/Dockerfile @@ -7,9 +7,7 @@ WORKDIR /airbyte/integration_code # upgrade pip to the latest version RUN apk --no-cache upgrade \ && pip install --upgrade pip \ - && apk --no-cache add tzdata \ - && apk --no-cache add git \ - && apk --no-cache add build-base + && apk --no-cache add tzdata build-base COPY setup.py ./ @@ -31,10 +29,12 @@ RUN apk --no-cache add bash # copy payload code only COPY main.py ./ -COPY source_zoom_singer ./source_zoom_singer +COPY source_zoom ./source_zoom + ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" + ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.4 -LABEL io.airbyte.name=airbyte/source-zoom-singer +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-zoom diff --git a/airbyte-integrations/connectors/source-zoom-singer/unit_tests/unit_test.py b/airbyte-integrations/connectors/source-zoom/__init__.py similarity index 57% rename from airbyte-integrations/connectors/source-zoom-singer/unit_tests/unit_test.py rename to airbyte-integrations/connectors/source-zoom/__init__.py index dddaea0060fa..1100c1c58cf5 100644 --- a/airbyte-integrations/connectors/source-zoom-singer/unit_tests/unit_test.py +++ b/airbyte-integrations/connectors/source-zoom/__init__.py @@ -1,7 +1,3 @@ # # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # - - -def test_example_method(): - assert True diff --git a/airbyte-integrations/connectors/source-zoom/acceptance-test-config.yml b/airbyte-integrations/connectors/source-zoom/acceptance-test-config.yml new file mode 100644 index 000000000000..23948f4f26d1 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/acceptance-test-config.yml @@ -0,0 +1,44 @@ +# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-zoom:dev +tests: + spec: + - spec_path: "source_zoom/spec.yaml" + connection: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + timeout_seconds: 3600 + empty_streams: + - "meeting_registrants" + - "meeting_polls" + - "meeting_poll_results" + - "meeting_registration_questions" + - "webinars" + - "webinar_panelists" + - "webinar_registrants" + - "webinar_absentees" + - "webinar_polls" + - "webinar_poll_results" + - "webinar_registration_questions" + - "webinar_tracking_sources" + - "webinar_qna_results" + - "report_meetings" + - "report_meeting_participants" + - "report_webinars" + - "report_webinar_participants" + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + ignored_fields: + "meetings": + - "start_url" + "webinars": + - "start_url" + timeout_seconds: 3600 diff --git a/airbyte-integrations/connectors/source-zoom/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-zoom/acceptance-test-docker.sh new file mode 100755 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-zoom/build.gradle b/airbyte-integrations/connectors/source-zoom/build.gradle new file mode 100644 index 000000000000..fa9adb45e4e0 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_zoom' +} diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/__init__.py b/airbyte-integrations/connectors/source-zoom/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/abnormal_state.json b/airbyte-integrations/connectors/source-zoom/integration_tests/abnormal_state.json new file mode 100644 index 000000000000..848a6177c4b7 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/abnormal_state.json @@ -0,0 +1,5 @@ +{ + "users": { + "updated_at": "2222-01-21T00:00:00.000Z" + } +} diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-zoom/integration_tests/acceptance.py new file mode 100644 index 000000000000..950b53b59d41 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/acceptance.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + yield diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..4be548978f15 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json @@ -0,0 +1,213 @@ +{ + "streams": [ + { + "stream": { + "name": "users", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "meetings", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "meeting_registrants", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "meeting_polls", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "meeting_poll_results", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "meeting_registration_questions", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinars", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_panelists", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_registrants", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_absentees", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_polls", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_poll_results", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_registration_questions", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_tracking_sources", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "webinar_qna_results", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "report_meetings", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "report_meeting_participants", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "report_webinars", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "report_webinar_participants", + "json_schema": {}, + "supported_sync_modes": [ + "full_refresh" + ] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-zoom/integration_tests/invalid_config.json new file mode 100644 index 000000000000..6a603fda8000 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/invalid_config.json @@ -0,0 +1,3 @@ +{ + "jwt_token": "dummy" +} diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-zoom/integration_tests/sample_config.json new file mode 100644 index 000000000000..f875ad8416c6 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/sample_config.json @@ -0,0 +1,3 @@ +{ + "jwt_token": "abcd" +} diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json new file mode 100644 index 000000000000..0151c6fc660e --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/airbyte-integrations/connectors/source-zoom-singer/main.py b/airbyte-integrations/connectors/source-zoom/main.py similarity index 68% rename from airbyte-integrations/connectors/source-zoom-singer/main.py rename to airbyte-integrations/connectors/source-zoom/main.py index 135a9790d88f..4b6bfd836670 100644 --- a/airbyte-integrations/connectors/source-zoom-singer/main.py +++ b/airbyte-integrations/connectors/source-zoom/main.py @@ -6,8 +6,8 @@ import sys from airbyte_cdk.entrypoint import launch -from source_zoom_singer import SourceZoomSinger +from source_zoom import SourceZoom if __name__ == "__main__": - source = SourceZoomSinger() + source = SourceZoom() launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-zoom/requirements.txt b/airbyte-integrations/connectors/source-zoom/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-zoom/setup.py b/airbyte-integrations/connectors/source-zoom/setup.py new file mode 100644 index 000000000000..e646cdcd338b --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.1", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_zoom", + description="Source implementation for Zoom.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/__init__.py b/airbyte-integrations/connectors/source-zoom/source_zoom/__init__.py new file mode 100644 index 000000000000..4fb74e1fc140 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceZoom + +__all__ = ["SourceZoom"] diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_poll_results.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_poll_results.json new file mode 100644 index 000000000000..0f4b97875223 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_poll_results.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "meeting_uuid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "question_details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "date_time": { + "type": "string" + }, + "polling_id": { + "type": "string" + }, + "question": { + "type": "string" + } + }, + "required": [] + } + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json new file mode 100644 index 000000000000..989fddd5626f --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json @@ -0,0 +1,96 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "meeting_id": { + "type": "number" + }, + "status": { + "type": "string" + }, + "anonymous": { + "type": "boolean" + }, + "poll_type": { + "type": "number" + }, + "questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer_max_character": { + "type": "number" + }, + "answer_min_character": { + "type": "number" + }, + "answer_required": { + "type": "boolean" + }, + "answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "case_sensitive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "prompts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "prompt_question": { + "type": "string" + }, + "prompt_right_answers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [] + } + }, + "rating_max_label": { + "type": "string" + }, + "rating_max_value": { + "type": "number" + }, + "rating_min_label": { + "type": "string" + }, + "rating_min_value": { + "type": "number" + }, + "right_answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_as_dropdown": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [] + } + }, + "title": { + "type": "string" + } + } +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json new file mode 100644 index 000000000000..c5dc946fb693 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json @@ -0,0 +1,86 @@ +{ + "$schema":"http://json-schema.org/draft-07/schema#", + "properties": { + "meeting_id": { + "type": "number" + }, + "id": { + "type": "string" + }, + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "country": { + "type": "string" + }, + "custom_questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + ] + } + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "industry": { + "type": "string" + }, + "job_title": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "no_of_employees": { + "type": "string" + }, + "org": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "purchasing_time_frame": { + "type": "string" + }, + "role_in_purchase_process": { + "type": "string" + }, + "state": { + "type": "string" + }, + "status": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "create_time": { + "type": "string" + }, + "join_url": { + "type": "string" + } + }, + "required": [ + ] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json new file mode 100644 index 000000000000..aa396bf02ef2 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "meeting_id": { + "type": [ + "string" + ] + }, + "custom_questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [] + } + }, + "questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field_name": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [] + } + } + } +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json new file mode 100644 index 000000000000..06c7d95dc1f1 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json @@ -0,0 +1,408 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "assistant_id": { + "type": "string" + }, + "host_email": { + "type": "string" + }, + "host_id": { + "type": "string" + }, + "id": { + "type": "number" + }, + "uuid": { + "type": "string" + }, + "agenda": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "encrypted_password": { + "type": "string" + }, + "h323_password": { + "type": "string" + }, + "join_url": { + "type": "string" + }, + "occurrences": { + "type": "array", + "items": { + "type": "object", + "properties": { + "duration": { + "type": "number" + }, + "occurrence_id": { + "type": "string" + }, + "start_time": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + ] + } + }, + "password": { + "type": "string" + }, + "pmi": { + "type": "string" + }, + "pre_schedule": { + "type": "boolean" + }, + "recurrence": { + "type": "object", + "properties": { + "end_date_time": { + "type": "string" + }, + "end_times": { + "type": "number" + }, + "monthly_day": { + "type": "number" + }, + "monthly_week": { + "type": "number" + }, + "monthly_week_day": { + "type": "number" + }, + "repeat_interval": { + "type": "number" + }, + "type": { + "type": "number" + }, + "weekly_days": { + "type": "string" + } + }, + "required": [ + ] + }, + "settings": { + "type": "object", + "properties": { + "allow_multiple_devices": { + "type": "boolean" + }, + "alternative_hosts": { + "type": "string" + }, + "alternative_hosts_email_notification": { + "type": "boolean" + }, + "alternative_host_update_polls": { + "type": "boolean" + }, + "approval_type": { + "type": "number" + }, + "approved_or_denied_countries_or_regions": { + "type": "object", + "properties": { + "approved_list": { + "type": "array", + "items": { + "type": "string" + } + }, + "denied_list": { + "type": "array", + "items": { + "type": "string" + } + }, + "enable": { + "type": "boolean" + }, + "method": { + "type": "string" + } + }, + "required": [ + ] + }, + "audio": { + "type": "string" + }, + "authentication_domains": { + "type": "string" + }, + "authentication_exception": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "join_url": { + "type": "string" + } + }, + "required": [ + ] + } + }, + "authentication_name": { + "type": "string" + }, + "authentication_option": { + "type": "string" + }, + "auto_recording": { + "type": "string" + }, + "breakout_room": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "rooms": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "participants": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + ] + } + } + }, + "required": [ + ] + }, + "calendar_type": { + "type": "number" + }, + "close_registration": { + "type": "boolean" + }, + "contact_email": { + "type": "string" + }, + "contact_name": { + "type": "string" + }, + "custom_keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + ] + } + }, + "email_notification": { + "type": "boolean" + }, + "encryption_type": { + "type": "string" + }, + "focus_mode": { + "type": "boolean" + }, + "global_dial_in_countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "global_dial_in_numbers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "city": { + "type": "string" + }, + "country": { + "type": "string" + }, + "country_name": { + "type": "string" + }, + "number": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + ] + } + }, + "host_video": { + "type": "boolean" + }, + "jbh_time": { + "type": "number" + }, + "join_before_host": { + "type": "boolean" + }, + "language_interpretation": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "interpreters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "languages": { + "type": "string" + } + }, + "required": [ + ] + } + } + }, + "required": [ + ] + }, + "meeting_authentication": { + "type": "boolean" + }, + "mute_upon_entry": { + "type": "boolean" + }, + "participant_video": { + "type": "boolean" + }, + "private_meeting": { + "type": "boolean" + }, + "registrants_confirmation_email": { + "type": "boolean" + }, + "registrants_email_notification": { + "type": "boolean" + }, + "registration_type": { + "type": "number" + }, + "show_share_button": { + "type": "boolean" + }, + "use_pmi": { + "type": "boolean" + }, + "waiting_room": { + "type": "boolean" + }, + "waiting_room_options": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "admit_type": { + "type": "number" + }, + "auto_admit": { + "type": "number" + }, + "internal_user_auto_admit": { + "type": "number" + } + }, + "required": [ + ] + }, + "watermark": { + "type": "boolean" + }, + "host_save_video_order": { + "type": "boolean" + } + }, + "required": [ + ] + }, + "start_time": { + "type": "string" + }, + "start_url": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "topic": { + "type": "string" + }, + "tracking_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "value": { + "type": "string" + }, + "visible": { + "type": "boolean" + } + }, + "required": [ + ] + } + }, + "type": { + "type": "number" + } + }, + "required": [ + ] + +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meeting_participants.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meeting_participants.json new file mode 100644 index 000000000000..763392427fc4 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meeting_participants.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "meeting_uuid": { + "type": "string" + }, + "customer_key": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "failover": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "join_time": { + "type": "string" + }, + "leave_time": { + "type": "string" + }, + "name": { + "type": "string" + }, + "registrant_id": { + "type": "string" + }, + "user_email": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "bo_mtg_id": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meetings.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meetings.json new file mode 100644 index 000000000000..e7b31f338a72 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_meetings.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "meeting_uuid": { + "type": "string" + }, + "custom_keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "dept": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "end_time": { + "type": "string" + }, + "id": { + "type": "number" + }, + "participants_count": { + "type": "number" + }, + "start_time": { + "type": "string" + }, + "topic": { + "type": "string" + }, + "total_minutes": { + "type": "number" + }, + "tracking_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "type": { + "type": "number" + }, + "user_email": { + "type": "string" + }, + "user_name": { + "type": "string" + }, + "uuid": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinar_participants.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinar_participants.json new file mode 100644 index 000000000000..bfba6ff87d93 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinar_participants.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_uuid": { + "type": "string" + }, + "customer_key": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "failover": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "join_time": { + "type": "string" + }, + "leave_time": { + "type": "string" + }, + "name": { + "type": "string" + }, + "registrant_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "user_email": { + "type": "string" + }, + "user_id": { + "type": "string" + } + }, + "required": [ + "customer_key", + "duration", + "failover", + "id", + "join_time", + "leave_time", + "name", + "registrant_id", + "status", + "user_email", + "user_id" + ] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinars.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinars.json new file mode 100644 index 000000000000..b8ae1ed0ceac --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/report_webinars.json @@ -0,0 +1,76 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_uuid": { + "type": "string" + }, + "custom_keys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "dept": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "end_time": { + "type": "string" + }, + "id": { + "type": "number" + }, + "participants_count": { + "type": "number" + }, + "start_time": { + "type": "string" + }, + "topic": { + "type": "string" + }, + "total_minutes": { + "type": "number" + }, + "tracking_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "type": { + "type": "number" + }, + "user_email": { + "type": "string" + }, + "user_name": { + "type": "string" + }, + "uuid": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json new file mode 100644 index 000000000000..8e2bdef40615 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "properties": { + "created_at": { + "type": "string" + }, + "custom_attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + ] + } + }, + "dept": { + "type": "string" + }, + "email": { + "type": "string" + }, + "employee_unique_id": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "group_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "im_group_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "last_client_version": { + "type": "string" + }, + "last_login_time": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "plan_united_type": { + "type": "string" + }, + "pmi": { + "type": "number" + }, + "role_id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "type": { + "type": "number" + }, + "verified": { + "type": "number" + } + }, + "required": [ + ] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_absentees.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_absentees.json new file mode 100644 index 000000000000..ced32756af1a --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_absentees.json @@ -0,0 +1,85 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_uuid": { + "type": "string" + }, + "id": { + "type": "string" + }, + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "country": { + "type": "string" + }, + "custom_questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "industry": { + "type": "string" + }, + "job_title": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "no_of_employees": { + "type": "string" + }, + "org": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "purchasing_time_frame": { + "type": "string" + }, + "role_in_purchase_process": { + "type": "string" + }, + "state": { + "type": "string" + }, + "status": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "create_time": { + "type": "string" + }, + "join_url": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_panelists.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_panelists.json new file mode 100644 index 000000000000..53801958fa0e --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_panelists.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_id": { + "type": "number" + }, + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "join_url": { + "type": "string" + }, + "virtual_background_id": { + "type": "string" + }, + "name_tag_id": { + "type": "string" + }, + "name_tag_name": { + "type": "string" + }, + "name_tag_pronouns": { + "type": "string" + }, + "name_tag_description": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_poll_results.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_poll_results.json new file mode 100644 index 000000000000..dbb102491ec1 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_poll_results.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_uuid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "question_details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "date_time": { + "type": "string" + }, + "polling_id": { + "type": "string" + }, + "question": { + "type": "string" + } + }, + "required": [] + } + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_polls.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_polls.json new file mode 100644 index 000000000000..35ed3e392162 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_polls.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "status": { + "type": "string" + }, + "anonymous": { + "type": "boolean" + }, + "poll_type": { + "type": "number" + }, + "questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer_max_character": { + "type": "number" + }, + "answer_min_character": { + "type": "number" + }, + "answer_required": { + "type": "boolean" + }, + "answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "case_sensitive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "prompts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "prompt_question": { + "type": "string" + }, + "prompt_right_answers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [] + } + }, + "rating_max_label": { + "type": "string" + }, + "rating_max_value": { + "type": "number" + }, + "rating_min_label": { + "type": "string" + }, + "rating_min_value": { + "type": "number" + }, + "right_answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "show_as_dropdown": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [] + } + }, + "title": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_qna_results.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_qna_results.json new file mode 100644 index 000000000000..361b24a5fc56 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_qna_results.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_uuid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "question_details": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer": { + "type": "string" + }, + "question": { + "type": "string" + } + }, + "required": [] + } + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registrants.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registrants.json new file mode 100644 index 000000000000..0b5a7cdaf6c2 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registrants.json @@ -0,0 +1,85 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "address": { + "type": "string" + }, + "city": { + "type": "string" + }, + "comments": { + "type": "string" + }, + "country": { + "type": "string" + }, + "custom_questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "email": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "industry": { + "type": "string" + }, + "job_title": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "no_of_employees": { + "type": "string" + }, + "org": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "purchasing_time_frame": { + "type": "string" + }, + "role_in_purchase_process": { + "type": "string" + }, + "state": { + "type": "string" + }, + "status": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "create_time": { + "type": "string" + }, + "join_url": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registration_questions.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registration_questions.json new file mode 100644 index 000000000000..46f0dad22ea0 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_registration_questions.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_id": { + "type": "string" + }, + "custom_questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "required": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [] + } + }, + "questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field_name": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [] + } + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_tracking_sources.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_tracking_sources.json new file mode 100644 index 000000000000..b7cab1839c57 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinar_tracking_sources.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "webinar_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "registration_count": { + "type": "number" + }, + "source_name": { + "type": "string" + }, + "tracking_url": { + "type": "string" + }, + "visitor_count": { + "type": "number" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinars.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinars.json new file mode 100644 index 000000000000..850b0c16c0c9 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/webinars.json @@ -0,0 +1,322 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "host_email": { + "type": "string" + }, + "host_id": { + "type": "string" + }, + "id": { + "type": "number" + }, + "uuid": { + "type": "string" + }, + "agenda": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "duration": { + "type": "number" + }, + "join_url": { + "type": "string" + }, + "occurrences": { + "type": "array", + "items": { + "type": "object", + "properties": { + "duration": { + "type": "number" + }, + "occurrence_id": { + "type": "string" + }, + "start_time": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [] + } + }, + "password": { + "type": "string" + }, + "recurrence": { + "type": "object", + "properties": { + "end_date_time": { + "type": "string" + }, + "end_times": { + "type": "number" + }, + "monthly_day": { + "type": "number" + }, + "monthly_week": { + "type": "number" + }, + "monthly_week_day": { + "type": "number" + }, + "repeat_interval": { + "type": "number" + }, + "type": { + "type": "number" + }, + "weekly_days": { + "type": "string" + } + }, + "required": [] + }, + "settings": { + "type": "object", + "properties": { + "allow_multiple_devices": { + "type": "boolean" + }, + "alternative_hosts": { + "type": "string" + }, + "alternative_host_update_polls": { + "type": "boolean" + }, + "approval_type": { + "type": "number" + }, + "attendees_and_panelists_reminder_email_notification": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "type": { + "type": "number" + } + }, + "required": [] + }, + "audio": { + "type": "string" + }, + "authentication_domains": { + "type": "string" + }, + "authentication_name": { + "type": "string" + }, + "authentication_option": { + "type": "string" + }, + "auto_recording": { + "type": "string" + }, + "close_registration": { + "type": "boolean" + }, + "contact_email": { + "type": "string" + }, + "contact_name": { + "type": "string" + }, + "email_language": { + "type": "string" + }, + "follow_up_absentees_email_notification": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "type": { + "type": "number" + } + }, + "required": [] + }, + "follow_up_attendees_email_notification": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "type": { + "type": "number" + } + }, + "required": [] + }, + "global_dial_in_countries": { + "type": "array", + "items": { + "type": "string" + } + }, + "hd_video": { + "type": "boolean" + }, + "hd_video_for_attendees": { + "type": "boolean" + }, + "host_video": { + "type": "boolean" + }, + "language_interpretation": { + "type": "object", + "properties": { + "enable": { + "type": "boolean" + }, + "interpreters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "languages": { + "type": "string" + } + }, + "required": [] + } + } + }, + "required": [] + }, + "panelist_authentication": { + "type": "boolean" + }, + "meeting_authentication": { + "type": "boolean" + }, + "add_watermark": { + "type": "boolean" + }, + "add_audio_watermark": { + "type": "boolean" + }, + "notify_registrants": { + "type": "boolean" + }, + "on_demand": { + "type": "boolean" + }, + "panelists_invitation_email_notification": { + "type": "boolean" + }, + "panelists_video": { + "type": "boolean" + }, + "post_webinar_survey": { + "type": "boolean" + }, + "practice_session": { + "type": "boolean" + }, + "question_and_answer": { + "type": "object", + "properties": { + "allow_anonymous_questions": { + "type": "boolean" + }, + "answer_questions": { + "type": "string" + }, + "attendees_can_comment": { + "type": "boolean" + }, + "attendees_can_upvote": { + "type": "boolean" + }, + "allow_auto_reply": { + "type": "boolean" + }, + "auto_reply_text": { + "type": "string" + }, + "enable": { + "type": "boolean" + } + }, + "required": [] + }, + "registrants_confirmation_email": { + "type": "boolean" + }, + "registrants_email_notification": { + "type": "boolean" + }, + "registrants_restrict_number": { + "type": "number" + }, + "registration_type": { + "type": "number" + }, + "send_1080p_video_to_attendees": { + "type": "boolean" + }, + "show_share_button": { + "type": "boolean" + }, + "survey_url": { + "type": "string" + }, + "enable_session_branding": { + "type": "boolean" + } + }, + "required": [] + }, + "start_time": { + "type": "string" + }, + "start_url": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "topic": { + "type": "string" + }, + "tracking_fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [] + } + }, + "type": { + "type": "number" + }, + "is_simulive": { + "type": "boolean" + }, + "record_file_id": { + "type": "string" + } + }, + "required": [] +} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/source.py b/airbyte-integrations/connectors/source-zoom/source_zoom/source.py new file mode 100644 index 000000000000..f7c16e43a355 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/source.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource + +""" +This file provides the necessary constructs to interpret a provided declarative YAML configuration file into +source connector. + +WARNING: Do not modify this file. +""" + + +# Declarative Source +class SourceZoom(YamlDeclarativeSource): + def __init__(self): + super().__init__(**{"path_to_yaml": "zoom.yaml"}) diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/spec.yaml b/airbyte-integrations/connectors/source-zoom/source_zoom/spec.yaml new file mode 100644 index 000000000000..a8170e08c3b7 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/spec.yaml @@ -0,0 +1,13 @@ +documentationUrl: https://docs.airbyte.com/integrations/sources/zoom +connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Zoom Spec + type: object + required: + - jwt_token + additionalProperties: true + properties: + jwt_token: + type: string + description: JWT Token + airbyte_secret: true diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml b/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml new file mode 100644 index 000000000000..77fae4e527d3 --- /dev/null +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml @@ -0,0 +1,815 @@ +version: "0.1.0" + +definitions: + requester: + url_base: "https://api.zoom.us/v2/" + http_method: "GET" + authenticator: + type: BearerAuthenticator + api_token: "{{ config['jwt_token'] }}" + + zoom_paginator: + type: DefaultPaginator + pagination_strategy: + type: "CursorPagination" + cursor_value: "{{ response.next_page_token }}" + stop_condition: "{{ response.next_page_token == '' }}" + page_size: 30 + page_size_option: + field_name: "page_size" + inject_into: "request_parameter" + page_token_option: + field_name: "next_page_token" + inject_into: "request_parameter" + url_base: "*ref(definitions.requester.url_base)" + + + retriever: + requester: + $ref: "*ref(definitions.requester)" + + schema_loader: + type: JsonSchema + file_path: "./source_zoom/schemas/{{ options['name'] }}.json" + + + users_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["users"] + $ref: "*ref(definitions.retriever)" + $options: + name: "users" + primary_key: "id" + path: "/users" + + + meetings_list_tmp_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "meetings_list_tmp" + primary_key: "id" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["meetings"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/users/{{ stream_slice.parent_id }}/meetings" + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.users_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + + + + meetings_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "meetings" + primary_key: "id" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: [] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/meetings/{{ stream_slice.parent_id }}" + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + + + meeting_registrants_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "meeting_registrants" + primary_key: "id" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["registrants"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/meetings/{{ stream_slice.parent_id }}/registrants" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + # Meeting {meetingId} is not found or has expired. This meeting has not set registration as required: {meetingId}. + - predicate: "{{ response.code == 300 }}" + action: IGNORE + - type: DefaultErrorHandler # we're adding this DefaultErrorHandler for 429, 5XX errors etc; + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["meeting_id"] + value: "{{ stream_slice.parent_id }}" + + + meeting_polls_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "meeting_polls" + primary_key: "id" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["polls"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/meetings/{{ stream_slice.parent_id }}/polls" + error_handler: + type: CompositeErrorHandler + # ignore 400 error; We get this error if Meeting poll is not enabled for the meeting, or scheduling capabilities aren't in the account + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["meeting_id"] + value: "{{ stream_slice.parent_id }}" + + + meeting_poll_results_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "meeting_poll_results" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["questions"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/past_meetings/{{ stream_slice.parent_id }}/polls" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + # 400 error is thrown for meetings created an year ago + # 404 error is thrown if the meeting has not enabled polls (from observation, not written in docs) + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["meeting_uuid"] + value: "{{ stream_slice.parent_id }}" + + + meeting_registration_questions_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "meeting_registration_questions" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: [] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/meetings/{{ stream_slice.parent_id }}/registrants/questions" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + # ignore 400 error; We get this error if Bad Request or Meeting hosting and scheduling capabilities are not allowed for your user account. + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["meeting_id"] + value: "{{ stream_slice.parent_id }}" + + + webinars_list_tmp_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinars_list_tmp" + primary_key: "id" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["webinars"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/users/{{ stream_slice.parent_id }}/webinars" + error_handler: + type: CompositeErrorHandler + # ignore 400 error; We get this error if Meeting is more than created an year ago + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.users_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + + webinars_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinars" + primary_key: "id" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: [] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/webinars/{{ stream_slice.parent_id }}" + error_handler: + type: CompositeErrorHandler + # ignore 400 error + error_handlers: + - type: DefaultErrorHandler + response_filters: + # When parent stream throws error; then ideally we should have an empty array, and no /webinars/{id} should be called. But somehow we're calling it right now with None. :( + # More context: https://github.com/airbytehq/airbyte/issues/18046 + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + + webinar_panelists_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_panelists" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["panelists"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/webinars/{{ stream_slice.parent_id }}/panelists" + error_handler: + type: CompositeErrorHandler + # ignore 400 error + error_handlers: + - type: DefaultErrorHandler + response_filters: + # Same problem as "webinars_stream" for 404! and we get 400 error if the account isn't PRO. + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_id"] + value: "{{ stream_slice.parent_id }}" + + + webinar_registrants_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_registrants" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["registrants"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/webinars/{{ stream_slice.parent_id }}/registrants" + error_handler: + type: CompositeErrorHandler + # ignore 400 error + error_handlers: + - type: DefaultErrorHandler + response_filters: + # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_id"] + value: "{{ stream_slice.parent_id }}" + + + webinar_absentees_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_absentees" + primary_key: "id" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["registrants"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/past_webinars/{{ stream_slice.parent_uuid }}/absentees" + error_handler: + type: CompositeErrorHandler + # ignore 400 error + error_handlers: + - type: DefaultErrorHandler + response_filters: + # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_uuid" + transformations: + - type: AddFields + fields: + - path: ["webinar_uuid"] + value: "{{ stream_slice.parent_uuid }}" + + + webinar_polls_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_polls" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["polls"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/webinars/{{ stream_slice.parent_id }}/polls" + error_handler: + type: CompositeErrorHandler + # ignore 400 error; We get this error if Webinar poll is disabled + error_handlers: + - type: DefaultErrorHandler + response_filters: + # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_id"] + value: "{{ stream_slice.parent_id }}" + + + + webinar_poll_results_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_poll_results" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["questions"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/past_webinars/{{ stream_slice.parent_id }}/polls" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_uuid"] + value: "{{ stream_slice.parent_id }}" + + + webinar_registration_questions_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_registration_questions" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: [] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/webinars/{{ stream_slice.parent_id }}/registrants/questions" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + # the docs says 404 code, but that's incorrect (from observation); + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_id"] + value: "{{ stream_slice.parent_id }}" + + + + webinar_tracking_sources_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_tracking_sources" + primary_key: "id" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["tracking_sources"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/webinars/{{ stream_slice.parent_id }}/tracking_sources" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "id" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_id"] + value: "{{ stream_slice.parent_id }}" + + + webinar_qna_results_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "webinar_qna_results" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["questions"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/past_webinars/{{ stream_slice.parent_id }}/qa" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400,404] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_uuid"] + value: "{{ stream_slice.parent_id }}" + + + + report_meetings_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "report_meetings" + primary_key: "id" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["tracking_sources"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/report/meetings/{{ stream_slice.parent_id }}" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["meeting_uuid"] + value: "{{ stream_slice.parent_id }}" + + + + + + report_meeting_participants_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "report_meeting_participants" + primary_key: "id" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["participants"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/report/meetings/{{ stream_slice.parent_id }}/participants" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.meetings_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["meeting_uuid"] + value: "{{ stream_slice.parent_id }}" + + + report_webinars_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "report_webinars" + retriever: + paginator: + type: NoPagination + record_selector: + extractor: + type: DpathExtractor + field_pointer: [] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/report/webinars/{{ stream_slice.parent_id }}" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_uuid"] + value: "{{ stream_slice.parent_id }}" + + + + report_webinar_participants_stream: + schema_loader: + $ref: "*ref(definitions.schema_loader)" + $options: + name: "report_webinar_participants" + retriever: + paginator: + $ref: "*ref(definitions.zoom_paginator)" + record_selector: + extractor: + type: DpathExtractor + field_pointer: ["participants"] + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: "/report/webinars/{{ stream_slice.parent_id }}/participants" + error_handler: + type: CompositeErrorHandler + error_handlers: + - type: DefaultErrorHandler + response_filters: + - http_codes: [400] + action: IGNORE + - type: DefaultErrorHandler + stream_slicer: + type: SubstreamSlicer + parent_stream_configs: + - stream: "*ref(definitions.webinars_list_tmp_stream)" + parent_key: "uuid" + stream_slice_field: "parent_id" + transformations: + - type: AddFields + fields: + - path: ["webinar_uuid"] + value: "{{ stream_slice.parent_id }}" + + +streams: + - "*ref(definitions.users_stream)" + - "*ref(definitions.meetings_stream)" + - "*ref(definitions.meeting_registrants_stream)" + - "*ref(definitions.meeting_polls_stream)" + - "*ref(definitions.meeting_poll_results_stream)" + - "*ref(definitions.meeting_registration_questions_stream)" + - "*ref(definitions.webinars_stream)" + - "*ref(definitions.webinar_panelists_stream)" + - "*ref(definitions.webinar_registrants_stream)" + - "*ref(definitions.webinar_absentees_stream)" + - "*ref(definitions.webinar_polls_stream)" + - "*ref(definitions.webinar_poll_results_stream)" + - "*ref(definitions.webinar_registration_questions_stream)" + - "*ref(definitions.webinar_tracking_sources_stream)" + - "*ref(definitions.webinar_qna_results_stream)" + - "*ref(definitions.report_meetings_stream)" + - "*ref(definitions.report_meeting_participants_stream)" + - "*ref(definitions.report_webinars_stream)" + - "*ref(definitions.report_webinar_participants_stream)" + + +check: + stream_names: + - "users" diff --git a/docs/integrations/sources/zoom.md b/docs/integrations/sources/zoom.md index 59f8fbcc25dc..861dc20ade09 100644 --- a/docs/integrations/sources/zoom.md +++ b/docs/integrations/sources/zoom.md @@ -2,13 +2,14 @@ ## Overview -The Zoom source supports Full Refresh syncs. That is, every time a sync is run, Airbyte will copy all rows in the tables and columns you set up for replication into the destination in a new table. -This Zoom source wraps the [Singer Zoom Tap](https://github.com/singer-io/tap-zoom). +The following connector allows airbyte users to fetch various meetings & webinar data points from the [Zoom](https://zoom.us) source. This connector is built entirely using the [low-code CDK](https://docs.airbyte.com/connector-development/config-based/low-code-cdk-overview/). + +Please note that currently, it only supports Full Refresh syncs. That is, every time a sync is run, Airbyte will copy all rows in the tables and columns you set up for replication into the destination in a new table. ### Output schema -Several output streams are available from this source: +Currently this source supports the following output streams/endpoints from Zoom: * [Users](https://marketplace.zoom.us/docs/api-reference/zoom-api/users/users) * [Meetings](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetings) @@ -16,7 +17,6 @@ Several output streams are available from this source: * [Meeting Polls](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingpolls) * [Meeting Poll Results](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/listpastmeetingpolls) * [Meeting Questions](https://marketplace.zoom.us/docs/api-reference/zoom-api/meetings/meetingregistrantsquestionsget) - * [Meeting Files](https://marketplace.zoom.us/docs/api-reference/zoom-api/deprecated-api-endpoints/listpastmeetingfiles) * [Webinars](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinars) * [Webinar Panelists](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarpanelists) * [Webinar Registrants](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarregistrants) @@ -26,7 +26,6 @@ Several output streams are available from this source: * [Webinar Questions](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/webinarregistrantsquestionsget) * [Webinar Tracking Sources](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/gettrackingsources) * [Webinar Q&A Results](https://marketplace.zoom.us/docs/api-reference/zoom-api/webinars/listpastwebinarqa) - * [Webinar Files](https://marketplace.zoom.us/docs/api-reference/zoom-api/deprecated-api-endpoints/listpastwebinarfiles) * [Report Meetings](https://marketplace.zoom.us/docs/api-reference/zoom-api/reports/reportmeetingdetails) * [Report Meeting Participants](https://marketplace.zoom.us/docs/api-reference/zoom-api/reports/reportmeetingparticipants) * [Report Webinars](https://marketplace.zoom.us/docs/api-reference/zoom-api/reports/reportwebinardetails) @@ -46,9 +45,9 @@ If there are more endpoints you'd like Airbyte to support, please [create an iss ### Performance considerations -The connector is restricted by normal Zoom [requests limitation](https://marketplace.zoom.us/docs/api-reference/rate-limits#rate-limit-changes). +Most of the endpoints this connector access is restricted by standard Zoom [requests limitation](https://marketplace.zoom.us/docs/api-reference/rate-limits#rate-limit-changes), with a few exceptions. For more info, please check zoom API documentation. We’ve added appropriate retries if we hit the rate-limiting threshold. -The Zoom connector should not run into Zoom API limitations under normal usage. Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. ## Getting started @@ -60,7 +59,3 @@ The Zoom connector should not run into Zoom API limitations under normal usage. Please read [How to generate your JWT Token](https://marketplace.zoom.us/docs/guides/build/jwt-app). -| Version | Date | Pull Request | Subject | -| :--- | :--- | :--- | :--- | -| 0.2.4 | 2021-07-06 | [4539](https://github.com/airbytehq/airbyte/pull/4539) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | - From 260e89f23cb966db96ad426308ecaebb605cf791 Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 25 Oct 2022 19:36:36 +0200 Subject: [PATCH 314/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20SurveyMonkey:?= =?UTF-8?q?=20fix=20expected=20test=20result=20according=20to=200.2.2=20up?= =?UTF-8?q?date=20(#18431)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../source-surveymonkey/acceptance-test-config.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml b/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml index 0feabb8df976..8ba77b8d308e 100644 --- a/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-surveymonkey/acceptance-test-config.yml @@ -1,21 +1,27 @@ connector_image: airbyte/source-surveymonkey:dev -tests: +acceptance_tests: spec: + tests: - spec_path: "source_surveymonkey/spec.json" connection: + tests: - config_path: "secrets/config.json" status: "succeed" - config_path: "integration_tests/invalid_config.json" - status: "exception" + status: "failed" discovery: + tests: - config_path: "secrets/config.json" basic_read: + tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" incremental: + tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" future_state_path: "integration_tests/abnormal_state.json" full_refresh: + tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" From 05390991eca4240f13104a6f4998458935a35770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Werner=20Fouch=C3=A9?= Date: Tue, 25 Oct 2022 20:09:53 +0200 Subject: [PATCH 315/498] Update postgres.md (#18432) --- docs/integrations/destinations/postgres.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/destinations/postgres.md b/docs/integrations/destinations/postgres.md index 1aa587411e1a..7893d51ddb57 100644 --- a/docs/integrations/destinations/postgres.md +++ b/docs/integrations/destinations/postgres.md @@ -40,7 +40,7 @@ You need a Postgres user with the following permissions: You can create such a user by running: ``` -CREATE USER airbyte_user PASSWORD ; +CREATE USER airbyte_user WITH PASSWORD ''; GRANT CREATE, TEMPORARY ON DATABASE TO airbyte_user; ``` From 47f77f1be3eb8b85f70684c2d3b42531d5af3d59 Mon Sep 17 00:00:00 2001 From: Amruta Ranade <11484018+Amruta-Ranade@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:58:09 -0400 Subject: [PATCH 316/498] Update README.md --- docs/integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 3fb03697b854..26454b792a87 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -117,7 +117,7 @@ For more information about the grading system, see [Product Release Stages](http | [OneSignal](sources/onesignal.md) | Alpha | No | | [OpenWeather](sources/openweather.md) | Alpha | No | | [Oracle DB](sources/oracle.md) | Alpha | Yes | -| [Oracle Netsuite](sources/netsuite.md) | Generally Available | Yes | +| [Oracle Netsuite](sources/netsuite.md) | Alpha | Yes | | [Oracle PeopleSoft](sources/oracle-peoplesoft.md) | Alpha | No | | [Oracle Siebel CRM](sources/oracle-siebel-crm.md) | Alpha | No | | [Orb](sources/orb.md) | Alpha | Yes | From 0865c62ee62c1cce0c52bb20c067183c8c7176ce Mon Sep 17 00:00:00 2001 From: Augustin Date: Tue, 25 Oct 2022 21:45:12 +0200 Subject: [PATCH 317/498] SAT: make all tests mandatory when `test_strictness_level` == `high` (#18414) --- .../bases/source-acceptance-test/CHANGELOG.md | 3 + .../bases/source-acceptance-test/Dockerfile | 2 +- .../source_acceptance_test/base.py | 3 + .../source_acceptance_test/plugin.py | 93 +++++++++---- .../unit_tests/test_plugin.py | 122 ++++++++++++++++++ .../source-pokeapi/acceptance-test-config.yml | 7 +- .../source-acceptance-tests-reference.md | 46 +++++++ 7 files changed, 247 insertions(+), 29 deletions(-) create mode 100644 airbyte-integrations/bases/source-acceptance-test/unit_tests/test_plugin.py diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index 389270e9a22c..e3dfba7c4c1d 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.13 +Fail tests in `high` `test_strictness_level` if all tests are not configured. [#18414](https://github.com/airbytehq/airbyte/pull/18414/). + ## 0.2.12 Declare `bypass_reason` field in test configuration. [#18364](https://github.com/airbytehq/airbyte/pull/18364). diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index 21c9e87828cc..d6c9ce15342e 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.12 +LABEL io.airbyte.version=0.2.13 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/base.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/base.py index f29e537cb8ee..bb7b6488c7f0 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/base.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/base.py @@ -5,6 +5,7 @@ import inflection import pytest +from source_acceptance_test.config import Config @pytest.mark.usefixtures("inputs") @@ -16,3 +17,5 @@ def config_key(cls): if class_name.startswith("Test"): class_name = class_name[len("Test") :] return inflection.underscore(class_name) + + MANDATORY_FOR_TEST_STRICTNESS_LEVELS = [Config.TestStrictnessLevel.high] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py index 74c432caa01a..52bf61f0366a 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/plugin.py @@ -3,12 +3,16 @@ # +from enum import Enum from pathlib import Path -from typing import List +from typing import Callable, List, Tuple, Type import pytest from _pytest.config import Config from _pytest.config.argparsing import Parser +from source_acceptance_test.base import BaseTest +from source_acceptance_test.config import Config as AcceptanceTestConfig +from source_acceptance_test.config import GenericTestConfig from source_acceptance_test.utils import diff_dicts, load_config HERE = Path(__file__).parent.absolute() @@ -34,36 +38,75 @@ def pytest_addoption(parser): ) +class TestAction(Enum): + PARAMETRIZE = 1 + SKIP = 2 + FAIL = 3 + + def pytest_generate_tests(metafunc): """Hook function to customize test discovery and parametrization. - It does two things: - 1. skip test class if its name omitted in the config file (or it has no inputs defined) - 2. parametrize each test with inputs from config file. - - For example config file contains this: - tests: - test_suite1: - - input1: value1 - input2: value2 - - input1: value3 - input2: value4 - test_suite2: [] - - Hook function will skip test_suite2 and test_suite3, but parametrize test_suite1 with two sets of inputs. + It parametrizes, skips or fails a discovered test according the test configuration. """ if "inputs" in metafunc.fixturenames: - config_key = metafunc.cls.config_key() - test_name = f"{metafunc.cls.__name__}.{metafunc.function.__name__}" - config = load_config(metafunc.config.getoption("--acceptance-test-config")) - if not hasattr(config.acceptance_tests, config_key) or not getattr(config.acceptance_tests, config_key): - pytest.skip(f"Skipping {test_name} because not found in the config") + test_config_key = metafunc.cls.config_key() + global_config = load_config(metafunc.config.getoption("--acceptance-test-config")) + test_configuration: GenericTestConfig = getattr(global_config.acceptance_tests, test_config_key, None) + test_action, reason = parametrize_skip_or_fail( + metafunc.cls, metafunc.function, global_config.test_strictness_level, test_configuration + ) + + if test_action == TestAction.PARAMETRIZE: + metafunc.parametrize("inputs", test_configuration.tests) + if test_action == TestAction.SKIP: + pytest.skip(reason) + if test_action == TestAction.FAIL: + pytest.fail(reason) + + +def parametrize_skip_or_fail( + TestClass: Type[BaseTest], + test_function: Callable, + global_test_mode: AcceptanceTestConfig.TestStrictnessLevel, + test_configuration: GenericTestConfig, +) -> Tuple[TestAction, str]: + """Use the current test strictness level and test configuration to determine if the discovered test should be parametrized, skipped or failed. + We parametrize a test if: + - the configuration declares tests. + We skip a test if: + - the configuration does not declare tests and: + - the current test mode allows this test to be skipped. + - Or a bypass_reason is declared in the test configuration. + We fail a test if: + - the configuration does not declare the test but the discovered test is declared as mandatory for the current test strictness level. + Args: + TestClass (Type[BaseTest]): The discovered test class + test_function (Callable): The discovered test function + global_test_mode (AcceptanceTestConfig.TestStrictnessLevel): The global test strictness level (from the global configuration object) + test_configuration (GenericTestConfig): The current test configuration. + + Returns: + Tuple[TestAction, str]: The test action the execution should take and the reason why. + """ + test_name = f"{TestClass.__name__}.{test_function.__name__}" + test_mode_can_skip_this_test = global_test_mode not in TestClass.MANDATORY_FOR_TEST_STRICTNESS_LEVELS + skipping_reason_prefix = f"Skipping {test_name}: " + default_skipping_reason = skipping_reason_prefix + "not found in the config." + + if test_configuration is None: + if test_mode_can_skip_this_test: + return TestAction.SKIP, default_skipping_reason else: - test_inputs = getattr(config.acceptance_tests, config_key).tests - if not test_inputs: - pytest.skip(f"Skipping {test_name} because no inputs provided") - - metafunc.parametrize("inputs", test_inputs) + return ( + TestAction.FAIL, + f"{test_name} failed: it was not configured but must be according to the current {global_test_mode} test strictness level.", + ) + else: + if test_configuration.tests is not None: + return TestAction.PARAMETRIZE, f"Parametrize {test_name}: tests are configured." + else: + return TestAction.SKIP, skipping_reason_prefix + test_configuration.bypass_reason def pytest_collection_modifyitems(config, items): diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_plugin.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_plugin.py new file mode 100644 index 000000000000..854cc782c64f --- /dev/null +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_plugin.py @@ -0,0 +1,122 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import pytest +from source_acceptance_test import config, plugin + +HIGH_TEST_STRICTNESS_LEVEL = config.Config.TestStrictnessLevel.high +LOW_TEST_STRICTNESS_LEVEL = config.Config.TestStrictnessLevel.low + +PARAMETRIZE_ACTION = plugin.TestAction.PARAMETRIZE +SKIP_ACTION = plugin.TestAction.SKIP +FAIL_ACTION = plugin.TestAction.FAIL + + +class MyTestClass: + def dumb_test_function(self): + assert 2 > 1 + + +@pytest.mark.parametrize( + "parametrize_skip_or_fail_return", + [(PARAMETRIZE_ACTION, "parametrize reason"), (SKIP_ACTION, "skip reason"), (FAIL_ACTION, "fail_reason")], +) +def test_pytest_generate_tests(mocker, parametrize_skip_or_fail_return): + test_config = config.Config( + connector_image="foo", + acceptance_tests=config.AcceptanceTestConfigurations(spec=config.GenericTestConfig(tests=[config.SpecTestConfig()])), + ) + mocker.patch.object(plugin.pytest, "skip") + mocker.patch.object(plugin.pytest, "fail") + mocker.patch.object(plugin, "parametrize_skip_or_fail", mocker.Mock(return_value=parametrize_skip_or_fail_return)) + mocker.patch.object(plugin, "load_config", mocker.Mock(return_value=test_config)) + metafunc_mock = mocker.Mock( + fixturenames=["inputs"], + function=mocker.Mock(__name__="test_function"), + cls=mocker.Mock(config_key=mocker.Mock(return_value="spec"), __name__="MyTest"), + ) + plugin.pytest_generate_tests(metafunc_mock) + action, reason = parametrize_skip_or_fail_return + if action == PARAMETRIZE_ACTION: + metafunc_mock.parametrize.assert_called_once_with("inputs", test_config.acceptance_tests.spec.tests) + if action == SKIP_ACTION: + plugin.pytest.skip.assert_called_once_with(reason) + if action == FAIL_ACTION: + plugin.pytest.fail.assert_called_once_with(reason) + + +@pytest.mark.parametrize( + "TestClass, test_class_MANDATORY_FOR_TEST_STRICTNESS_LEVELS, global_test_mode, test_configuration, expected_action, expected_reason", + [ + pytest.param( + MyTestClass, + (HIGH_TEST_STRICTNESS_LEVEL), + HIGH_TEST_STRICTNESS_LEVEL, + None, + FAIL_ACTION, + "MyTestClass.dumb_test_function failed: it was not configured but must be according to the current high test strictness level.", + id="Discovered test is mandatory in high test strictness level, we're in high test strictness level, it was not configured: FAIL", + ), + pytest.param( + MyTestClass, + (HIGH_TEST_STRICTNESS_LEVEL), + LOW_TEST_STRICTNESS_LEVEL, + None, + SKIP_ACTION, + "Skipping MyTestClass.dumb_test_function: not found in the config.", + id="Discovered test is mandatory in high test strictness level, we are in low strictness level, it is not configured: SKIP", + ), + pytest.param( + MyTestClass, + set(), + HIGH_TEST_STRICTNESS_LEVEL, + None, + SKIP_ACTION, + "Skipping MyTestClass.dumb_test_function: not found in the config.", + id="Discovered test is not mandatory in any test strictness level, it was not configured: SKIP", + ), + pytest.param( + MyTestClass, + (HIGH_TEST_STRICTNESS_LEVEL), + HIGH_TEST_STRICTNESS_LEVEL, + config.GenericTestConfig(bypass_reason="A good reason."), + SKIP_ACTION, + "Skipping MyTestClass.dumb_test_function: A good reason.", + id="Discovered test is mandatory in high test strictness level, a bypass reason was provided: SKIP", + ), + pytest.param( + MyTestClass, + (HIGH_TEST_STRICTNESS_LEVEL), + LOW_TEST_STRICTNESS_LEVEL, + config.GenericTestConfig(bypass_reason="A good reason."), + SKIP_ACTION, + "Skipping MyTestClass.dumb_test_function: A good reason.", + id="Discovered test is mandatory in high test strictness level, we are in low test strictness level, a bypass reason was provided: SKIP (with bypass reason shown)", + ), + pytest.param( + MyTestClass, + (HIGH_TEST_STRICTNESS_LEVEL), + HIGH_TEST_STRICTNESS_LEVEL, + config.GenericTestConfig(tests=[config.SpecTestConfig()]), + PARAMETRIZE_ACTION, + "Parametrize MyTestClass.dumb_test_function: tests are configured.", + id="[High test strictness level] Discovered test is configured: PARAMETRIZE", + ), + pytest.param( + MyTestClass, + (HIGH_TEST_STRICTNESS_LEVEL), + LOW_TEST_STRICTNESS_LEVEL, + config.GenericTestConfig(tests=[config.SpecTestConfig()]), + PARAMETRIZE_ACTION, + "Parametrize MyTestClass.dumb_test_function: tests are configured.", + id="[Low test strictness level] Discovered test is configured: PARAMETRIZE", + ), + ], +) +def test_parametrize_skip_or_fail( + TestClass, test_class_MANDATORY_FOR_TEST_STRICTNESS_LEVELS, global_test_mode, test_configuration, expected_action, expected_reason +): + TestClass.MANDATORY_FOR_TEST_STRICTNESS_LEVELS = test_class_MANDATORY_FOR_TEST_STRICTNESS_LEVELS + test_action, reason = plugin.parametrize_skip_or_fail(TestClass, TestClass.dumb_test_function, global_test_mode, test_configuration) + assert (test_action, reason) == (expected_action, expected_reason) diff --git a/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml b/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml index 6e648fb7f970..a98c1c1a9a9e 100644 --- a/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-pokeapi/acceptance-test-config.yml @@ -1,9 +1,8 @@ -# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) -# for more information about how to configure these tests connector_image: airbyte/source-pokeapi:dev +test_strictness_level: high acceptance_tests: spec: - bypass_reason: "TODO: activate this test!" + bypass_reason: "The spec is currently invalid: it has additionalProperties set to false" connection: tests: - config_path: "integration_tests/config.json" @@ -19,3 +18,5 @@ acceptance_tests: tests: - config_path: "integration_tests/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" + incremental: + bypass_reason: "This connector does not support incremental syncs." diff --git a/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md b/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md index 0aa513a396c5..f14f25767c1e 100644 --- a/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md +++ b/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md @@ -232,3 +232,49 @@ This test verifies that sync produces no records when run with the STATE with ab | `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | | `future_state_path` | string | None | Path to the state file with abnormally large cursor values | | `timeout_seconds` | int | 20\*60 | Test execution timeout in seconds | + + +## Strictness level + +To enforce maximal coverage of acceptances tests we expose a `test_strictness_level` field at the root of the `acceptance-test-config.yml` configuration. +The default `test_strictness_level` is `low`, but for generally available connectors it is expected to be eventually set to `high`. + +### Test enforcements in `high` test strictness level + +#### All acceptance tests are declared, a `bypass_reason` is filled if a test can't run +In `high` test strictness level we expect all acceptance tests to be declared: +* `spec` +* `connection` +* `discovery` +* `basic_read` +* `full_refresh` +* `incremental` + +If a test can't be run for a valid technical or organizational reason a `bypass_reason` can be declared to skip this test. +E.G. `source-pokeapi` does not support incremental syncs, we can skip this test when `test_strictness_level` is `high` by setting a `bypass_reason` under `incremental`. +```yaml +connector_image: "airbyte/source-pokeapi" +test_strictness_level: high +acceptance_tests: + spec: + tests: + - spec_path: "source_pokeapi/spec.json" + connection: + tests: + - config_path: "integration_tests/config.json" + status: "succeed" + discovery: + tests: + - config_path: "integration_tests/config.json" + basic_read: + tests: + - config_path: "integration_tests/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + full_refresh: + tests: + - config_path: "integration_tests/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + incremental: + bypass_reason: "Incremental syncs are not supported on this connector." +``` + From 066f19b84ceb85b2e56603643505cd03a149395d Mon Sep 17 00:00:00 2001 From: Malik Diarra Date: Tue, 25 Oct 2022 12:55:38 -0700 Subject: [PATCH 318/498] Refactor SourceHandler:buildSourceRead function (#18430) This function takes as input a `sourceId` and performs a database call to retrieve the full source information. This refactoring ensures that call sites that already have the full object can bypass that database call. This is especially important for methods that fetch and return a list of sources. --- .../server/handlers/SourceHandler.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java index 743c4b9e4f74..767e3e839759 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/SourceHandler.java @@ -100,16 +100,17 @@ public SourceRead createSource(final SourceCreate sourceCreate) spec); // read configuration from db - return buildSourceRead(sourceId, spec); + return buildSourceRead(configRepository.getSourceConnection(sourceId), spec); } public SourceRead updateSource(final SourceUpdate sourceUpdate) throws ConfigNotFoundException, IOException, JsonValidationException { + final UUID sourceId = sourceUpdate.getSourceId(); final SourceConnection updatedSource = configurationUpdate - .source(sourceUpdate.getSourceId(), sourceUpdate.getName(), + .source(sourceId, sourceUpdate.getName(), sourceUpdate.getConnectionConfiguration()); - final ConnectorSpecification spec = getSpecFromSourceId(updatedSource.getSourceId()); + final ConnectorSpecification spec = getSpecFromSourceId(sourceId); validateSource(spec, sourceUpdate.getConnectionConfiguration()); // persist @@ -123,7 +124,7 @@ public SourceRead updateSource(final SourceUpdate sourceUpdate) spec); // read configuration from db - return buildSourceRead(sourceUpdate.getSourceId(), spec); + return buildSourceRead(configRepository.getSourceConnection(sourceId), spec); } public SourceRead getSource(final SourceIdRequestBody sourceIdRequestBody) @@ -166,7 +167,7 @@ public SourceReadList listSourcesForWorkspace(final WorkspaceIdRequestBody works final List reads = Lists.newArrayList(); for (final SourceConnection sc : sourceConnections) { - reads.add(buildSourceRead(sc.getSourceId())); + reads.add(buildSourceRead(sc)); } return new SourceReadList().sources(reads); @@ -183,7 +184,7 @@ public SourceReadList listSourcesForSourceDefinition(final SourceDefinitionIdReq final List reads = Lists.newArrayList(); for (final SourceConnection sourceConnection : sourceConnections) { - reads.add(buildSourceRead(sourceConnection.getSourceId())); + reads.add(buildSourceRead(sourceConnection)); } return new SourceReadList().sources(reads); @@ -195,7 +196,7 @@ public SourceReadList searchSources(final SourceSearch sourceSearch) for (final SourceConnection sci : configRepository.listSourceConnection()) { if (!sci.getTombstone()) { - final SourceRead sourceRead = buildSourceRead(sci.getSourceId()); + final SourceRead sourceRead = buildSourceRead(sci); if (connectionsHandler.matchSearch(sourceSearch, sourceRead)) { reads.add(sourceRead); } @@ -242,15 +243,20 @@ public void deleteSource(final SourceRead source) private SourceRead buildSourceRead(final UUID sourceId) throws ConfigNotFoundException, IOException, JsonValidationException { // read configuration from db - final StandardSourceDefinition sourceDef = configRepository.getSourceDefinitionFromSource(sourceId); + final SourceConnection sourceConnection = configRepository.getSourceConnection(sourceId); + return buildSourceRead(sourceConnection); + } + + private SourceRead buildSourceRead(final SourceConnection sourceConnection) + throws ConfigNotFoundException, IOException, JsonValidationException { + final StandardSourceDefinition sourceDef = configRepository.getSourceDefinitionFromSource(sourceConnection.getSourceId()); final ConnectorSpecification spec = sourceDef.getSpec(); - return buildSourceRead(sourceId, spec); + return buildSourceRead(sourceConnection, spec); } - private SourceRead buildSourceRead(final UUID sourceId, final ConnectorSpecification spec) + private SourceRead buildSourceRead(final SourceConnection sourceConnection, final ConnectorSpecification spec) throws ConfigNotFoundException, IOException, JsonValidationException { // read configuration from db - final SourceConnection sourceConnection = configRepository.getSourceConnection(sourceId); final StandardSourceDefinition standardSourceDefinition = configRepository .getStandardSourceDefinition(sourceConnection.getSourceDefinitionId()); final JsonNode sanitizedConfig = secretsProcessor.prepareSecretsForOutput(sourceConnection.getConfiguration(), spec.getConnectionSpecification()); From 56df0579db8f425b90c40e34b9dfb171f79c31fe Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 25 Oct 2022 13:18:06 -0700 Subject: [PATCH 319/498] Make YouTube login button Google styled (#18443) --- .../ServiceForm/components/Sections/auth/AuthButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx index b87f9480241e..b2ad1869d7eb 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Sections/auth/AuthButton.tsx @@ -22,6 +22,7 @@ function isGoogleConnector(connectorDefinitionId: string): boolean { "71607ba1-c0ac-4799-8049-7f4b90dd50f7", // google sheets source "a4cbd2d1-8dbe-4818-b8bc-b90ad782d12a", // google sheets destination "ed9dfefa-1bbc-419d-8c5e-4d78f0ef6734", // google workspace admin reports + "afa734e4-3571-11ec-991a-1e0031268139", // YouTube analytics ].includes(connectorDefinitionId); } From f83fd5b0af52525bd5e19ac6538fc3c0419d3557 Mon Sep 17 00:00:00 2001 From: Sophia Wiley <106352739+sophia-wiley@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:24:13 -0500 Subject: [PATCH 320/498] Edited Notion Doc (#18405) * edited doc * some wording changes * edited step --- docs/integrations/sources/notion.md | 117 +++++++++++++++------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/docs/integrations/sources/notion.md b/docs/integrations/sources/notion.md index 10f97cb37b3d..c4d2a8bbcc39 100644 --- a/docs/integrations/sources/notion.md +++ b/docs/integrations/sources/notion.md @@ -1,75 +1,80 @@ # Notion -Notion is a productivity and project management software. It was designed to help organizations coordinate deadlines, objectives, and assignments. - -## Prerequisites -* Created Notion account with integration on [my integrations](https://www.notion.so/my-integrations) page or an account that is the owner of a workspace. - -## Airbyte Open Source -* Start Date -* Token (received when integration was created). - -## Airbyte Cloud -* Start Date -* Client ID (received when integration was created). -* Client Secret (received when integration was created). - -## Setup guide -### Step 1: Set up Notion - -1. Create account on Notion by following link [signup](https://www.notion.so/signup) -2. Login to your Notion account and go to [my integrations](https://www.notion.so/my-integrations) page. -3. Create a **new integration**. You must be the owner of a workspace to create a new integration. Make sure to check the `Read content` capability. -4. Check the appropriate user capability depending on your use case. -5. Check the settings **Integration type** and select **Public** (OAuth2.0 authentication) or **Internal** (Token authorization) integration -6. If you select Public integration you need to go to the opened section **OAuth Domain & URIs** and fill all fields of form you've received. -7. Click `Submit`. -8. Copy the **access_token** or **client_id** and **client_secret** from the next screen depending on selected authentication method. - -## Step 2: Set up the Notion connector in Airbyte - -### For Airbyte Cloud: - -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+ new source**. -3. On the source setup page, select **Notion** from the Source type dropdown and enter a name for this connector. -4. Add required Start date -5. Choose the method of authentication -6. If you select Token authentication - fill the field **token** with **access_token** in setup Notion step (8) -7. If you select OAuth2.0 authorization - Click `Authenticate your Notion account`. -8. Log in and Authorize the Notion account. Select the pages you wish to allow Airbyte to access. -10. Click `Set up source`. - -### For Airbyte Open Source: -1. Go to local Airbyte page. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+ new source**. -3. On the Set up the source page, enter the name for the connector and select **Notion** from the Source type dropdown. -4. Add required Start date -5. Copy and paste values from setup Notion step (8): - 1) **client_id** - 2) **client_secret** -7. Click `Set up source`. +This page contains the setup guide and reference information for the Notion source connector. + +## Setup guide​ + +### Step 1: Set up Notion​ + +1. Create a new integration on the [My integrations](https://www.notion.so/my-integrations) page. + +:::note + +You must be the owner of a Notion workspace to create a new integration. + +::: + +2. Fill out the form. Make sure to check **Read content** and check any other [capabilities](https://developers.notion.com/reference/capabilities) you want to authorize. +3. Click **Submit**. +4. In the **Integration type** section, select either **Internal integration** (token authorization) or **Public integration** (OAuth2.0 authentication). +5. Check the capabilities you want to authorize. +6. If you select **Public integration**, fill out the fields in the **OAuth Domain & URIs** section. +7. Click **Save changes**. +8. Copy the Internal Access Token if you are using the [internal integration](https://developers.notion.com/docs/authorization#authorizing-internal-integrations), or copy the `access_token`, `client_id`, and `client_secret` if you are using the [public integration](https://developers.notion.com/docs/authorization#authorizing-public-integrations). + +### Step 2: Set up the Notion connector in Airbyte + +#### For Airbyte Cloud + +1. Log in to your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Notion** from the **Source type** dropdown. +4. Enter a name for your source. +5. Choose the method of authentication: + * If you select **Access Token**, paste the access token from [Step 8](#step-1-set-up-notion​). + * If you select **OAuth2.0** authorization, click **Authenticate your Notion account**. + * Log in and Authorize the Notion account. Select the permissions you want to allow Airbyte. +6. Enter the **Start Date** in YYYY-MM-DDT00:00:00Z format. All data generated after this date will be replicated. If this field is blank, Airbyte will replicate all data. +7. Click **Set up source**. + +#### For Airbyte Open Source + +1. Log in to your Airbyte Open Source account. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Notion** from the **Source type** dropdown. +4. Enter a name for your source. +5. Choose the method of authentication: + * If you select **Access Token**, paste the access token from [Step 8](#step-1-set-up-notion​). + * If you select **OAuth2.0** authorization, paste the client ID, access token, and client secret from [Step 8](#step-1-set-up-notion​). +6. Enter the **Start Date** in YYYY-MM-DDT00:00:00Z format. All data generated after this date will be replicated. If this field is blank, Airbyte will replicate all data. +7. Click **Set up source**. ## Supported sync modes The Notion source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): - - Full Refresh - - Incremental (partially) +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) (partially) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams +The Notion source connector supports the following streams. For more information, see the [Notion API](https://developers.notion.com/reference/intro). + * [blocks](https://developers.notion.com/reference/retrieve-a-block) * [databases](https://developers.notion.com/reference/retrieve-a-database) * [pages](https://developers.notion.com/reference/retrieve-a-page) -* [users](https://developers.notion.com/reference/retrieve-a-get-users) (this stream does not support **Incremental** - _Append Sync_ mode) +* [users](https://developers.notion.com/reference/get-user) + +:::note -For more information, see the [Notion API](https://developers.notion.com/reference/intro). +The users stream does not support Incremental - Append sync mode. -## Performance considerations +::: -The connector is restricted by normal Notion [rate limits and size limits](https://developers.notion.com/reference/errors#request-limits). +## Performance considerations -The Notion connector should not run into Notion API limitations under normal usage. Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +The connector is restricted by Notion [request limits](https://developers.notion.com/reference/request-limits). The Notion connector should not run into Notion API limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. ## Changelog From 726d3bde4d7baade97f8ce7f5abab93c9347d2c7 Mon Sep 17 00:00:00 2001 From: Augustin Date: Tue, 25 Oct 2022 22:31:16 +0200 Subject: [PATCH 321/498] SAT: enforce `bypass_reason` declaration on `empty_streams` when `test_strictness_level == high` (#18425) --- .../bases/source-acceptance-test/CHANGELOG.md | 3 + .../bases/source-acceptance-test/Dockerfile | 2 +- .../source_acceptance_test/config.py | 17 ++- .../source_acceptance_test/tests/test_core.py | 15 +- .../unit_tests/test_core.py | 78 +++++++++-- .../source-acceptance-tests-reference.md | 132 +++++++++++------- 6 files changed, 175 insertions(+), 72 deletions(-) diff --git a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md index e3dfba7c4c1d..8db3b2cf26bf 100644 --- a/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md +++ b/airbyte-integrations/bases/source-acceptance-test/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.2.14 +Fail basic read in `high` `test_strictness_level` if no `bypass_reason` is set on empty_streams. [#18425](https://github.com/airbytehq/airbyte/pull/18425/). + ## 0.2.13 Fail tests in `high` `test_strictness_level` if all tests are not configured. [#18414](https://github.com/airbytehq/airbyte/pull/18414/). diff --git a/airbyte-integrations/bases/source-acceptance-test/Dockerfile b/airbyte-integrations/bases/source-acceptance-test/Dockerfile index d6c9ce15342e..92171914db04 100644 --- a/airbyte-integrations/bases/source-acceptance-test/Dockerfile +++ b/airbyte-integrations/bases/source-acceptance-test/Dockerfile @@ -33,7 +33,7 @@ COPY pytest.ini setup.py ./ COPY source_acceptance_test ./source_acceptance_test RUN pip install . -LABEL io.airbyte.version=0.2.13 +LABEL io.airbyte.version=0.2.14 LABEL io.airbyte.name=airbyte/source-acceptance-test ENTRYPOINT ["python", "-m", "pytest", "-p", "source_acceptance_test.plugin", "-r", "fEsx"] diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py index f73d81c0de12..a172c2ebab90 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/config.py @@ -93,10 +93,20 @@ def validate_extra_records(cls, extra_records, values): return extra_records +class EmptyStreamConfiguration(BaseConfig): + name: str + bypass_reason: Optional[str] = Field(default=None, description="Reason why this stream is considered empty.") + + def __hash__(self): # make it hashable + return hash((type(self),) + tuple(self.__dict__.values())) + + class BasicReadTestConfig(BaseConfig): config_path: str = config_path configured_catalog_path: Optional[str] = configured_catalog_path - empty_streams: Set[str] = Field(default_factory=set, description="We validate that all streams has records. These are exceptions") + empty_streams: Set[EmptyStreamConfiguration] = Field( + default_factory=set, description="We validate that all streams has records. These are exceptions" + ) expect_records: Optional[ExpectedRecordsConfig] = Field(description="Expected records from the read") validate_schema: bool = Field(True, description="Ensure that records match the schema of the corresponding stream") # TODO: remove this field after https://github.com/airbytehq/airbyte/issues/8312 is done @@ -206,6 +216,11 @@ def migrate_legacy_to_current_config(legacy_config: dict) -> dict: migrated_config["acceptance_tests"] = {} for test_name, test_configs in legacy_config["tests"].items(): migrated_config["acceptance_tests"][test_name] = {"tests": test_configs} + for basic_read_tests in migrated_config["acceptance_tests"].get("basic_read", {}).get("tests", []): + if "empty_streams" in basic_read_tests: + basic_read_tests["empty_streams"] = [ + {"name": empty_stream_name} for empty_stream_name in basic_read_tests.get("empty_streams", []) + ] return migrated_config @root_validator(pre=True) diff --git a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py index 3cdfc2987d7f..e8c3cae89639 100644 --- a/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py +++ b/airbyte-integrations/bases/source-acceptance-test/source_acceptance_test/tests/test_core.py @@ -27,7 +27,7 @@ from docker.errors import ContainerError from jsonschema._utils import flatten from source_acceptance_test.base import BaseTest -from source_acceptance_test.config import BasicReadTestConfig, ConnectionTestConfig, DiscoveryTestConfig, SpecTestConfig +from source_acceptance_test.config import BasicReadTestConfig, Config, ConnectionTestConfig, DiscoveryTestConfig, SpecTestConfig from source_acceptance_test.utils import ConnectorRunner, SecretDict, filter_output, make_hashable, verify_records_schema from source_acceptance_test.utils.backward_compatibility import CatalogDiffChecker, SpecDiffChecker, validate_previous_configs from source_acceptance_test.utils.common import find_all_values_for_key_in_schema, find_keyword_schema @@ -385,13 +385,14 @@ def _validate_empty_streams(self, records, configured_catalog, allowed_empty_str """ Only certain streams allowed to be empty """ + allowed_empty_stream_names = set([allowed_empty_stream.name for allowed_empty_stream in allowed_empty_streams]) counter = Counter(record.stream for record in records) all_streams = set(stream.stream.name for stream in configured_catalog.streams) streams_with_records = set(counter.keys()) streams_without_records = all_streams - streams_with_records - streams_without_records = streams_without_records - allowed_empty_streams + streams_without_records = streams_without_records - allowed_empty_stream_names assert not streams_without_records, f"All streams should return some records, streams without records: {streams_without_records}" def _validate_field_appears_at_least_once_in_stream(self, records: List, schema: Dict): @@ -466,7 +467,9 @@ def test_read( expected_records: List[AirbyteRecordMessage], docker_runner: ConnectorRunner, detailed_logger, + test_strictness_level: Config.TestStrictnessLevel, ): + self.enforce_strictness_level(test_strictness_level, inputs) output = docker_runner.call_read(connector_config, configured_catalog) records = [message.record for message in filter_output(output, Type.RECORD)] @@ -578,3 +581,11 @@ def group_by_stream(records: List[AirbyteRecordMessage]) -> MutableMapping[str, result[record.stream].append(record.data) return result + + @staticmethod + def enforce_strictness_level(test_strictness_level: Config.TestStrictnessLevel, inputs: BasicReadTestConfig): + if test_strictness_level is Config.TestStrictnessLevel.high: + if inputs.empty_streams: + all_empty_streams_have_bypass_reasons = all([bool(empty_stream.bypass_reason) for empty_stream in inputs.empty_streams]) + if not all_empty_streams_have_bypass_reasons: + pytest.fail("A bypass_reason must be filled in for all empty streams when test_strictness_level is set to high.") diff --git a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py index eec175467915..2b6b43823764 100644 --- a/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py +++ b/airbyte-integrations/bases/source-acceptance-test/unit_tests/test_core.py @@ -20,7 +20,8 @@ TraceType, Type, ) -from source_acceptance_test.config import BasicReadTestConfig +from source_acceptance_test.config import BasicReadTestConfig, Config, EmptyStreamConfiguration +from source_acceptance_test.tests import test_core from source_acceptance_test.tests.test_core import TestBasicRead as _TestBasicRead from source_acceptance_test.tests.test_core import TestDiscovery as _TestDiscovery @@ -236,22 +237,30 @@ def test_additional_properties_is_true(discovered_catalog, expectation): @pytest.mark.parametrize( - "schema, record, should_fail", + "schema, record, expectation", [ - ({"type": "object"}, {"aa": 23}, False), - ({"type": "object"}, {}, False), - ({"type": "object", "properties": {"created": {"type": "string"}}}, {"aa": 23}, True), - ({"type": "object", "properties": {"created": {"type": "string"}}}, {"created": "23"}, False), - ({"type": "object", "properties": {"created": {"type": "string"}}}, {"root": {"created": "23"}}, True), + ({"type": "object"}, {"aa": 23}, does_not_raise()), + ({"type": "object"}, {}, does_not_raise()), + ( + {"type": "object", "properties": {"created": {"type": "string"}}}, + {"aa": 23}, + pytest.raises(AssertionError, match="should have some fields mentioned by json schema"), + ), + ({"type": "object", "properties": {"created": {"type": "string"}}}, {"created": "23"}, does_not_raise()), + ( + {"type": "object", "properties": {"created": {"type": "string"}}}, + {"root": {"created": "23"}}, + pytest.raises(AssertionError, match="should have some fields mentioned by json schema"), + ), # Recharge shop stream case ( {"type": "object", "properties": {"shop": {"type": ["null", "object"]}, "store": {"type": ["null", "object"]}}}, {"shop": {"a": "23"}, "store": {"b": "23"}}, - False, + does_not_raise(), ), ], ) -def test_read(schema, record, should_fail): +def test_read(mocker, schema, record, expectation): catalog = ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( @@ -267,11 +276,10 @@ def test_read(schema, record, should_fail): AirbyteMessage(type=Type.RECORD, record=AirbyteRecordMessage(stream="test_stream", data=record, emitted_at=111)) ] t = _TestBasicRead() - if should_fail: - with pytest.raises(AssertionError, match="should have some fields mentioned by json schema"): - t.test_read(None, catalog, input_config, [], docker_runner_mock, MagicMock()) - else: - t.test_read(None, catalog, input_config, [], docker_runner_mock, MagicMock()) + t.enforce_strictness_level = mocker.Mock() + with expectation: + t.test_read(None, catalog, input_config, [], docker_runner_mock, MagicMock(), Config.TestStrictnessLevel.low) + t.enforce_strictness_level.assert_called_with(Config.TestStrictnessLevel.low, input_config) @pytest.mark.parametrize( @@ -836,3 +844,45 @@ def test_validate_field_appears_at_least_once(records, configured_catalog, expec t._validate_field_appears_at_least_once(records=records, configured_catalog=configured_catalog) else: t._validate_field_appears_at_least_once(records=records, configured_catalog=configured_catalog) + + +@pytest.mark.parametrize( + "test_strictness_level, basic_read_test_config, expect_test_failure", + [ + pytest.param( + Config.TestStrictnessLevel.low, + BasicReadTestConfig(config_path="config_path", empty_streams={EmptyStreamConfiguration(name="my_empty_stream")}), + False, + id="[LOW test strictness level] Empty streams can be declared without bypass_reason.", + ), + pytest.param( + Config.TestStrictnessLevel.low, + BasicReadTestConfig( + config_path="config_path", empty_streams={EmptyStreamConfiguration(name="my_empty_stream", bypass_reason="good reason")} + ), + False, + id="[LOW test strictness level] Empty streams can be declared with a bypass_reason.", + ), + pytest.param( + Config.TestStrictnessLevel.high, + BasicReadTestConfig(config_path="config_path", empty_streams={EmptyStreamConfiguration(name="my_empty_stream")}), + True, + id="[HIGH test strictness level] Empty streams can't be declared without bypass_reason.", + ), + pytest.param( + Config.TestStrictnessLevel.high, + BasicReadTestConfig( + config_path="config_path", empty_streams={EmptyStreamConfiguration(name="my_empty_stream", bypass_reason="good reason")} + ), + False, + id="[HIGH test strictness level] Empty streams can be declared with a bypass_reason.", + ), + ], +) +def test_enforce_strictness_level(mocker, test_strictness_level, basic_read_test_config, expect_test_failure): + mocker.patch.object(test_core, "pytest") + assert _TestBasicRead.enforce_strictness_level(test_strictness_level, basic_read_test_config) is None + if expect_test_failure: + test_core.pytest.fail.assert_called_once() + else: + test_core.pytest.fail.assert_not_called() diff --git a/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md b/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md index f14f25767c1e..68c231abf9a3 100644 --- a/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md +++ b/docs/connector-development/testing-connectors/source-acceptance-tests-reference.md @@ -102,22 +102,22 @@ Verify that a `spec` operation issued to the connector returns a valid connector Additional tests are validating the backward compatibility of the current specification compared to the specification of the previous connector version. If no previous connector version is found (by default the test looks for a docker image with the same name but with the `latest` tag), this test is skipped. These backward compatibility tests can be bypassed by changing the value of the `backward_compatibility_tests_config.disable_for_version` input in `acceptance-test-config.yml` (see below). -| Input | Type | Default | Note | -| :--- | :--- | :--- |:-------------------------------------------------------------------------------------------------| -| `spec_path` | string | `secrets/spec.json` | Path to a YAML or JSON file representing the spec expected to be output by this connector | -| `backward_compatibility_tests_config.previous_connector_version` | string | `latest` | Previous connector version to use for backward compatibility tests (expects a version following semantic versioning). | -| `backward_compatibility_tests_config.disable_for_version` | string | None | Disable the backward compatibility test for a specific version (expects a version following semantic versioning). | -| `timeout_seconds` | int | 10 | Test execution timeout in seconds | +| Input | Type | Default | Note | +| :--------------------------------------------------------------- | :----- | :------------------ | :-------------------------------------------------------------------------------------------------------------------- | +| `spec_path` | string | `secrets/spec.json` | Path to a YAML or JSON file representing the spec expected to be output by this connector | +| `backward_compatibility_tests_config.previous_connector_version` | string | `latest` | Previous connector version to use for backward compatibility tests (expects a version following semantic versioning). | +| `backward_compatibility_tests_config.disable_for_version` | string | None | Disable the backward compatibility test for a specific version (expects a version following semantic versioning). | +| `timeout_seconds` | int | 10 | Test execution timeout in seconds | ## Test Connection Verify that a check operation issued to the connector with the input config file returns a successful response. -| Input | Type | Default | Note | -| :--- | :--- | :--- | :--- | -| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | -| `status` | `succeed` `failed` `exception` | | Indicate if connection check should succeed with provided config | -| `timeout_seconds` | int | 30 | Test execution timeout in seconds | +| Input | Type | Default | Note | +| :---------------- | :----------------------------- | :-------------------- | :----------------------------------------------------------------- | +| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | +| `status` | `succeed` `failed` `exception` | | Indicate if connection check should succeed with provided config | +| `timeout_seconds` | int | 30 | Test execution timeout in seconds | ## Test Discovery @@ -125,43 +125,45 @@ Verifies when a `discover` operation is run on the connector using the given con Additional tests are validating the backward compatibility of the discovered catalog compared to the catalog of the previous connector version. If no previous connector version is found (by default the test looks for a docker image with the same name but with the `latest` tag), this test is skipped. These backward compatibility tests can be bypassed by changing the value of the `backward_compatibility_tests_config.disable_for_version` input in `acceptance-test-config.yml` (see below). -| Input | Type | Default | Note | -| :--- | :--- | :--- | :--- | -| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | -| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | -| `timeout_seconds` | int | 30 | Test execution timeout in seconds | -| `backward_compatibility_tests_config.previous_connector_version` | string | `latest` | Previous connector version to use for backward compatibility tests (expects a version following semantic versioning). | -| `backward_compatibility_tests_config.disable_for_version` | string | None | Disable the backward compatibility test for a specific version (expects a version following semantic versioning). | +| Input | Type | Default | Note | +| :--------------------------------------------------------------- | :----- | :------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------- | +| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | +| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | +| `timeout_seconds` | int | 30 | Test execution timeout in seconds | +| `backward_compatibility_tests_config.previous_connector_version` | string | `latest` | Previous connector version to use for backward compatibility tests (expects a version following semantic versioning). | +| `backward_compatibility_tests_config.disable_for_version` | string | None | Disable the backward compatibility test for a specific version (expects a version following semantic versioning). | ## Test Basic Read Configuring all streams in the input catalog to full refresh mode verifies that a read operation produces some RECORD messages. Each stream should have some data, if you can't guarantee this for particular streams - add them to the `empty_streams` list. Set `validate_data_points=True` if possible. This validation is going to be enabled by default and won't be configurable in future releases. -| Input | Type | Default | Note | -|:----------------------------------| :--- | :--- | :--- | -| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | -| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | -| `empty_streams` | array | \[\] | List of streams that might be empty | -| `validate_schema` | boolean | True | Verify that structure and types of records matches the schema from discovery command | -| `validate_data_points` | boolean | False | Validate that all fields in all streams contained at least one data point | -| `timeout_seconds` | int | 5\*60 | Test execution timeout in seconds | -| `expect_trace_message_on_failure` | boolean | True | Ensure that a trace message is emitted when the connector crashes | -| `expect_records` | object | None | Compare produced records with expected records, see details below | -| `expect_records.path` | string | | File with expected records | -| `expect_records.extra_fields` | boolean | False | Allow output records to have other fields i.e: expected records are a subset | -| `expect_records.exact_order` | boolean | False | Ensure that records produced in exact same order | -| `expect_records.extra_records` | boolean | True | Allow connector to produce extra records, but still enforce all records from the expected file to be produced | +| Input | Type | Default | Note | +| :-------------------------------- | :--------------- | :------------------------------------------ | :------------------------------------------------------------------------------------------------------------ | +| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | +| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | +| `empty_streams` | array of objects | \[\] | List of streams that might be empty with a `bypass_reason` | +| `empty_streams[0].name` | string | | Name of the empty stream | +| `empty_streams[0].bypass_reason` | string | None | Reason why this stream is empty | +| `validate_schema` | boolean | True | Verify that structure and types of records matches the schema from discovery command | +| `validate_data_points` | boolean | False | Validate that all fields in all streams contained at least one data point | +| `timeout_seconds` | int | 5\*60 | Test execution timeout in seconds | +| `expect_trace_message_on_failure` | boolean | True | Ensure that a trace message is emitted when the connector crashes | +| `expect_records` | object | None | Compare produced records with expected records, see details below | +| `expect_records.path` | string | | File with expected records | +| `expect_records.extra_fields` | boolean | False | Allow output records to have other fields i.e: expected records are a subset | +| `expect_records.exact_order` | boolean | False | Ensure that records produced in exact same order | +| `expect_records.extra_records` | boolean | True | Allow connector to produce extra records, but still enforce all records from the expected file to be produced | `expect_records` is a nested configuration, if omitted - the part of the test responsible for record matching will be skipped. Due to the fact that we can't identify records without primary keys, only the following flag combinations are supported: | extra\_fields | exact\_order | extra\_records | -| :--- | :--- | :--- | -| x | x | | -| | x | x | -| | x | | -| | | x | -| | | | +| :------------ | :----------- | :------------- | +| x | x | | +| | x | x | +| | x | | +| | | x | +| | | | ### Schema format checking @@ -186,12 +188,12 @@ In general, the expected\_records.json should contain the subset of output of th This test performs two read operations on all streams which support full refresh syncs. It then verifies that the RECORD messages output from both were identical or the former is a strict subset of the latter. -| Input | Type | Default | Note | -| :--- | :--- | :--- | :--- | -| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | -| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | -| `timeout_seconds` | int | 20\*60 | Test execution timeout in seconds | -| `ignored_fields` | dict | None |For each stream, list of fields path ignoring in sequential reads test| +| Input | Type | Default | Note | +| :------------------------ | :----- | :------------------------------------------ | :--------------------------------------------------------------------- | +| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | +| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | +| `timeout_seconds` | int | 20\*60 | Test execution timeout in seconds | +| `ignored_fields` | dict | None | For each stream, list of fields path ignoring in sequential reads test | ## Test Incremental sync @@ -200,7 +202,7 @@ This test performs two read operations on all streams which support full refresh This test verifies that all streams in the input catalog which support incremental sync can do so correctly. It does this by running two read operations: the first takes the configured catalog and config provided to this test as input. It then verifies that the sync produced a non-zero number of `RECORD` and `STATE` messages. The second read takes the same catalog and config used in the first test, plus the last `STATE` message output by the first read operation as the input state file. It verifies that either no records are produced \(since we read all records in the first sync\) or all records that produced have cursor value greater or equal to cursor value from `STATE` message. This test is performed only for streams that support incremental. Streams that do not support incremental sync are ignored. If no streams in the input catalog support incremental sync, this test is skipped. | Input | Type | Default | Note | -|:--------------------------|:-------|:--------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| :------------------------ | :----- | :------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | | `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | | `cursor_paths` | dict | {} | For each stream, the path of its cursor field in the output state messages. If omitted the path will be taken from the last piece of path from stream cursor\_field. | @@ -211,14 +213,14 @@ This test verifies that all streams in the input catalog which support increment This test offers more comprehensive verification that all streams in the input catalog which support incremental syncs perform the sync correctly. It does so in two phases. The first phase uses the configured catalog and config provided to this test as input to make a request to the partner API and assemble the complete set of messages to be synced. It then verifies that the sync produced a non-zero number of `RECORD` and `STATE` messages. This set of messages is partitioned into batches of a `STATE` message followed by zero or more `RECORD` messages. For each batch of messages, the initial `STATE` message is used as input for a read operation to get records with respect to the cursor. The test then verifies that all of the `RECORDS` retrieved have a cursor value greater or equal to the cursor from the current `STATE` message. This test is performed only for streams that support incremental. Streams that do not support incremental sync are ignored. If no streams in the input catalog support incremental sync, this test is skipped. -| Input | Type | Default | Note | -|:--------------------------|:-------|:--------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Input | Type | Default | Note | +| :------------------------------------- | :----- | :------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | | `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | | `cursor_paths` | dict | {} | For each stream, the path of its cursor field in the output state messages. If omitted the path will be taken from the last piece of path from stream cursor\_field. | | `timeout_seconds` | int | 20\*60 | Test execution timeout in seconds | | `threshold_days` | int | 0 | For date-based cursors, allow records to be emitted with a cursor value this number of days before the state value. | -| `skip_comprehensive_incremental_tests` | bool | false | For non-GA and in-development connectors, control whether the more comprehensive incremental tests will be skipped | +| `skip_comprehensive_incremental_tests` | bool | false | For non-GA and in-development connectors, control whether the more comprehensive incremental tests will be skipped | **Note that this test samples a fraction of stream slices across an incremental sync in order to reduce test duration and avoid spamming partner APIs** @@ -226,12 +228,12 @@ This test offers more comprehensive verification that all streams in the input c This test verifies that sync produces no records when run with the STATE with abnormally large values -| Input | Type | Default | Note | | -|:--------------------------|:-------|:--------|:-----|:--------------------------------------------------------------------------| -| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | -| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | -| `future_state_path` | string | None | Path to the state file with abnormally large cursor values | -| `timeout_seconds` | int | 20\*60 | Test execution timeout in seconds | +| Input | Type | Default | Note | | +| :------------------------ | :----- | :------------------------------------------ | :----------------------------------------------------------------- | :-- | +| `config_path` | string | `secrets/config.json` | Path to a JSON object representing a valid connector configuration | | +| `configured_catalog_path` | string | `integration_tests/configured_catalog.json` | Path to configured catalog | | +| `future_state_path` | string | None | Path to the state file with abnormally large cursor values | | +| `timeout_seconds` | int | 20\*60 | Test execution timeout in seconds | | ## Strictness level @@ -278,3 +280,25 @@ acceptance_tests: bypass_reason: "Incremental syncs are not supported on this connector." ``` +#### Basic read: no empty streams are allowed without a `bypass_reason` +In `high` test strictness level we expect that all streams declared in `empty-streams` to have a `bypass_reason` filled in. + + +E.G. Two streams from `source-recharge` can't be seeded with test data, they are declared as `empty_stream` we an explicit bypass reason. + +```yaml +connector_image: airbyte/source-recharge:dev +test_strictness_level: high +acceptance_tests: + basic_read: + tests: + - config_path: secrets/config.json + configured_catalog_path: integration_tests/streams_with_output_records_catalog.json + empty_streams: + - name: collections + bypass_reason: "This stream can't be seeded in our sandbox account" + - name: discounts + bypass_reason: "This stream can't be seeded in our sandbox account" + timeout_seconds: 1200 +... +``` From 434a556dd8fc950e866396a141abb188e96badec Mon Sep 17 00:00:00 2001 From: Benoit Moriceau Date: Tue, 25 Oct 2022 14:23:47 -0700 Subject: [PATCH 322/498] Fix the tag in openAPI (#18445) * Fix the tag * remove unused --- .../airbyte/api/client/AirbyteApiClient.java | 8 +- airbyte-api/src/main/openapi/config.yaml | 8 +- .../utils/AirbyteAcceptanceTestHarness.java | 4 +- .../test/acceptance/BasicAcceptanceTests.java | 40 +- .../test/acceptance/CdcAcceptanceTests.java | 16 +- .../sync/PersistStateActivityImpl.java | 4 +- .../sync/PersistStateActivityTest.java | 14 +- .../api/generated-api-html/index.html | 450 +++++++++--------- 8 files changed, 278 insertions(+), 266 deletions(-) diff --git a/airbyte-api/src/main/java/io/airbyte/api/client/AirbyteApiClient.java b/airbyte-api/src/main/java/io/airbyte/api/client/AirbyteApiClient.java index bffc7c276ddb..89c08ff3a45c 100644 --- a/airbyte-api/src/main/java/io/airbyte/api/client/AirbyteApiClient.java +++ b/airbyte-api/src/main/java/io/airbyte/api/client/AirbyteApiClient.java @@ -16,6 +16,7 @@ import io.airbyte.api.client.generated.SourceApi; import io.airbyte.api.client.generated.SourceDefinitionApi; import io.airbyte.api.client.generated.SourceDefinitionSpecificationApi; +import io.airbyte.api.client.generated.StateApi; import io.airbyte.api.client.generated.WorkspaceApi; import io.airbyte.api.client.invoker.generated.ApiClient; @@ -46,8 +47,8 @@ public class AirbyteApiClient { private final WorkspaceApi workspaceApi; private final HealthApi healthApi; private final DbMigrationApi dbMigrationApi; - private final AttemptApi attemptApi; + private final StateApi stateApi; public AirbyteApiClient(final ApiClient apiClient) { connectionApi = new ConnectionApi(apiClient); @@ -64,6 +65,7 @@ public AirbyteApiClient(final ApiClient apiClient) { healthApi = new HealthApi(apiClient); dbMigrationApi = new DbMigrationApi(apiClient); attemptApi = new AttemptApi(apiClient); + stateApi = new StateApi(apiClient); } public ConnectionApi getConnectionApi() { @@ -122,4 +124,8 @@ public AttemptApi getAttemptApi() { return attemptApi; } + public StateApi getStateApi() { + return stateApi; + } + } diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index baa488de77df..f7b574f1e96b 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -63,6 +63,8 @@ tags: description: Export/Import Airbyte Configuration and Database resources. - name: attempt description: Interactions with attempt related resources. + - name: state + description: Interactions with state related resources. paths: /v1/workspaces/create: @@ -1389,7 +1391,7 @@ paths: /v1/state/get: post: tags: - - connection + - state summary: Fetch the current state for a connection. operationId: getState requestBody: @@ -1412,7 +1414,7 @@ paths: /v1/state/create_or_update: post: tags: - - connection + - state - internal summary: Create or update the state for a connection. operationId: createOrUpdateState @@ -1994,7 +1996,7 @@ paths: /v1/web_backend/state/get_type: post: tags: - - connection + - web_backend summary: Fetch the current state type for a connection. operationId: getStateType requestBody: diff --git a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java index 761ef8f63a83..433f7e82ab4e 100644 --- a/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java +++ b/airbyte-test-utils/src/main/java/io/airbyte/test/utils/AirbyteAcceptanceTestHarness.java @@ -797,11 +797,11 @@ public static JobRead waitWhileJobHasStatus(final JobsApi jobsApi, @SuppressWarnings("BusyWait") public static ConnectionState waitForConnectionState(final AirbyteApiClient apiClient, final UUID connectionId) throws ApiException, InterruptedException { - ConnectionState connectionState = apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); + ConnectionState connectionState = apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); int count = 0; while (count < 60 && (connectionState.getState() == null || connectionState.getState().isNull())) { LOGGER.info("fetching connection state. attempt: {}", count++); - connectionState = apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); + connectionState = apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); sleep(1000); } return connectionState; diff --git a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java index b1584e3f2c0b..4f9baa0bc91b 100644 --- a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java +++ b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/BasicAcceptanceTests.java @@ -611,7 +611,7 @@ void testIncrementalSync() throws Exception { final JobInfoRead connectionSyncRead1 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead1.getJob()); - LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -631,7 +631,7 @@ void testIncrementalSync() throws Exception { final JobInfoRead connectionSyncRead2 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead2.getJob()); - LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertRawDestinationContains(expectedRecords, new SchemaTableNamePair(PUBLIC, STREAM_NAME)); @@ -642,7 +642,7 @@ void testIncrementalSync() throws Exception { waitWhileJobHasStatus(apiClient.getJobsApi(), jobInfoRead.getJob(), Sets.newHashSet(JobStatus.PENDING, JobStatus.RUNNING, JobStatus.INCOMPLETE, JobStatus.FAILED)); - LOGGER.info("state after reset: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after reset: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertRawDestinationContains(Collections.emptyList(), new SchemaTableNamePair(PUBLIC, STREAM_NAME)); @@ -652,7 +652,7 @@ void testIncrementalSync() throws Exception { final JobInfoRead connectionSyncRead3 = apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead3.getJob()); - LOGGER.info("state after sync 3: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 3: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -906,7 +906,7 @@ void testSyncAfterUpgradeToPerStreamState(final TestInfo testInfo) throws Except final JobInfoRead connectionSyncRead1 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead1.getJob()); - LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -930,7 +930,7 @@ void testSyncAfterUpgradeToPerStreamState(final TestInfo testInfo) throws Except final JobInfoRead connectionSyncRead2 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead2.getJob()); - LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertRawDestinationContains(expectedRecords, new SchemaTableNamePair(PUBLIC, STREAM_NAME)); @@ -940,7 +940,7 @@ void testSyncAfterUpgradeToPerStreamState(final TestInfo testInfo) throws Except waitWhileJobHasStatus(apiClient.getJobsApi(), jobInfoRead.getJob(), Sets.newHashSet(JobStatus.PENDING, JobStatus.RUNNING, JobStatus.INCOMPLETE, JobStatus.FAILED)); - LOGGER.info("state after reset: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after reset: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertRawDestinationContains(Collections.emptyList(), new SchemaTableNamePair(PUBLIC, STREAM_NAME)); @@ -952,7 +952,7 @@ void testSyncAfterUpgradeToPerStreamState(final TestInfo testInfo) throws Except final JobInfoRead connectionSyncRead3 = apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead3.getJob()); - final ConnectionState state = apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); + final ConnectionState state = apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); LOGGER.info("state after sync 3: {}", state); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -995,7 +995,7 @@ void testSyncAfterUpgradeToPerStreamStateWithNoNewData(final TestInfo testInfo) final JobInfoRead connectionSyncRead1 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead1.getJob()); - LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -1008,7 +1008,7 @@ void testSyncAfterUpgradeToPerStreamStateWithNoNewData(final TestInfo testInfo) final JobInfoRead connectionSyncRead2 = apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead2.getJob()); - LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); final JobInfoRead syncJob = apiClient.getJobsApi().getJobInfo(new JobIdRequestBody().id(connectionSyncRead2.getJob().getId())); final Optional result = syncJob.getAttempts().stream() @@ -1086,7 +1086,7 @@ void testResetAllWhenSchemaIsModifiedForLegacySource() throws Exception { return null; }); final ConnectionState initSyncState = - apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connection.getConnectionId())); + apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connection.getConnectionId())); LOGGER.info("ConnectionState after the initial sync: " + initSyncState.toString()); testHarness.assertSourceAndDestinationDbInSync(false); @@ -1127,7 +1127,7 @@ void testResetAllWhenSchemaIsModifiedForLegacySource() throws Exception { return null; }); final ConnectionState postResetState = - apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connection.getConnectionId())); + apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connection.getConnectionId())); LOGGER.info("ConnectionState after the update request: {}", postResetState.toString()); // Wait until the sync from the UpdateConnection is finished @@ -1136,7 +1136,7 @@ void testResetAllWhenSchemaIsModifiedForLegacySource() throws Exception { waitForSuccessfulJob(apiClient.getJobsApi(), syncFromTheUpdate); final ConnectionState postUpdateState = - apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connection.getConnectionId())); + apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connection.getConnectionId())); LOGGER.info("ConnectionState after the final sync: {}", postUpdateState.toString()); LOGGER.info("Inspecting DBs After the final sync"); @@ -1202,7 +1202,7 @@ void testIncrementalSyncMultipleStreams() throws Exception { final JobInfoRead connectionSyncRead1 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead1.getJob()); - LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_ONE, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -1232,7 +1232,7 @@ void testIncrementalSyncMultipleStreams() throws Exception { final JobInfoRead connectionSyncRead2 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead2.getJob()); - LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info(STATE_AFTER_SYNC_TWO, apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertRawDestinationContains(expectedRecordsIdAndName, new SchemaTableNamePair(PUBLIC_SCHEMA_NAME, STREAM_NAME)); testHarness.assertRawDestinationContains(expectedRecordsCoolEmployees, new SchemaTableNamePair(STAGING_SCHEMA_NAME, COOL_EMPLOYEES_TABLE_NAME)); @@ -1245,7 +1245,7 @@ void testIncrementalSyncMultipleStreams() throws Exception { waitWhileJobHasStatus(apiClient.getJobsApi(), jobInfoRead.getJob(), Sets.newHashSet(JobStatus.PENDING, JobStatus.RUNNING, JobStatus.INCOMPLETE, JobStatus.FAILED)); - LOGGER.info("state after reset: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after reset: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertRawDestinationContains(Collections.emptyList(), new SchemaTableNamePair(PUBLIC, STREAM_NAME)); @@ -1255,7 +1255,7 @@ void testIncrementalSyncMultipleStreams() throws Exception { final JobInfoRead connectionSyncRead3 = apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead3.getJob()); - LOGGER.info("state after sync 3: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 3: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); testHarness.assertSourceAndDestinationDbInSync(WITHOUT_SCD_TABLE); @@ -1407,7 +1407,7 @@ void testPartialResetResetAllWhenSchemaIsModified(final TestInfo testInfo) throw } private void assertStreamStateContainsStream(final UUID connectionId, final List expectedStreamDescriptors) throws ApiException { - final ConnectionState state = apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); + final ConnectionState state = apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); final List streamDescriptors = state.getStreamState().stream().map(StreamState::getStreamDescriptor).toList(); Assertions.assertTrue(streamDescriptors.containsAll(expectedStreamDescriptors) && expectedStreamDescriptors.containsAll(streamDescriptors)); @@ -1441,14 +1441,14 @@ private JobRead waitUntilTheNextJobIsStarted(final UUID connectionId) throws Exc * @param maxRetries the number of times to retry * @throws InterruptedException */ - private void waitForSuccessfulJobWithRetries(final UUID connectionId, int maxRetries) throws InterruptedException { + private void waitForSuccessfulJobWithRetries(final UUID connectionId, final int maxRetries) throws InterruptedException { int i; for (i = 0; i < maxRetries; i++) { try { final JobRead jobInfo = testHarness.getMostRecentSyncJobId(connectionId); waitForSuccessfulJob(apiClient.getJobsApi(), jobInfo); break; - } catch (Exception e) { + } catch (final Exception e) { LOGGER.info("Something went wrong querying jobs API, retrying..."); } sleep(Duration.ofSeconds(30).toMillis()); diff --git a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/CdcAcceptanceTests.java b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/CdcAcceptanceTests.java index 745e2e5c27e0..807d6a7c73a0 100644 --- a/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/CdcAcceptanceTests.java +++ b/airbyte-tests/src/acceptanceTests/java/io/airbyte/test/acceptance/CdcAcceptanceTests.java @@ -168,7 +168,7 @@ void testIncrementalCdcSync(final TestInfo testInfo) throws Exception { final JobInfoRead connectionSyncRead1 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead1.getJob()); - LOGGER.info("state after sync 1: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 1: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); final Database source = testHarness.getSourceDatabase(); @@ -218,7 +218,7 @@ void testIncrementalCdcSync(final TestInfo testInfo) throws Exception { final JobInfoRead connectionSyncRead2 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead2.getJob()); - LOGGER.info("state after sync 2: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 2: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); assertDestinationMatches(ID_AND_NAME_TABLE, expectedIdAndNameRecords); assertDestinationMatches(COLOR_PALETTE_TABLE, expectedColorPaletteRecords); @@ -230,7 +230,7 @@ void testIncrementalCdcSync(final TestInfo testInfo) throws Exception { final JobInfoRead jobInfoRead = apiClient.getConnectionApi().resetConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), jobInfoRead.getJob()); - LOGGER.info("state after reset: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after reset: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); assertDestinationMatches(ID_AND_NAME_TABLE, Collections.emptyList()); assertDestinationMatches(COLOR_PALETTE_TABLE, Collections.emptyList()); @@ -241,7 +241,7 @@ void testIncrementalCdcSync(final TestInfo testInfo) throws Exception { final JobInfoRead connectionSyncRead3 = apiClient.getConnectionApi().syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead3.getJob()); - LOGGER.info("state after sync 3: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 3: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); expectedIdAndNameRecords = getCdcRecordMatchersFromSource(source, ID_AND_NAME_TABLE); assertDestinationMatches(ID_AND_NAME_TABLE, expectedIdAndNameRecords); @@ -285,7 +285,7 @@ void testDeleteRecordCdcSync(final TestInfo testInfo) throws Exception { final JobInfoRead connectionSyncRead1 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead1.getJob()); - LOGGER.info("state after sync 1: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 1: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); final Database source = testHarness.getSourceDatabase(); final List expectedIdAndNameRecords = getCdcRecordMatchersFromSource(source, ID_AND_NAME_TABLE); @@ -309,7 +309,7 @@ void testDeleteRecordCdcSync(final TestInfo testInfo) throws Exception { final JobInfoRead connectionSyncRead2 = apiClient.getConnectionApi() .syncConnection(new ConnectionIdRequestBody().connectionId(connectionId)); waitForSuccessfulJob(apiClient.getJobsApi(), connectionSyncRead2.getJob()); - LOGGER.info("state after sync 2: {}", apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); + LOGGER.info("state after sync 2: {}", apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId))); assertDestinationMatches(ID_AND_NAME_TABLE, expectedIdAndNameRecords); } @@ -559,7 +559,7 @@ private void assertDestinationMatches(final String streamName, final List expectedStreams) throws ApiException { - final ConnectionState state = apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); + final ConnectionState state = apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); LOGGER.info("state: {}", state); assertEquals(ConnectionStateType.GLOBAL, state.getStateType()); final List stateStreams = state.getGlobalState().getStreamStates().stream().map(StreamState::getStreamDescriptor).toList(); @@ -569,7 +569,7 @@ private void assertGlobalStateContainsStreams(final UUID connectionId, final Lis } private void assertNoState(final UUID connectionId) throws ApiException { - final ConnectionState state = apiClient.getConnectionApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); + final ConnectionState state = apiClient.getStateApi().getState(new ConnectionIdRequestBody().connectionId(connectionId)); assertEquals(ConnectionStateType.NOT_SET, state.getStateType()); assertNull(state.getState()); assertNull(state.getStreamState()); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java index 40c37265b79f..f9ed3057066c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/PersistStateActivityImpl.java @@ -54,7 +54,7 @@ public boolean persist(final UUID connectionId, final StandardSyncOutput syncOut try { final Optional maybeStateWrapper = StateMessageHelper.getTypedState(state.getState(), featureFlags.useStreamCapableState()); if (maybeStateWrapper.isPresent()) { - final ConnectionState previousState = airbyteApiClient.getConnectionApi() + final ConnectionState previousState = airbyteApiClient.getStateApi() .getState(new ConnectionIdRequestBody().connectionId(connectionId)); if (featureFlags.needStateValidation() && previousState != null) { final StateType newStateType = maybeStateWrapper.get().getStateType(); @@ -65,7 +65,7 @@ public boolean persist(final UUID connectionId, final StandardSyncOutput syncOut } } - airbyteApiClient.getConnectionApi().createOrUpdateState( + airbyteApiClient.getStateApi().createOrUpdateState( new ConnectionStateCreateOrUpdate() .connectionId(connectionId) .connectionState(StateConverter.toClient(connectionId, maybeStateWrapper.orElse(null)))); diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/PersistStateActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/PersistStateActivityTest.java index 63075a57b16f..f132fda23a7b 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/PersistStateActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/sync/PersistStateActivityTest.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.api.client.AirbyteApiClient; -import io.airbyte.api.client.generated.ConnectionApi; +import io.airbyte.api.client.generated.StateApi; import io.airbyte.api.client.invoker.generated.ApiException; import io.airbyte.api.client.model.generated.ConnectionStateCreateOrUpdate; import io.airbyte.commons.features.FeatureFlags; @@ -47,7 +47,7 @@ class PersistStateActivityTest { AirbyteApiClient airbyteApiClient; @Mock - ConnectionApi connectionApi; + StateApi stateApi; @Mock FeatureFlags featureFlags; @@ -59,7 +59,7 @@ class PersistStateActivityTest { @BeforeEach void init() { - Mockito.lenient().when(airbyteApiClient.getConnectionApi()).thenReturn(connectionApi); + Mockito.lenient().when(airbyteApiClient.getStateApi()).thenReturn(stateApi); mockedStateMessageHelper = Mockito.mockStatic(StateMessageHelper.class, Mockito.CALLS_REAL_METHODS); } @@ -88,7 +88,7 @@ void testPersist() throws ApiException { persistStateActivity.persist(CONNECTION_ID, new StandardSyncOutput().withState(state), new ConfiguredAirbyteCatalog()); // The ser/der of the state into a state wrapper is tested in StateMessageHelperTest - Mockito.verify(connectionApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); + Mockito.verify(stateApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); } // For per-stream state, we expect there to be state for each stream within the configured catalog @@ -114,7 +114,7 @@ void testPersistWithValidMissingStateDuringMigration() throws ApiException { mockedStateMessageHelper.when(() -> StateMessageHelper.isMigration(Mockito.eq(StateType.STREAM), Mockito.any(StateType.class))).thenReturn(true); persistStateActivity.persist(CONNECTION_ID, syncOutput, migrationConfiguredCatalog); - Mockito.verify(connectionApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); + Mockito.verify(stateApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); } @Test @@ -141,7 +141,7 @@ void testPersistWithValidStateDuringMigration() throws ApiException { Mockito.when(featureFlags.useStreamCapableState()).thenReturn(true); mockedStateMessageHelper.when(() -> StateMessageHelper.isMigration(Mockito.eq(StateType.STREAM), Mockito.any(StateType.class))).thenReturn(true); persistStateActivity.persist(CONNECTION_ID, syncOutput, migrationConfiguredCatalog); - Mockito.verify(connectionApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); + Mockito.verify(stateApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); } // Global stream states do not need to be validated during the migration to per-stream state @@ -161,7 +161,7 @@ void testPersistWithGlobalStateDuringMigration() throws ApiException { persistStateActivity.persist(CONNECTION_ID, syncOutput, migrationConfiguredCatalog); final PersistStateActivityImpl persistStateSpy = spy(persistStateActivity); Mockito.verify(persistStateSpy, Mockito.times(0)).validateStreamStates(Mockito.any(), Mockito.any()); - Mockito.verify(connectionApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); + Mockito.verify(stateApi).createOrUpdateState(Mockito.any(ConnectionStateCreateOrUpdate.class)); } } diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index af3d93edd239..47c77b8a394d 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -226,11 +226,8 @@

    Attempt

    Connection

    +

    State

    +

    WebBackend

      +
    • post /v1/web_backend/state/get_type
    • post /v1/web_backend/connections/create
    • post /v1/web_backend/connections/get
    • post /v1/web_backend/workspace/state
    • @@ -554,88 +557,6 @@

      422

      InvalidInputExceptionInfo
      -
      -
      - Up -
      post /v1/state/create_or_update
      -
      Create or update the state for a connection. (createOrUpdateState)
      -
      - - -

      Consumes

      - This API call consumes the following media types via the Content-Type request header: -
        -
      • application/json
      • -
      - -

      Request body

      -
      -
      ConnectionStateCreateOrUpdate ConnectionStateCreateOrUpdate (required)
      - -
      Body Parameter
      - -
      - - - - -

      Return type

      - - - - -

      Example data

      -
      Content-Type: application/json
      -
      {
      -  "globalState" : {
      -    "streamStates" : [ {
      -      "streamDescriptor" : {
      -        "name" : "name",
      -        "namespace" : "namespace"
      -      }
      -    }, {
      -      "streamDescriptor" : {
      -        "name" : "name",
      -        "namespace" : "namespace"
      -      }
      -    } ]
      -  },
      -  "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
      -  "streamState" : [ {
      -    "streamDescriptor" : {
      -      "name" : "name",
      -      "namespace" : "namespace"
      -    }
      -  }, {
      -    "streamDescriptor" : {
      -      "name" : "name",
      -      "namespace" : "namespace"
      -    }
      -  } ]
      -}
      - -

      Produces

      - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
        -
      • application/json
      • -
      - -

      Responses

      -

      200

      - Successful operation - ConnectionState -

      404

      - Object with given id was not found. - NotFoundKnownExceptionInfo -

      422

      - Input failed validation - InvalidInputExceptionInfo -
      -
      Up @@ -803,144 +724,6 @@

      422

      InvalidInputExceptionInfo

      -
      -
      - Up -
      post /v1/state/get
      -
      Fetch the current state for a connection. (getState)
      -
      - - -

      Consumes

      - This API call consumes the following media types via the Content-Type request header: -
        -
      • application/json
      • -
      - -

      Request body

      -
      -
      ConnectionIdRequestBody ConnectionIdRequestBody (required)
      - -
      Body Parameter
      - -
      - - - - -

      Return type

      - - - - -

      Example data

      -
      Content-Type: application/json
      -
      {
      -  "globalState" : {
      -    "streamStates" : [ {
      -      "streamDescriptor" : {
      -        "name" : "name",
      -        "namespace" : "namespace"
      -      }
      -    }, {
      -      "streamDescriptor" : {
      -        "name" : "name",
      -        "namespace" : "namespace"
      -      }
      -    } ]
      -  },
      -  "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
      -  "streamState" : [ {
      -    "streamDescriptor" : {
      -      "name" : "name",
      -      "namespace" : "namespace"
      -    }
      -  }, {
      -    "streamDescriptor" : {
      -      "name" : "name",
      -      "namespace" : "namespace"
      -    }
      -  } ]
      -}
      - -

      Produces

      - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
        -
      • application/json
      • -
      - -

      Responses

      -

      200

      - Successful operation - ConnectionState -

      404

      - Object with given id was not found. - NotFoundKnownExceptionInfo -

      422

      - Input failed validation - InvalidInputExceptionInfo -
      -
      -
      -
      - Up -
      post /v1/web_backend/state/get_type
      -
      Fetch the current state type for a connection. (getStateType)
      -
      - - -

      Consumes

      - This API call consumes the following media types via the Content-Type request header: -
        -
      • application/json
      • -
      - -

      Request body

      -
      -
      ConnectionIdRequestBody ConnectionIdRequestBody (required)
      - -
      Body Parameter
      - -
      - - - - -

      Return type

      - - - - -

      Example data

      -
      Content-Type: application/json
      -
      null
      - -

      Produces

      - This API call produces the following media types according to the Accept request header; - the media type will be conveyed by the Content-Type response header. -
        -
      • application/json
      • -
      - -

      Responses

      -

      200

      - Successful operation - ConnectionStateType -

      404

      - Object with given id was not found. - NotFoundKnownExceptionInfo -

      422

      - Input failed validation - InvalidInputExceptionInfo -
      -
      Up @@ -8244,7 +8027,228 @@

      422

      InvalidInputExceptionInfo

      +

      State

      +
      +
      + Up +
      post /v1/state/create_or_update
      +
      Create or update the state for a connection. (createOrUpdateState)
      +
      + + +

      Consumes

      + This API call consumes the following media types via the Content-Type request header: +
        +
      • application/json
      • +
      + +

      Request body

      +
      +
      ConnectionStateCreateOrUpdate ConnectionStateCreateOrUpdate (required)
      + +
      Body Parameter
      + +
      + + + + +

      Return type

      + + + + +

      Example data

      +
      Content-Type: application/json
      +
      {
      +  "globalState" : {
      +    "streamStates" : [ {
      +      "streamDescriptor" : {
      +        "name" : "name",
      +        "namespace" : "namespace"
      +      }
      +    }, {
      +      "streamDescriptor" : {
      +        "name" : "name",
      +        "namespace" : "namespace"
      +      }
      +    } ]
      +  },
      +  "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
      +  "streamState" : [ {
      +    "streamDescriptor" : {
      +      "name" : "name",
      +      "namespace" : "namespace"
      +    }
      +  }, {
      +    "streamDescriptor" : {
      +      "name" : "name",
      +      "namespace" : "namespace"
      +    }
      +  } ]
      +}
      + +

      Produces

      + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
        +
      • application/json
      • +
      + +

      Responses

      +

      200

      + Successful operation + ConnectionState +

      404

      + Object with given id was not found. + NotFoundKnownExceptionInfo +

      422

      + Input failed validation + InvalidInputExceptionInfo +
      +
      +
      +
      + Up +
      post /v1/state/get
      +
      Fetch the current state for a connection. (getState)
      +
      + + +

      Consumes

      + This API call consumes the following media types via the Content-Type request header: +
        +
      • application/json
      • +
      + +

      Request body

      +
      +
      ConnectionIdRequestBody ConnectionIdRequestBody (required)
      + +
      Body Parameter
      + +
      + + + + +

      Return type

      + + + + +

      Example data

      +
      Content-Type: application/json
      +
      {
      +  "globalState" : {
      +    "streamStates" : [ {
      +      "streamDescriptor" : {
      +        "name" : "name",
      +        "namespace" : "namespace"
      +      }
      +    }, {
      +      "streamDescriptor" : {
      +        "name" : "name",
      +        "namespace" : "namespace"
      +      }
      +    } ]
      +  },
      +  "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91",
      +  "streamState" : [ {
      +    "streamDescriptor" : {
      +      "name" : "name",
      +      "namespace" : "namespace"
      +    }
      +  }, {
      +    "streamDescriptor" : {
      +      "name" : "name",
      +      "namespace" : "namespace"
      +    }
      +  } ]
      +}
      + +

      Produces

      + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
        +
      • application/json
      • +
      + +

      Responses

      +

      200

      + Successful operation + ConnectionState +

      404

      + Object with given id was not found. + NotFoundKnownExceptionInfo +

      422

      + Input failed validation + InvalidInputExceptionInfo +
      +

      WebBackend

      +
      +
      + Up +
      post /v1/web_backend/state/get_type
      +
      Fetch the current state type for a connection. (getStateType)
      +
      + + +

      Consumes

      + This API call consumes the following media types via the Content-Type request header: +
        +
      • application/json
      • +
      + +

      Request body

      +
      +
      ConnectionIdRequestBody ConnectionIdRequestBody (required)
      + +
      Body Parameter
      + +
      + + + + +

      Return type

      + + + + +

      Example data

      +
      Content-Type: application/json
      +
      null
      + +

      Produces

      + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
        +
      • application/json
      • +
      + +

      Responses

      +

      200

      + Successful operation + ConnectionStateType +

      404

      + Object with given id was not found. + NotFoundKnownExceptionInfo +

      422

      + Input failed validation + InvalidInputExceptionInfo +
      +
      Up From 21431f62972ec1a5e3549f6d0eb239b1d6154061 Mon Sep 17 00:00:00 2001 From: Xiaohan Song Date: Tue, 25 Oct 2022 14:27:42 -0700 Subject: [PATCH 323/498] use api to do jobpersistence query (#18308) * use api to do jobpersistence query * renaming some variables * fix test --- airbyte-api/src/main/openapi/config.yaml | 41 +++++ .../models/AttemptNormalizationStatus.java | 2 +- .../airbyte/server/apis/ConfigurationApi.java | 6 + .../server/converters/JobConverter.java | 11 ++ .../server/handlers/JobHistoryHandler.java | 7 + .../handlers/JobHistoryHandlerTest.java | 16 ++ .../NormalizationSummaryCheckActivity.java | 3 +- ...NormalizationSummaryCheckActivityImpl.java | 44 +++--- .../temporal/sync/SyncWorkflowImpl.java | 3 +- ...NormalizationSummaryCheckActivityTest.java | 58 ++++--- .../api/generated-api-html/index.html | 145 ++++++++++++++++++ 11 files changed, 291 insertions(+), 45 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index f7b574f1e96b..5681bbdbcc70 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -2171,6 +2171,26 @@ paths: $ref: "#/components/responses/NotFoundResponse" "422": $ref: "#/components/responses/InvalidInputResponse" + /v1/jobs/get_normalization_status: + post: + tags: + - jobs + - internal + summary: Get normalization status to determine if we can bypass normalization phase + operationId: getAttemptNormalizationStatusesForJob + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/JobIdRequestBody" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/AttemptNormalizationStatusReadList" + /v1/health: get: tags: @@ -2244,6 +2264,7 @@ paths: application/json: schema: $ref: "#/components/schemas/InternalOperationResult" + components: securitySchemes: bearerAuth: @@ -4840,6 +4861,26 @@ components: properties: succeeded: type: boolean + AttemptNormalizationStatusReadList: + type: object + properties: + attemptNormalizationStatuses: + type: array + items: + $ref: "#/components/schemas/AttemptNormalizationStatusRead" + AttemptNormalizationStatusRead: + type: object + properties: + attemptNumber: + $ref: "#/components/schemas/AttemptNumber" + hasRecordsCommitted: + type: boolean + recordsCommitted: + type: integer + format: int64 + hasNormalizationFailed: + type: boolean + InvalidInputProperty: type: object required: diff --git a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/models/AttemptNormalizationStatus.java b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/models/AttemptNormalizationStatus.java index 9575bb8e9968..4c106dd654ab 100644 --- a/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/models/AttemptNormalizationStatus.java +++ b/airbyte-persistence/job-persistence/src/main/java/io/airbyte/persistence/job/models/AttemptNormalizationStatus.java @@ -6,4 +6,4 @@ import java.util.Optional; -public record AttemptNormalizationStatus(long attemptNumber, Optional recordsCommitted, boolean normalizationFailed) {} +public record AttemptNormalizationStatus(int attemptNumber, Optional recordsCommitted, boolean normalizationFailed) {} diff --git a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java index 81fc1daff33b..cf2b731a246e 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java +++ b/airbyte-server/src/main/java/io/airbyte/server/apis/ConfigurationApi.java @@ -5,6 +5,7 @@ package io.airbyte.server.apis; import io.airbyte.analytics.TrackingClient; +import io.airbyte.api.model.generated.AttemptNormalizationStatusReadList; import io.airbyte.api.model.generated.CheckConnectionRead; import io.airbyte.api.model.generated.CheckOperationRead; import io.airbyte.api.model.generated.CompleteDestinationOAuthRequest; @@ -789,6 +790,11 @@ public JobDebugInfoRead getJobDebugInfo(final JobIdRequestBody jobIdRequestBody) return execute(() -> jobHistoryHandler.getJobDebugInfo(jobIdRequestBody)); } + @Override + public AttemptNormalizationStatusReadList getAttemptNormalizationStatusesForJob(final JobIdRequestBody jobIdRequestBody) { + return execute(() -> jobHistoryHandler.getAttemptNormalizationStatuses(jobIdRequestBody)); + } + @Override public File getLogs(final LogsRequestBody logsRequestBody) { return execute(() -> logsHandler.getLogs(workspaceRoot, workerEnvironment, logConfigs, logsRequestBody)); diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/JobConverter.java b/airbyte-server/src/main/java/io/airbyte/server/converters/JobConverter.java index 614344230532..52c28f3640f1 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/JobConverter.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/JobConverter.java @@ -9,6 +9,7 @@ import io.airbyte.api.model.generated.AttemptFailureSummary; import io.airbyte.api.model.generated.AttemptFailureType; import io.airbyte.api.model.generated.AttemptInfoRead; +import io.airbyte.api.model.generated.AttemptNormalizationStatusRead; import io.airbyte.api.model.generated.AttemptRead; import io.airbyte.api.model.generated.AttemptStats; import io.airbyte.api.model.generated.AttemptStatus; @@ -38,6 +39,7 @@ import io.airbyte.config.helpers.LogClientSingleton; import io.airbyte.config.helpers.LogConfigs; import io.airbyte.persistence.job.models.Attempt; +import io.airbyte.persistence.job.models.AttemptNormalizationStatus; import io.airbyte.persistence.job.models.Job; import io.airbyte.server.scheduler.SynchronousJobMetadata; import io.airbyte.server.scheduler.SynchronousResponse; @@ -240,4 +242,13 @@ public SynchronousJobRead getSynchronousJobRead(final SynchronousJobMetadata met .logs(getLogRead(metadata.getLogPath())); } + public static AttemptNormalizationStatusRead convertAttemptNormalizationStatus( + AttemptNormalizationStatus databaseStatus) { + return new AttemptNormalizationStatusRead() + .attemptNumber(databaseStatus.attemptNumber()) + .hasRecordsCommitted(!databaseStatus.recordsCommitted().isEmpty()) + .recordsCommitted(databaseStatus.recordsCommitted().orElse(0L)) + .hasNormalizationFailed(databaseStatus.normalizationFailed()); + } + } diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java index bb4f7bbb551f..e25bee37f04b 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/JobHistoryHandler.java @@ -5,6 +5,7 @@ package io.airbyte.server.handlers; import com.google.common.base.Preconditions; +import io.airbyte.api.model.generated.AttemptNormalizationStatusReadList; import io.airbyte.api.model.generated.ConnectionRead; import io.airbyte.api.model.generated.DestinationDefinitionIdRequestBody; import io.airbyte.api.model.generated.DestinationDefinitionRead; @@ -146,6 +147,12 @@ public List getLatestSyncJobsForConnections(final List connection .collect(Collectors.toList()); } + public AttemptNormalizationStatusReadList getAttemptNormalizationStatuses(final JobIdRequestBody jobIdRequestBody) throws IOException { + return new AttemptNormalizationStatusReadList() + .attemptNormalizationStatuses(jobPersistence.getAttemptNormalizationStatusesForJob(jobIdRequestBody.getId()).stream() + .map(JobConverter::convertAttemptNormalizationStatus).collect(Collectors.toList())); + } + public List getRunningSyncJobForConnections(final List connectionIds) throws IOException { return jobPersistence.getRunningSyncJobForConnections(connectionIds).stream() .map(JobConverter::getJobRead) diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/JobHistoryHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/JobHistoryHandlerTest.java index 3d4f6e942401..d53044e95596 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/JobHistoryHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/JobHistoryHandlerTest.java @@ -20,6 +20,7 @@ import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.persistence.job.models.Attempt; +import io.airbyte.persistence.job.models.AttemptNormalizationStatus; import io.airbyte.persistence.job.models.AttemptStatus; import io.airbyte.persistence.job.models.Job; import io.airbyte.persistence.job.models.JobStatus; @@ -376,4 +377,19 @@ void testEnumConversion() { assertTrue(Enums.isCompatible(JobConfig.ConfigType.class, JobConfigType.class)); } + @Test + @DisplayName("Should return attempt normalization info for the job") + void testGetAttemptNormalizationStatuses() throws IOException { + + AttemptNormalizationStatus databaseReadResult = new AttemptNormalizationStatus(1, Optional.of(10L), /* hasNormalizationFailed= */ false); + + when(jobPersistence.getAttemptNormalizationStatusesForJob(JOB_ID)).thenReturn(List.of(databaseReadResult)); + + AttemptNormalizationStatusReadList expectedStatus = new AttemptNormalizationStatusReadList().attemptNormalizationStatuses( + List.of(new AttemptNormalizationStatusRead().attemptNumber(1).hasRecordsCommitted(true).hasNormalizationFailed(false).recordsCommitted(10L))); + + assertEquals(expectedStatus, jobHistoryHandler.getAttemptNormalizationStatuses(new JobIdRequestBody().id(JOB_ID))); + + } + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivity.java index f4948483c187..9601c0cdf0d6 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivity.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivity.java @@ -6,13 +6,12 @@ import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; -import java.io.IOException; import java.util.Optional; @ActivityInterface public interface NormalizationSummaryCheckActivity { @ActivityMethod - boolean shouldRunNormalization(Long jobId, Long attemptId, Optional numCommittedRecords) throws IOException; + boolean shouldRunNormalization(Long jobId, Long attemptId, Optional numCommittedRecords); } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java index 8d508538f214..ad6a37961b06 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/NormalizationSummaryCheckActivityImpl.java @@ -8,13 +8,15 @@ import static io.airbyte.metrics.lib.ApmTraceConstants.Tags.JOB_ID_KEY; import datadog.trace.api.Trace; +import io.airbyte.api.client.AirbyteApiClient; +import io.airbyte.api.client.invoker.generated.ApiException; +import io.airbyte.api.client.model.generated.AttemptNormalizationStatusRead; +import io.airbyte.api.client.model.generated.AttemptNormalizationStatusReadList; +import io.airbyte.api.client.model.generated.JobIdRequestBody; import io.airbyte.metrics.lib.ApmTraceUtils; -import io.airbyte.persistence.job.JobPersistence; -import io.airbyte.persistence.job.models.AttemptNormalizationStatus; +import io.temporal.activity.Activity; import jakarta.inject.Singleton; -import java.io.IOException; import java.util.Comparator; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; @@ -25,42 +27,44 @@ @Singleton public class NormalizationSummaryCheckActivityImpl implements NormalizationSummaryCheckActivity { - private final Optional jobPersistence; + private final AirbyteApiClient airbyteApiClient; - public NormalizationSummaryCheckActivityImpl(final Optional jobPersistence) { - this.jobPersistence = jobPersistence; + public NormalizationSummaryCheckActivityImpl(AirbyteApiClient airbyteApiClient) { + this.airbyteApiClient = airbyteApiClient; } @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) @Override @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") - public boolean shouldRunNormalization(final Long jobId, final Long attemptNumber, final Optional numCommittedRecords) throws IOException { + public boolean shouldRunNormalization(final Long jobId, final Long attemptNumber, final Optional numCommittedRecords) { ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, jobId)); - // if job persistence is unavailable, default to running normalization - if (jobPersistence.isEmpty()) { - return true; - } - // if the count of committed records for this attempt is > 0 OR if it is null, // then we should run normalization if (numCommittedRecords.isEmpty() || numCommittedRecords.get() > 0) { return true; } - final List attemptNormalizationStatuses = jobPersistence.get().getAttemptNormalizationStatusesForJob(jobId); + final AttemptNormalizationStatusReadList AttemptNormalizationStatusReadList; + try { + AttemptNormalizationStatusReadList = airbyteApiClient.getJobsApi().getAttemptNormalizationStatusesForJob(new JobIdRequestBody().id(jobId)); + } catch (ApiException e) { + throw Activity.wrap(e); + } final AtomicLong totalRecordsCommitted = new AtomicLong(0L); final AtomicBoolean shouldReturnTrue = new AtomicBoolean(false); - attemptNormalizationStatuses.stream().sorted(Comparator.comparing(AttemptNormalizationStatus::attemptNumber).reversed()).toList() + AttemptNormalizationStatusReadList.getAttemptNormalizationStatuses().stream().sorted(Comparator.comparing( + AttemptNormalizationStatusRead::getAttemptNumber).reversed()).toList() .forEach(n -> { - if (n.attemptNumber() == attemptNumber) { + // Have to cast it because attemptNumber is read from JobRunConfig. + if (n.getAttemptNumber().intValue() == attemptNumber) { return; } // if normalization succeeded from a previous attempt succeeded, // we can stop looking for previous attempts - if (!n.normalizationFailed()) { + if (!n.getHasNormalizationFailed()) { return; } @@ -68,11 +72,11 @@ public boolean shouldRunNormalization(final Long jobId, final Long attemptNumber // committed number // if there is no data recorded for the number of committed records, we should assume that there // were committed records and run normalization - if (n.recordsCommitted().isEmpty()) { + if (!n.getHasRecordsCommitted()) { shouldReturnTrue.set(true); return; - } else if (n.recordsCommitted().get() != 0L) { - totalRecordsCommitted.addAndGet(n.recordsCommitted().get()); + } else if (n.getRecordsCommitted().longValue() != 0L) { + totalRecordsCommitted.addAndGet(n.getRecordsCommitted()); } }); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java index 8270db6c7493..d75dec375dc1 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/SyncWorkflowImpl.java @@ -27,7 +27,6 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.workers.temporal.annotations.TemporalActivityStub; import io.temporal.workflow.Workflow; -import java.io.IOException; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -92,7 +91,7 @@ public StandardSyncOutput run(final JobRunConfig jobRunConfig, try { shouldRun = normalizationSummaryCheckActivity.shouldRunNormalization(Long.valueOf(jobRunConfig.getJobId()), jobRunConfig.getAttemptId(), Optional.ofNullable(syncOutput.getStandardSyncSummary().getTotalStats().getRecordsCommitted())); - } catch (final IOException e) { + } catch (final Exception e) { shouldRun = true; } if (!shouldRun) { diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/NormalizationSummaryCheckActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/NormalizationSummaryCheckActivityTest.java index 4ddd6039db9a..ba508937d5e1 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/NormalizationSummaryCheckActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/NormalizationSummaryCheckActivityTest.java @@ -5,11 +5,15 @@ package io.airbyte.workers.temporal.scheduling.activities; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -import io.airbyte.persistence.job.JobPersistence; -import io.airbyte.persistence.job.models.AttemptNormalizationStatus; +import io.airbyte.api.client.AirbyteApiClient; +import io.airbyte.api.client.generated.JobsApi; +import io.airbyte.api.client.invoker.generated.ApiException; +import io.airbyte.api.client.model.generated.AttemptNormalizationStatusRead; +import io.airbyte.api.client.model.generated.AttemptNormalizationStatusReadList; +import io.airbyte.api.client.model.generated.JobIdRequestBody; import io.airbyte.workers.temporal.sync.NormalizationSummaryCheckActivityImpl; -import java.io.IOException; import java.util.List; import java.util.Optional; import lombok.extern.slf4j.Slf4j; @@ -17,7 +21,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @Slf4j @@ -26,47 +29,62 @@ class NormalizationSummaryCheckActivityTest { private static final Long JOB_ID = 10L; static private NormalizationSummaryCheckActivityImpl normalizationSummaryCheckActivity; - static private JobPersistence mJobPersistence; + static private AirbyteApiClient airbyteApiClient; + static private JobsApi jobsApi; @BeforeAll static void setUp() { - mJobPersistence = mock(JobPersistence.class); - normalizationSummaryCheckActivity = new NormalizationSummaryCheckActivityImpl(Optional.of(mJobPersistence)); + airbyteApiClient = mock(AirbyteApiClient.class); + jobsApi = mock(JobsApi.class); + when(airbyteApiClient.getJobsApi()).thenReturn(jobsApi); + normalizationSummaryCheckActivity = new NormalizationSummaryCheckActivityImpl(airbyteApiClient); } @Test - void testShouldRunNormalizationRecordsCommittedOnFirstAttemptButNotCurrentAttempt() throws IOException { + void testShouldRunNormalizationRecordsCommittedOnFirstAttemptButNotCurrentAttempt() throws ApiException { // Attempt 1 committed records, but normalization failed // Attempt 2 did not commit records, normalization failed (or did not run) - final AttemptNormalizationStatus attempt1 = new AttemptNormalizationStatus(1, Optional.of(10L), true); - final AttemptNormalizationStatus attempt2 = new AttemptNormalizationStatus(2, Optional.of(0L), true); - Mockito.when(mJobPersistence.getAttemptNormalizationStatusesForJob(JOB_ID)).thenReturn(List.of(attempt1, attempt2)); + final AttemptNormalizationStatusRead attempt1 = + new AttemptNormalizationStatusRead().attemptNumber(1).hasRecordsCommitted(true).recordsCommitted(10L).hasNormalizationFailed(true); + final AttemptNormalizationStatusRead attempt2 = + new AttemptNormalizationStatusRead().attemptNumber(2).hasRecordsCommitted(true).recordsCommitted(0L).hasNormalizationFailed(true); + + when(jobsApi.getAttemptNormalizationStatusesForJob(new JobIdRequestBody().id(JOB_ID))) + .thenReturn(new AttemptNormalizationStatusReadList().attemptNormalizationStatuses(List.of(attempt1, attempt2))); Assertions.assertThat(true).isEqualTo(normalizationSummaryCheckActivity.shouldRunNormalization(JOB_ID, 3L, Optional.of(0L))); } @Test - void testShouldRunNormalizationRecordsCommittedOnCurrentAttempt() throws IOException { + void testShouldRunNormalizationRecordsCommittedOnCurrentAttempt() throws ApiException { Assertions.assertThat(true).isEqualTo(normalizationSummaryCheckActivity.shouldRunNormalization(JOB_ID, 3L, Optional.of(30L))); } @Test - void testShouldRunNormalizationNoRecordsCommittedOnCurrentAttemptOrPreviousAttempts() throws IOException { + void testShouldRunNormalizationNoRecordsCommittedOnCurrentAttemptOrPreviousAttempts() throws ApiException { // No attempts committed any records // Normalization did not run on any attempts - final AttemptNormalizationStatus attempt1 = new AttemptNormalizationStatus(1, Optional.of(0L), true); - final AttemptNormalizationStatus attempt2 = new AttemptNormalizationStatus(2, Optional.of(0L), true); - Mockito.when(mJobPersistence.getAttemptNormalizationStatusesForJob(JOB_ID)).thenReturn(List.of(attempt1, attempt2)); + final AttemptNormalizationStatusRead attempt1 = + new AttemptNormalizationStatusRead().attemptNumber(1).hasRecordsCommitted(true).recordsCommitted(0L).hasNormalizationFailed(true); + final AttemptNormalizationStatusRead attempt2 = + new AttemptNormalizationStatusRead().attemptNumber(2).hasRecordsCommitted(true).recordsCommitted(0L).hasNormalizationFailed(true); + + when(jobsApi.getAttemptNormalizationStatusesForJob(new JobIdRequestBody().id(JOB_ID))) + .thenReturn(new AttemptNormalizationStatusReadList().attemptNormalizationStatuses(List.of(attempt1, attempt2))); Assertions.assertThat(false).isEqualTo(normalizationSummaryCheckActivity.shouldRunNormalization(JOB_ID, 3L, Optional.of(0L))); } @Test - void testShouldRunNormalizationNoRecordsCommittedOnCurrentAttemptPreviousAttemptsSucceeded() throws IOException { + void testShouldRunNormalizationNoRecordsCommittedOnCurrentAttemptPreviousAttemptsSucceeded() throws ApiException { // Records committed on first two attempts and normalization succeeded // No records committed on current attempt and normalization has not yet run - final AttemptNormalizationStatus attempt1 = new AttemptNormalizationStatus(1, Optional.of(10L), false); - final AttemptNormalizationStatus attempt2 = new AttemptNormalizationStatus(2, Optional.of(20L), false); - Mockito.when(mJobPersistence.getAttemptNormalizationStatusesForJob(JOB_ID)).thenReturn(List.of(attempt1, attempt2)); + final AttemptNormalizationStatusRead attempt1 = + new AttemptNormalizationStatusRead().attemptNumber(1).hasRecordsCommitted(true).recordsCommitted(10L).hasNormalizationFailed(false); + final AttemptNormalizationStatusRead attempt2 = + new AttemptNormalizationStatusRead().attemptNumber(2).hasRecordsCommitted(true).recordsCommitted(20L).hasNormalizationFailed(false); + + when(jobsApi.getAttemptNormalizationStatusesForJob(new JobIdRequestBody().id(JOB_ID))) + .thenReturn(new AttemptNormalizationStatusReadList().attemptNormalizationStatuses(List.of(attempt1, attempt2))); Assertions.assertThat(false).isEqualTo(normalizationSummaryCheckActivity.shouldRunNormalization(JOB_ID, 3L, Optional.of(0L))); } diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 47c77b8a394d..48dd1c63f7be 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -280,11 +280,13 @@

      Health

      Internal

      Jobs


      +
      +
      + Up +
      post /v1/jobs/get_normalization_status
      +
      Get normalization status to determine if we can bypass normalization phase (getAttemptNormalizationStatusesForJob)
      +
      + + +

      Consumes

      + This API call consumes the following media types via the Content-Type request header: +
        +
      • application/json
      • +
      + +

      Request body

      +
      +
      JobIdRequestBody JobIdRequestBody (optional)
      + +
      Body Parameter
      + +
      + + + + +

      Return type

      + + + + +

      Example data

      +
      Content-Type: application/json
      +
      {
      +  "attemptNormalizationStatuses" : [ {
      +    "attemptNumber" : 0,
      +    "recordsCommitted" : 6,
      +    "hasRecordsCommitted" : true,
      +    "hasNormalizationFailed" : true
      +  }, {
      +    "attemptNumber" : 0,
      +    "recordsCommitted" : 6,
      +    "hasRecordsCommitted" : true,
      +    "hasNormalizationFailed" : true
      +  } ]
      +}
      + +

      Produces

      + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
        +
      • application/json
      • +
      + +

      Responses

      +

      200

      + Successful operation + AttemptNormalizationStatusReadList +
      +
      Up @@ -4135,6 +4199,68 @@

      422

      InvalidInputExceptionInfo

      +
      +
      + Up +
      post /v1/jobs/get_normalization_status
      +
      Get normalization status to determine if we can bypass normalization phase (getAttemptNormalizationStatusesForJob)
      +
      + + +

      Consumes

      + This API call consumes the following media types via the Content-Type request header: +
        +
      • application/json
      • +
      + +

      Request body

      +
      +
      JobIdRequestBody JobIdRequestBody (optional)
      + +
      Body Parameter
      + +
      + + + + +

      Return type

      + + + + +

      Example data

      +
      Content-Type: application/json
      +
      {
      +  "attemptNormalizationStatuses" : [ {
      +    "attemptNumber" : 0,
      +    "recordsCommitted" : 6,
      +    "hasRecordsCommitted" : true,
      +    "hasNormalizationFailed" : true
      +  }, {
      +    "attemptNumber" : 0,
      +    "recordsCommitted" : 6,
      +    "hasRecordsCommitted" : true,
      +    "hasNormalizationFailed" : true
      +  } ]
      +}
      + +

      Produces

      + This API call produces the following media types according to the Accept request header; + the media type will be conveyed by the Content-Type response header. +
        +
      • application/json
      • +
      + +

      Responses

      +

      200

      + Successful operation + AttemptNormalizationStatusReadList +
      +
      +
      +

      AttemptNormalizationStatusRead - Up

      +
      +
      +
      attemptNumber (optional)
      Integer format: int32
      +
      hasRecordsCommitted (optional)
      +
      recordsCommitted (optional)
      Long format: int64
      +
      hasNormalizationFailed (optional)
      +
      +
      +
      +

      AttemptNormalizationStatusReadList - Up

      +
      +
      +
      attemptNormalizationStatuses (optional)
      +
      +

      AttemptRead - Up

      From 30a8d1767e441bf9f038dc4f953643cbd0ed85d3 Mon Sep 17 00:00:00 2001 From: Anne <102554163+alovew@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:56:06 -0700 Subject: [PATCH 324/498] RefreshSchema Activity (#18388) * Add RefreshSchema Activity --- .../types/ActorCatalogFetchEvent.yaml | 3 + .../config/persistence/ConfigRepository.java | 4 +- .../config/persistence/DbConverter.java | 5 +- .../temporal/sync/RefreshSchemaActivity.java | 18 ++++++ .../sync/RefreshSchemaActivityImpl.java | 46 +++++++++++++++ .../activities/RefreshSchemaActivityTest.java | 59 +++++++++++++++++++ 6 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java diff --git a/airbyte-config/config-models/src/main/resources/types/ActorCatalogFetchEvent.yaml b/airbyte-config/config-models/src/main/resources/types/ActorCatalogFetchEvent.yaml index bd2b5206c62b..3bb598c7bd59 100644 --- a/airbyte-config/config-models/src/main/resources/types/ActorCatalogFetchEvent.yaml +++ b/airbyte-config/config-models/src/main/resources/types/ActorCatalogFetchEvent.yaml @@ -25,3 +25,6 @@ properties: type: string connectorVersion: type: string + createdAt: + type: integer + format: int64 diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index da59d93e9068..38acad7f9fe4 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -997,8 +997,8 @@ public Map getMostRecentActorCatalogFetchEventForS return database.query(ctx -> ctx.fetch( """ - select actor_catalog_id, actor_id from - (select actor_catalog_id, actor_id, rank() over (partition by actor_id order by created_at desc) as creation_order_rank + select actor_catalog_id, actor_id, created_at from + (select actor_catalog_id, actor_id, created_at, rank() over (partition by actor_id order by created_at desc) as creation_order_rank from public.actor_catalog_fetch_event ) table_with_rank where creation_order_rank = 1; diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index ea4548d9f78a..96a705dddb90 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -38,6 +38,8 @@ import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConnectorSpecification; +import java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -199,7 +201,8 @@ public static ActorCatalog buildActorCatalog(final Record record) { public static ActorCatalogFetchEvent buildActorCatalogFetchEvent(final Record record) { return new ActorCatalogFetchEvent() .withActorId(record.get(ACTOR_CATALOG_FETCH_EVENT.ACTOR_ID)) - .withActorCatalogId(record.get(ACTOR_CATALOG_FETCH_EVENT.ACTOR_CATALOG_ID)); + .withActorCatalogId(record.get(ACTOR_CATALOG_FETCH_EVENT.ACTOR_CATALOG_ID)) + .withCreatedAt(record.get(ACTOR_CATALOG_FETCH_EVENT.CREATED_AT, LocalDateTime.class).toEpochSecond(ZoneOffset.UTC)); } public static WorkspaceServiceAccount buildWorkspaceServiceAccount(final Record record) { diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java new file mode 100644 index 000000000000..aa283703b844 --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.sync; + +import io.temporal.activity.ActivityInterface; +import io.temporal.activity.ActivityMethod; +import java.io.IOException; +import java.util.UUID; + +@ActivityInterface +public interface RefreshSchemaActivity { + + @ActivityMethod + boolean shouldRefreshSchema(UUID sourceCatalogId) throws IOException; + +} diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java new file mode 100644 index 000000000000..9def0b60ce1a --- /dev/null +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.sync; + +import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; + +import datadog.trace.api.Trace; +import io.airbyte.config.ActorCatalogFetchEvent; +import io.airbyte.config.persistence.ConfigRepository; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.Optional; +import java.util.UUID; + +public class RefreshSchemaActivityImpl implements RefreshSchemaActivity { + + private final Optional configRepository; + + public RefreshSchemaActivityImpl(Optional configRepository) { + this.configRepository = configRepository; + } + + @Override + @Trace(operationName = ACTIVITY_TRACE_OPERATION_NAME) + public boolean shouldRefreshSchema(UUID sourceCatalogId) throws IOException { + // if job persistence is unavailable, default to skipping the schema refresh + if (configRepository.isEmpty()) { + return false; + } + + return !schemaRefreshRanRecently(sourceCatalogId); + } + + private boolean schemaRefreshRanRecently(UUID sourceCatalogId) throws IOException { + Optional mostRecentFetchEvent = configRepository.get().getMostRecentActorCatalogFetchEventForSource(sourceCatalogId); + + if (mostRecentFetchEvent.isEmpty()) { + return false; + } + + return mostRecentFetchEvent.get().getCreatedAt() > OffsetDateTime.now().minusHours(24l).toEpochSecond(); + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java new file mode 100644 index 000000000000..d7bbdb739761 --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.temporal.scheduling.activities; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.airbyte.config.ActorCatalogFetchEvent; +import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.workers.temporal.sync.RefreshSchemaActivityImpl; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.Optional; +import java.util.UUID; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class RefreshSchemaActivityTest { + + static private ConfigRepository mConfigRepository; + static private RefreshSchemaActivityImpl refreshSchemaActivity; + + static private final UUID SOURCE_ID = UUID.randomUUID(); + + @BeforeEach + void setUp() { + mConfigRepository = mock(ConfigRepository.class); + refreshSchemaActivity = new RefreshSchemaActivityImpl(Optional.of(mConfigRepository)); + } + + @Test + void testShouldRefreshSchemaNoRecentRefresh() throws IOException { + when(mConfigRepository.getMostRecentActorCatalogFetchEventForSource(SOURCE_ID)).thenReturn(Optional.empty()); + Assertions.assertThat(true).isEqualTo(refreshSchemaActivity.shouldRefreshSchema(SOURCE_ID)); + } + + @Test + void testShouldRefreshSchemaRecentRefreshOver24HoursAgo() throws IOException { + Long twoDaysAgo = OffsetDateTime.now().minusHours(48l).toEpochSecond(); + ActorCatalogFetchEvent fetchEvent = new ActorCatalogFetchEvent().withActorCatalogId(UUID.randomUUID()).withCreatedAt(twoDaysAgo); + when(mConfigRepository.getMostRecentActorCatalogFetchEventForSource(SOURCE_ID)).thenReturn(Optional.ofNullable(fetchEvent)); + Assertions.assertThat(true).isEqualTo(refreshSchemaActivity.shouldRefreshSchema(SOURCE_ID)); + } + + @Test + void testShouldRefreshSchemaRecentRefreshLessThan24HoursAgo() throws IOException { + Long twelveHoursAgo = OffsetDateTime.now().minusHours(12l).toEpochSecond(); + ActorCatalogFetchEvent fetchEvent = new ActorCatalogFetchEvent().withActorCatalogId(UUID.randomUUID()).withCreatedAt(twelveHoursAgo); + when(mConfigRepository.getMostRecentActorCatalogFetchEventForSource(SOURCE_ID)).thenReturn(Optional.ofNullable(fetchEvent)); + Assertions.assertThat(false).isEqualTo(refreshSchemaActivity.shouldRefreshSchema(SOURCE_ID)); + } + +} From b0a8e5473fd7f25cf75bccc71b611800e6c7da31 Mon Sep 17 00:00:00 2001 From: Akash Kulkarni <113392464+akashkulk@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:06:03 -0700 Subject: [PATCH 325/498] Follow-up fix for SSHExceptions (#18383) * Follow-up fix for SSHExceptions * Catch RuntimeException instead of augmenting connection error messaging for now * Update tests * Bump versions + docs * auto-bump connector version * auto-bump connector version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 6 +- .../src/main/resources/seed/source_specs.yaml | 6 +- .../integrations/base/ssh/SshTunnel.java | 4 +- .../base/ssh/SshWrappedDestination.java | 2 +- .../base/ssh/SshWrappedSource.java | 2 +- .../source-mssql-strict-encrypt/Dockerfile | 2 +- .../connectors/source-mssql/Dockerfile | 2 +- .../source-mysql-strict-encrypt/Dockerfile | 2 +- ...StrictEncryptJdbcSourceAcceptanceTest.java | 8 +- .../connectors/source-mysql/Dockerfile | 2 +- .../source-postgres-strict-encrypt/Dockerfile | 2 +- .../connectors/source-postgres/Dockerfile | 2 +- docs/integrations/sources/mssql.md | 115 +++++----- docs/integrations/sources/mysql.md | 145 ++++++------- docs/integrations/sources/postgres.md | 201 +++++++++--------- 15 files changed, 254 insertions(+), 247 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 3a8b6f2bbb13..f4f283b7565b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -658,7 +658,7 @@ - name: Microsoft SQL Server (MSSQL) sourceDefinitionId: b5ea17b1-f170-46dc-bc31-cc744ca984c1 dockerRepository: airbyte/source-mssql - dockerImageTag: 0.4.23 + dockerImageTag: 0.4.24 documentationUrl: https://docs.airbyte.com/integrations/sources/mssql icon: mssql.svg sourceType: database @@ -706,7 +706,7 @@ - name: MySQL sourceDefinitionId: 435bb9a5-7887-4809-aa58-28c27df0d7ad dockerRepository: airbyte/source-mysql - dockerImageTag: 1.0.7 + dockerImageTag: 1.0.8 documentationUrl: https://docs.airbyte.com/integrations/sources/mysql icon: mysql.svg sourceType: database @@ -864,7 +864,7 @@ - name: Postgres sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres - dockerImageTag: 1.0.19 + dockerImageTag: 1.0.20 documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 9c48061ef3bb..dd388830bbca 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -6330,7 +6330,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mssql:0.4.23" +- dockerImage: "airbyte/source-mssql:0.4.24" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/mssql" connectionSpecification: @@ -7160,7 +7160,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mysql:1.0.7" +- dockerImage: "airbyte/source-mysql:1.0.8" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/mysql" connectionSpecification: @@ -8792,7 +8792,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-postgres:1.0.19" +- dockerImage: "airbyte/source-postgres:1.0.20" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java index 0647f775ea34..f6d908c3f311 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java @@ -303,7 +303,7 @@ public void close() { * @see loadKeyPairs() */ - KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException, ConnectionErrorException { + KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException { final String validatedKey = validateKey(); final var keyPairs = SecurityUtils .getKeyPairResourceParser() @@ -312,7 +312,7 @@ KeyPair getPrivateKeyPair() throws IOException, GeneralSecurityException, Connec if (keyPairs != null && keyPairs.iterator().hasNext()) { return keyPairs.iterator().next(); } - throw new ConnectionErrorException("Unable to load private key pairs, verify key pairs are properly inputted"); + throw new RuntimeException("Unable to load private key pairs, verify key pairs are properly inputted"); } private String validateKey() { diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java index 2e1735bdc18a..38c6f20ed3cb 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java @@ -67,7 +67,7 @@ public AirbyteConnectionStatus check(final JsonNode config) throws Exception { try { return (endPointKey != null) ? SshTunnel.sshWrap(config, endPointKey, delegate::check) : SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); - } catch (final SshException | ConnectionErrorException e) { + } catch (final RuntimeException e) { final String sshErrorMessage = "Could not connect with provided SSH configuration. Error: " + e.getMessage(); AirbyteTraceMessageUtility.emitConfigErrorTrace(e, sshErrorMessage); return new AirbyteConnectionStatus() diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java index eec5220391c5..1596b29340b3 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java @@ -43,7 +43,7 @@ public ConnectorSpecification spec() throws Exception { public AirbyteConnectionStatus check(final JsonNode config) throws Exception { try { return SshTunnel.sshWrap(config, hostKey, portKey, delegate::check); - } catch (final SshException | ConnectionErrorException e) { + } catch (final RuntimeException e) { final String sshErrorMessage = "Could not connect with provided SSH configuration. Error: " + e.getMessage(); AirbyteTraceMessageUtility.emitConfigErrorTrace(e, sshErrorMessage); return new AirbyteConnectionStatus() diff --git a/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile index 8ba8453b09ed..29a82548717a 100644 --- a/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mssql-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-mssql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.4.23 +LABEL io.airbyte.version=0.4.24 LABEL io.airbyte.name=airbyte/source-mssql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mssql/Dockerfile b/airbyte-integrations/connectors/source-mssql/Dockerfile index 44c38200c998..1b369f4b119d 100644 --- a/airbyte-integrations/connectors/source-mssql/Dockerfile +++ b/airbyte-integrations/connectors/source-mssql/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-mssql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=0.4.23 +LABEL io.airbyte.version=0.4.24 LABEL io.airbyte.name=airbyte/source-mssql diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile index f0a549848bbb..820305f7665f 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.7 +LABEL io.airbyte.version=1.0.8 LABEL io.airbyte.name=airbyte/source-mysql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java index ba22c3d1e924..62afb15893f0 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/src/test/java/io/airbyte/integrations/source/mysql_strict_encrypt/MySqlStrictEncryptJdbcSourceAcceptanceTest.java @@ -303,7 +303,9 @@ void testStrictSSLSecuredWithTunnel() throws Exception { .putIfAbsent(JdbcUtils.SSL_MODE_KEY, Jsons.jsonNode(sslMode)); ((ObjectNode) config).putIfAbsent("tunnel_method", Jsons.jsonNode(tunnelMode)); - final Exception exception = assertThrows(NullPointerException.class, () -> source.check(config)); + final AirbyteConnectionStatus actual = source.check(config); + assertEquals(Status.FAILED, actual.getStatus()); + assertTrue(actual.getMessage().contains("Could not connect with provided SSH configuration.")); } @Test @@ -322,7 +324,9 @@ void testStrictSSLUnsecuredWithTunnel() throws Exception { .putIfAbsent(JdbcUtils.SSL_MODE_KEY, Jsons.jsonNode(sslMode)); ((ObjectNode) config).putIfAbsent("tunnel_method", Jsons.jsonNode(tunnelMode)); - final Exception exception = assertThrows(NullPointerException.class, () -> source.check(config)); + final AirbyteConnectionStatus actual = source.check(config); + assertEquals(Status.FAILED, actual.getStatus()); + assertTrue(actual.getMessage().contains("Could not connect with provided SSH configuration.")); } @Override diff --git a/airbyte-integrations/connectors/source-mysql/Dockerfile b/airbyte-integrations/connectors/source-mysql/Dockerfile index 74587aade9e7..547ac9477d5d 100644 --- a/airbyte-integrations/connectors/source-mysql/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.7 +LABEL io.airbyte.version=1.0.8 LABEL io.airbyte.name=airbyte/source-mysql diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile index 4b79ea19f4b7..41f77a3f95ca 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.19 +LABEL io.airbyte.version=1.0.20 LABEL io.airbyte.name=airbyte/source-postgres-strict-encrypt diff --git a/airbyte-integrations/connectors/source-postgres/Dockerfile b/airbyte-integrations/connectors/source-postgres/Dockerfile index 029074f82560..04c905dd55c4 100644 --- a/airbyte-integrations/connectors/source-postgres/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.19 +LABEL io.airbyte.version=1.0.20 LABEL io.airbyte.name=airbyte/source-postgres diff --git a/docs/integrations/sources/mssql.md b/docs/integrations/sources/mssql.md index 30273680457a..10f1a90311c5 100644 --- a/docs/integrations/sources/mssql.md +++ b/docs/integrations/sources/mssql.md @@ -339,61 +339,62 @@ WHERE actor_definition_id ='b5ea17b1-f170-46dc-bc31-cc744ca984c1' AND (configura ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0.4.23 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | -| 0.4.22 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | -| 0.4.21 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | -| 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | -| 0.4.17 | 2022-09-01 | [16261](https://github.com/airbytehq/airbyte/pull/16261) | Emit state messages more frequently | -| 0.4.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.4.15 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.4.14 | 2022-08-10 | [15430](https://github.com/airbytehq/airbyte/pull/15430) | fixed a bug on handling special character on database name | -| 0.4.13 | 2022-08-04 | [15268](https://github.com/airbytehq/airbyte/pull/15268) | Added [] enclosing to escape special character in the database name | -| 0.4.12 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.4.11 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.4.10 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.4.9 | 2022-07-05 | [14379](https://github.com/airbytehq/airbyte/pull/14379) | Aligned Normal and CDC migration + added some fixes for datatypes processing | -| 0.4.8 | 2022-06-24 | [14121](https://github.com/airbytehq/airbyte/pull/14121) | Omit using 'USE' keyword on Azure SQL with CDC | -| 0.4.5 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.4.3 | 2022-06-17 | [13887](https://github.com/airbytehq/airbyte/pull/13887) | Increase version to include changes from [13854](https://github.com/airbytehq/airbyte/pull/13854) | -| 0.4.2 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | -| 0.4.1 | 2022-05-25 | [13419](https://github.com/airbytehq/airbyte/pull/13419) | Correct enum for Standard method. | -| 0.4.0 | 2022-05-25 | [12759](https://github.com/airbytehq/airbyte/pull/12759) [13168](https://github.com/airbytehq/airbyte/pull/13168) | For CDC, Add option to ignore existing data and only sync new changes from the database. | -| 0.3.22 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.3.21 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.3.19 | 2022-03-31 | [11495](https://github.com/airbytehq/airbyte/pull/11495) | Adds Support to Chinese MSSQL Server Agent | -| 0.3.18 | 2022-03-29 | [11010](https://github.com/airbytehq/airbyte/pull/11010) | Adds JDBC Params | -| 0.3.17 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.3.16 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.3.15 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | -| 0.3.14 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | -| 0.3.13 | 2022-01-07 | [9094](https://github.com/airbytehq/airbyte/pull/9094) | Added support for missed data types | -| 0.3.12 | 2021-12-30 | [9206](https://github.com/airbytehq/airbyte/pull/9206) | Update connector fields title/description | -| 0.3.11 | 2021-12-24 | [8958](https://github.com/airbytehq/airbyte/pull/8958) | Add support for JdbcType.ARRAY | -| 0.3.10 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | | -| 0.3.9 | 2021-11-09 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Improve support for binary and varbinary data types | | -| 0.3.8 | 2021-10-26 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Fixed data type (smalldatetime, smallmoney) conversion from mssql source | | -| 0.3.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | | -| 0.3.6 | 2021-09-17 | [6318](https://github.com/airbytehq/airbyte/pull/6318) | Added option to connect to DB via SSH | | -| 0.3.4 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | | -| 0.3.3 | 2021-07-05 | [4689](https://github.com/airbytehq/airbyte/pull/4689) | Add CDC support | | -| 0.3.2 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | | -| 0.3.1 | 2021-06-08 | [3893](https://github.com/airbytehq/airbyte/pull/3893) | Enable SSL connection | | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | | -| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | | -| 0.1.11 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | \] | -| 0.1.10 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | | -| 0.1.9 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | | -| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | | -| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | | -| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | | -| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.4.24 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages | +| 0.4.23 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | +| 0.4.22 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | +| 0.4.21 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 0.4.20 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.4.19 | 2022-09-05 | [16002](https://github.com/airbytehq/airbyte/pull/16002) | Added ability to specify schemas for discovery during setting connector up | +| 0.4.18 | 2022-09-03 | [14910](https://github.com/airbytehq/airbyte/pull/14910) | Standardize spec for CDC replication. Replace the `replication_method` enum with a config object with a `method` enum field. | +| 0.4.17 | 2022-09-01 | [16261](https://github.com/airbytehq/airbyte/pull/16261) | Emit state messages more frequently | +| 0.4.16 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.4.15 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.4.14 | 2022-08-10 | [15430](https://github.com/airbytehq/airbyte/pull/15430) | fixed a bug on handling special character on database name | +| 0.4.13 | 2022-08-04 | [15268](https://github.com/airbytehq/airbyte/pull/15268) | Added [] enclosing to escape special character in the database name | +| 0.4.12 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.4.11 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.4.10 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.4.9 | 2022-07-05 | [14379](https://github.com/airbytehq/airbyte/pull/14379) | Aligned Normal and CDC migration + added some fixes for datatypes processing | +| 0.4.8 | 2022-06-24 | [14121](https://github.com/airbytehq/airbyte/pull/14121) | Omit using 'USE' keyword on Azure SQL with CDC | +| 0.4.5 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.4.3 | 2022-06-17 | [13887](https://github.com/airbytehq/airbyte/pull/13887) | Increase version to include changes from [13854](https://github.com/airbytehq/airbyte/pull/13854) | +| 0.4.2 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | +| 0.4.1 | 2022-05-25 | [13419](https://github.com/airbytehq/airbyte/pull/13419) | Correct enum for Standard method. | +| 0.4.0 | 2022-05-25 | [12759](https://github.com/airbytehq/airbyte/pull/12759) [13168](https://github.com/airbytehq/airbyte/pull/13168) | For CDC, Add option to ignore existing data and only sync new changes from the database. | +| 0.3.22 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.3.21 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.3.19 | 2022-03-31 | [11495](https://github.com/airbytehq/airbyte/pull/11495) | Adds Support to Chinese MSSQL Server Agent | +| 0.3.18 | 2022-03-29 | [11010](https://github.com/airbytehq/airbyte/pull/11010) | Adds JDBC Params | +| 0.3.17 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.3.16 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.3.15 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | Add `-XX:+ExitOnOutOfMemoryError` JVM option | +| 0.3.14 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | +| 0.3.13 | 2022-01-07 | [9094](https://github.com/airbytehq/airbyte/pull/9094) | Added support for missed data types | +| 0.3.12 | 2021-12-30 | [9206](https://github.com/airbytehq/airbyte/pull/9206) | Update connector fields title/description | +| 0.3.11 | 2021-12-24 | [8958](https://github.com/airbytehq/airbyte/pull/8958) | Add support for JdbcType.ARRAY | +| 0.3.10 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | | +| 0.3.9 | 2021-11-09 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Improve support for binary and varbinary data types | | +| 0.3.8 | 2021-10-26 | [7386](https://github.com/airbytehq/airbyte/pull/7386) | Fixed data type (smalldatetime, smallmoney) conversion from mssql source | | +| 0.3.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | | +| 0.3.6 | 2021-09-17 | [6318](https://github.com/airbytehq/airbyte/pull/6318) | Added option to connect to DB via SSH | | +| 0.3.4 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | | +| 0.3.3 | 2021-07-05 | [4689](https://github.com/airbytehq/airbyte/pull/4689) | Add CDC support | | +| 0.3.2 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | | +| 0.3.1 | 2021-06-08 | [3893](https://github.com/airbytehq/airbyte/pull/3893) | Enable SSL connection | | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | | +| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | | +| 0.1.11 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | \] | +| 0.1.10 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | | +| 0.1.9 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | | +| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | | +| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | | +| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | | +| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | | diff --git a/docs/integrations/sources/mysql.md b/docs/integrations/sources/mysql.md index 40801c2bdddc..3800f49df1d4 100644 --- a/docs/integrations/sources/mysql.md +++ b/docs/integrations/sources/mysql.md @@ -249,76 +249,77 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura ``` ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:-----------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.7 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | -| 1.0.6 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | -| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | -| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | -| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | -| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | -| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | -| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | -| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | -| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | -| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | -| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | -| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | -| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | -| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | -| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | -| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | -| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | -| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | -| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | -| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | -| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | -| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | -| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | -| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | -| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | -| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | -| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | -| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | -| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | -| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | -| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | -| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | -| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | -| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | -| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | -| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | -| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | -| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | -| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | -| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | -| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | -| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | -| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | -| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | -| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | -| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | -| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | -| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | -| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:--------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.8 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages | +| 1.0.7 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | +| 1.0.6 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | +| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | +| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | +| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | +| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | +| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | +| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | +| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | +| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | +| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | +| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | +| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | +| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | +| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | +| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | +| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | +| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | +| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | +| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | +| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | +| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | +| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | +| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | +| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | +| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | +| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | +| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | +| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | +| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | +| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | +| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | +| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | +| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | +| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | +| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | +| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | +| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | +| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | +| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | +| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | +| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | +| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | +| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | +| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | +| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | +| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | +| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | +| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | +| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index 20fa4a1c7399..a0fb80c91a52 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -396,104 +396,105 @@ The root causes is that the WALs needed for the incremental sync has been remove ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.19 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Destinations | -| 1.0.18 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | -| 1.0.17 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | -| 1.0.16 | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | -| 1.0.14 | 2022-10-03 | [17515](https://github.com/airbytehq/airbyte/pull/17515) | Fix an issue preventing connection using client certificate | -| 1.0.13 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | -| 1.0.12 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt postgres source | -| 1.0.11 | 2022-09-26 | [17131](https://github.com/airbytehq/airbyte/pull/17131) | Allow nullable columns to be used as cursor | -| 1.0.10 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 1.0.9 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | -| 1.0.8 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | -| 1.0.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source postgres | -| 1.0.6 | 2022-08-30 | [16138](https://github.com/airbytehq/airbyte/pull/16138) | Remove unnecessary logging | -| 1.0.5 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Add support for connection over SSL in CDC mode | -| 1.0.4 | 2022-08-23 | [15877](https://github.com/airbytehq/airbyte/pull/15877) | Fix temporal data type bug which was causing failure in CDC mode | -| 1.0.3 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 1.0.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 1.0.1 | 2022-08-10 | [15496](https://github.com/airbytehq/airbyte/pull/15496) | Fix state emission in incremental sync | -| | 2022-08-10 | [15481](https://github.com/airbytehq/airbyte/pull/15481) | Fix data handling from WAL logs in CDC mode | -| 1.0.0 | 2022-08-05 | [15380](https://github.com/airbytehq/airbyte/pull/15380) | Change connector label to generally_available (requires [upgrading](https://docs.airbyte.com/operator-guides/upgrading-airbyte/) your Airbyte platform to `v0.40.0-alpha`) | -| 0.4.44 | 2022-08-05 | [15342](https://github.com/airbytehq/airbyte/pull/15342) | Adjust titles and descriptions in spec.json | -| 0.4.43 | 2022-08-03 | [15226](https://github.com/airbytehq/airbyte/pull/15226) | Make connectionTimeoutMs configurable through JDBC url parameters | -| 0.4.42 | 2022-08-03 | [15273](https://github.com/airbytehq/airbyte/pull/15273) | Fix a bug in `0.4.36` and correctly parse the CDC initial record waiting time | -| 0.4.41 | 2022-08-03 | [15077](https://github.com/airbytehq/airbyte/pull/15077) | Sync data from beginning if the LSN is no longer valid in CDC | -| | 2022-08-03 | [14903](https://github.com/airbytehq/airbyte/pull/14903) | Emit state messages more frequently (⛔ this version has a bug; use `1.0.1` instead) | -| 0.4.40 | 2022-08-03 | [15187](https://github.com/airbytehq/airbyte/pull/15187) | Add support for BCE dates/timestamps | -| | 2022-08-03 | [14534](https://github.com/airbytehq/airbyte/pull/14534) | Align regular and CDC integration tests and data mappers | -| 0.4.39 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.4.38 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | -| 0.4.37 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.4.36 | 2022-07-21 | [14451](https://github.com/airbytehq/airbyte/pull/14451) | Make initial CDC waiting time configurable (⛔ this version has a bug and will not work; use `0.4.42` instead) | -| 0.4.35 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.4.34 | 2022-07-17 | [13840](https://github.com/airbytehq/airbyte/pull/13840) | Added the ability to connect using different SSL modes and SSL certificates. | -| 0.4.33 | 2022-07-14 | [14586](https://github.com/airbytehq/airbyte/pull/14586) | Validate source JDBC url parameters | -| 0.4.32 | 2022-07-07 | [14694](https://github.com/airbytehq/airbyte/pull/14694) | Force to produce LEGACY state if the use stream capable feature flag is set to false | -| 0.4.31 | 2022-07-07 | [14447](https://github.com/airbytehq/airbyte/pull/14447) | Under CDC mode, retrieve only those tables included in the publications | -| 0.4.30 | 2022-06-30 | [14251](https://github.com/airbytehq/airbyte/pull/14251) | Use more simple and comprehensive query to get selectable tables | -| 0.4.29 | 2022-06-29 | [14265](https://github.com/airbytehq/airbyte/pull/14265) | Upgrade postgresql JDBC version to 42.3.5 | -| 0.4.28 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.4.26 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 0.4.25 | 2022-06-15 | [13823](https://github.com/airbytehq/airbyte/pull/13823) | Publish adaptive postgres source that enforces ssl on cloud + Debezium version upgrade to 1.9.2 from 1.4.2 | -| 0.4.24 | 2022-06-14 | [13549](https://github.com/airbytehq/airbyte/pull/13549) | Fixed truncated precision if the value of microseconds or seconds is 0 | -| 0.4.23 | 2022-06-13 | [13655](https://github.com/airbytehq/airbyte/pull/13745) | Fixed handling datetime cursors when upgrading from older versions of the connector | -| 0.4.22 | 2022-06-09 | [13655](https://github.com/airbytehq/airbyte/pull/13655) | Fixed bug with unsupported date-time datatypes during incremental sync | -| 0.4.21 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | -| 0.4.20 | 2022-06-02 | [13367](https://github.com/airbytehq/airbyte/pull/13367) | Added convertion hstore to json format | -| 0.4.19 | 2022-05-25 | [13166](https://github.com/airbytehq/airbyte/pull/13166) | Added timezone awareness and handle BC dates | -| 0.4.18 | 2022-05-25 | [13083](https://github.com/airbytehq/airbyte/pull/13083) | Add support for tsquey type | -| 0.4.17 | 2022-05-19 | [13016](https://github.com/airbytehq/airbyte/pull/13016) | CDC modify schema to allow null values | -| 0.4.16 | 2022-05-14 | [12840](https://github.com/airbytehq/airbyte/pull/12840) | Added custom JDBC parameters field | -| 0.4.15 | 2022-05-13 | [12834](https://github.com/airbytehq/airbyte/pull/12834) | Fix the bug that the connector returns empty catalog for Azure Postgres database | -| 0.4.14 | 2022-05-08 | [12689](https://github.com/airbytehq/airbyte/pull/12689) | Add table retrieval according to role-based `SELECT` privilege | -| 0.4.13 | 2022-05-05 | [10230](https://github.com/airbytehq/airbyte/pull/10230) | Explicitly set null value for field in json | -| 0.4.12 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.4.11 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.4.10 | 2022-04-08 | [11798](https://github.com/airbytehq/airbyte/pull/11798) | Fixed roles for fetching materialized view processing | -| 0.4.8 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.4.7 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.4.6 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | (unpublished) Add `-XX:+ExitOnOutOfMemoryError` JVM option | -| 0.4.5 | 2022-02-08 | [10173](https://github.com/airbytehq/airbyte/pull/10173) | Improved discovering tables in case if user does not have permissions to any table | -| 0.4.4 | 2022-01-26 | [9807](https://github.com/airbytehq/airbyte/pull/9807) | Update connector fields title/description | -| 0.4.3 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | -| 0.4.2 | 2022-01-13 | [9360](https://github.com/airbytehq/airbyte/pull/9360) | Added schema selection | -| 0.4.1 | 2022-01-05 | [9116](https://github.com/airbytehq/airbyte/pull/9116) | Added materialized views processing | -| 0.4.0 | 2021-12-13 | [8726](https://github.com/airbytehq/airbyte/pull/8726) | Support all Postgres types | -| 0.3.17 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.3.16 | 2021-11-28 | [7995](https://github.com/airbytehq/airbyte/pull/7995) | Fixed money type with amount > 1000 | -| 0.3.15 | 2021-11-26 | [8066](https://github.com/airbytehq/airbyte/pull/8266) | Fixed the case, when Views are not listed during schema discovery | -| 0.3.14 | 2021-11-17 | [8010](https://github.com/airbytehq/airbyte/pull/8010) | Added checking of privileges before table internal discovery | -| 0.3.13 | 2021-10-26 | [7339](https://github.com/airbytehq/airbyte/pull/7339) | Support or improve support for Interval, Money, Date, various geometric data types, inventory_items, and others | -| 0.3.12 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | -| 0.3.11 | 2021-09-02 | [5742](https://github.com/airbytehq/airbyte/pull/5742) | Add SSH Tunnel support | -| 0.3.9 | 2021-08-17 | [5304](https://github.com/airbytehq/airbyte/pull/5304) | Fix CDC OOM issue | -| 0.3.8 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | -| 0.3.4 | 2021-06-09 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | -| 0.3.3 | 2021-06-08 | [3960](https://github.com/airbytehq/airbyte/pull/3960) | Add method field in specification parameters | -| 0.3.2 | 2021-05-26 | [3179](https://github.com/airbytehq/airbyte/pull/3179) | Remove `isCDC` logging | -| 0.3.1 | 2021-04-21 | [2878](https://github.com/airbytehq/airbyte/pull/2878) | Set defined cursor for CDC | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | -| 0.2.7 | 2021-04-16 | [2923](https://github.com/airbytehq/airbyte/pull/2923) | SSL spec as optional | -| 0.2.6 | 2021-04-16 | [2757](https://github.com/airbytehq/airbyte/pull/2757) | Support SSL connection | -| 0.2.5 | 2021-04-12 | [2859](https://github.com/airbytehq/airbyte/pull/2859) | CDC bugfix | -| 0.2.4 | 2021-04-09 | [2548](https://github.com/airbytehq/airbyte/pull/2548) | Support CDC | -| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | -| 0.1.13 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | -| 0.1.12 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | -| 0.1.11 | 2021-01-25 | [1765](https://github.com/airbytehq/airbyte/pull/1765) | Add field titles to specification | -| 0.1.10 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | -| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | -| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | -| 0.1.7 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySql to use new JdbcSource | -| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | -| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:-------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.20 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages | +| 1.0.19 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Destinations | +| 1.0.18 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | +| 1.0.17 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| 1.0.16 | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Handle 24:00:00 value for Time column | +| 1.0.14 | 2022-10-03 | [17515](https://github.com/airbytehq/airbyte/pull/17515) | Fix an issue preventing connection using client certificate | +| 1.0.13 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | +| 1.0.12 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt postgres source | +| 1.0.11 | 2022-09-26 | [17131](https://github.com/airbytehq/airbyte/pull/17131) | Allow nullable columns to be used as cursor | +| 1.0.10 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 1.0.9 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | +| 1.0.8 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | +| 1.0.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source postgres | +| 1.0.6 | 2022-08-30 | [16138](https://github.com/airbytehq/airbyte/pull/16138) | Remove unnecessary logging | +| 1.0.5 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Add support for connection over SSL in CDC mode | +| 1.0.4 | 2022-08-23 | [15877](https://github.com/airbytehq/airbyte/pull/15877) | Fix temporal data type bug which was causing failure in CDC mode | +| 1.0.3 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 1.0.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 1.0.1 | 2022-08-10 | [15496](https://github.com/airbytehq/airbyte/pull/15496) | Fix state emission in incremental sync | +| | 2022-08-10 | [15481](https://github.com/airbytehq/airbyte/pull/15481) | Fix data handling from WAL logs in CDC mode | +| 1.0.0 | 2022-08-05 | [15380](https://github.com/airbytehq/airbyte/pull/15380) | Change connector label to generally_available (requires [upgrading](https://docs.airbyte.com/operator-guides/upgrading-airbyte/) your Airbyte platform to `v0.40.0-alpha`) | +| 0.4.44 | 2022-08-05 | [15342](https://github.com/airbytehq/airbyte/pull/15342) | Adjust titles and descriptions in spec.json | +| 0.4.43 | 2022-08-03 | [15226](https://github.com/airbytehq/airbyte/pull/15226) | Make connectionTimeoutMs configurable through JDBC url parameters | +| 0.4.42 | 2022-08-03 | [15273](https://github.com/airbytehq/airbyte/pull/15273) | Fix a bug in `0.4.36` and correctly parse the CDC initial record waiting time | +| 0.4.41 | 2022-08-03 | [15077](https://github.com/airbytehq/airbyte/pull/15077) | Sync data from beginning if the LSN is no longer valid in CDC | +| | 2022-08-03 | [14903](https://github.com/airbytehq/airbyte/pull/14903) | Emit state messages more frequently (⛔ this version has a bug; use `1.0.1` instead) | +| 0.4.40 | 2022-08-03 | [15187](https://github.com/airbytehq/airbyte/pull/15187) | Add support for BCE dates/timestamps | +| | 2022-08-03 | [14534](https://github.com/airbytehq/airbyte/pull/14534) | Align regular and CDC integration tests and data mappers | +| 0.4.39 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.4.38 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | +| 0.4.37 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.4.36 | 2022-07-21 | [14451](https://github.com/airbytehq/airbyte/pull/14451) | Make initial CDC waiting time configurable (⛔ this version has a bug and will not work; use `0.4.42` instead) | +| 0.4.35 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.4.34 | 2022-07-17 | [13840](https://github.com/airbytehq/airbyte/pull/13840) | Added the ability to connect using different SSL modes and SSL certificates. | +| 0.4.33 | 2022-07-14 | [14586](https://github.com/airbytehq/airbyte/pull/14586) | Validate source JDBC url parameters | +| 0.4.32 | 2022-07-07 | [14694](https://github.com/airbytehq/airbyte/pull/14694) | Force to produce LEGACY state if the use stream capable feature flag is set to false | +| 0.4.31 | 2022-07-07 | [14447](https://github.com/airbytehq/airbyte/pull/14447) | Under CDC mode, retrieve only those tables included in the publications | +| 0.4.30 | 2022-06-30 | [14251](https://github.com/airbytehq/airbyte/pull/14251) | Use more simple and comprehensive query to get selectable tables | +| 0.4.29 | 2022-06-29 | [14265](https://github.com/airbytehq/airbyte/pull/14265) | Upgrade postgresql JDBC version to 42.3.5 | +| 0.4.28 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.4.26 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 0.4.25 | 2022-06-15 | [13823](https://github.com/airbytehq/airbyte/pull/13823) | Publish adaptive postgres source that enforces ssl on cloud + Debezium version upgrade to 1.9.2 from 1.4.2 | +| 0.4.24 | 2022-06-14 | [13549](https://github.com/airbytehq/airbyte/pull/13549) | Fixed truncated precision if the value of microseconds or seconds is 0 | +| 0.4.23 | 2022-06-13 | [13655](https://github.com/airbytehq/airbyte/pull/13745) | Fixed handling datetime cursors when upgrading from older versions of the connector | +| 0.4.22 | 2022-06-09 | [13655](https://github.com/airbytehq/airbyte/pull/13655) | Fixed bug with unsupported date-time datatypes during incremental sync | +| 0.4.21 | 2022-06-06 | [13435](https://github.com/airbytehq/airbyte/pull/13435) | Adjust JDBC fetch size based on max memory and max row size | +| 0.4.20 | 2022-06-02 | [13367](https://github.com/airbytehq/airbyte/pull/13367) | Added convertion hstore to json format | +| 0.4.19 | 2022-05-25 | [13166](https://github.com/airbytehq/airbyte/pull/13166) | Added timezone awareness and handle BC dates | +| 0.4.18 | 2022-05-25 | [13083](https://github.com/airbytehq/airbyte/pull/13083) | Add support for tsquey type | +| 0.4.17 | 2022-05-19 | [13016](https://github.com/airbytehq/airbyte/pull/13016) | CDC modify schema to allow null values | +| 0.4.16 | 2022-05-14 | [12840](https://github.com/airbytehq/airbyte/pull/12840) | Added custom JDBC parameters field | +| 0.4.15 | 2022-05-13 | [12834](https://github.com/airbytehq/airbyte/pull/12834) | Fix the bug that the connector returns empty catalog for Azure Postgres database | +| 0.4.14 | 2022-05-08 | [12689](https://github.com/airbytehq/airbyte/pull/12689) | Add table retrieval according to role-based `SELECT` privilege | +| 0.4.13 | 2022-05-05 | [10230](https://github.com/airbytehq/airbyte/pull/10230) | Explicitly set null value for field in json | +| 0.4.12 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.4.11 | 2022-04-11 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.4.10 | 2022-04-08 | [11798](https://github.com/airbytehq/airbyte/pull/11798) | Fixed roles for fetching materialized view processing | +| 0.4.8 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.4.7 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.4.6 | 2022-02-14 | [10256](https://github.com/airbytehq/airbyte/pull/10256) | (unpublished) Add `-XX:+ExitOnOutOfMemoryError` JVM option | +| 0.4.5 | 2022-02-08 | [10173](https://github.com/airbytehq/airbyte/pull/10173) | Improved discovering tables in case if user does not have permissions to any table | +| 0.4.4 | 2022-01-26 | [9807](https://github.com/airbytehq/airbyte/pull/9807) | Update connector fields title/description | +| 0.4.3 | 2022-01-24 | [9554](https://github.com/airbytehq/airbyte/pull/9554) | Allow handling of java sql date in CDC | +| 0.4.2 | 2022-01-13 | [9360](https://github.com/airbytehq/airbyte/pull/9360) | Added schema selection | +| 0.4.1 | 2022-01-05 | [9116](https://github.com/airbytehq/airbyte/pull/9116) | Added materialized views processing | +| 0.4.0 | 2021-12-13 | [8726](https://github.com/airbytehq/airbyte/pull/8726) | Support all Postgres types | +| 0.3.17 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.3.16 | 2021-11-28 | [7995](https://github.com/airbytehq/airbyte/pull/7995) | Fixed money type with amount > 1000 | +| 0.3.15 | 2021-11-26 | [8066](https://github.com/airbytehq/airbyte/pull/8266) | Fixed the case, when Views are not listed during schema discovery | +| 0.3.14 | 2021-11-17 | [8010](https://github.com/airbytehq/airbyte/pull/8010) | Added checking of privileges before table internal discovery | +| 0.3.13 | 2021-10-26 | [7339](https://github.com/airbytehq/airbyte/pull/7339) | Support or improve support for Interval, Money, Date, various geometric data types, inventory_items, and others | +| 0.3.12 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | +| 0.3.11 | 2021-09-02 | [5742](https://github.com/airbytehq/airbyte/pull/5742) | Add SSH Tunnel support | +| 0.3.9 | 2021-08-17 | [5304](https://github.com/airbytehq/airbyte/pull/5304) | Fix CDC OOM issue | +| 0.3.8 | 2021-08-13 | [4699](https://github.com/airbytehq/airbyte/pull/4699) | Added json config validator | +| 0.3.4 | 2021-06-09 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for Kubernetes support | +| 0.3.3 | 2021-06-08 | [3960](https://github.com/airbytehq/airbyte/pull/3960) | Add method field in specification parameters | +| 0.3.2 | 2021-05-26 | [3179](https://github.com/airbytehq/airbyte/pull/3179) | Remove `isCDC` logging | +| 0.3.1 | 2021-04-21 | [2878](https://github.com/airbytehq/airbyte/pull/2878) | Set defined cursor for CDC | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | +| 0.2.7 | 2021-04-16 | [2923](https://github.com/airbytehq/airbyte/pull/2923) | SSL spec as optional | +| 0.2.6 | 2021-04-16 | [2757](https://github.com/airbytehq/airbyte/pull/2757) | Support SSL connection | +| 0.2.5 | 2021-04-12 | [2859](https://github.com/airbytehq/airbyte/pull/2859) | CDC bugfix | +| 0.2.4 | 2021-04-09 | [2548](https://github.com/airbytehq/airbyte/pull/2548) | Support CDC | +| 0.2.3 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | +| 0.1.13 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | +| 0.1.12 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | +| 0.1.11 | 2021-01-25 | [1765](https://github.com/airbytehq/airbyte/pull/1765) | Add field titles to specification | +| 0.1.10 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | +| 0.1.9 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | +| 0.1.8 | 2021-01-13 | [1588](https://github.com/airbytehq/airbyte/pull/1588) | Handle invalid numeric values in JDBC source | +| 0.1.7 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySql to use new JdbcSource | +| 0.1.6 | 2020-12-09 | [1172](https://github.com/airbytehq/airbyte/pull/1172) | Support incremental sync | +| 0.1.5 | 2020-11-30 | [1038](https://github.com/airbytehq/airbyte/pull/1038) | Change JDBC sources to discover more than standard schemas | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | From c466878fb99c1d7ab0f97329a90fe97c448f5d3e Mon Sep 17 00:00:00 2001 From: Malik Diarra Date: Tue, 25 Oct 2022 15:16:48 -0700 Subject: [PATCH 326/498] Refactor the buildDestinationRead function (#18446) Create a new buildDestinationRead function, that function can be called by top-level functions in the handler that already have a DestinationConnection object. --- .../server/handlers/DestinationHandler.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java index 27ec317b83c5..73515d520eb5 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java @@ -100,7 +100,7 @@ public DestinationRead createDestination(final DestinationCreate destinationCrea false); // read configuration from db - return buildDestinationRead(destinationId, spec); + return buildDestinationRead(configRepository.getDestinationConnection(destinationId), spec); } public void deleteDestination(final DestinationIdRequestBody destinationIdRequestBody) @@ -157,7 +157,8 @@ public DestinationRead updateDestination(final DestinationUpdate destinationUpda updatedDestination.getTombstone()); // read configuration from db - return buildDestinationRead(destinationUpdate.getDestinationId(), spec); + return buildDestinationRead( + configRepository.getDestinationConnection(destinationUpdate.getDestinationId()), spec); } public DestinationRead getDestination(final DestinationIdRequestBody destinationIdRequestBody) @@ -206,7 +207,7 @@ public DestinationReadList listDestinationsForWorkspace(final WorkspaceIdRequest continue; } - reads.add(buildDestinationRead(dci.getDestinationId())); + reads.add(buildDestinationRead(dci)); } return new DestinationReadList().destinations(reads); @@ -224,7 +225,7 @@ public DestinationReadList listDestinationsForDestinationDefinition(final Destin continue; } - reads.add(buildDestinationRead(destinationConnection.getDestinationId())); + reads.add(buildDestinationRead(destinationConnection)); } return new DestinationReadList().destinations(reads); @@ -236,7 +237,7 @@ public DestinationReadList searchDestinations(final DestinationSearch destinatio for (final DestinationConnection dci : configRepository.listDestinationConnection()) { if (!dci.getTombstone()) { - final DestinationRead destinationRead = buildDestinationRead(dci.getDestinationId()); + final DestinationRead destinationRead = buildDestinationRead(dci); if (connectionsHandler.matchSearch(destinationSearch, destinationRead)) { reads.add(destinationRead); } @@ -273,15 +274,20 @@ private void persistDestinationConnection(final String name, } private DestinationRead buildDestinationRead(final UUID destinationId) throws JsonValidationException, IOException, ConfigNotFoundException { - final ConnectorSpecification spec = getSpec(configRepository.getDestinationConnection(destinationId).getDestinationDefinitionId()); - return buildDestinationRead(destinationId, spec); + return buildDestinationRead(configRepository.getDestinationConnection(destinationId)); } - private DestinationRead buildDestinationRead(final UUID destinationId, final ConnectorSpecification spec) + private DestinationRead buildDestinationRead(final DestinationConnection destinationConnection) + throws JsonValidationException, IOException, ConfigNotFoundException { + final ConnectorSpecification spec = getSpec(destinationConnection.getDestinationDefinitionId()); + return buildDestinationRead(destinationConnection, spec); + } + + private DestinationRead buildDestinationRead(final DestinationConnection destinationConnection, final ConnectorSpecification spec) throws ConfigNotFoundException, IOException, JsonValidationException { // remove secrets from config before returning the read - final DestinationConnection dci = Jsons.clone(configRepository.getDestinationConnection(destinationId)); + final DestinationConnection dci = Jsons.clone(destinationConnection); dci.setConfiguration(secretsProcessor.prepareSecretsForOutput(dci.getConfiguration(), spec.getConnectionSpecification())); final StandardDestinationDefinition standardDestinationDefinition = From 9b57d972af277a66a6f5bbff21cfbc00ad34d30b Mon Sep 17 00:00:00 2001 From: N-o-Z Date: Wed, 26 Oct 2022 01:21:32 +0300 Subject: [PATCH 327/498] =?UTF-8?q?=F0=9F=90=9B=20Fix=20S3=20destination?= =?UTF-8?q?=20integration=20tests=20to=20use=20bucket=20path=20(#18031)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix S3 destination tests * Add Changelog * CR Fixes * add unit test * version bump * fix dockerfile * auto-bump connector version Co-authored-by: Edward Gao Co-authored-by: Octavia Squidington III --- .../seed/destination_definitions.yaml | 2 +- .../resources/seed/destination_specs.yaml | 2 +- .../destination/s3/BaseS3Destination.java | 3 +- .../destination/s3/S3BaseChecks.java | 17 +++---- .../destination/s3/S3StorageOperations.java | 5 +- .../destination/s3/S3BaseChecksTest.java | 49 +++++++++++++++++++ .../connectors/destination-s3/Dockerfile | 6 +-- docs/integrations/destinations/s3.md | 1 + 8 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 780c0dc5312f..eafa3ec2441f 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -257,7 +257,7 @@ - name: S3 destinationDefinitionId: 4816b78f-1489-44c1-9060-4b19d5fa9362 dockerRepository: airbyte/destination-s3 - dockerImageTag: 0.3.16 + dockerImageTag: 0.3.17 documentationUrl: https://docs.airbyte.com/integrations/destinations/s3 icon: s3.svg resourceRequirements: diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 1f71e6f53c4c..1186afc086a8 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -4664,7 +4664,7 @@ supported_destination_sync_modes: - "append" - "overwrite" -- dockerImage: "airbyte/destination-s3:0.3.16" +- dockerImage: "airbyte/destination-s3:0.3.17" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/s3" connectionSpecification: diff --git a/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/BaseS3Destination.java b/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/BaseS3Destination.java index 075faba07235..0119ad41511f 100644 --- a/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/BaseS3Destination.java +++ b/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/BaseS3Destination.java @@ -42,9 +42,8 @@ public AirbyteConnectionStatus check(final JsonNode config) { try { final S3DestinationConfig destinationConfig = configFactory.getS3DestinationConfig(config, storageProvider()); final AmazonS3 s3Client = destinationConfig.getS3Client(); - final S3StorageOperations storageOperations = new S3StorageOperations(nameTransformer, s3Client, destinationConfig); - S3BaseChecks.attemptS3WriteAndDelete(storageOperations, destinationConfig, destinationConfig.getBucketName()); + S3BaseChecks.testIAMUserHasListObjectPermission(s3Client, destinationConfig.getBucketName()); S3BaseChecks.testSingleUpload(s3Client, destinationConfig.getBucketName(), destinationConfig.getBucketPath()); S3BaseChecks.testMultipartUpload(s3Client, destinationConfig.getBucketName(), destinationConfig.getBucketPath()); diff --git a/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3BaseChecks.java b/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3BaseChecks.java index 1dc91a13ba8e..26ae7718c9dd 100644 --- a/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3BaseChecks.java +++ b/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3BaseChecks.java @@ -37,10 +37,8 @@ public static void attemptS3WriteAndDelete(final S3StorageOperations storageOper public static void testSingleUpload(final AmazonS3 s3Client, final String bucketName, final String bucketPath) { LOGGER.info("Started testing if all required credentials assigned to user for single file uploading"); - if (bucketPath.endsWith("/")) { - throw new RuntimeException("Bucket Path should not end with /"); - } - final String testFile = bucketPath + "/" + "test_" + System.currentTimeMillis(); + final var prefix = bucketPath.endsWith("/") ? bucketPath : bucketPath + "/"; + final String testFile = prefix + "test_" + System.currentTimeMillis(); try { s3Client.putObject(bucketName, testFile, "this is a test file"); } finally { @@ -51,10 +49,8 @@ public static void testSingleUpload(final AmazonS3 s3Client, final String bucket public static void testMultipartUpload(final AmazonS3 s3Client, final String bucketName, final String bucketPath) throws IOException { LOGGER.info("Started testing if all required credentials assigned to user for multipart upload"); - if (bucketPath.endsWith("/")) { - throw new RuntimeException("Bucket Path should not end with /"); - } - final String testFile = bucketPath + "/" + "test_" + System.currentTimeMillis(); + final var prefix = bucketPath.endsWith("/") ? bucketPath : bucketPath + "/"; + final String testFile = prefix + "test_" + System.currentTimeMillis(); final StreamTransferManager manager = StreamTransferManagerFactory.create(bucketName, testFile, s3Client).get(); boolean success = false; try (final MultiPartOutputStream outputStream = manager.getMultiPartOutputStreams().get(0); @@ -96,7 +92,7 @@ static void attemptS3WriteAndDelete(final S3StorageOperations storageOperations, final S3DestinationConfig s3Config, final String bucketPath, final AmazonS3 s3) { - final var prefix = bucketPath.isEmpty() ? "" : bucketPath + (bucketPath.endsWith("/") ? "" : "/"); + final var prefix = bucketPath.endsWith("/") ? bucketPath : bucketPath + "/"; final String outputTableName = prefix + "_airbyte_connection_test_" + UUID.randomUUID().toString().replaceAll("-", ""); attemptWriteAndDeleteS3Object(storageOperations, s3Config, outputTableName, s3); } @@ -106,8 +102,9 @@ private static void attemptWriteAndDeleteS3Object(final S3StorageOperations stor final String outputTableName, final AmazonS3 s3) { final var s3Bucket = s3Config.getBucketName(); + final var bucketPath = s3Config.getBucketPath(); - storageOperations.createBucketObjectIfNotExists(s3Bucket); + storageOperations.createBucketObjectIfNotExists(bucketPath); s3.putObject(s3Bucket, outputTableName, "check-content"); testIAMUserHasListObjectPermission(s3, s3Bucket); s3.deleteObject(s3Bucket, outputTableName); diff --git a/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3StorageOperations.java b/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3StorageOperations.java index a4d6370cc02d..ef333e649bfd 100644 --- a/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3StorageOperations.java +++ b/airbyte-integrations/bases/base-java-s3/src/main/java/io/airbyte/integrations/destination/s3/S3StorageOperations.java @@ -96,14 +96,15 @@ public String getBucketObjectPath(final String namespace, final String streamNam @Override public void createBucketObjectIfNotExists(final String objectPath) { final String bucket = s3Config.getBucketName(); + final String folderPath = objectPath.endsWith("/") ? objectPath : objectPath + "/"; if (!doesBucketExist(bucket)) { LOGGER.info("Bucket {} does not exist; creating...", bucket); s3Client.createBucket(bucket); LOGGER.info("Bucket {} has been created.", bucket); } - if (!s3Client.doesObjectExist(bucket, objectPath)) { + if (!s3Client.doesObjectExist(bucket, folderPath)) { LOGGER.info("Storage Object {}/{} does not exist in bucket; creating...", bucket, objectPath); - s3Client.putObject(bucket, objectPath.endsWith("/") ? objectPath : objectPath + "/", ""); + s3Client.putObject(bucket, folderPath, ""); LOGGER.info("Storage Object {}/{} has been created in bucket.", bucket, objectPath); } } diff --git a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java new file mode 100644 index 000000000000..e1e478a76730 --- /dev/null +++ b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java @@ -0,0 +1,49 @@ +package io.airbyte.integrations.destination.s3; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.ArgumentMatchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ListObjectsRequest; +import io.airbyte.integrations.destination.s3.util.S3NameTransformer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; + +public class S3BaseChecksTest { + + private AmazonS3 s3Client; + + @BeforeEach + public void setup() { + s3Client = mock(AmazonS3.class); + } + + @Test + public void attemptWriteAndDeleteS3Object_should_createSpecificFiles() { + S3DestinationConfig config = new S3DestinationConfig( + null, + "test_bucket", + "test/bucket/path", + null, + null, + null, + null, + s3Client + ); + S3StorageOperations operations = new S3StorageOperations(new S3NameTransformer(), s3Client, config); + when(s3Client.doesObjectExist("test_bucket", "test/bucket/path/")).thenReturn(false); + + S3BaseChecks.attemptS3WriteAndDelete(operations, config, "test/bucket/path"); + + verify(s3Client).putObject("test_bucket", "test/bucket/path/", ""); + verify(s3Client).putObject(eq("test_bucket"), startsWith("test/bucket/path/_airbyte_connection_test_"), anyString()); + verify(s3Client).listObjects(ArgumentMatchers.argThat(request -> "test_bucket".equals(request.getBucketName()))); + verify(s3Client).deleteObject(eq("test_bucket"), startsWith("test/bucket/path/_airbyte_connection_test_")); + } +} diff --git a/airbyte-integrations/connectors/destination-s3/Dockerfile b/airbyte-integrations/connectors/destination-s3/Dockerfile index 96b7b1227ee0..eb28aa69ba80 100644 --- a/airbyte-integrations/connectors/destination-s3/Dockerfile +++ b/airbyte-integrations/connectors/destination-s3/Dockerfile @@ -22,8 +22,8 @@ RUN /bin/bash -c 'set -e && \ yum install lzop lzo lzo-dev -y; \ elif [ "$ARCH" == "aarch64" ] || [ "$ARCH" = "arm64" ]; then \ echo "$ARCH" && \ - yum group install -y "Development Tools" \ - yum install lzop lzo lzo-dev wget curl unzip zip maven git -y; \ + yum group install -y "Development Tools"; \ + yum install lzop lzo lzo-dev wget curl unzip zip maven git which -y; \ wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz -P /tmp; \ cd /tmp && tar xvfz lzo-2.10.tar.gz; \ cd /tmp/lzo-2.10/ && ./configure --enable-shared --prefix /usr/local/lzo-2.10; \ @@ -40,5 +40,5 @@ RUN /bin/bash -c 'set -e && \ echo "unknown arch" ;\ fi' -LABEL io.airbyte.version=0.3.16 +LABEL io.airbyte.version=0.3.17 LABEL io.airbyte.name=airbyte/destination-s3 diff --git a/docs/integrations/destinations/s3.md b/docs/integrations/destinations/s3.md index 882cf1929561..946b1162ce6d 100644 --- a/docs/integrations/destinations/s3.md +++ b/docs/integrations/destinations/s3.md @@ -323,6 +323,7 @@ In order for everything to work correctly, it is also necessary that the user wh | Version | Date | Pull Request | Subject | |:--------|:-----------|:-----------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------| +| 0.3.17 | 2022-10-15 | [\#18031](https://github.com/airbytehq/airbyte/pull/18031) | Fix integration tests to use bucket path | | 0.3.16 | 2022-10-03 | [\#17340](https://github.com/airbytehq/airbyte/pull/17340) | Enforced encrypted only traffic to S3 buckets and check logic | | 0.3.15 | 2022-09-01 | [\#16243](https://github.com/airbytehq/airbyte/pull/16243) | Fix Json to Avro conversion when there is field name clash from combined restrictions (`anyOf`, `oneOf`, `allOf` fields). | | 0.3.14 | 2022-08-24 | [\#15207](https://github.com/airbytehq/airbyte/pull/15207) | Fix S3 bucket path to be used for check. | From ba21d9048c395686be97012fd6f59359870c9671 Mon Sep 17 00:00:00 2001 From: Edward Gao Date: Tue, 25 Oct 2022 15:41:19 -0700 Subject: [PATCH 328/498] fix formatting + imports (#18457) --- .../integrations/destination/s3/S3BaseChecksTest.java | 9 ++++++--- .../java/io/airbyte/integrations/base/ssh/SshTunnel.java | 1 - .../integrations/base/ssh/SshWrappedDestination.java | 2 -- .../airbyte/integrations/base/ssh/SshWrappedSource.java | 2 -- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java index e1e478a76730..69828a08e466 100644 --- a/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java +++ b/airbyte-integrations/bases/base-java-s3/src/test/java/io/airbyte/integrations/destination/s3/S3BaseChecksTest.java @@ -1,8 +1,11 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + package io.airbyte.integrations.destination.s3; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.matches; import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -34,8 +37,7 @@ public void attemptWriteAndDeleteS3Object_should_createSpecificFiles() { null, null, null, - s3Client - ); + s3Client); S3StorageOperations operations = new S3StorageOperations(new S3NameTransformer(), s3Client, config); when(s3Client.doesObjectExist("test_bucket", "test/bucket/path/")).thenReturn(false); @@ -46,4 +48,5 @@ public void attemptWriteAndDeleteS3Object_should_createSpecificFiles() { verify(s3Client).listObjects(ArgumentMatchers.argThat(request -> "test_bucket".equals(request.getBucketName()))); verify(s3Client).deleteObject(eq("test_bucket"), startsWith("test/bucket/path/_airbyte_connection_test_")); } + } diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java index f6d908c3f311..261699be2168 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshTunnel.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Preconditions; -import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.functional.CheckedConsumer; import io.airbyte.commons.functional.CheckedFunction; import io.airbyte.commons.json.Jsons; diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java index 38c6f20ed3cb..56b489d8eaca 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedDestination.java @@ -6,7 +6,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.resources.MoreResources; import io.airbyte.integrations.base.AirbyteMessageConsumer; @@ -19,7 +18,6 @@ import io.airbyte.protocol.models.ConnectorSpecification; import java.util.List; import java.util.function.Consumer; -import org.apache.sshd.common.SshException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java index 1596b29340b3..1c955ab09f65 100644 --- a/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java +++ b/airbyte-integrations/bases/base-java/src/main/java/io/airbyte/integrations/base/ssh/SshWrappedSource.java @@ -5,7 +5,6 @@ package io.airbyte.integrations.base.ssh; import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.exceptions.ConnectionErrorException; import io.airbyte.commons.util.AutoCloseableIterator; import io.airbyte.commons.util.AutoCloseableIterators; import io.airbyte.integrations.base.AirbyteTraceMessageUtility; @@ -17,7 +16,6 @@ import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConnectorSpecification; import java.util.List; -import org.apache.sshd.common.SshException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 5361f143226ad6fa34308da9c23208071a3c0a5f Mon Sep 17 00:00:00 2001 From: Anne <102554163+alovew@users.noreply.github.com> Date: Tue, 25 Oct 2022 19:47:37 -0700 Subject: [PATCH 329/498] RefreshSchema method on RefreshSchema activity (#18460) * Add refreshSchema method to RefreshSchema activity --- .../temporal/sync/RefreshSchemaActivity.java | 5 +++++ .../sync/RefreshSchemaActivityImpl.java | 15 +++++++++++++- .../activities/RefreshSchemaActivityTest.java | 20 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java index aa283703b844..3da403ea19ed 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivity.java @@ -4,6 +4,9 @@ package io.airbyte.workers.temporal.sync; +import io.airbyte.api.client.invoker.generated.ApiException; +import io.airbyte.config.persistence.ConfigNotFoundException; +import io.airbyte.validation.json.JsonValidationException; import io.temporal.activity.ActivityInterface; import io.temporal.activity.ActivityMethod; import java.io.IOException; @@ -15,4 +18,6 @@ public interface RefreshSchemaActivity { @ActivityMethod boolean shouldRefreshSchema(UUID sourceCatalogId) throws IOException; + public void refreshSchema(UUID sourceCatalogId) throws JsonValidationException, ConfigNotFoundException, IOException, ApiException; + } diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java index 9def0b60ce1a..bc12f37d0ff6 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/sync/RefreshSchemaActivityImpl.java @@ -7,6 +7,9 @@ import static io.airbyte.metrics.lib.ApmTraceConstants.ACTIVITY_TRACE_OPERATION_NAME; import datadog.trace.api.Trace; +import io.airbyte.api.client.generated.SourceApi; +import io.airbyte.api.client.invoker.generated.ApiException; +import io.airbyte.api.client.model.generated.SourceDiscoverSchemaRequestBody; import io.airbyte.config.ActorCatalogFetchEvent; import io.airbyte.config.persistence.ConfigRepository; import java.io.IOException; @@ -18,8 +21,11 @@ public class RefreshSchemaActivityImpl implements RefreshSchemaActivity { private final Optional configRepository; - public RefreshSchemaActivityImpl(Optional configRepository) { + private final SourceApi sourceApi; + + public RefreshSchemaActivityImpl(Optional configRepository, SourceApi sourceApi) { this.configRepository = configRepository; + this.sourceApi = sourceApi; } @Override @@ -33,6 +39,13 @@ public boolean shouldRefreshSchema(UUID sourceCatalogId) throws IOException { return !schemaRefreshRanRecently(sourceCatalogId); } + @Override + public void refreshSchema(UUID sourceCatalogId) throws ApiException { + SourceDiscoverSchemaRequestBody requestBody = + new SourceDiscoverSchemaRequestBody().sourceId(sourceCatalogId).disableCache(true); + sourceApi.discoverSchemaForSource(requestBody); + } + private boolean schemaRefreshRanRecently(UUID sourceCatalogId) throws IOException { Optional mostRecentFetchEvent = configRepository.get().getMostRecentActorCatalogFetchEventForSource(sourceCatalogId); diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java index d7bbdb739761..26086b1dd0d9 100644 --- a/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java +++ b/airbyte-workers/src/test/java/io/airbyte/workers/temporal/scheduling/activities/RefreshSchemaActivityTest.java @@ -5,8 +5,13 @@ package io.airbyte.workers.temporal.scheduling.activities; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.airbyte.api.client.generated.SourceApi; +import io.airbyte.api.client.invoker.generated.ApiException; +import io.airbyte.api.client.model.generated.SourceDiscoverSchemaRequestBody; import io.airbyte.config.ActorCatalogFetchEvent; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.workers.temporal.sync.RefreshSchemaActivityImpl; @@ -24,6 +29,9 @@ class RefreshSchemaActivityTest { static private ConfigRepository mConfigRepository; + + static private SourceApi mSourceApi; + static private RefreshSchemaActivityImpl refreshSchemaActivity; static private final UUID SOURCE_ID = UUID.randomUUID(); @@ -31,7 +39,8 @@ class RefreshSchemaActivityTest { @BeforeEach void setUp() { mConfigRepository = mock(ConfigRepository.class); - refreshSchemaActivity = new RefreshSchemaActivityImpl(Optional.of(mConfigRepository)); + mSourceApi = mock(SourceApi.class); + refreshSchemaActivity = new RefreshSchemaActivityImpl(Optional.of(mConfigRepository), mSourceApi); } @Test @@ -56,4 +65,13 @@ void testShouldRefreshSchemaRecentRefreshLessThan24HoursAgo() throws IOException Assertions.assertThat(false).isEqualTo(refreshSchemaActivity.shouldRefreshSchema(SOURCE_ID)); } + @Test + void testRefreshSchema() throws ApiException { + UUID sourceId = UUID.randomUUID(); + refreshSchemaActivity.refreshSchema(sourceId); + SourceDiscoverSchemaRequestBody requestBody = + new SourceDiscoverSchemaRequestBody().sourceId(sourceId).disableCache(true); + verify(mSourceApi, times(1)).discoverSchemaForSource(requestBody); + } + } From 96dfa8715bc71202adc296e9f4e3beeb4aae86e2 Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Wed, 26 Oct 2022 08:30:38 +0200 Subject: [PATCH 330/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Tiktok=20Market?= =?UTF-8?q?ing:=20fix=20test=20timeout=20(#18433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../source-tiktok-marketing/acceptance-test-config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml b/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml index 971be94ebdb6..98613225c304 100644 --- a/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-tiktok-marketing/acceptance-test-config.yml @@ -68,23 +68,23 @@ tests: incremental: - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_basic.json" - timeout_seconds: 3600 + timeout_seconds: 7200 future_state_path: "integration_tests/abnormal_state.json" - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_reports_daily.json" - timeout_seconds: 3600 + timeout_seconds: 7200 future_state_path: "integration_tests/abnormal_state.json" # LIFETIME granularity: does not support incremental sync full_refresh: - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_basic.json" - timeout_seconds: 3600 + timeout_seconds: 7200 ignored_fields: # Important: sometimes some streams does not return the same records in subsequent syncs "ad_groups": ["dayparting", "enable_search_result", "display_mode", "schedule_infos", "feed_type", "status" ] - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_reports_daily.json" - timeout_seconds: 3600 + timeout_seconds: 7200 - config_path: "secrets/prod_config.json" configured_catalog_path: "integration_tests/streams_reports_lifetime.json" - timeout_seconds: 3600 \ No newline at end of file + timeout_seconds: 7200 \ No newline at end of file From 3fd3cf6eeda32ebe9d2029bd63f3fb5c9eb47aa7 Mon Sep 17 00:00:00 2001 From: VitaliiMaltsev <39538064+VitaliiMaltsev@users.noreply.github.com> Date: Wed, 26 Oct 2022 11:18:20 +0300 Subject: [PATCH 331/498] Postgres Source : updated docs for CDC setup (#18291) * Postgres Source updated docs for CDC setup * fixed typo * fixed typo * rephrased * rephrased --- docs/integrations/sources/postgres.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index a0fb80c91a52..40771e5f74bf 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -232,12 +232,14 @@ SELECT pg_create_logical_replication_slot('airbyte_slot', 'wal2json'); For each table you want to replicate with CDC, add the replication identity (the method of distinguishing between rows) first: -To use primary keys to distinguish between rows, run: - +To use primary keys to distinguish between rows for tables that don't have a large amount of data per row, run: ``` ALTER TABLE tbl1 REPLICA IDENTITY DEFAULT; ``` - +In case your tables use data types that support [TOAST](https://www.postgresql.org/docs/current/storage-toast.html) and have very large field values, use: +``` +ALTER TABLE tbl1 REPLICA IDENTITY FULL; +``` After setting the replication identity, run: ``` From 79f2118fc56ada722802f9229f1055846c014245 Mon Sep 17 00:00:00 2001 From: oneshcheret <33333155+sashaNeshcheret@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:26:46 +0300 Subject: [PATCH 332/498] Source postgres: disable allow and prefer ssl modes in CDC mode (#18256) * Source postgres: disable ssl_mode allow and prefer in CDC mode * Source postgres: code review fixes * Source postgres: code review fixes * Source postgres: improve ssl mode handling * Source postgres: bump version * auto-bump connector version * Source postgres: bump version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 4 +-- .../src/main/resources/seed/source_specs.yaml | 4 +-- .../source-alloydb-strict-encrypt/Dockerfile | 2 +- .../connectors/source-alloydb/Dockerfile | 2 +- .../source-postgres-strict-encrypt/Dockerfile | 2 +- .../connectors/source-postgres/Dockerfile | 2 +- .../source/postgres/PostgresSource.java | 21 ++++++++++++ .../postgres/PostgresSourceSSLTest.java | 32 +++++++++++++++++++ docs/integrations/sources/alloydb.md | 1 + docs/integrations/sources/postgres.md | 1 + 10 files changed, 63 insertions(+), 8 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index f4f283b7565b..93af0f4ac89a 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -17,7 +17,7 @@ - name: AlloyDB for PostgreSQL sourceDefinitionId: 1fa90628-2b9e-11ed-a261-0242ac120002 dockerRepository: airbyte/source-alloydb - dockerImageTag: 1.0.15 + dockerImageTag: 1.0.16 documentationUrl: https://docs.airbyte.com/integrations/sources/alloydb icon: alloydb.svg sourceType: database @@ -864,7 +864,7 @@ - name: Postgres sourceDefinitionId: decd338e-5647-4c0b-adf4-da0e75f5a750 dockerRepository: airbyte/source-postgres - dockerImageTag: 1.0.20 + dockerImageTag: 1.0.21 documentationUrl: https://docs.airbyte.com/integrations/sources/postgres icon: postgresql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index dd388830bbca..e42c740451bf 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -198,7 +198,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-alloydb:1.0.15" +- dockerImage: "airbyte/source-alloydb:1.0.16" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: @@ -8792,7 +8792,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-postgres:1.0.20" +- dockerImage: "airbyte/source-postgres:1.0.21" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/postgres" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile index 8410e032a8cd..4eae2bdf2c0a 100644 --- a/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-alloydb-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-alloydb-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.15 +LABEL io.airbyte.version=1.0.16 LABEL io.airbyte.name=airbyte/source-alloydb-strict-encrypt diff --git a/airbyte-integrations/connectors/source-alloydb/Dockerfile b/airbyte-integrations/connectors/source-alloydb/Dockerfile index 284a1d21d21f..7bffadf916b1 100644 --- a/airbyte-integrations/connectors/source-alloydb/Dockerfile +++ b/airbyte-integrations/connectors/source-alloydb/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-alloydb COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.15 +LABEL io.airbyte.version=1.0.16 LABEL io.airbyte.name=airbyte/source-alloydb diff --git a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile index 41f77a3f95ca..4730a7787898 100644 --- a/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres-strict-encrypt/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.20 +LABEL io.airbyte.version=1.0.21 LABEL io.airbyte.name=airbyte/source-postgres-strict-encrypt diff --git a/airbyte-integrations/connectors/source-postgres/Dockerfile b/airbyte-integrations/connectors/source-postgres/Dockerfile index 04c905dd55c4..fe732c06f6c5 100644 --- a/airbyte-integrations/connectors/source-postgres/Dockerfile +++ b/airbyte-integrations/connectors/source-postgres/Dockerfile @@ -16,5 +16,5 @@ ENV APPLICATION source-postgres COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.20 +LABEL io.airbyte.version=1.0.21 LABEL io.airbyte.name=airbyte/source-postgres diff --git a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java index 0facfaadf8f8..ddabe5f727b7 100644 --- a/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java +++ b/airbyte-integrations/connectors/source-postgres/src/main/java/io/airbyte/integrations/source/postgres/PostgresSource.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import io.airbyte.commons.features.EnvVariableFeatureFlags; import io.airbyte.commons.features.FeatureFlags; import io.airbyte.commons.functional.CheckedConsumer; @@ -78,6 +79,7 @@ public class PostgresSource extends AbstractJdbcSource implements Sour private static final int INTERMEDIATE_STATE_EMISSION_FREQUENCY = 10_000; public static final String PARAM_SSLMODE = "sslmode"; + public static final String SSL_MODE = "ssl_mode"; public static final String PARAM_SSL = "ssl"; public static final String PARAM_SSL_TRUE = "true"; public static final String PARAM_SSL_FALSE = "false"; @@ -87,11 +89,13 @@ public class PostgresSource extends AbstractJdbcSource implements Sour public static final String CA_CERTIFICATE_PATH = "ca_certificate_path"; public static final String SSL_KEY = "sslkey"; public static final String SSL_PASSWORD = "sslpassword"; + public static final String MODE = "mode"; static final Map SSL_JDBC_PARAMETERS = ImmutableMap.of( "ssl", "true", "sslmode", "require"); private List schemas; private final FeatureFlags featureFlags; + private static final Set INVALID_CDC_SSL_MODES = ImmutableSet.of("allow", "prefer"); public static Source sshWrappedSource() { return new SshWrappedSource(new PostgresSource(), JdbcUtils.HOST_LIST_KEY, JdbcUtils.PORT_LIST_KEY); @@ -464,6 +468,23 @@ public static void main(final String[] args) throws Exception { LOGGER.info("completed source: {}", PostgresSource.class); } + @Override + public AirbyteConnectionStatus check(final JsonNode config) throws Exception { + if (PostgresUtils.isCdc(config)) { + if (config.has(SSL_MODE) && config.get(SSL_MODE).has(MODE)){ + String sslModeValue = config.get(SSL_MODE).get(MODE).asText(); + if (INVALID_CDC_SSL_MODES.contains(sslModeValue)) { + return new AirbyteConnectionStatus() + .withStatus(Status.FAILED) + .withMessage(String.format( + "In CDC replication mode ssl value '%s' is invalid. Please use one of the following SSL modes: disable, require, verify-ca, verify-full", + sslModeValue)); + } + } + } + return super.check(config); + } + @Override protected String toSslJdbcParam(final SslMode sslMode) { return toSslJdbcParamInternal(sslMode); diff --git a/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceSSLTest.java b/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceSSLTest.java index 2b4cdce416a8..b4b7aec93524 100644 --- a/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceSSLTest.java +++ b/airbyte-integrations/connectors/source-postgres/src/test/java/io/airbyte/integrations/source/postgres/PostgresSourceSSLTest.java @@ -25,6 +25,7 @@ import io.airbyte.db.factory.DatabaseDriver; import io.airbyte.db.jdbc.JdbcUtils; import io.airbyte.protocol.models.AirbyteCatalog; +import io.airbyte.protocol.models.AirbyteConnectionStatus; import io.airbyte.protocol.models.AirbyteMessage; import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.CatalogHelpers; @@ -35,6 +36,7 @@ import io.airbyte.test.utils.PostgreSQLContainerHelper; import java.math.BigDecimal; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -197,4 +199,34 @@ void testIsCdc() { assertTrue(PostgresUtils.isCdc(config)); } + @Test + void testAllowSSLWithCdcReplicationMethod() throws Exception { + + JsonNode config = getCDCAndSslModeConfig("allow"); + + final AirbyteConnectionStatus actual = new PostgresSource().check(config); + assertEquals(AirbyteConnectionStatus.Status.FAILED, actual.getStatus()); + assertTrue(actual.getMessage().contains("In CDC replication mode ssl value 'allow' is invalid")); + } + + @Test + void testPreferSSLWithCdcReplicationMethod() throws Exception { + + JsonNode config = getCDCAndSslModeConfig("prefer"); + + final AirbyteConnectionStatus actual = new PostgresSource().check(config); + assertEquals(AirbyteConnectionStatus.Status.FAILED, actual.getStatus()); + assertTrue(actual.getMessage().contains("In CDC replication mode ssl value 'prefer' is invalid")); + } + + private JsonNode getCDCAndSslModeConfig(String sslMode) { + return Jsons.jsonNode(ImmutableMap.builder() + .put(JdbcUtils.SSL_KEY, true) + .put(JdbcUtils.SSL_MODE_KEY, Map.of(JdbcUtils.MODE_KEY, sslMode)) + .put("replication_method", Map.of("method", "CDC", + "replication_slot", "slot", + "publication", "ab_pub")) + .build()); + } + } diff --git a/docs/integrations/sources/alloydb.md b/docs/integrations/sources/alloydb.md index fce3378b4c40..3b50d9ae8bf1 100644 --- a/docs/integrations/sources/alloydb.md +++ b/docs/integrations/sources/alloydb.md @@ -331,6 +331,7 @@ According to Postgres [documentation](https://www.postgresql.org/docs/14/datatyp | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:-------------------------------------------------| +| 1.0.16 | 2022-10-25 | [18256](https://github.com/airbytehq/airbyte/pull/18256) | Disable allow and prefer ssl modes in CDC mode | | | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | | 1.0.15 | 2022-10-11 | [17782](https://github.com/airbytehq/airbyte/pull/17782) | Align with Postgres source v.1.0.15 | | 1.0.0 | 2022-09-15 | [16776](https://github.com/airbytehq/airbyte/pull/16776) | Align with strict-encrypt version | diff --git a/docs/integrations/sources/postgres.md b/docs/integrations/sources/postgres.md index 40771e5f74bf..fd9ed9734971 100644 --- a/docs/integrations/sources/postgres.md +++ b/docs/integrations/sources/postgres.md @@ -400,6 +400,7 @@ The root causes is that the WALs needed for the incremental sync has been remove | Version | Date | Pull Request | Subject | |:--------|:-----------|:-------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.21 | 2022-10-25 | [18256](https://github.com/airbytehq/airbyte/pull/18256) | Disable allow and prefer ssl modes in CDC mode | | 1.0.20 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages | | 1.0.19 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Destinations | | 1.0.18 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | From 14175520025ba46752835d1ce1bdaa003f39991f Mon Sep 17 00:00:00 2001 From: Denys Davydov Date: Wed, 26 Oct 2022 14:06:23 +0300 Subject: [PATCH 333/498] Source Twilio: implement slicing (#18423) * #804 source twilio: implement slicing * #804 source twilio - format code * #804 source twilio - implement slicing * #804 source twilio: configure incremental streams * #804 fix expected records * #804 extend Calls stream schema * #804 source twilio: allow configurable slice for SATs * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-twilio/Dockerfile | 2 +- .../source-twilio/acceptance-test-config.yml | 2 + .../integration_tests/expected_records.txt | 24 ++- .../source_twilio/schemas/calls.json | 6 + .../source-twilio/source_twilio/source.py | 1 + .../source-twilio/source_twilio/streams.py | 178 +++++++++++------- .../source-twilio/unit_tests/test_streams.py | 65 ++++--- docs/integrations/sources/twilio.md | 1 + 10 files changed, 180 insertions(+), 103 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 93af0f4ac89a..cb3f6a0e3597 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1111,7 +1111,7 @@ - name: Twilio sourceDefinitionId: b9dc6155-672e-42ea-b10d-9f1f1fb95ab1 dockerRepository: airbyte/source-twilio - dockerImageTag: 0.1.12 + dockerImageTag: 0.1.13 documentationUrl: https://docs.airbyte.com/integrations/sources/twilio icon: twilio.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index e42c740451bf..34b6ea17cc2d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -11610,7 +11610,7 @@ oauthFlowOutputParameters: - - "token" - - "key" -- dockerImage: "airbyte/source-twilio:0.1.12" +- dockerImage: "airbyte/source-twilio:0.1.13" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/twilio" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-twilio/Dockerfile b/airbyte-integrations/connectors/source-twilio/Dockerfile index 1b7b749e874c..321fa362410a 100644 --- a/airbyte-integrations/connectors/source-twilio/Dockerfile +++ b/airbyte-integrations/connectors/source-twilio/Dockerfile @@ -12,5 +12,5 @@ COPY main.py ./ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.12 +LABEL io.airbyte.version=0.1.13 LABEL io.airbyte.name=airbyte/source-twilio diff --git a/airbyte-integrations/connectors/source-twilio/acceptance-test-config.yml b/airbyte-integrations/connectors/source-twilio/acceptance-test-config.yml index cd4f3a170c7a..e970764760a7 100644 --- a/airbyte-integrations/connectors/source-twilio/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-twilio/acceptance-test-config.yml @@ -16,6 +16,8 @@ tests: configured_catalog_path: "integration_tests/no_empty_streams_catalog.json" expect_records: path: "integration_tests/expected_records.txt" + empty_streams: ["alerts"] + timeout_seconds: 600 incremental: - config_path: "secrets/config.json" # usage records stream produces and error if cursor date gte than current date diff --git a/airbyte-integrations/connectors/source-twilio/integration_tests/expected_records.txt b/airbyte-integrations/connectors/source-twilio/integration_tests/expected_records.txt index f3353926de87..7e0f8873a16e 100644 --- a/airbyte-integrations/connectors/source-twilio/integration_tests/expected_records.txt +++ b/airbyte-integrations/connectors/source-twilio/integration_tests/expected_records.txt @@ -31,7 +31,6 @@ {"stream": "available_phone_number_countries", "data": {"country_code": "DK", "country": "Denmark", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/DK.json", "beta": false, "subresource_uris": {"toll_free": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/DK/TollFree.json", "mobile": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/DK/Mobile.json"}}, "emitted_at": 1664560270815} {"stream": "available_phone_number_countries", "data": {"country_code": "UG", "country": "Uganda", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/UG.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/UG/Local.json", "toll_free": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/UG/TollFree.json"}}, "emitted_at": 1664560270815} {"stream": "available_phone_number_countries", "data": {"country_code": "MX", "country": "Mexico", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/MX.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/MX/Local.json", "toll_free": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/MX/TollFree.json"}}, "emitted_at": 1664560270816} -{"stream": "available_phone_number_countries", "data": {"country_code": "IS", "country": "Iceland", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/IS.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/IS/Local.json", "mobile": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/IS/Mobile.json"}}, "emitted_at": 1664560270816} {"stream": "available_phone_number_countries", "data": {"country_code": "DZ", "country": "Algeria", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/DZ.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/DZ/Local.json"}}, "emitted_at": 1664560270816} {"stream": "available_phone_number_countries", "data": {"country_code": "ZA", "country": "South Africa", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/ZA.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/ZA/Local.json", "mobile": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/ZA/Mobile.json"}}, "emitted_at": 1664560270816} {"stream": "available_phone_number_countries", "data": {"country_code": "HR", "country": "Croatia", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/HR.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/HR/Local.json", "toll_free": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/AvailablePhoneNumbers/HR/TollFree.json"}}, "emitted_at": 1664560270816} @@ -98,7 +97,6 @@ {"stream": "available_phone_number_countries", "data": {"country_code": "DK", "country": "Denmark", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/DK.json", "beta": false, "subresource_uris": {"toll_free": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/DK/TollFree.json", "mobile": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/DK/Mobile.json"}}, "emitted_at": 1664560271158} {"stream": "available_phone_number_countries", "data": {"country_code": "UG", "country": "Uganda", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/UG.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/UG/Local.json", "toll_free": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/UG/TollFree.json"}}, "emitted_at": 1664560271158} {"stream": "available_phone_number_countries", "data": {"country_code": "MX", "country": "Mexico", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/MX.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/MX/Local.json", "toll_free": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/MX/TollFree.json"}}, "emitted_at": 1664560271158} -{"stream": "available_phone_number_countries", "data": {"country_code": "IS", "country": "Iceland", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/IS.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/IS/Local.json", "mobile": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/IS/Mobile.json"}}, "emitted_at": 1664560271158} {"stream": "available_phone_number_countries", "data": {"country_code": "DZ", "country": "Algeria", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/DZ.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/DZ/Local.json"}}, "emitted_at": 1664560271158} {"stream": "available_phone_number_countries", "data": {"country_code": "ZA", "country": "South Africa", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/ZA.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/ZA/Local.json", "mobile": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/ZA/Mobile.json"}}, "emitted_at": 1664560271158} {"stream": "available_phone_number_countries", "data": {"country_code": "HR", "country": "Croatia", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/HR.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/HR/Local.json", "toll_free": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/HR/TollFree.json"}}, "emitted_at": 1664560271158} @@ -145,17 +143,17 @@ {"stream": "available_phone_number_countries", "data": {"country_code": "AR", "country": "Argentina", "uri": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/AR.json", "beta": false, "subresource_uris": {"local": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/AR/Local.json", "toll_free": "/2010-04-01/Accounts/AC4cac489c46197c9ebc91c840120a4dee/AvailablePhoneNumbers/AR/TollFree.json"}}, "emitted_at": 1664560271163} {"stream": "incoming_phone_numbers", "data": {"sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "friendly_name": "2FA Number - PLEASE DO NOT TOUCH. Use another number for anythin", "phone_number": "+12056561170", "voice_url": "https://handler.twilio.com/twiml/EH7af811843f38093d724a5c2e80b3eabe", "voice_method": "POST", "voice_fallback_url": "", "voice_fallback_method": "POST", "voice_caller_id_lookup": false, "date_created": "2020-12-11T04:28:40Z", "date_updated": "2022-09-23T14:47:41Z", "sms_url": "https://webhooks.twilio.com/v1/Accounts/ACdade166c12e160e9ed0a6088226718fb/Flows/FWbd726b7110b21294a9f27a47f4ab0080", "sms_method": "POST", "sms_fallback_url": "", "sms_fallback_method": "POST", "address_requirements": "none", "beta": false, "capabilities": {"voice": true, "sms": true, "mms": true}, "status_callback": "", "status_callback_method": "POST", "api_version": "2010-04-01", "voice_application_sid": "", "sms_application_sid": "", "origin": "twilio", "trunk_sid": null, "emergency_status": "Active", "emergency_address_sid": null, "emergency_address_status": "unregistered", "address_sid": null, "identity_sid": null, "bundle_sid": null, "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/IncomingPhoneNumbers/PNe40bd7f3ac343b32fd51275d2d5b3dcc.json", "status": "in-use"}, "emitted_at": 1655893245291} {"stream": "keys", "data": {"date_updated": "2021-02-01T07:30:21Z", "date_created": "2021-02-01T07:30:21Z", "friendly_name": "Studio API Key", "sid": "SK60085e9cfc3d94aa1b987b25c78067a9"}, "emitted_at": 1655893247168} -{"stream": "calls", "data": {"date_updated": "2022-06-17T22:28:34Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 61, "from": "+15312726629", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAe71d3c7533543b5c81b1be3fc5affa2b", "queue_time": 0, "price": -0.017, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-17T22:27:33Z", "date_created": "2022-06-17T22:27:32Z", "from_formatted": "(531) 272-6629", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-17T22:28:34Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249727} -{"stream": "calls", "data": {"date_updated": "2022-06-17T13:36:17Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 96, "from": "+17372040136", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA0a47223735162e1a7df2738327bda2ab", "queue_time": 0, "price": -0.017, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-17T13:34:41Z", "date_created": "2022-06-17T13:34:41Z", "from_formatted": "(737) 204-0136", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-17T13:36:17Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249739} -{"stream": "calls", "data": {"date_updated": "2022-06-16T20:02:43Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 124, "from": "+17372040136", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAace5c8813c499253bbbff29ad0da0ccb", "queue_time": 0, "price": -0.0255, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-16T20:00:39Z", "date_created": "2022-06-16T20:00:39Z", "from_formatted": "(737) 204-0136", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-16T20:02:43Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249745} -{"stream": "calls", "data": {"date_updated": "2022-06-02T12:54:05Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 5, "from": "+12059675338", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAa24e9fbcb6eba3c8cfefc248a3c0b5b4", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-02T12:54:00Z", "date_created": "2022-06-02T12:54:00Z", "from_formatted": "(205) 967-5338", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-02T12:54:05Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249752} -{"stream": "calls", "data": {"date_updated": "2022-05-26T22:14:18Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 69, "from": "+13343585579", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA65f8d6ee9f8783233750f2b0f99cf1b3", "queue_time": 0, "price": -0.017, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-05-26T22:13:09Z", "date_created": "2022-05-26T22:13:09Z", "from_formatted": "(334) 358-5579", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-05-26T22:14:18Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249756} -{"stream": "calls", "data": {"date_updated": "2022-05-24T23:00:40Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 31, "from": "+14156896198", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA5b6907d5ebca072c9bd0f46952b886b6", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-05-24T23:00:09Z", "date_created": "2022-05-24T23:00:09Z", "from_formatted": "(415) 689-6198", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-05-24T23:00:40Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249759} -{"stream": "calls", "data": {"date_updated": "2022-05-11T18:21:15Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 23, "from": "+12137661124", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA696bd2d2e37ef8501f443807dce444a9", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-05-11T18:20:52Z", "date_created": "2022-05-11T18:20:52Z", "from_formatted": "(213) 766-1124", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-05-11T18:21:15Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249762} -{"stream": "calls", "data": {"date_updated": "2022-04-20T17:33:25Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 5, "from": "+12059736828", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAe86d27d7aba7c857135b46f52f578d0b", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-04-20T17:33:20Z", "date_created": "2022-04-20T17:33:20Z", "from_formatted": "(205) 973-6828", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-04-20T17:33:25Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249765} -{"stream": "calls", "data": {"date_updated": "2022-04-06T21:01:01Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 6, "from": "+13017951000", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAade9599c9cf53091c1787898093e2675", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-04-06T21:00:55Z", "date_created": "2022-04-06T21:00:55Z", "from_formatted": "(301) 795-1000", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-04-06T21:01:01Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249767} -{"stream": "calls", "data": {"date_updated": "2022-04-06T20:57:37Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 6, "from": "+13017951000", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAa3887d4de4849a630bc369351f300171", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-04-06T20:57:31Z", "date_created": "2022-04-06T20:57:31Z", "from_formatted": "(301) 795-1000", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-04-06T20:57:37Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249769} -{"stream": "calls", "data": {"date_updated": "2022-03-13T23:56:37Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 13, "from": "+12059203962", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA78611ecf5e7f101b1a59be31b8f520f7", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-03-13T23:56:24Z", "date_created": "2022-03-13T23:56:24Z", "from_formatted": "(205) 920-3962", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-03-13T23:56:37Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json"}}, "emitted_at": 1655893249771} +{"stream": "calls", "data": {"date_updated": "2022-06-17T22:28:34Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 61, "from": "+15312726629", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAe71d3c7533543b5c81b1be3fc5affa2b", "queue_time": 0, "price": -0.017, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-17T22:27:33Z", "date_created": "2022-06-17T22:27:32Z", "from_formatted": "(531) 272-6629", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-17T22:28:34Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe71d3c7533543b5c81b1be3fc5affa2b/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249727} +{"stream": "calls", "data": {"date_updated": "2022-06-17T13:36:17Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 96, "from": "+17372040136", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA0a47223735162e1a7df2738327bda2ab", "queue_time": 0, "price": -0.017, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-17T13:34:41Z", "date_created": "2022-06-17T13:34:41Z", "from_formatted": "(737) 204-0136", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-17T13:36:17Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA0a47223735162e1a7df2738327bda2ab/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249739} +{"stream": "calls", "data": {"date_updated": "2022-06-16T20:02:43Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 124, "from": "+17372040136", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAace5c8813c499253bbbff29ad0da0ccb", "queue_time": 0, "price": -0.0255, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-16T20:00:39Z", "date_created": "2022-06-16T20:00:39Z", "from_formatted": "(737) 204-0136", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-16T20:02:43Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAace5c8813c499253bbbff29ad0da0ccb/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249745} +{"stream": "calls", "data": {"date_updated": "2022-06-02T12:54:05Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 5, "from": "+12059675338", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAa24e9fbcb6eba3c8cfefc248a3c0b5b4", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-06-02T12:54:00Z", "date_created": "2022-06-02T12:54:00Z", "from_formatted": "(205) 967-5338", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-06-02T12:54:05Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa24e9fbcb6eba3c8cfefc248a3c0b5b4/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249752} +{"stream": "calls", "data": {"date_updated": "2022-05-26T22:14:18Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 69, "from": "+13343585579", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA65f8d6ee9f8783233750f2b0f99cf1b3", "queue_time": 0, "price": -0.017, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-05-26T22:13:09Z", "date_created": "2022-05-26T22:13:09Z", "from_formatted": "(334) 358-5579", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-05-26T22:14:18Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA65f8d6ee9f8783233750f2b0f99cf1b3/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249756} +{"stream": "calls", "data": {"date_updated": "2022-05-24T23:00:40Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 31, "from": "+14156896198", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA5b6907d5ebca072c9bd0f46952b886b6", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-05-24T23:00:09Z", "date_created": "2022-05-24T23:00:09Z", "from_formatted": "(415) 689-6198", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-05-24T23:00:40Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA5b6907d5ebca072c9bd0f46952b886b6/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249759} +{"stream": "calls", "data": {"date_updated": "2022-05-11T18:21:15Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 23, "from": "+12137661124", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA696bd2d2e37ef8501f443807dce444a9", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-05-11T18:20:52Z", "date_created": "2022-05-11T18:20:52Z", "from_formatted": "(213) 766-1124", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-05-11T18:21:15Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA696bd2d2e37ef8501f443807dce444a9/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249762} +{"stream": "calls", "data": {"date_updated": "2022-04-20T17:33:25Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 5, "from": "+12059736828", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAe86d27d7aba7c857135b46f52f578d0b", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-04-20T17:33:20Z", "date_created": "2022-04-20T17:33:20Z", "from_formatted": "(205) 973-6828", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-04-20T17:33:25Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAe86d27d7aba7c857135b46f52f578d0b/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249765} +{"stream": "calls", "data": {"date_updated": "2022-04-06T21:01:01Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 6, "from": "+13017951000", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAade9599c9cf53091c1787898093e2675", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-04-06T21:00:55Z", "date_created": "2022-04-06T21:00:55Z", "from_formatted": "(301) 795-1000", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-04-06T21:01:01Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAade9599c9cf53091c1787898093e2675/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249767} +{"stream": "calls", "data": {"date_updated": "2022-04-06T20:57:37Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 6, "from": "+13017951000", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CAa3887d4de4849a630bc369351f300171", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-04-06T20:57:31Z", "date_created": "2022-04-06T20:57:31Z", "from_formatted": "(301) 795-1000", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-04-06T20:57:37Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CAa3887d4de4849a630bc369351f300171/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249769} +{"stream": "calls", "data": {"date_updated": "2022-03-13T23:56:37Z", "price_unit": "USD", "parent_call_sid": null, "caller_name": null, "duration": 13, "from": "+12059203962", "to": "+12056561170", "annotation": null, "answered_by": null, "sid": "CA78611ecf5e7f101b1a59be31b8f520f7", "queue_time": 0, "price": -0.0085, "api_version": "2010-04-01", "status": "completed", "direction": "inbound", "start_time": "2022-03-13T23:56:24Z", "date_created": "2022-03-13T23:56:24Z", "from_formatted": "(205) 920-3962", "group_sid": null, "trunk_sid": "", "forwarded_from": "+12056561170", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "end_time": "2022-03-13T23:56:37Z", "to_formatted": "(205) 656-1170", "phone_number_sid": "PNe40bd7f3ac343b32fd51275d2d5b3dcc", "subresource_uris": {"feedback": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Feedback.json", "notifications": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Notifications.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Recordings.json", "streams": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Streams.json", "payments": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Payments.json", "siprec": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Siprec.json", "events": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/Events.json", "feedback_summaries": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/FeedbackSummary.json", "user_defined_messages": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/UserDefinedMessages.json", "user_defined_message_subscriptions": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Calls/CA78611ecf5e7f101b1a59be31b8f520f7/UserDefinedMessageSubscriptions.json"}}, "emitted_at": 1655893249771} {"stream": "conferences", "data": {"status": "completed", "reason_conference_ended": "last-participant-left", "date_updated": "2022-09-23T14:44:41Z", "region": "us1", "friendly_name": "test_conference", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Conferences/CFca0fa08200f55a6d60779d18b644a675.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "call_sid_ending_conference": "CA8858f240bdccfb3393def1682c2dbdf0", "sid": "CFca0fa08200f55a6d60779d18b644a675", "date_created": "2022-09-23T14:44:11Z", "api_version": "2010-04-01", "subresource_uris": {"participants": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Conferences/CFca0fa08200f55a6d60779d18b644a675/Participants.json", "recordings": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/Conferences/CFca0fa08200f55a6d60779d18b644a675/Recordings.json"}}, "emitted_at": 1663955824121} {"stream": "outgoing_caller_ids", "data": {"phone_number": "+14153597503", "date_updated": "2020-11-17T04:17:37Z", "friendly_name": "(415) 359-7503", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/OutgoingCallerIds/PN16ba111c0df5756cfe37044ed0ee3136.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "sid": "PN16ba111c0df5756cfe37044ed0ee3136", "date_created": "2020-11-17T04:17:37Z"}, "emitted_at": 1655893253929} {"stream": "outgoing_caller_ids", "data": {"phone_number": "+18023494963", "date_updated": "2020-12-11T04:28:02Z", "friendly_name": "(802) 349-4963", "uri": "/2010-04-01/Accounts/ACdade166c12e160e9ed0a6088226718fb/OutgoingCallerIds/PN726d635f970c30193cd12e7b994510a1.json", "account_sid": "ACdade166c12e160e9ed0a6088226718fb", "sid": "PN726d635f970c30193cd12e7b994510a1", "date_created": "2020-12-11T04:28:02Z"}, "emitted_at": 1655893253943} diff --git a/airbyte-integrations/connectors/source-twilio/source_twilio/schemas/calls.json b/airbyte-integrations/connectors/source-twilio/source_twilio/schemas/calls.json index 7ed73f1fb168..68bf0132a1a3 100644 --- a/airbyte-integrations/connectors/source-twilio/source_twilio/schemas/calls.json +++ b/airbyte-integrations/connectors/source-twilio/source_twilio/schemas/calls.json @@ -109,6 +109,12 @@ }, "streams": { "type": ["null", "string"] + }, + "user_defined_message_subscriptions": { + "type": ["null", "string"] + }, + "user_defined_messages": { + "type": ["null", "string"] } } } diff --git a/airbyte-integrations/connectors/source-twilio/source_twilio/source.py b/airbyte-integrations/connectors/source-twilio/source_twilio/source.py index 274f60e347c7..e591df1a0843 100644 --- a/airbyte-integrations/connectors/source-twilio/source_twilio/source.py +++ b/airbyte-integrations/connectors/source-twilio/source_twilio/source.py @@ -69,6 +69,7 @@ def streams(self, config: Mapping[str, Any]) -> List[Stream]: "authenticator": auth, "start_date": config["start_date"], "lookback_window": config.get("lookback_window", 0), + "slice_step_map": config.get("slice_step_map", {}), } # Fix for `Date range specified in query is partially or entirely outside of retention window of 400 days` diff --git a/airbyte-integrations/connectors/source-twilio/source_twilio/streams.py b/airbyte-integrations/connectors/source-twilio/source_twilio/streams.py index 1c3ea2412491..8a1c10307925 100644 --- a/airbyte-integrations/connectors/source-twilio/source_twilio/streams.py +++ b/airbyte-integrations/connectors/source-twilio/source_twilio/streams.py @@ -2,8 +2,10 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +import copy from abc import ABC, abstractmethod -from typing import Any, Iterable, List, Mapping, MutableMapping, Optional +from functools import cached_property +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Union from urllib.parse import parse_qsl, urlparse import pendulum @@ -11,7 +13,9 @@ from airbyte_cdk.models import SyncMode from airbyte_cdk.sources.streams import IncrementalMixin from airbyte_cdk.sources.streams.http import HttpStream +from airbyte_cdk.sources.streams.http.auth.core import HttpAuthenticator from airbyte_cdk.sources.utils.transform import TransformConfig, TypeTransformer +from requests.auth import AuthBase TWILIO_API_URL_BASE = "https://api.twilio.com" TWILIO_API_URL_BASE_VERSIONED = f"{TWILIO_API_URL_BASE}/2010-04-01/" @@ -24,9 +28,6 @@ class TwilioStream(HttpStream, ABC): page_size = 1000 transformer: TypeTransformer = TypeTransformer(TransformConfig.DefaultSchemaNormalization | TransformConfig.CustomSchemaNormalization) - def __init__(self, **kwargs): - super().__init__(**kwargs) - @property def data_field(self): return self.name @@ -73,9 +74,12 @@ def backoff_time(self, response: requests.Response) -> Optional[float]: return float(backoff_time) def request_params( - self, stream_state: Mapping[str, Any], next_page_token: Mapping[str, Any] = None, **kwargs + self, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, ) -> MutableMapping[str, Any]: - params = super().request_params(stream_state=stream_state, next_page_token=next_page_token, **kwargs) + params = super().request_params(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token) params["PageSize"] = self.page_size if next_page_token: params.update(**next_page_token) @@ -98,17 +102,41 @@ def custom_transform_function(original_value: Any, field_schema: Mapping[str, An class IncrementalTwilioStream(TwilioStream, IncrementalMixin): time_filter_template = "YYYY-MM-DD HH:mm:ss[Z]" + # This attribute allows balancing between sync speed and memory consumption. + # The greater a slice is - the bigger memory consumption and the faster syncs are since fewer requests are made. + slice_step_default = pendulum.duration(years=1) + # time gap between when previous slice ends and current slice begins + slice_granularity = pendulum.duration(microseconds=1) state_checkpoint_interval = 1000 - def __init__(self, start_date: str = None, lookback_window: int = 0, **kwargs): - super().__init__(**kwargs) + def __init__( + self, + authenticator: Union[AuthBase, HttpAuthenticator], + start_date: str = None, + lookback_window: int = 0, + slice_step_map: Mapping[str, int] = None, + ): + super().__init__(authenticator) + slice_step = (slice_step_map or {}).get(self.name) + self._slice_step = slice_step and pendulum.duration(days=slice_step) self._start_date = start_date if start_date is not None else "1970-01-01T00:00:00Z" self._lookback_window = lookback_window self._cursor_value = None + @property + def slice_step(self): + return self._slice_step or self.slice_step_default + @property @abstractmethod - def incremental_filter_field(self) -> str: + def lower_boundary_filter_field(self) -> str: + """ + return: date filter query parameter name + """ + + @property + @abstractmethod + def upper_boundary_filter_field(self) -> str: """ return: date filter query parameter name """ @@ -123,7 +151,7 @@ def state(self) -> Mapping[str, Any]: return {} @state.setter - def state(self, value: Mapping[str, Any]): + def state(self, value: MutableMapping[str, Any]): if self._lookback_window and value.get(self.cursor_field): new_start_date = ( pendulum.parse(value[self.cursor_field]) - pendulum.duration(minutes=self._lookback_window) @@ -132,12 +160,38 @@ def state(self, value: Mapping[str, Any]): value[self.cursor_field] = new_start_date self._cursor_value = value.get(self.cursor_field) + def generate_date_ranges(self, super_slice: MutableMapping[str, Any]) -> Iterable[Optional[MutableMapping[str, Any]]]: + end_datetime = pendulum.now() + start_datetime = min(end_datetime, pendulum.parse(self.state.get(self.cursor_field, self._start_date))) + current_start = start_datetime + current_end = start_datetime + while current_end < end_datetime: + current_end = min(end_datetime, current_start + self.slice_step) + slice_ = copy.deepcopy(super_slice) if super_slice else {} + slice_[self.lower_boundary_filter_field] = current_start.format(self.time_filter_template) + slice_[self.upper_boundary_filter_field] = current_end.format(self.time_filter_template) + yield slice_ + current_start = current_end + self.slice_granularity + + def stream_slices( + self, sync_mode: SyncMode, cursor_field: List[str] = None, stream_state: Mapping[str, Any] = None + ) -> Iterable[Optional[Mapping[str, Any]]]: + for super_slice in super().stream_slices(sync_mode=sync_mode, cursor_field=cursor_field, stream_state=stream_state): + yield from self.generate_date_ranges(super_slice) + def request_params( - self, stream_state: Mapping[str, Any], next_page_token: Mapping[str, Any] = None, **kwargs + self, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, ) -> MutableMapping[str, Any]: - params = super().request_params(stream_state=stream_state, next_page_token=next_page_token, **kwargs) - start_date = self.state.get(self.cursor_field, self._start_date) - params[self.incremental_filter_field] = pendulum.parse(start_date).format(self.time_filter_template) + params = super().request_params(stream_state=stream_state, stream_slice=stream_slice, next_page_token=next_page_token) + lower_bound = stream_slice and stream_slice.get(self.lower_boundary_filter_field) + upper_bound = stream_slice and stream_slice.get(self.upper_boundary_filter_field) + if lower_bound: + params[self.lower_boundary_filter_field] = lower_bound + if upper_bound: + params[self.upper_boundary_filter_field] = upper_bound return params def read_records( @@ -165,6 +219,7 @@ class TwilioNestedStream(TwilioStream): """ media_exist_validation = {} + uri_from_subresource = True def path(self, stream_slice: Mapping[str, Any], **kwargs): return stream_slice["subresource_uri"] @@ -180,21 +235,30 @@ def parent_stream(self) -> TwilioStream: :return: parent stream class """ + @cached_property + def parent_stream_instance(self): + return self.parent_stream(authenticator=self.authenticator) + + def parent_record_to_stream_slice(self, record: Mapping[str, Any]) -> Mapping[str, Any]: + return {"subresource_uri": record["subresource_uris"][self.subresource_uri_key]} + def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: - stream_instance = self.parent_stream(authenticator=self.authenticator) + stream_instance = self.parent_stream_instance stream_slices = stream_instance.stream_slices(sync_mode=SyncMode.full_refresh, cursor_field=stream_instance.cursor_field) for stream_slice in stream_slices: for item in stream_instance.read_records( sync_mode=SyncMode.full_refresh, stream_slice=stream_slice, cursor_field=stream_instance.cursor_field ): - if item.get("subresource_uris", {}).get(self.subresource_uri_key): + if not self.uri_from_subresource: + yield self.parent_record_to_stream_slice(item) + elif item.get("subresource_uris", {}).get(self.subresource_uri_key): validated = True for key, value in self.media_exist_validation.items(): validated = item.get(key) and item.get(key) != value if not validated: break if validated: - yield {"subresource_uri": item["subresource_uris"][self.subresource_uri_key]} + yield self.parent_record_to_stream_slice(item) class Accounts(TwilioStream): @@ -214,18 +278,13 @@ class DependentPhoneNumbers(TwilioNestedStream): parent_stream = Addresses url_base = TWILIO_API_URL_BASE_VERSIONED + uri_from_subresource = False def path(self, stream_slice: Mapping[str, Any], **kwargs): return f"Accounts/{stream_slice['account_sid']}/Addresses/{stream_slice['sid']}/DependentPhoneNumbers.json" - def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: - stream_instance = self.parent_stream(authenticator=self.authenticator) - stream_slices = stream_instance.stream_slices(sync_mode=SyncMode.full_refresh, cursor_field=stream_instance.cursor_field) - for stream_slice in stream_slices: - for item in stream_instance.read_records( - sync_mode=SyncMode.full_refresh, stream_slice=stream_slice, cursor_field=stream_instance.cursor_field - ): - yield {"sid": item["sid"], "account_sid": item["account_sid"]} + def parent_record_to_stream_slice(self, record: Mapping[str, Any]) -> Mapping[str, Any]: + return {"sid": record["sid"], "account_sid": record["account_sid"]} class Applications(TwilioNestedStream): @@ -287,22 +346,26 @@ class Keys(TwilioNestedStream): parent_stream = Accounts -class Calls(TwilioNestedStream, IncrementalTwilioStream): +class Calls(IncrementalTwilioStream, TwilioNestedStream): """https://www.twilio.com/docs/voice/api/call-resource#create-a-call-resource""" parent_stream = Accounts - incremental_filter_field = "EndTime>" + lower_boundary_filter_field = "EndTime>" + upper_boundary_filter_field = "EndTime<" cursor_field = "end_time" time_filter_template = "YYYY-MM-DD" + slice_granularity = pendulum.duration(days=1) -class Conferences(TwilioNestedStream, IncrementalTwilioStream): +class Conferences(IncrementalTwilioStream, TwilioNestedStream): """https://www.twilio.com/docs/voice/api/conference-resource#read-multiple-conference-resources""" parent_stream = Accounts - incremental_filter_field = "DateCreated>" + lower_boundary_filter_field = "DateCreated>" + upper_boundary_filter_field = "DateCreated<" cursor_field = "date_created" time_filter_template = "YYYY-MM-DD" + slice_granularity = pendulum.duration(days=1) class ConferenceParticipants(TwilioNestedStream): @@ -324,11 +387,12 @@ class OutgoingCallerIds(TwilioNestedStream): parent_stream = Accounts -class Recordings(TwilioNestedStream, IncrementalTwilioStream): +class Recordings(IncrementalTwilioStream, TwilioNestedStream): """https://www.twilio.com/docs/voice/api/recording#read-multiple-recording-resources""" parent_stream = Accounts - incremental_filter_field = "DateCreated>" + lower_boundary_filter_field = "DateCreated>" + upper_boundary_filter_field = "DateCreated<" cursor_field = "date_created" @@ -344,46 +408,35 @@ class Queues(TwilioNestedStream): parent_stream = Accounts -class Messages(TwilioNestedStream, IncrementalTwilioStream): +class Messages(IncrementalTwilioStream, TwilioNestedStream): """https://www.twilio.com/docs/sms/api/message-resource#read-multiple-message-resources""" parent_stream = Accounts - incremental_filter_field = "DateSent>" + slice_step_default = pendulum.duration(days=1) + lower_boundary_filter_field = "DateSent>" + upper_boundary_filter_field = "DateSent<" cursor_field = "date_sent" -class MessageMedia(TwilioNestedStream, IncrementalTwilioStream): +class MessageMedia(IncrementalTwilioStream, TwilioNestedStream): """https://www.twilio.com/docs/sms/api/media-resource#read-multiple-media-resources""" parent_stream = Messages data_field = "media_list" subresource_uri_key = "media" media_exist_validation = {"num_media": "0"} - incremental_filter_field = "DateCreated>" + lower_boundary_filter_field = "DateCreated>" + upper_boundary_filter_field = "DateCreated<" cursor_field = "date_created" - def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: - stream_instance = self.parent_stream( - authenticator=self.authenticator, start_date=self._start_date, lookback_window=self._lookback_window - ) - stream_slices = stream_instance.stream_slices(sync_mode=SyncMode.full_refresh, cursor_field=stream_instance.cursor_field) - for stream_slice in stream_slices: - for item in stream_instance.read_records( - sync_mode=SyncMode.full_refresh, stream_slice=stream_slice, cursor_field=stream_instance.cursor_field - ): - if item.get("subresource_uris", {}).get(self.subresource_uri_key): - validated = True - for key, value in self.media_exist_validation.items(): - validated = item.get(key) and item.get(key) != value - if not validated: - break - if validated: - - yield {"subresource_uri": item["subresource_uris"][self.subresource_uri_key]} + @cached_property + def parent_stream_instance(self): + return self.parent_stream(authenticator=self.authenticator, start_date=self._start_date, lookback_window=self._lookback_window) class UsageNestedStream(TwilioNestedStream): url_base = TWILIO_API_URL_BASE_VERSIONED + uri_from_subresource = False @property @abstractmethod @@ -395,23 +448,19 @@ def path_name(self) -> str: def path(self, stream_slice: Mapping[str, Any], **kwargs): return f"Accounts/{stream_slice['account_sid']}/Usage/{self.path_name}.json" - def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, any]]]: - stream_instance = self.parent_stream(authenticator=self.authenticator) - stream_slices = stream_instance.stream_slices(sync_mode=SyncMode.full_refresh, cursor_field=stream_instance.cursor_field) - for stream_slice in stream_slices: - for item in stream_instance.read_records( - sync_mode=SyncMode.full_refresh, stream_slice=stream_slice, cursor_field=stream_instance.cursor_field - ): - yield {"account_sid": item["sid"]} + def parent_record_to_stream_slice(self, record: Mapping[str, Any]) -> Mapping[str, Any]: + return {"account_sid": record["sid"]} -class UsageRecords(UsageNestedStream, IncrementalTwilioStream): +class UsageRecords(IncrementalTwilioStream, UsageNestedStream): """https://www.twilio.com/docs/usage/api/usage-record#read-multiple-usagerecord-resources""" parent_stream = Accounts - incremental_filter_field = "StartDate" + lower_boundary_filter_field = "StartDate" + upper_boundary_filter_field = "EndDate" cursor_field = "start_date" time_filter_template = "YYYY-MM-DD" + slice_granularity = pendulum.duration(days=1) path_name = "Records" primary_key = [["account_sid"], ["category"]] changeable_fields = ["as_of"] @@ -429,7 +478,8 @@ class Alerts(IncrementalTwilioStream): """https://www.twilio.com/docs/usage/monitor-alert#read-multiple-alert-resources""" url_base = TWILIO_MONITOR_URL_BASE - incremental_filter_field = "StartDate" + lower_boundary_filter_field = "StartDate=" + upper_boundary_filter_field = "EndDate=" cursor_field = "date_generated" def path(self, **kwargs): diff --git a/airbyte-integrations/connectors/source-twilio/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-twilio/unit_tests/test_streams.py index b53d5412f046..a51d80aad627 100644 --- a/airbyte-integrations/connectors/source-twilio/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-twilio/unit_tests/test_streams.py @@ -2,14 +2,16 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +from contextlib import nullcontext from unittest.mock import patch +import pendulum import pytest import requests from airbyte_cdk.sources.streams.http import HttpStream from source_twilio.auth import HttpBasicAuthenticator from source_twilio.source import SourceTwilio -from source_twilio.streams import Accounts, Addresses, Calls, DependentPhoneNumbers, MessageMedia, Messages, UsageTriggers +from source_twilio.streams import Accounts, Addresses, Alerts, Calls, DependentPhoneNumbers, MessageMedia, TwilioNestedStream, UsageTriggers TEST_CONFIG = { "account_sid": "airbyte.io", @@ -135,29 +137,25 @@ class TestIncrementalTwilioStream: CONFIG.pop("auth_token") @pytest.mark.parametrize( - "stream_cls, expected", - [ - (Calls, "EndTime>"), - ], - ) - def test_incremental_filter_field(self, stream_cls, expected): - stream = stream_cls(**self.CONFIG) - result = stream.incremental_filter_field - assert result == expected - - @pytest.mark.parametrize( - "stream_cls, next_page_token, expected", + "stream_cls, stream_slice, next_page_token, expected", [ ( Calls, + {"EndTime>": "2022-01-01", "EndTime<": "2022-01-02"}, {"Page": "2", "PageSize": "1000", "PageToken": "PAAD42931b949c0dedce94b2f93847fdcf95"}, - {"EndTime>": "2022-01-01", "Page": "2", "PageSize": "1000", "PageToken": "PAAD42931b949c0dedce94b2f93847fdcf95"}, + { + "EndTime>": "2022-01-01", + "EndTime<": "2022-01-02", + "Page": "2", + "PageSize": "1000", + "PageToken": "PAAD42931b949c0dedce94b2f93847fdcf95", + }, ), ], ) - def test_request_params(self, stream_cls, next_page_token, expected): + def test_request_params(self, stream_cls, stream_slice, next_page_token, expected): stream = stream_cls(**self.CONFIG) - result = stream.request_params(stream_state=None, next_page_token=next_page_token) + result = stream.request_params(stream_state=None, stream_slice=stream_slice, next_page_token=next_page_token) assert result == expected @pytest.mark.parametrize( @@ -172,6 +170,33 @@ def test_read_records(self, stream_cls, record, expected): result = stream.read_records(sync_mode=None) assert list(result) == expected + @pytest.mark.parametrize( + "stream_cls, parent_cls_records, extra_slice_keywords", + [ + (Calls, [{"subresource_uris": {"calls": "123"}}, {"subresource_uris": {"calls": "124"}}], ["subresource_uri"]), + (Alerts, [{}], []), + ], + ) + def test_stream_slices(self, mocker, stream_cls, parent_cls_records, extra_slice_keywords): + stream = stream_cls( + authenticator=TEST_CONFIG.get("authenticator"), start_date=pendulum.now().subtract(months=13).to_iso8601_string() + ) + expected_slices = 2 * len(parent_cls_records) # 2 per year slices per each parent slice + if isinstance(stream, TwilioNestedStream): + slices_mock_context = mocker.patch.object(stream.parent_stream_instance, "stream_slices", return_value=[{}]) + records_mock_context = mocker.patch.object(stream.parent_stream_instance, "read_records", return_value=parent_cls_records) + else: + slices_mock_context, records_mock_context = nullcontext(), nullcontext() + with slices_mock_context: + with records_mock_context: + slices = list(stream.stream_slices(sync_mode="incremental")) + assert len(slices) == expected_slices + for slice_ in slices: + if isinstance(stream, TwilioNestedStream): + for kw in extra_slice_keywords: + assert kw in slice_ + assert slice_[stream.lower_boundary_filter_field] <= slice_[stream.upper_boundary_filter_field] + class TestTwilioNestedStream: @@ -205,12 +230,6 @@ def test_media_exist_validation(self, stream_cls, expected): [{"subresource_uris": {"addresses": "123"}, "sid": "123", "account_sid": "456"}], [{"sid": "123", "account_sid": "456"}], ), - ( - MessageMedia, - Messages, - [{"subresource_uris": {"media": "1234"}, "num_media": "1", "sid": "123", "account_sid": "456"}], - [{"subresource_uri": "1234"}], - ), ], ) def test_stream_slices(self, stream_cls, parent_stream, record, expected): @@ -218,7 +237,7 @@ def test_stream_slices(self, stream_cls, parent_stream, record, expected): with patch.object(Accounts, "read_records", return_value=record): with patch.object(parent_stream, "stream_slices", return_value=record): with patch.object(parent_stream, "read_records", return_value=record): - result = stream.stream_slices() + result = stream.stream_slices(sync_mode="full_refresh") assert list(result) == expected diff --git a/docs/integrations/sources/twilio.md b/docs/integrations/sources/twilio.md index e5308a4d4f0a..3eb166abc814 100644 --- a/docs/integrations/sources/twilio.md +++ b/docs/integrations/sources/twilio.md @@ -80,6 +80,7 @@ For more information, see [the Twilio docs for rate limitations](https://support | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------------------------| +| 0.1.13 | 2022-10-25 | [18423](https://github.com/airbytehq/airbyte/pull/18423) | Implement datetime slicing for streams supporting incremental syncs | | 0.1.11 | 2022-09-30 | [17478](https://github.com/airbytehq/airbyte/pull/17478) | Add lookback_window parameters | | 0.1.10 | 2022-09-29 | [17410](https://github.com/airbytehq/airbyte/pull/17410) | Migrate to per-stream states | | 0.1.9 | 2022-09-26 | [17134](https://github.com/airbytehq/airbyte/pull/17134) | Add test data for Message Media and Conferences | From d32cec811331e280a46dcb01fdeef153e8d86379 Mon Sep 17 00:00:00 2001 From: Neil Macneale V Date: Wed, 26 Oct 2022 04:06:37 -0700 Subject: [PATCH 334/498] Fix broken table in the docs (#18466) --- docs/integrations/sources/fauna.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/integrations/sources/fauna.md b/docs/integrations/sources/fauna.md index 4e502b35b6e4..a5e9269a5e09 100644 --- a/docs/integrations/sources/fauna.md +++ b/docs/integrations/sources/fauna.md @@ -194,6 +194,7 @@ inferred that the document ID refers to a document within the synced collection. Every ref is serialized as a JSON object with 2 or 3 fields, as listed above. The `type` field must be one of these strings: + | Reference Type | `type` string | | --------------------------------------------------------------------------------------- | ------------------- | | Document | `"document"` | From f38df6619a9474c433cfb75f3cdb28c4de02e1c5 Mon Sep 17 00:00:00 2001 From: Ahmed Mousa <38728315+RobLucchi@users.noreply.github.com> Date: Wed, 26 Oct 2022 14:09:15 +0200 Subject: [PATCH 335/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Connector:=20Clock?= =?UTF-8?q?ify=20[python=20cdk]=20(#17767)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * new connector source-clockify * feat: enable caching for streams (users, projects) * chore: pep8 changes at unit_tests * chore: pep8 changes at integration_tests * chore: update schema * Adds python formatting, removes unused import. * Makes the task duration field nullable to pass integration test. * fix: add second type to null values to the schema files * Adds a null fallback value to task duration. * Updates airbyte-cdk dependency. * Adds UUID in source definitions. * auto-bump connector version * Requested changes. * add clockify to source def seed * correct spec.json add titles * add icon * run format * remove source spec * correct spec * add eof gitignore * auto-bump connector version Co-authored-by: nataly Co-authored-by: Marcos Marx Co-authored-by: Nataly Merezhuk <65251165+natalyjazzviolin@users.noreply.github.com> Co-authored-by: Octavia Squidington III Co-authored-by: marcosmarxm --- .../src/main/resources/icons/clockify.svg | 5 + .../resources/seed/source_definitions.yaml | 8 + .../src/main/resources/seed/source_specs.yaml | 25 +++ .../source-asana/unit_tests/test_source.py | 5 +- .../connectors/source-clockify/.dockerignore | 6 + .../connectors/source-clockify/.gitignore | 3 + .../connectors/source-clockify/Dockerfile | 38 ++++ .../connectors/source-clockify/README.md | 132 +++++++++++++ .../acceptance-test-config.yml | 19 ++ .../source-clockify/acceptance-test-docker.sh | 16 ++ .../connectors/source-clockify/build.gradle | 9 + .../integration_tests/__init__.py | 3 + .../integration_tests/acceptance.py | 14 ++ .../integration_tests/configured_catalog.json | 109 +++++++++++ .../integration_tests/invalid_config.json | 4 + .../integration_tests/sample_config.json | 4 + .../connectors/source-clockify/main.py | 13 ++ .../source-clockify/requirements.txt | 2 + .../connectors/source-clockify/setup.py | 25 +++ .../source_clockify/__init__.py | 8 + .../source_clockify/schemas/clients.json | 24 +++ .../source_clockify/schemas/projects.json | 173 ++++++++++++++++++ .../source_clockify/schemas/tags.json | 18 ++ .../source_clockify/schemas/tasks.json | 78 ++++++++ .../source_clockify/schemas/time_entries.json | 66 +++++++ .../source_clockify/schemas/user_groups.json | 21 +++ .../source_clockify/schemas/users.json | 133 ++++++++++++++ .../source-clockify/source_clockify/source.py | 33 ++++ .../source-clockify/source_clockify/spec.json | 23 +++ .../source_clockify/streams.py | 123 +++++++++++++ .../source-clockify/unit_tests/__init__.py | 3 + .../source-clockify/unit_tests/conftest.py | 10 + .../source-clockify/unit_tests/test_source.py | 33 ++++ .../unit_tests/test_streams.py | 49 +++++ .../unit_tests/test_streams.py | 4 +- 35 files changed, 1235 insertions(+), 4 deletions(-) create mode 100644 airbyte-config/init/src/main/resources/icons/clockify.svg create mode 100644 airbyte-integrations/connectors/source-clockify/.dockerignore create mode 100644 airbyte-integrations/connectors/source-clockify/.gitignore create mode 100644 airbyte-integrations/connectors/source-clockify/Dockerfile create mode 100644 airbyte-integrations/connectors/source-clockify/README.md create mode 100644 airbyte-integrations/connectors/source-clockify/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-clockify/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-clockify/build.gradle create mode 100644 airbyte-integrations/connectors/source-clockify/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-clockify/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-clockify/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-clockify/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-clockify/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-clockify/main.py create mode 100644 airbyte-integrations/connectors/source-clockify/requirements.txt create mode 100644 airbyte-integrations/connectors/source-clockify/setup.py create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/__init__.py create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/clients.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/projects.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tags.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tasks.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/time_entries.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/user_groups.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/schemas/users.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/source.py create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/spec.json create mode 100644 airbyte-integrations/connectors/source-clockify/source_clockify/streams.py create mode 100644 airbyte-integrations/connectors/source-clockify/unit_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-clockify/unit_tests/conftest.py create mode 100644 airbyte-integrations/connectors/source-clockify/unit_tests/test_source.py create mode 100644 airbyte-integrations/connectors/source-clockify/unit_tests/test_streams.py diff --git a/airbyte-config/init/src/main/resources/icons/clockify.svg b/airbyte-config/init/src/main/resources/icons/clockify.svg new file mode 100644 index 000000000000..562fbab20a7e --- /dev/null +++ b/airbyte-config/init/src/main/resources/icons/clockify.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index cb3f6a0e3597..35de0b71653b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -220,6 +220,14 @@ documentationUrl: https://docs.airbyte.com/integrations/sources/courier sourceType: api releaseStage: alpha +- name: Clockify + sourceDefinitionId: e71aae8a-5143-11ed-bdc3-0242ac120002 + dockerRepository: airbyte/source-clockify + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/clockify + icon: clockify.svg + sourceType: api + releaseStage: alpha - name: Customer.io sourceDefinitionId: c47d6804-8b98-449f-970a-5ddb5cb5d7aa dockerRepository: farosai/airbyte-customer-io-source diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 34b6ea17cc2d..3c949efa6894 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2187,6 +2187,31 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-clockify:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/sources/clockify" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Clockify Spec" + type: "object" + required: + - "workspace_id" + - "api_key" + additionalProperties: true + properties: + workspace_id: + title: "Workspace Id" + description: "WorkSpace Id" + type: "string" + api_key: + title: "API Key" + description: "You can get your api access_key here This API is Case Sensitive." + type: "string" + airbyte_secret: true + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "farosai/airbyte-customer-io-source:0.1.23" spec: documentationUrl: "https://docs.faros.ai" diff --git a/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py b/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py index dd13b87ef40d..666c5aa26d9b 100644 --- a/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py +++ b/airbyte-integrations/connectors/source-asana/unit_tests/test_source.py @@ -1,7 +1,8 @@ # # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # -from unittest.mock import patch, PropertyMock + +from unittest.mock import PropertyMock, patch from airbyte_cdk.logger import AirbyteLogger from source_asana.source import SourceAsana @@ -27,7 +28,7 @@ def test_check_connection_empty_config(config): def test_check_connection_exception(config): - with patch('source_asana.streams.Workspaces.use_cache', new_callable=PropertyMock, return_value=False): + with patch("source_asana.streams.Workspaces.use_cache", new_callable=PropertyMock, return_value=False): ok, error_msg = SourceAsana().check_connection(logger, config=config) assert not ok diff --git a/airbyte-integrations/connectors/source-clockify/.dockerignore b/airbyte-integrations/connectors/source-clockify/.dockerignore new file mode 100644 index 000000000000..a5f918cda764 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_clockify +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-clockify/.gitignore b/airbyte-integrations/connectors/source-clockify/.gitignore new file mode 100644 index 000000000000..945168c8f81b --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/.gitignore @@ -0,0 +1,3 @@ +users.yml +projects.yml +schemas diff --git a/airbyte-integrations/connectors/source-clockify/Dockerfile b/airbyte-integrations/connectors/source-clockify/Dockerfile new file mode 100644 index 000000000000..a19cd8507ded --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.13-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_clockify ./source_clockify + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-clockify diff --git a/airbyte-integrations/connectors/source-clockify/README.md b/airbyte-integrations/connectors/source-clockify/README.md new file mode 100644 index 000000000000..0f4544e0896e --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/README.md @@ -0,0 +1,132 @@ +# Clockify Source + +This is the repository for the Clockify source connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/clockify). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.9.0` + +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python -m venv .venv +``` + +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +pip install '.[tests]' +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-clockify:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/clockify) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_clockify/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source clockify test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-clockify:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-clockify:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-clockify:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-clockify:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-clockify:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-clockify:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-clockify:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-clockify:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-clockify/acceptance-test-config.yml b/airbyte-integrations/connectors/source-clockify/acceptance-test-config.yml new file mode 100644 index 000000000000..451c4f772c54 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/acceptance-test-config.yml @@ -0,0 +1,19 @@ +# See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-clockify:dev +tests: + spec: + - spec_path: "source_clockify/spec.json" + connection: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + - config_path: "secrets/config.json" + basic_read: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + full_refresh: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-clockify/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-clockify/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-clockify/build.gradle b/airbyte-integrations/connectors/source-clockify/build.gradle new file mode 100644 index 000000000000..9fbbaa9b16f7 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_clockify' +} diff --git a/airbyte-integrations/connectors/source-clockify/integration_tests/__init__.py b/airbyte-integrations/connectors/source-clockify/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-clockify/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-clockify/integration_tests/acceptance.py new file mode 100644 index 000000000000..950b53b59d41 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/integration_tests/acceptance.py @@ -0,0 +1,14 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + yield diff --git a/airbyte-integrations/connectors/source-clockify/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-clockify/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..a939aad354a8 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/integration_tests/configured_catalog.json @@ -0,0 +1,109 @@ +{ + "streams": [ + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "users", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + }, + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "projects", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + }, + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "clients", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + }, + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "tags", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + }, + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "user_groups", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + }, + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "time_entries", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + }, + { + "cursor_field": null, + "destination_sync_mode": "append", + "primary_key": null, + "stream": { + "default_cursor_field": null, + "json_schema": {}, + "name": "tasks", + "namespace": null, + "source_defined_cursor": null, + "source_defined_primary_key": null, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh" + } + ] +} diff --git a/airbyte-integrations/connectors/source-clockify/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-clockify/integration_tests/invalid_config.json new file mode 100644 index 000000000000..306220396277 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/integration_tests/invalid_config.json @@ -0,0 +1,4 @@ +{ + "api_key": "", + "workspace_id": "" +} diff --git a/airbyte-integrations/connectors/source-clockify/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-clockify/integration_tests/sample_config.json new file mode 100644 index 000000000000..b5ef13de8006 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/integration_tests/sample_config.json @@ -0,0 +1,4 @@ +{ + "api_key": "", + "workspace_id": "" +} diff --git a/airbyte-integrations/connectors/source-clockify/main.py b/airbyte-integrations/connectors/source-clockify/main.py new file mode 100644 index 000000000000..72002deb5c04 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_clockify import SourceClockify + +if __name__ == "__main__": + source = SourceClockify() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-clockify/requirements.txt b/airbyte-integrations/connectors/source-clockify/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-clockify/setup.py b/airbyte-integrations/connectors/source-clockify/setup.py new file mode 100644 index 000000000000..4e83a518e06e --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/setup.py @@ -0,0 +1,25 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.2.0", +] + +TEST_REQUIREMENTS = ["pytest~=6.1", "pytest-mock~=3.6.1", "source-acceptance-test", "responses"] + +setup( + name="source_clockify", + description="Source implementation for Clockify.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/__init__.py b/airbyte-integrations/connectors/source-clockify/source_clockify/__init__.py new file mode 100644 index 000000000000..952d1a5a6623 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceClockify + +__all__ = ["SourceClockify"] diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/clients.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/clients.json new file mode 100644 index 000000000000..00a10979e40c --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/clients.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "address": { + "type": ["null", "string"] + }, + "archived": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "note": { + "type": ["null", "string"] + }, + "workspaceId": { + "type": "string" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/projects.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/projects.json new file mode 100644 index 000000000000..faa01b58a0fd --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/projects.json @@ -0,0 +1,173 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "archived": { + "type": "boolean" + }, + "billable": { + "type": "boolean" + }, + "budgetEstimate": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "integer" + }, + { + "properties": { + "estimate": { + "type": ["null", "string"] + }, + "type": { + "type": "string" + }, + "resetOption": { + "type": ["null", "string"] + }, + "active": { + "type": "boolean" + } + }, + "type": "object" + } + ] + }, + "clientId": { + "type": "string" + }, + "clientName": { + "type": "string" + }, + "color": { + "type": "string" + }, + "costRate": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + }, + { + "properties": { + "amount": { + "type": ["null", "string", "integer"] + }, + "currency": { + "type": ["null", "string"] + } + }, + "type": "object" + } + ] + }, + "duration": { + "type": "string" + }, + "estimate": { + "properties": { + "estimate": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "hourlyRate": { + "properties": { + "amount": { + "type": "integer" + }, + "currency": { + "type": "string" + } + }, + "type": "object" + }, + "id": { + "type": "string" + }, + "memberships": { + "items": { + "properties": { + "costRate": { + "type": "null" + }, + "hourlyRate": { + "anyOf": [ + { + "type": "null" + }, + { + "properties": { + "amount": { + "type": "integer" + }, + "currency": { + "type": "string" + } + }, + "type": "object" + } + ] + }, + "membershipStatus": { + "type": "string" + }, + "membershipType": { + "type": "string" + }, + "targetId": { + "type": "string" + }, + "userId": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "note": { + "type": "string" + }, + "public": { + "type": "boolean" + }, + "template": { + "type": "boolean" + }, + "timeEstimate": { + "properties": { + "active": { + "type": "boolean" + }, + "estimate": { + "type": "string" + }, + "includeNonBillable": { + "type": "boolean" + }, + "resetOption": { + "type": ["null", "string"] + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "workspaceId": { + "type": "string" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tags.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tags.json new file mode 100644 index 000000000000..75b53dd8cfea --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tags.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "archived": { + "type": "boolean" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "workspaceId": { + "type": "string" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tasks.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tasks.json new file mode 100644 index 000000000000..441785586cce --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/tasks.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "assigneeId": { + "type": ["null", "string"] + }, + "assigneeIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "billable": { + "type": "boolean" + }, + "costRate": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + }, + { + "properties": { + "amount": { + "type": ["null", "string", "integer"] + }, + "currency": { + "type": ["null", "string"] + } + }, + "type": "object" + } + ] + }, + "duration": { + "type": ["null", "string"] + }, + "estimate": { + "type": "string" + }, + "hourlyRate": { + "anyOf": [ + { + "type": "null" + }, + { + "properties": { + "amount": { + "type": "integer" + }, + "currency": { + "type": "string" + } + }, + "type": "object" + } + ] + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "projectId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "userGroupIds": { + "type": "array" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/time_entries.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/time_entries.json new file mode 100644 index 000000000000..8fb163736583 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/time_entries.json @@ -0,0 +1,66 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "billable": { + "type": "boolean" + }, + "customFieldValues": { + "type": "array" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isLocked": { + "type": "boolean" + }, + "kioskId": { + "type": ["null", "string"] + }, + "projectId": { + "type": ["null", "string"] + }, + "tagIds": { + "anyOf": [ + { + "type": "null" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "taskId": { + "type": ["null", "string"] + }, + "timeInterval": { + "properties": { + "duration": { + "type": "string" + }, + "end": { + "type": "string" + }, + "start": { + "type": "string" + } + }, + "type": "object" + }, + "type": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "workspaceId": { + "type": "string" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/user_groups.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/user_groups.json new file mode 100644 index 000000000000..f7ffaae51e6b --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/user_groups.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "userIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "workspaceId": { + "type": "string" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/users.json b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/users.json new file mode 100644 index 000000000000..7d3d6d27cbd7 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/schemas/users.json @@ -0,0 +1,133 @@ +{ + "$schema": "http://json-schema.org/schema#", + "properties": { + "activeWorkspace": { + "type": "string" + }, + "customFields": { + "type": "array" + }, + "defaultWorkspace": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "string" + }, + "memberships": { + "type": "array" + }, + "name": { + "type": "string" + }, + "profilePicture": { + "type": "string" + }, + "settings": { + "properties": { + "alerts": { + "type": "boolean" + }, + "approval": { + "type": "boolean" + }, + "collapseAllProjectLists": { + "type": "boolean" + }, + "dashboardPinToTop": { + "type": "boolean" + }, + "dashboardSelection": { + "type": "string" + }, + "dashboardViewType": { + "type": "string" + }, + "dateFormat": { + "type": "string" + }, + "groupSimilarEntriesDisabled": { + "type": "boolean" + }, + "isCompactViewOn": { + "type": "boolean" + }, + "lang": { + "type": "string" + }, + "longRunning": { + "type": "boolean" + }, + "multiFactorEnabled": { + "type": "boolean" + }, + "myStartOfDay": { + "type": "string" + }, + "onboarding": { + "type": "boolean" + }, + "projectListCollapse": { + "type": "integer" + }, + "projectPickerTaskFilter": { + "type": "boolean" + }, + "pto": { + "type": "boolean" + }, + "reminders": { + "type": "boolean" + }, + "scheduledReports": { + "type": "boolean" + }, + "scheduling": { + "type": "boolean" + }, + "sendNewsletter": { + "type": "boolean" + }, + "showOnlyWorkingDays": { + "type": "boolean" + }, + "summaryReportSettings": { + "properties": { + "group": { + "type": "string" + }, + "subgroup": { + "type": "string" + } + }, + "type": "object" + }, + "theme": { + "type": "string" + }, + "timeFormat": { + "type": "string" + }, + "timeTrackingManual": { + "type": "boolean" + }, + "timeZone": { + "type": "string" + }, + "weekStart": { + "type": "string" + }, + "weeklyUpdates": { + "type": "boolean" + } + }, + "type": "object" + }, + "status": { + "type": "string" + } + }, + "type": "object" +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/source.py b/airbyte-integrations/connectors/source-clockify/source_clockify/source.py new file mode 100644 index 000000000000..b4bcd649b241 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/source.py @@ -0,0 +1,33 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from typing import Any, List, Mapping, Tuple + +from airbyte_cdk.models import SyncMode +from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.streams.http.requests_native_auth.token import TokenAuthenticator + +from .streams import Clients, Projects, Tags, Tasks, TimeEntries, UserGroups, Users + + +# Source +class SourceClockify(AbstractSource): + def check_connection(self, logger, config) -> Tuple[bool, any]: + try: + workspace_stream = Users( + authenticator=TokenAuthenticator(token=config["api_key"], auth_header="X-Api-Key", auth_method=""), + workspace_id=config["workspace_id"], + ) + next(workspace_stream.read_records(sync_mode=SyncMode.full_refresh)) + return True, None + except Exception as e: + return False, f"Please check that your API key and workspace id are entered correctly: {repr(e)}" + + def streams(self, config: Mapping[str, Any]) -> List[Stream]: + authenticator = TokenAuthenticator(token=config["api_key"], auth_header="X-Api-Key", auth_method="") + + args = {"authenticator": authenticator, "workspace_id": config["workspace_id"]} + + return [Users(**args), Projects(**args), Clients(**args), Tags(**args), UserGroups(**args), TimeEntries(**args), Tasks(**args)] diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/spec.json b/airbyte-integrations/connectors/source-clockify/source_clockify/spec.json new file mode 100644 index 000000000000..ecd182c8e160 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/spec.json @@ -0,0 +1,23 @@ +{ + "documentationUrl": "https://docs.airbyte.com/integrations/sources/clockify", + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Clockify Spec", + "type": "object", + "required": ["workspace_id", "api_key"], + "additionalProperties": true, + "properties": { + "workspace_id": { + "title": "Workspace Id", + "description": "WorkSpace Id", + "type": "string" + }, + "api_key": { + "title": "API Key", + "description": "You can get your api access_key here This API is Case Sensitive.", + "type": "string", + "airbyte_secret": true + } + } + } +} diff --git a/airbyte-integrations/connectors/source-clockify/source_clockify/streams.py b/airbyte-integrations/connectors/source-clockify/source_clockify/streams.py new file mode 100644 index 000000000000..4fb36a3977fa --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/source_clockify/streams.py @@ -0,0 +1,123 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from abc import ABC +from typing import Any, Iterable, Mapping, MutableMapping, Optional + +import requests +from airbyte_cdk.models import SyncMode +from airbyte_cdk.sources.streams.http import HttpStream, HttpSubStream +from requests.auth import AuthBase + + +class ClockifyStream(HttpStream, ABC): + url_base = "https://api.clockify.me/api/v1/" + page_size = 50 + page = 1 + primary_key = None + + def __init__(self, workspace_id: str, **kwargs): + super().__init__(**kwargs) + self.workspace_id = workspace_id + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + next_page = response.json() + self.page = self.page + 1 + if next_page: + return {"page": self.page} + + def request_params(self, next_page_token: Mapping[str, Any] = None, **kwargs) -> MutableMapping[str, Any]: + params = { + "page-size": self.page_size, + } + + if next_page_token: + params.update(next_page_token) + + return params + + def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: + yield from response.json() + + +class Users(ClockifyStream): + @property + def use_cache(self) -> bool: + return True + + def path(self, **kwargs) -> str: + return f"workspaces/{self.workspace_id}/users" + + +class Projects(ClockifyStream): + @property + def use_cache(self) -> bool: + return True + + def path(self, **kwargs) -> str: + return f"workspaces/{self.workspace_id}/projects" + + +class Clients(ClockifyStream): + def path(self, **kwargs) -> str: + return f"workspaces/{self.workspace_id}/clients" + + +class Tags(ClockifyStream): + def path(self, **kwargs) -> str: + return f"workspaces/{self.workspace_id}/tags" + + +class UserGroups(ClockifyStream): + def path(self, **kwargs) -> str: + return f"workspaces/{self.workspace_id}/user-groups" + + +class TimeEntries(HttpSubStream, ClockifyStream): + def __init__(self, authenticator: AuthBase, workspace_id: Mapping[str, Any], **kwargs): + super().__init__( + authenticator=authenticator, + workspace_id=workspace_id, + parent=Users(authenticator=authenticator, workspace_id=workspace_id, **kwargs), + ) + + def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, Any]]]: + """ + self.authenticator (which should be used as the + authenticator for Users) is object of NoAuth() + + so self._session.auth is used instead + """ + users_stream = Users(authenticator=self._session.auth, workspace_id=self.workspace_id) + for user in users_stream.read_records(sync_mode=SyncMode.full_refresh): + yield {"user_id": user["id"]} + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + user_id = stream_slice["user_id"] + return f"workspaces/{self.workspace_id}/user/{user_id}/time-entries" + + +class Tasks(HttpSubStream, ClockifyStream): + def __init__(self, authenticator: AuthBase, workspace_id: Mapping[str, Any], **kwargs): + super().__init__( + authenticator=authenticator, + workspace_id=workspace_id, + parent=Projects(authenticator=authenticator, workspace_id=workspace_id, **kwargs), + ) + + def stream_slices(self, **kwargs) -> Iterable[Optional[Mapping[str, Any]]]: + """ + self.authenticator (which should be used as the + authenticator for Projects) is object of NoAuth() + + so self._session.auth is used instead + """ + projects_stream = Projects(authenticator=self._session.auth, workspace_id=self.workspace_id) + for project in projects_stream.read_records(sync_mode=SyncMode.full_refresh): + yield {"project_id": project["id"]} + + def path(self, stream_slice: Mapping[str, Any] = None, **kwargs) -> str: + project_id = stream_slice["project_id"] + return f"workspaces/{self.workspace_id}/projects/{project_id}/tasks" diff --git a/airbyte-integrations/connectors/source-clockify/unit_tests/__init__.py b/airbyte-integrations/connectors/source-clockify/unit_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/unit_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-clockify/unit_tests/conftest.py b/airbyte-integrations/connectors/source-clockify/unit_tests/conftest.py new file mode 100644 index 000000000000..ae1ad3fd9d3d --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/unit_tests/conftest.py @@ -0,0 +1,10 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import pytest + + +@pytest.fixture(scope="session", name="config") +def config_fixture(): + return {"api_key": "test_api_key", "workspace_id": "workspace_id"} diff --git a/airbyte-integrations/connectors/source-clockify/unit_tests/test_source.py b/airbyte-integrations/connectors/source-clockify/unit_tests/test_source.py new file mode 100644 index 000000000000..3f6200a9ef68 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/unit_tests/test_source.py @@ -0,0 +1,33 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from unittest.mock import MagicMock + +import responses +from source_clockify.source import SourceClockify + + +def setup_responses(): + responses.add( + responses.GET, + "https://api.clockify.me/api/v1/workspaces/workspace_id/users", + json={"access_token": "test_api_key", "expires_in": 3600}, + ) + + +@responses.activate +def test_check_connection(config): + setup_responses() + source = SourceClockify() + logger_mock = MagicMock() + assert source.check_connection(logger_mock, config) == (True, None) + + +def test_streams(mocker): + source = SourceClockify() + config_mock = MagicMock() + streams = source.streams(config_mock) + + expected_streams_number = 7 + assert len(streams) == expected_streams_number diff --git a/airbyte-integrations/connectors/source-clockify/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-clockify/unit_tests/test_streams.py new file mode 100644 index 000000000000..e64e95119dc0 --- /dev/null +++ b/airbyte-integrations/connectors/source-clockify/unit_tests/test_streams.py @@ -0,0 +1,49 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from unittest.mock import MagicMock + +import pytest +from airbyte_cdk.models import SyncMode +from source_clockify.streams import ClockifyStream + + +@pytest.fixture +def patch_base_class(mocker): + # Mock abstract methods to enable instantiating abstract class + mocker.patch.object(ClockifyStream, "path", "v0/example_endpoint") + mocker.patch.object(ClockifyStream, "primary_key", "test_primary_key") + mocker.patch.object(ClockifyStream, "__abstractmethods__", set()) + + +def test_request_params(patch_base_class): + stream = ClockifyStream(workspace_id=MagicMock()) + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_params = {"page-size": 50} + assert stream.request_params(**inputs) == expected_params + + +def test_next_page_token(patch_base_class): + stream = ClockifyStream(workspace_id=MagicMock()) + inputs = {"response": MagicMock()} + expected_token = {"page": 2} + assert stream.next_page_token(**inputs) == expected_token + + +def test_read_records(patch_base_class): + stream = ClockifyStream(workspace_id=MagicMock()) + assert stream.read_records(sync_mode=SyncMode.full_refresh) + + +def test_request_headers(patch_base_class): + stream = ClockifyStream(workspace_id=MagicMock()) + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_headers = {} + assert stream.request_headers(**inputs) == expected_headers + + +def test_http_method(patch_base_class): + stream = ClockifyStream(workspace_id=MagicMock()) + expected_method = "GET" + assert stream.http_method == expected_method diff --git a/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py index d0b8b899456a..00315a9bc5fa 100644 --- a/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py +++ b/airbyte-integrations/connectors/source-freshdesk/unit_tests/test_streams.py @@ -4,7 +4,7 @@ import random from typing import Any, MutableMapping -from unittest.mock import patch, PropertyMock +from unittest.mock import PropertyMock, patch import pytest from airbyte_cdk.models import SyncMode @@ -126,7 +126,7 @@ def test_incremental(stream, resource, authenticator, config, requests_mock): highest_updated_at = "2022-04-25T22:00:00Z" other_updated_at = "2022-04-01T00:00:00Z" highest_index = random.randint(0, 24) - with patch(f'source_freshdesk.streams.{stream.__name__}.use_cache', new_callable=PropertyMock, return_value=False): + with patch(f"source_freshdesk.streams.{stream.__name__}.use_cache", new_callable=PropertyMock, return_value=False): requests_mock.register_uri( "GET", f"/api/{resource}", From 418e238a2e30189d1a87885214f188e1e99cb2c3 Mon Sep 17 00:00:00 2001 From: Malik Diarra Date: Wed, 26 Oct 2022 08:00:14 -0700 Subject: [PATCH 336/498] Optimize listDestinationsForWorkspace route (#18456) Add a function to fetch all destinations in a workspace --- .../config/persistence/ConfigRepository.java | 16 ++++++++++++++++ .../ConfigRepositoryE2EReadWriteTest.java | 9 +++++++++ .../server/handlers/DestinationHandler.java | 10 +--------- .../server/handlers/DestinationHandlerTest.java | 3 ++- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 38acad7f9fe4..7f9a7d011981 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -650,6 +650,22 @@ public List listDestinationConnection() throws JsonValida return persistence.listConfigs(ConfigSchema.DESTINATION_CONNECTION, DestinationConnection.class); } + /** + * Returns all destinations for a workspace. Does not contain secrets. + * + * @param workspaceId - id of the workspace + * @return destinations + * @throws IOException - you never know when you IO + */ + public List listWorkspaceDestinationConnection(UUID workspaceId) throws IOException { + final Result result = database.query(ctx -> ctx.select(asterisk()) + .from(ACTOR) + .where(ACTOR.ACTOR_TYPE.eq(ActorType.destination)) + .and(ACTOR.WORKSPACE_ID.eq(workspaceId)) + .andNot(ACTOR.TOMBSTONE).fetch()); + return result.stream().map(DbConverter::buildDestinationConnection).collect(Collectors.toList()); + } + public StandardSync getStandardSync(final UUID connectionId) throws JsonValidationException, IOException, ConfigNotFoundException { return persistence.getConfig(ConfigSchema.STANDARD_SYNC, connectionId.toString(), StandardSync.class); } diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index 552355066b5e..e3f3cee96398 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -306,6 +306,15 @@ void testListWorkspaceSources() throws IOException { assertThat(sources).hasSameElementsAs(expectedSources); } + @Test + void testListWorkspaceDestinations() throws IOException { + UUID workspaceId = MockData.standardWorkspaces().get(0).getWorkspaceId(); + final List expectedDestinations = MockData.destinationConnections().stream() + .filter(destination -> destination.getWorkspaceId().equals(workspaceId)).collect(Collectors.toList()); + final List destinations = configRepository.listWorkspaceDestinationConnection(workspaceId); + assertThat(destinations).hasSameElementsAs(expectedDestinations); + } + @Test void testSourceDefinitionGrants() throws IOException { final UUID workspaceId = MockData.standardWorkspaces().get(0).getWorkspaceId(); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java index 73515d520eb5..4263e25e5df4 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/DestinationHandler.java @@ -198,15 +198,7 @@ public DestinationReadList listDestinationsForWorkspace(final WorkspaceIdRequest throws ConfigNotFoundException, IOException, JsonValidationException { final List reads = Lists.newArrayList(); - for (final DestinationConnection dci : configRepository.listDestinationConnection()) { - if (!dci.getWorkspaceId().equals(workspaceIdRequestBody.getWorkspaceId())) { - continue; - } - - if (dci.getTombstone()) { - continue; - } - + for (final DestinationConnection dci : configRepository.listWorkspaceDestinationConnection(workspaceIdRequestBody.getWorkspaceId())) { reads.add(buildDestinationRead(dci)); } diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/DestinationHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/DestinationHandlerTest.java index 6dc096c862e1..28fb3868bb2b 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/DestinationHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/DestinationHandlerTest.java @@ -230,7 +230,8 @@ void testListDestinationForWorkspace() throws JsonValidationException, ConfigNot final WorkspaceIdRequestBody workspaceIdRequestBody = new WorkspaceIdRequestBody().workspaceId(destinationConnection.getWorkspaceId()); when(configRepository.getDestinationConnection(destinationConnection.getDestinationId())).thenReturn(destinationConnection); - when(configRepository.listDestinationConnection()).thenReturn(Lists.newArrayList(destinationConnection)); + when(configRepository.listWorkspaceDestinationConnection(destinationConnection.getWorkspaceId())) + .thenReturn(Lists.newArrayList(destinationConnection)); when(configRepository.getStandardDestinationDefinition(standardDestinationDefinition.getDestinationDefinitionId())) .thenReturn(standardDestinationDefinition); when(secretsProcessor.prepareSecretsForOutput(destinationConnection.getConfiguration(), From 2f9ab62de81aa5cafc9010801cd22e5c2fb8f0c7 Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Wed, 26 Oct 2022 11:19:46 -0400 Subject: [PATCH 337/498] Add new streams table components (#18262) * Add new streams table components * Update CatalogTree and Catalog section to render new components based on env-based flag * Add basic Bulk edit panel, stream panel, table row, table headers * Split the overlay out of the modal and make it its own component. Use in StreamPanel * Add Fields to en.json * StreamPanel -> StreamDetailsPanel * Add new stream fields table components * Use new stream fiels table props iface on Stream details panel --- .../connection/CatalogTree/CatalogSection.tsx | 33 +++- .../connection/CatalogTree/CatalogTree.tsx | 20 +- .../CatalogTree/next/BulkEditPanel.tsx | 180 ++++++++++++++++++ .../next/CatalogTreeTableHeader.tsx | 70 +++++++ .../next/CatalogTreeTableRow.module.scss | 48 +++++ .../CatalogTree/next/CatalogTreeTableRow.tsx | 147 ++++++++++++++ .../next/StreamConnectionHeader.module.scss | 20 ++ .../next/StreamConnectionHeader.tsx | 27 +++ .../next/StreamDetailsPanel.module.scss | 19 ++ .../CatalogTree/next/StreamDetailsPanel.tsx | 54 ++++++ .../CatalogTree/next/StreamFieldsTable.tsx | 45 +++++ .../next/StreamFieldsTableHeader.tsx | 33 ++++ .../CatalogTree/next/StreamFieldsTableRow.tsx | 66 +++++++ .../next/StreamPanelHeader.module.scss | 21 ++ .../CatalogTree/next/StreamPanelHeader.tsx | 55 ++++++ .../src/components/ui/Modal/Modal.module.scss | 9 - .../src/components/ui/Modal/Modal.tsx | 3 +- .../components/ui/Overlay/Overlay.module.scss | 10 + .../src/components/ui/Overlay/Overlay.tsx | 3 + .../components/ui/Overlay/index.stories.tsx | 14 ++ airbyte-webapp/src/locales/en.json | 1 + 21 files changed, 859 insertions(+), 19 deletions(-) create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/BulkEditPanel.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTable.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableHeader.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableRow.tsx create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.tsx create mode 100644 airbyte-webapp/src/components/ui/Overlay/Overlay.module.scss create mode 100644 airbyte-webapp/src/components/ui/Overlay/Overlay.tsx create mode 100644 airbyte-webapp/src/components/ui/Overlay/index.stories.tsx diff --git a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx index 2e1c03fdeba5..5306123c3085 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx @@ -18,6 +18,8 @@ import { equal, naturalComparatorBy } from "utils/objects"; import { ConnectionFormValues, SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig"; import styles from "./CatalogSection.module.scss"; +import { CatalogTreeTableRow } from "./next/CatalogTreeTableRow"; +import { StreamDetailsPanel } from "./next/StreamDetailsPanel"; import { StreamFieldTable } from "./StreamFieldTable"; import { StreamHeader } from "./StreamHeader"; import { flatten, getPathType } from "./utils"; @@ -41,6 +43,8 @@ const CatalogSectionInner: React.FC = ({ errors, changedSelected, }) => { + const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; + const { destDefinition: { supportedDestinationSyncModes }, } = useConnectionFormService(); @@ -135,9 +139,11 @@ const CatalogSectionInner: React.FC = ({ const hasFields = fields?.length > 0; const disabled = mode === "readonly"; + const StreamComponent = isNewStreamsTableEnabled ? CatalogTreeTableRow : StreamHeader; + return (
      - = ({ hasError={hasError} disabled={disabled} /> - {isRowExpanded && hasFields && ( -
      - -
      - )} + ) : ( +
      + +
      + ))}
      ); }; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx index b00a71c0cf9e..932a78b16d5f 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTree.tsx @@ -12,6 +12,9 @@ import { CatalogTreeBody } from "./CatalogTreeBody"; import { CatalogTreeHeader } from "./CatalogTreeHeader"; import { CatalogTreeSearch } from "./CatalogTreeSearch"; import { CatalogTreeSubheader } from "./CatalogTreeSubheader"; +import { BulkEditPanel } from "./next/BulkEditPanel"; +import { CatalogTreeTableHeader } from "./next/CatalogTreeTableHeader"; +import { StreamConnectionHeader } from "./next/StreamConnectionHeader"; interface CatalogTreeProps { streams: SyncSchemaStream[]; @@ -24,6 +27,7 @@ const CatalogTreeComponent: React.FC> onStreamsChanged, isLoading, }) => { + const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; const { mode } = useConnectionFormService(); const [searchString, setSearchString] = useState(""); @@ -53,11 +57,21 @@ const CatalogTreeComponent: React.FC> {mode !== "readonly" && } - - - + {isNewStreamsTableEnabled ? ( + <> + + + + ) : ( + <> + + + + + )} + {isNewStreamsTableEnabled && } ); }; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/BulkEditPanel.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/BulkEditPanel.tsx new file mode 100644 index 000000000000..7855697333de --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/BulkEditPanel.tsx @@ -0,0 +1,180 @@ +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import intersection from "lodash/intersection"; +import React, { useMemo } from "react"; +import { createPortal } from "react-dom"; +import { FormattedMessage } from "react-intl"; +import styled from "styled-components"; + +import { Cell, Header } from "components"; +import { Button } from "components/ui/Button"; +import { Switch } from "components/ui/Switch"; + +import { SyncSchemaField, SyncSchemaFieldObject, SyncSchemaStream, traverseSchemaToField } from "core/domain/catalog"; +import { DestinationSyncMode, SyncMode } from "core/request/AirbyteClient"; +import { useBulkEditService } from "hooks/services/BulkEdit/BulkEditService"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig"; + +import { pathDisplayName, PathPopout } from "../PathPopout"; +import { HeaderCell } from "../styles"; +import { SyncSettingsDropdown } from "../SyncSettingsDropdown"; +import { flatten, getPathType } from "../utils"; + +const SchemaHeader = styled(Header)` + position: fixed; + bottom: 0; + left: 122px; + z-index: 1000; + width: calc(100% - 152px); + bottom: 0; + height: unset; + background: ${({ theme }) => theme.primaryColor}; + border-radius: 8px 8px 0 0; +`; + +function calculateSharedFields(selectedBatchNodes: SyncSchemaStream[]) { + const primitiveFieldsByStream = selectedBatchNodes.map(({ stream }) => { + const traversedFields = traverseSchemaToField(stream?.jsonSchema, stream?.name); + const flattenedFields = flatten(traversedFields); + + return flattenedFields.filter(SyncSchemaFieldObject.isPrimitive); + }); + + const pathMap = new Map(); + + // calculate intersection of primitive fields across all selected streams + primitiveFieldsByStream.forEach((fields, index) => { + if (index === 0) { + fields.forEach((field) => pathMap.set(pathDisplayName(field.path), field)); + } else { + const fieldMap = new Set(fields.map((f) => pathDisplayName(f.path))); + pathMap.forEach((_, k) => (!fieldMap.has(k) ? pathMap.delete(k) : null)); + } + }); + + return Array.from(pathMap.values()); +} + +export const BulkEditPanel: React.FC = () => { + const { + destDefinition: { supportedDestinationSyncModes }, + } = useConnectionFormService(); + const { selectedBatchNodes, options, onChangeOption, onApply, isActive, onCancel } = useBulkEditService(); + + const availableSyncModes = useMemo( + () => + SUPPORTED_MODES.filter(([syncMode, destinationSyncMode]) => { + const supportableModes = intersection(selectedBatchNodes.flatMap((n) => n.stream?.supportedSyncModes)); + return supportableModes.includes(syncMode) && supportedDestinationSyncModes?.includes(destinationSyncMode); + }).map(([syncMode, destinationSyncMode]) => ({ + value: { syncMode, destinationSyncMode }, + })), + [selectedBatchNodes, supportedDestinationSyncModes] + ); + + const primitiveFields: SyncSchemaField[] = useMemo( + () => calculateSharedFields(selectedBatchNodes), + [selectedBatchNodes] + ); + + if (!isActive) { + return null; + } + + const pkRequired = options.destinationSyncMode === DestinationSyncMode.append_dedup; + const shouldDefinePk = selectedBatchNodes.every((n) => n.stream?.sourceDefinedPrimaryKey?.length === 0) && pkRequired; + const cursorRequired = options.syncMode === SyncMode.incremental; + const shouldDefineCursor = selectedBatchNodes.every((n) => !n.stream?.sourceDefinedCursor) && cursorRequired; + const numStreamsSelected = selectedBatchNodes.length; + + const pkType = getPathType(pkRequired, shouldDefinePk); + const cursorType = getPathType(cursorRequired, shouldDefineCursor); + + const paths = primitiveFields.map((f) => f.path); + + return createPortal( + + +
      {numStreamsSelected}
      + +
      + +
      + +
      + onChangeOption({ selected: !options.selected })} /> +
      + +
      + +
      + onChangeOption({ ...value })} + /> +
      + + {cursorType && ( + <> +
      + +
      + onChangeOption({ cursorField: path })} + pathType={cursorType} + paths={paths} + path={options.cursorField} + /> + + )} +
      + + {pkType && ( + <> +
      + +
      + onChangeOption({ primaryKey: path })} + pathType={pkType} + paths={paths} + path={options.primaryKey} + /> + + )} +
      + + + + +
      + +
      + onChangeOption({ ...value })} + /> +
      + + + + +
      , + document.body + ); +}; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx new file mode 100644 index 000000000000..2d3f11682aab --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx @@ -0,0 +1,70 @@ +import { FormattedMessage } from "react-intl"; + +import { Cell, Header } from "components/SimpleTableComponents"; +import { CheckBox } from "components/ui/CheckBox"; +import { InfoTooltip, TooltipLearnMoreLink } from "components/ui/Tooltip"; + +import { useBulkEditService } from "hooks/services/BulkEdit/BulkEditService"; +import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { links } from "utils/links"; + +export const CatalogTreeTableHeader: React.FC = () => { + const { mode } = useConnectionFormService(); + const { onCheckAll, selectedBatchNodeIds, allChecked } = useBulkEditService(); + + return ( +
      + + {mode !== "readonly" && ( + 0 && !allChecked} + checked={allChecked} + /> + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + ); +}; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss new file mode 100644 index 000000000000..557907278af4 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss @@ -0,0 +1,48 @@ +@use "scss/colors"; +@use "scss/variables"; + +.icon { + margin-right: 7px; + margin-top: -1px; + + &.plus { + color: colors.$green; + } + + &.minus { + color: colors.$red; + } +} + +.streamHeaderContent { + background: colors.$white; + border-bottom: 1px solid colors.$grey-50; + + &:hover { + background: colors.$grey-30; + cursor: pointer; + } + + &.disabledChange { + background-color: colors.$red-50; + } + + &.enabledChange { + background-color: colors.$green-50; + } + + &.error { + border: 1px solid colors.$red; + } + + &.selected { + background-color: colors.$blue-transparent; + } +} + +.streamRowCheckboxCell { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding-left: 27px; +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx new file mode 100644 index 000000000000..c651777b389f --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx @@ -0,0 +1,147 @@ +import { faArrowRight, faMinus, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classnames from "classnames"; +import { useMemo } from "react"; +import { FormattedMessage } from "react-intl"; + +import { Cell, Row } from "components/SimpleTableComponents"; +import { CheckBox } from "components/ui/CheckBox"; +import { Switch } from "components/ui/Switch"; + +import { useBulkEditSelect } from "hooks/services/BulkEdit/BulkEditService"; + +import { PathPopout } from "../PathPopout"; +import { StreamHeaderProps } from "../StreamHeader"; +import { HeaderCell } from "../styles"; +import styles from "./CatalogTreeTableRow.module.scss"; + +export const CatalogTreeTableRow: React.FC = ({ + stream, + destName, + destNamespace, + // onSelectSyncMode, + onSelectStream, + // availableSyncModes, + pkType, + onPrimaryKeyChange, + onCursorChange, + primitiveFields, + cursorType, + // isRowExpanded, + fields, + onExpand, + changedSelected, + hasError, + disabled, +}) => { + const { primaryKey, syncMode, cursorField, destinationSyncMode } = stream.config ?? {}; + const isStreamEnabled = stream.config?.selected; + + const { defaultCursorField } = stream.stream ?? {}; + const syncSchema = useMemo( + () => ({ + syncMode, + destinationSyncMode, + }), + [syncMode, destinationSyncMode] + ); + + const [isSelected, selectForBulkEdit] = useBulkEditSelect(stream.id); + + const paths = useMemo(() => primitiveFields.map((field) => field.path), [primitiveFields]); + const fieldCount = fields?.length ?? 0; + const onRowClick = fieldCount > 0 ? () => onExpand() : undefined; + + const iconStyle = classnames(styles.icon, { + [styles.plus]: isStreamEnabled, + [styles.minus]: !isStreamEnabled, + }); + + const streamHeaderContentStyle = classnames(styles.streamHeaderContent, { + [styles.enabledChange]: changedSelected && isStreamEnabled, + [styles.disabledChange]: changedSelected && !isStreamEnabled, + [styles.selected]: isSelected, + [styles.error]: hasError, + }); + + const checkboxCellCustomStyle = classnames(styles.checkboxCell, styles.streamRowCheckboxCell); + + return ( + + {!disabled && ( +
      + {changedSelected && ( +
      + {isStreamEnabled ? ( + + ) : ( + + )} +
      + )} + +
      + )} + + + + {fieldCount} + + {stream.stream?.namespace || } + + {stream.stream?.name} + + {disabled ? ( + + {syncSchema.syncMode} + + ) : ( + // TODO: Replace with Dropdown/Popout + syncSchema.syncMode + )} + + + {cursorType && ( + } + onPathChange={onCursorChange} + /> + )} + + + {pkType && ( + } + onPathChange={onPrimaryKeyChange} + /> + )} + + + + + + {destNamespace} + + + {destName} + + + {disabled ? ( + + {syncSchema.destinationSyncMode} + + ) : ( + // TODO: Replace with Dropdown/Popout + syncSchema.destinationSyncMode + )} + +
      + ); +}; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.module.scss new file mode 100644 index 000000000000..c2a9085a9b8e --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.module.scss @@ -0,0 +1,20 @@ +@use "scss/variables"; + +$icon-size: 15px; + +.container { + display: flex; + flex-direction: row; + justify-content: space-between; + padding: variables.$spacing-lg calc(variables.$spacing-lg * 2); +} + +.connector { + display: flex; + flex-direction: row; +} + +.icon { + height: $icon-size; + width: $icon-size; +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.tsx new file mode 100644 index 000000000000..cb65e0ca8f92 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamConnectionHeader.tsx @@ -0,0 +1,27 @@ +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService"; +import { useDestinationDefinition } from "services/connector/DestinationDefinitionService"; +import { useSourceDefinition } from "services/connector/SourceDefinitionService"; +import { getIcon } from "utils/imageUtils"; + +import styles from "./StreamConnectionHeader.module.scss"; + +const renderIcon = (icon?: string): JSX.Element =>
      {getIcon(icon)}
      ; + +export const StreamConnectionHeader: React.FC = () => { + const { + connection: { source, destination }, + } = useConnectionEditService(); + const sourceDefinition = useSourceDefinition(source.sourceDefinitionId); + const destinationDefinition = useDestinationDefinition(destination.destinationDefinitionId); + + return ( +
      +
      {renderIcon(sourceDefinition.icon)} Source
      + +
      {renderIcon(destinationDefinition.icon)} Destination
      +
      + ); +}; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.module.scss new file mode 100644 index 000000000000..706de7384422 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.module.scss @@ -0,0 +1,19 @@ +@use "scss/colors"; +@use "scss/variables"; +@use "scss/z-indices"; + +.dialog { + z-index: z-indices.$modal; +} + +.container { + position: fixed; + bottom: 0; + left: variables.$width-size-menu; + z-index: 1000; + width: calc(100% - variables.$width-size-menu); + height: calc(100vh - 100px); + background: colors.$white; + border-radius: 15px 15px 0 0; + box-shadow: 0 0 22px rgba(colors.$dark-blue-900, 12%); +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.tsx new file mode 100644 index 000000000000..3697534e3332 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamDetailsPanel.tsx @@ -0,0 +1,54 @@ +import { Dialog } from "@headlessui/react"; + +import { Overlay } from "components/ui/Overlay/Overlay"; + +import { AirbyteStream } from "core/request/AirbyteClient"; + +import { StreamConnectionHeader } from "./StreamConnectionHeader"; +import styles from "./StreamDetailsPanel.module.scss"; +import { StreamFieldsTable, StreamFieldsTableProps } from "./StreamFieldsTable"; +import { StreamPanelHeader } from "./StreamPanelHeader"; + +interface StreamDetailsPanelProps extends StreamFieldsTableProps { + disabled?: boolean; + onClose: () => void; + onSelectedChange: () => void; + stream?: AirbyteStream; +} + +export const StreamDetailsPanel: React.FC = ({ + config, + disabled, + onPkSelect, + onCursorSelect, + onClose, + onSelectedChange, + shouldDefinePk, + shouldDefineCursor, + stream, + syncSchemaFields, +}) => { + return ( + + + + + + + + + ); +}; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTable.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTable.tsx new file mode 100644 index 000000000000..52dc5610ca25 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTable.tsx @@ -0,0 +1,45 @@ +import { SyncSchemaField, SyncSchemaFieldObject } from "core/domain/catalog"; +import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; + +import { pathDisplayName } from "../PathPopout"; +import { TreeRowWrapper } from "../TreeRowWrapper"; +import { StreamFieldsTableHeader } from "./StreamFieldsTableHeader"; +import { StreamFieldsTableRow } from "./StreamFieldsTableRow"; + +export interface StreamFieldsTableProps { + config?: AirbyteStreamConfiguration; + onCursorSelect: (cursorPath: string[]) => void; + onPkSelect: (pkPath: string[]) => void; + shouldDefinePk: boolean; + shouldDefineCursor: boolean; + syncSchemaFields: SyncSchemaField[]; +} + +export const StreamFieldsTable: React.FC = ({ + config, + onPkSelect, + onCursorSelect, + shouldDefineCursor, + shouldDefinePk, + syncSchemaFields, +}) => { + return ( + <> + + + + {syncSchemaFields.map((field) => ( + + + + ))} + + ); +}; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableHeader.tsx new file mode 100644 index 000000000000..7db0ce0f52c8 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableHeader.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { HeaderCell, NameContainer } from "../styles"; + +export const StreamFieldsTableHeader: React.FC = React.memo(() => ( + <> + + + + + + + + + + + + + + + + + + + {/* + In the design, but we may be unable to get the destination data type + + + + */} + +)); diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableRow.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableRow.tsx new file mode 100644 index 000000000000..ea584c08da62 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamFieldsTableRow.tsx @@ -0,0 +1,66 @@ +import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import React from "react"; + +import { Cell } from "components/SimpleTableComponents"; +import { CheckBox } from "components/ui/CheckBox"; +import { RadioButton } from "components/ui/RadioButton"; + +import { SyncSchemaField } from "core/domain/catalog"; +import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; +import { equal } from "utils/objects"; +import { useTranslateDataType } from "utils/useTranslateDataType"; + +import DataTypeCell from "../DataTypeCell"; +import { pathDisplayName } from "../PathPopout"; +import { NameContainer } from "../styles"; + +interface StreamFieldsTableRowProps { + isPrimaryKeyEnabled: boolean; + isCursorEnabled: boolean; + + onPrimaryKeyChange: (pk: string[]) => void; + onCursorChange: (cs: string[]) => void; + field: SyncSchemaField; + config: AirbyteStreamConfiguration | undefined; +} + +const StreamFieldsTableRowComponent: React.FC = ({ + onPrimaryKeyChange, + onCursorChange, + field, + config, + isCursorEnabled, + isPrimaryKeyEnabled, +}) => { + const dataType = useTranslateDataType(field); + const name = pathDisplayName(field.path); + + const isCursor = equal(config?.cursorField, field.path); + const isPrimaryKey = !!config?.primaryKey?.some((p) => equal(p, field.path)); + + return ( + <> + + {name} + + {dataType} + {isCursorEnabled && onCursorChange(field.path)} />} + + {isPrimaryKeyEnabled && onPrimaryKeyChange(field.path)} />} + + + + + + {field.cleanedName} + + {/* + In the design, but we may be unable to get the destination data type + {dataType} + */} + + ); +}; + +export const StreamFieldsTableRow = React.memo(StreamFieldsTableRowComponent); diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.module.scss new file mode 100644 index 000000000000..557c6a268c23 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.module.scss @@ -0,0 +1,21 @@ +@use "scss/colors"; +@use "scss/variables"; + +.container { + border-radius: 10px; + margin: variables.$spacing-md variables.$spacing-lg; + background-color: colors.$grey-50; + display: flex; + flex-direction: row; + justify-content: space-between; + + & > :not(button) { + padding: variables.$spacing-md; + } +} + +.properties { + display: flex; + flex-direction: row; + gap: variables.$spacing-xl; +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.tsx new file mode 100644 index 000000000000..e40cdb29ba90 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPanelHeader.tsx @@ -0,0 +1,55 @@ +import { FormattedMessage } from "react-intl"; + +import { CrossIcon } from "components/icons/CrossIcon"; +import { Button } from "components/ui/Button"; +import { Switch } from "components/ui/Switch"; + +import { AirbyteStream, AirbyteStreamConfiguration } from "core/request/AirbyteClient"; + +import styles from "./StreamPanelHeader.module.scss"; + +interface StreamPanelHeaderProps { + config?: AirbyteStreamConfiguration; + disabled?: boolean; + onClose: () => void; + onSelectedChange: () => void; + stream?: AirbyteStream; +} + +interface SomethingProps { + messageId: string; + value?: string; +} + +export const StreamProperty: React.FC = ({ messageId, value }) => { + return ( + + + + + : {value} + + ); +}; + +export const StreamPanelHeader: React.FC = ({ + config, + disabled, + onClose, + onSelectedChange, + stream, +}) => { + return ( +
      +
      + +
      +
      + + + +
      +
      + ); +}; diff --git a/airbyte-webapp/src/components/ui/Modal/Modal.module.scss b/airbyte-webapp/src/components/ui/Modal/Modal.module.scss index 290d12289781..566759f29356 100644 --- a/airbyte-webapp/src/components/ui/Modal/Modal.module.scss +++ b/airbyte-webapp/src/components/ui/Modal/Modal.module.scss @@ -8,15 +8,6 @@ } } -.backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(colors.$black, 0.5); -} - .modalPageContainer { position: absolute; z-index: z-indices.$modal; diff --git a/airbyte-webapp/src/components/ui/Modal/Modal.tsx b/airbyte-webapp/src/components/ui/Modal/Modal.tsx index 857174bb281a..942dadc3ac0b 100644 --- a/airbyte-webapp/src/components/ui/Modal/Modal.tsx +++ b/airbyte-webapp/src/components/ui/Modal/Modal.tsx @@ -3,6 +3,7 @@ import classNames from "classnames"; import React, { useState } from "react"; import { Card } from "../Card"; +import { Overlay } from "../Overlay/Overlay"; import styles from "./Modal.module.scss"; export interface ModalProps { @@ -37,7 +38,7 @@ export const Modal: React.FC> = ({ return ( -
      +
      {cardless ? ( diff --git a/airbyte-webapp/src/components/ui/Overlay/Overlay.module.scss b/airbyte-webapp/src/components/ui/Overlay/Overlay.module.scss new file mode 100644 index 000000000000..0102f7db41b6 --- /dev/null +++ b/airbyte-webapp/src/components/ui/Overlay/Overlay.module.scss @@ -0,0 +1,10 @@ +@use "scss/colors"; + +.container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(colors.$black, 0.5); +} diff --git a/airbyte-webapp/src/components/ui/Overlay/Overlay.tsx b/airbyte-webapp/src/components/ui/Overlay/Overlay.tsx new file mode 100644 index 000000000000..1d9bdea7f3b8 --- /dev/null +++ b/airbyte-webapp/src/components/ui/Overlay/Overlay.tsx @@ -0,0 +1,3 @@ +import styles from "./Overlay.module.scss"; + +export const Overlay: React.FC = () =>

    SebS6^yALG$j3*V-nlIw&Y84wJ8bqy=e`*;pESB7`_6jk_Vl))C zbU`*er**s~P{j@tLpd2DcW&?-&;DZh=V)DSmHy3W$@fJkWn~yGDHWw*Tq5fJwDol9rwIyI#jin#Iz=u3 z0+*bff~31|TW$;8(PNAt4De=_>{o*73!0U?QVAl17#U=?I*M*2s9&G9iky)(pQz>3 z>7qO3hUb2PeRzvN%CaGcfyayybWHAvw!1aDL2q&!%3*9-G(TX=oS3coPse3P_{ctK zv1J+@+C`7$rcC15me(6WHngro5^s;7L5GXy3Edm*LurL!HEpU!+w3?PTzWJQB3-%5 zzlkbkO-o#gIwG!tBgH0fom`avQOTfe99Ve}-a8NF6 z=j@iPn5Z{h5ZFdj1n(jA&Ed+eR_lnInSpowXM3b+3hg2( zWb`$Btw!~DPu8da5b;lZYr3GX4tz>>(W&H8M2~W6vr!sUj)bw~h=n;s`jIk#`Az0E zY)F%SS2hzdb-ZnAXH-MV7i$z>vIRCS-djB#e65aju?@Ri^zBmK^EIc}G&0m;Qpg}y^TT4cK@0^+5{Ua2TK%37_rIwG z%_FXsi!t9Ji(*8r^5K?IMLi513MhFPjid-!^cGZTvqlEmGl)$oBi5l@Aq61YuY|pR zWAyw{%*@qq|3|~9X!3v@_u!5ARupya-LfXWG~3Uo@Yoxgzi>p=UWXF~56X1BWkXE# z!NRs0*uWvo0)>{+8i=L6oIkueRSz3NF~-fgV~&)FYR1=3VG^h~@J3pqtRJ|h#ds2e-Qa{3 z(8svoOIS0fmi4vH83&amsZdheIDgP~=pb%VE+;^0wJXRa0;S*IUT0VL4Uh$x}b(ru!SF%8rfG7nz;N>;=vr&hYnx%gb$6jmo zQK>s-&7h7ckPNxToa2M9@y4ZGo&Lw}^UOrNF22QZqqxJic0QwGHIU2@5sqy6V!dPua_j2T~M_008K z{Sd-HtXnGqTdd?zxmaPiOGaRY7xm`wlss~d#;nf$fOx9}ET1&q{ls5@60d4}gH*zH zt7T0#-}&;=l{s^)BW7Cos|p1B<+>KIW`5Hkrh(bcedy~aJ-fJzx{SQ%y~O`_!Tv%0 zZi1p68V+YRl5^DT;h)}RjiGxrvR1GgnEaS_A6VBSJNt<+=-*6t(r+m%_s)?b{jv~~ zKfmo88C#KNHVH@WXPD12=nvU-)_9uh{pF&4yM*JAsC+U@BP z9J0p2!eJnP(fe^rD7u|h?=9_@_k|z9>P1{cGJA@l-c$AY>E)u8=cDWv_l@h&gG*a& zrrk#4Ir3{ElemQtshhPYo2D85yT?bkqCHU2e7DADlu!oGR9Aiyqo~#stXNNN{~=f8ar!a>S+r1M_g#l6OV84T#M&j&DNhCGN~re*yOV&A~54 z(eejfjTY`xFIXsaVbIL8w@xsMCa*mOhTn=9-bb^e?L`-%$w2Ui)T~ChAx{UAH!kC{ z=tS|18rG4pcPUl~)xt|+1o=0t`nJOtCotY{#&s=e#b{&S)_a?b9KJ>8vGlzn;Sfgi z?ve2lcV`z#QEM6$UeO0vk(=t_8;c(+H_?k48{_2CO2hHQCCXl9kaof~LMkTyCZ+ED3yju`Bs)wC`Ku>-lozpG?y5g( z-@D9Q2Cc*)srDDgmyr8v3AiE9njgwRCxUXSXpU&|B4STxuPCJMyx`Hl%!MBO}gn z8`^x=dB`BJ^!l4lIB8^yv3>|1YyO^h!L%#(*^$cnlYY(zG`JNQ(=nTn>EEk;AxQl4 zF>zVR3)=e>gF8NkjS2zYyK__9TKY?Ms6VP6VPP$S{@9iFKt@KC!F@;D{|T|FU96V!>3}mJfdIF_LY=x!JGv91MaeSPYavz;3#ZRh?$~OG3w84n=$Dx?uW$RVOzBKYdHYtbu$0Y(?nGL(Y<=z z|AubtsKI^UlcYb%`Zu8}4aRi_!vjx7h>sQ&KM!aS@>J!vOcHN>KbWS&=L##l67Pe( zzg%ecn*n4t-KLRb5164toS?|E_nTd69xD<3PM1zdLym_O9>t@VuX``)Udth{12nH~UEd(`4x#Eyk!zGD< zZv-oU97}IMlG#}T+?+QmrQKCWfMf`}+_qpqm z%9U9pi>wbkEzeWe=Rp9jKL5A`L4##4x?L0QOr#@$&@R{{k_E9->B2+z!b9{zSRSX7 z)D;PlyI>t>?j_L60Hm~s_}51yndQ$lj9SO<5ggU0Kzaiui4I+$qK2Via~`rr-IlQ} zW5Lq@x?kx+F%U1~t1n-;?`!%NPVvUUEo|nyaCDjQ6iN6xDl`pbcZ9GbIa(&%|8FWH z^mHwuXE1m7pWw=Ni|s^?27rfF1VMHu3P;EI$0Cz-+P-cB;CU3yG3PKj>=nnG>e(^1 zEq)AAyli_+La!H4TeDqsY-QH z>NSeLZpV6BB(wkM*&^)iEY4ju&SqjIxG0b0u$W57?&)YqQNz#cT7^5;&}L+waVecj zr`FUa#dbWQTq}=jok^I#;%AX@8vC9A)aUQ(>B!B&{@867>-J_(zC*1GCW03NBA)6zw!lqzlsbo|3fS*9Zj zfhmmd5qhR0eryt*;dgjGK=rO-79pr7Ud)_KtV^pAMOi!iJVgkbJSc5* zWBt&uvQ?wmw}PzvumRt~5Z$&xy(o_kKJZ{7ZjrtoT9y>$>ga=LX4?=U@kvrSPvdTc zEsl_Dbgz_X(ya(LH)cq-;6@fLcrXpAHOD(a_I_cp8Dw_CY{MV*BKXrM$@EYFz#t;j zWAt*4nrZtgn8`aGmJhoc9hJAHqCdg;9+FgGe}^1%*CwPc8BZ}LF)0P)ERFz(3m=r$ z9HAd`{u;vvu+sJdqDy$N!lC+hIlr1nv>JaSWJb7EU ziOi`*UF2sUHFQJ2NctoxtHa5&r$}>qm_qIg(b$g!x#uyd?X$iuK6>22Ki-*C`=ckU zgNO^*I1Xt@4i1H7BW3BZS~#|vh6cun7ULED z+t?i3f!(bcvE3qvwZ|M%ViaQ^hZ^Tr;4RxU6R%D0Rs^~V7+W!XD}0hFh;z>hp6nM< zPA@d|KxXx6yAw4ov*L5lGwf9ni&O2D5j~yUsN+?;I9|c%E<8U)xECD5mdS3JYhzMM zZ3lJ)dhgKQn^zjBmZNYec9cn(NE#iu7F5UFyu9VypO7tVoaZma2sFLPxn9#8kkP*} zPwY8+K_WiSs#QnBc@AbF;0%v_<`H0sY{R;EpHbBp64C2=iKjh4&K$9Tlaq~*`>xID z=InxF+H`=la!;h(lLP+4FFMuJXxEnTn&9zMj_khOl=GD8V8-ryyJ#1>|cqq z3Z-gtq&Yz!ZKCN!6{wgMj(S%wO@Kt6wn-^<-oZW1D4bY2&mn<(6jq`m4(RN>_#Sx# zcFAp3YbT|nP~hjDW-IP5a6PtxGVtuy=@Qv<@ ziVYzZj(pS*As8{A5jodx3JusiU!bvu>!y+|ItFsD_@$fO(9OGm;a>x(FgwGUQM||d zlTYPmtgM+YmE>?0Omv`}xNR42=0d@h?x8CRseV{A#_ooB2)n7Xpu@~Rygx7@bf$k3Bed=aMn@VNUKez6z-(^1q4yzHK((iQvKtkVm7Yb1ZRtYL6&6Kk;m{gmIAFc#yxH-57vZ zu$BjEmg!%~Iq^@w8C~oyyxkBubFWI--Nx0s6li+&m!DjfaMOup~5il$yrDm}djF zOc|wHfOf-D8vChmqjM*M2my>qsqK2H?`A^|_AH!RC?Z6%y0%h^!_uAz3#tJ?wJOi@3_ z1C#3lD3T2DR5LK&ZG1kI8~($PQn`1elWz1rAY@3=e?C{1H>3#)n(O(86n#%B{k|r7 z&J>12`$FY?Yk#M8pAr@7_;LCl%jA^cGvs80>6Yk^g(ZN!4{Ha6TXpojGKfjEZlx1` zoM8DMnxyjd<(FE0%Mlis&i`PuX(qEfeeP}Vw+p@5y|?5*2{7>vS=p+k!!aXAug+N$ zVqTgy7ca5d14~cH%5ImB5bjR#w=WM93^N{cVxlh+WXBBhrhFshRK#A1s?|>8is=j? zhbr^qA@&k}ATi_N7q#Xq&{Jl6n*fI5umDo-s(ERo)J#(DWWMVuUbTYaf@yV;?vbkv z+qqRlEx@2Yd705+s0l>_?Wkn$>lIe@Lqk>}t_=T3wZ2?HKz(^>)R}dmQ>L@YPil*w zqOC*-JG1d4_4bzAdYs(#^sN@(vesZ1awXBsRHU0Le~K<7Fiz$`gJuo+W+(xnUxo}T z*I%Zz6V+Daxn$IMlSlGZuT))aNEuBbF;vE14*vN0TIlV*YCy`?FqMcO-3gIk@GfK5Om;zE*lE<*e{5mkH6sAdU;>fmSBW4wg6)TW&|(&JWZXw-R+9g4wE zoiHCEGk9U3%{`rIS>VYf}w@2l&2~I)ANF>RADRc+W48x?qC6gqx;sD>< z30$Y@<;@zDdV7>Bg^xbs{re6Z&TRW3eeP2FA@Fp)E(yQTR9AHKv z1s=RX)*_vM)$s#&PKtk@Kzm_|-m&U?Q53O12!$6H;-{58E}1(D$0>Aye%dApNh9tl zt_pfuiiX_F9@JPjT96!H?4H$Fwsg8qU7CfvS>=1WZ9Q~q9@SmK;@vTszlyJHL!Og>5n(THMOCBrXHn^*_nmG!%@W5c4nC0NnB9D3m z{i8760@vPH;_3Ha)&#&YufA5PR|xo+@qr2Mh>(5I3*#T`Ifj5wGUw=+nJH7j07A{fs9}@&5 z4>#X31>lRp-8vXV0454Gq8#BLJG_`{c)gSA%Bw@-~$Ob-ucoxw72S+Wxqq1;ls2ST6-W5w-7&M(9JuF zt+g~xFGm{vP9RdW7rqKWJ@}N{oI;{`Qy4i@w?`a|KC%aYxp#m@WK!Hs@EcdbH~Wa$ z4B3l11%!RraZkZGP%Z`hBwYiUb81M%^yA%AQ`ykT+!BznaUU$M90J7XKn#P?bWW9` z^sEk@IG!m(t=t{R3j{pgsJ7lO<)27wiFbT6PiUg+`%3Z+)!EmnqG-0xyeoc9(hH}aktD%=O_>H4jmqi;1F+PN7ZQWbnR+=2)vl-bo8u2P z=o1YGmomBlmQS@8DZY2BVDX|LjsSF{Rxaugl4CRSji(e%V>n0wek1$}(LGtS#A0hQ zS2-O&J1cxW(B^*Gc;JXTUjjzFNwz04`&T^PI2b+4tZ(RAF1|Cm7y0ZZ(V@TKP)p|C zi6G%)8q(O-qk;BUg6_TH&@lgWd^W|+mLJAAR{DmsC$kCZQ6zYpJaANn^5~ze@&x8y zgCiRT-^mlrw9fy<)K^92(FEHDf)m``NpN=y1pRP#g1fs9?(XjH?(Xgcx8T9u;qsq* z?ppU{W>593ntte+sqU^_8_3c<=g`bs^4<)BOn5wq7bc6LIO1_({qUEI%-TL`Hssfu zh3ZPYNoHtyRP8&-fE)i^=(Y_{*+rZ*O3F4O?tSxLp32t8KJfnVarGf&bbkRor)Nw$?~TvH6K*rvmwIc)IQ0^*z?e}9NRj!Do9jcs*P?Hku=Nqr~U z{p!_r(9bgq?W=ojmV#Y9I$_x%8_p_L@?kS$QXOar<#$*tj8~;lc3!Z83`3Y*Bmv46 z;PU$J0BItCY7hozF@F-m930*YDO8 z$f45(kVFdfE_T}->gB@qYze@E!9cit@CESva0naf7D$9NMhkYv*E1V3rW4$?9J>}D z&Jc4rLX@{MuB$MyWupCEhI^7qYgj8Jt9mFbrqHj#Xle}9cW@Z;)66-TON>Yb8)f-n z$RkHONLOYkG8qU>sIp`HMu}vTgnkeGp>Cx#IX7H0lSL8-(Pt&o_&Z#4JJ6qLe%Ok?;2E7MK&T6VK8G7{Ne&i}33FYNVKXD)e^&FW&F@g?o%SJ7G0aU^JD*akMRq<8@V} zZF_;1qc|;oGWFSkj*<6L_x$V52kJC+p5LOg74o75Q2T?Elr?rHJVbF<=0vloPF#QQ zQO;u2-KXwF5`?^-GHcCijk=~goU2Ru%$EvQ-4aZ-nH*Dh7DxeQ+jBqQy$p~8MN=E7ko$X?Ai^+Ov+6zs6 zvJ82=)|8OGvzStgOhJ|l7qsl0(;x$|w@OMAjas(0r_tjG`9SRR^{fePR8y6afy@}o z$m0ck^j0T9&jARYO6>iSTe~oR)${T=Y&-qH!4Dx<|ly zQ(eF+(jCFMM_@wsHp*gN!5}nQ=73<0{nhk0W+lUjB4btg6wANm{Pwa^K*+SowQrGDqu;0)oZF_X*Pr36_foP~IBDwl(>wdfn{1x8KPNV|R%JJ;b9 zXZfbsN>XXW1Dr(H$YdzM&i|f}S+D-mX*f}7-BK1w{jc*DRu-~(&X42ef?3s;#y}?aVUB=zfBhL*$C^rk zs4F%#YlOV%YvL@@TsYtU=`%9Q9sDmt5fvTuJ0!SC!PbRvrw)CZ`z*DBViBA@f zT!ukRR98#Dg(0kDl3F%d5s;X|-89in$wGwk0uDbKWuKvt8x66K>~^gx5x<)xIcV{Z zBv>f!Bx?J_g@m)TSFp6{IdiV8n#vuwvL|A=Pa!Syr=pt3Ef3^uYE^~s15aob&m#Jp zQVkAln0K6r$Zt<9tEO^WNm#N>$rjwozxp1b+O$-FW2Ji{l3hq0u_^>BJ08tr8n*Zv zgdo*RN!3vv$By|6j$lc8u63&qXb>+DZdSl1^q(RA1jznMrFW|uqo@!=2PBZSA4cy^ zWZkrU>jBxXPW9XBrqgmBjgOS8aKa$#C{VF)#-c7sY3Ij{kI5ojlHrMxx%zY!hBC#8 zQrr>1wHlY~H(zZ3=V4t#y-+g2ZSKafmyIn~lANmI$^vqne01k8WMiKBt^y(NEEOQj zV53BF<5C)=i!$IyaoP}XIiBQ3IsJk^U#6&fpw1gzQB?}Y*l1W3nQEvl!U{0Ra=XF%J{Walr0d<#0M}Z=6szhu(44v*Hw?p^0_%$psaba*BC*X26Ko zu^?oMGky~lRI+b8nw9AWGLCX8OqlYFl@?1XoEcTWWshQ5(O6T$-N$zy4vHX~X)MkV zyxewCE0;Y0ZAXLL*;)dNELE^sVMHBYIDq1y^(iF)oN9a?_ zU*uE|+?b;$G97a?n3s}UyZ@RtBv^jF%*2!xYsSnjl=k~-je)E)M`64lP;HY5TFlb1 zs_0S|%W=f>sI4QqieTd_x4(Wo2_&v$Ca0b}${KY= zJ`K1A%jo}U=QV_Y?lFBw0$3^S6wD8y3kmLfSf!Xp^(79t>bQRkb4_g61HMC?LkcVG zu!oX;@`c;b>N8l7qj^pUsc=(Z)1H_Nzub9+OSyJazmFTKq1(w!fcsQAV$x=*rP>_P zxAP*H_UpseHwo29Ul)^^By>_+;D5KH#XE_#l>>f506 z8rO-V+LW|Xy~Q?>VLwr}kuSv6!aDW3YU^ee;huhuCuQJkL#JYL84> z!!)~AEnf`w@`XBdw}xS|uJz_Fukh4=GF})!b>y$0dZ{vAC<;ah6-M)q^4m`;uo8=S5U?FYadQ1_bQD^&Ng(7iXT5UQ<0+iv`XJsQea@ALRFWX;Q z;T*g_NbJ>#WvH-k4`+C2ZHYZot#9~agUvZAp`qu^D&j@3P}Z|7+ci)EL(ORPKDSLX z7dQoE$9x;jMViuE!G%@zJLPqM(spUUqFBi5lv@?sr2o>o+#Fn?$j-Y$tZE@FA%Y}^ zEo{dFF%TsJzypj&Fa@Oi@O7zKYo0;o$K>0UAHtMs4cazC=pSS)zn#g5H z=Wvt$W4Y((IIH?)=F4oJK_J00crd_h%y~2~&S<*mRDc3aN5MFCJG6E@^?kr*>KmmX zReWqn>ZedGODENfZ0Tr|ae93zM?P0N_D&KM9a{ZP??aL$XVylXbP{uh=51gfXw8tM z|H)_$uyQL`@To9*{R3`Dh1P(XBB+Z&`%|SAqiPpTCIsJbC~b||WgPOEA7YjKM0IUhvLA{LB*PYQ=+B#RTPg5df(Gvg#GBqi}?9FYV^nW4w5f1E#au&pM7;!lf~VDJ&^(j&F%chK8Q>mrYl zvj~2gOyXKhi#IKfu$ge@x}>DlO~Ue;ucsgo7_UUGIOeaJ8{+X4nU#9dlw$s~%CYg~ zSSaQfE01;9w!WlP$sh2o_uZSuUCWj}D!-M!>s|SIjn@k84F%|P)FEYjBkzs{YMcPL z>LQK<#oAiDeZ+fd|Im#Qfn`RDr^r`-K83ig5rJfL&UX2G-EIeVyOyZT1Qs(js^J9_ z4c2(ODOQ?P_DMrahjO+|SRNeNm)SDIx!Aw9L8Ylk`n@$>)Mb*8X^w=y{c6pmdC0Pq zlb)6P39_%YSMJ!DeQP!>RX939_7K2y-H(aI652&$5*0^xG!X;}SN2F}9mKpZAcuar{*!*}85y!VWW^fm*Mm2je2om4 z9bGj_0!8T1i(GOpnU(#+*Yk(Bo^bq1P)F!-z;*chFRS=jmo;UW<^>%~1~On-83JAh zqP-6PGmpF0?|YSmfxzM~{wpLO&Y@)tZRr#bHa&k#<+K_{X*Fp&{0Fl>Fuc5YI0qzB zts0@W5Ms0|LgAQwWeybUNZI>SSp40ic|U>A**T`T$=Rk5BMFP4D*Qb^>%<6db#Tu= zjiZ&tg1RLWsndA~bue`v7)#)Sk}!;kG|~PrVQU;1HSRm-jMiqMp4_rdyJ2Gpb!s_I zJFI_KoY@#)ExZ4D%tcOO+oZQ5EA;`J5kFfjbNp(@+Y#3(Nnw%mD)J}usl2>4;j^i?{3r-{1aU)0573Amc<-m?`sqxvfq3d5HWajH*53!4_ zQaN_Y(8ARddX%chH{n1=4c71Z9Yv@%*su9RLgo*>Xlz^@6+RTpfcHU2w1YgH{^`H* z*;+4(==sv0{q+yEu`$iWl}!>1O(HDw zkQFt}kZDQ5Rg3hGbXJBjI}GV^1&Fqdh-^ zCp}*;d?_OJsI%!$p`GYU;I5S36&$*$vDC9x+j=x;emUu3guKQINt5|3)u8>Q!Ei}4 ztr9U&-lM9hu&00y()W-@af)xc$=GA& z7CFIm18(#=1L#_wz5O8|)2?PHpOwxxrl##4EETLSvw+y~pgvAMZ@dt*l*4zv(Sbss zSZ9_M9RA5FQ$GpH3TZALIz|$eqm7*J+wKFso4g)=d~#3cSK^OvkMVYwQ=a!u-qJtC zh#BH}#5sydH;zFc3}oI5Un;)s8l_2U6u!}HCB~ba5Dimof=NQzvUA#e`%%^mDmz-}5RobLEcbh3`{ysa2+#nO@ zD?Zfg_{q>x&gU!=@@*t=PybVA8}3r_1xq8pLx( zRI!n($|vO|8^2Xv>{?8=Y>~`k&@#_Tc5(Xt3ZxrdW=$^Hb0pF(S91VQEuXdLUKv>H zeq=CEg%f$bx!xobp~by8=@^>XKPBHdHa-%4G;?)eAk%euN0ip7HN3-XjYJ&&7NE=q z-Ti+g;4javD3kLM+=p3~X!?v*AsBbXMBRDBrb7HCR&tzj-3**Qj#{+!j(|riL^)eQ1O2N#?|%xN+B> z@oOuQ`lCFTD}L44`_1;CSCIx%N=0V}Os^GThqgpI(2{WrDFt`?MJSn8?-_pqPmDA)bY_0ZXN$QU zr|6@f!lJM53}dDF9)ePA%Lucj9I>tONhg0|c}$}`x^Ac!?6dof7OgaT`#XQ)d6Bq{ z8ZG?B`YavFiRb8=lzpUpYtiiy5wLO-QgSc_=Rea*b2l8bD*Y7v``G|G@QQ0K_!j@ei~ypwXpEsZw6?PF)Dsrm^7pWQS)xY(e>?WYdx3Hk$2(mL$X4d7kks zWL-1M<)13~t%&A1DB+dpl-=&c3r72CtTsZF6)JdZ=E`w~mIKoc2J-N^=6Cg7;ZPdZ@h@0qi1Zf$(kA`C=U*e2n!MHyN#059 zkl5$q;pwA9i&3;S33!KL+V%RuIwtS`^$l@S^_ju4vmf0r!{Q(HOgu<*xJ583Rc!Tg znxVgtMpa!oy1E!cP&;-zhBsVN-}w;x@I(nZoTn17WWqR3vYBR}U$%oFbn;2+u6ct` zw%VvyiX~hDT&2R2*(IJG+b)Y7oV%Q;i6h@v@;@FJc}FQkkDX(KxG#c;{PRv$v zPLGe4RaZBdA`N!Q!=c3Z1=+b z$)ZYzu^Q1mQ!xHs25K@e%Z{SsVa)y$#c#hCE_F)*Ch{>Ffay4i+mZyK=X^}O5|Su2jZUSp4t)l)Wg@k(l)6cVhc!-1huHl2G+Y@=#8N@FjA*SgoQ@yyK&Iz8&pe@cGIBx15yC!B7C z2je#Y>jWlQhw6xNFlm3ON4uT@;lUS`v)jRwrTsov&z5hGd>H23!Z-GI%SWqUPHz}L z@N=ze#v7gPQi5tANgKAy_dY4$u|*{uU>ON{#pSMw3z3t)iSJ z=TmMOld33CPIVb@>T1ejNyGZjC7#CL#~P(_e7hw_I@d5SmzRXb-jt}2^m173r%|3; z$eU*J=8$JcA)`?+j$mj1?E2>WF>?V*yS*M(0lR5%2LYs|N{>C(UW?&E&7AvP-^%N- z!FTOf>uDDflri;J-w@j1NM)6Wo{>B~(H0+BkZe&ze9hBREFQ%F1 zlv5NmocCkVqC*;9PA|ccVEkx&D5>agF#@fOqcS+=RB0&6>Ju)RBUf+028MzrDuyVT za&wE~59_hN!Zs+lX`pnU#Hbjc?TBCv*@7bG=DZdVgRns z&&E}^JfCw?WxqP0j(~ZBl9~dBYzZ*#jBJ$94GR>AG<2oG6~7MwmHm`8-vyn{t?4d) zK3zNF4&_ky8WmApfUDqge$lZu>=SW0H}7n}cN zNq-kK64|WIg1rN=ve5x1nCNu!x6%nmR(+`m1Exvc#kz?n)SU6^BH{hCIOcqCELd== zXS#e^NXT|4AswN>_EV9D9l%^p-+c%9^@p0MZ&$_@X=_BIy9K*HZXyNBaY~T#n-zd=%-WT*K;OfozrF(YXY$X^b#g};E9R8I$pL8I@J;I)x>M%kV_nPda`#&in&#$>Q~Tt&0oL%@Bpi`y_)ojppo!3qOM!o$ z?!g4eQPF814fmq4&vH!SN{zLgDnn)naDf3vLKp48f;&G=VyzRAZF4}UF~Nu`Wg|-$ zA)tbEP?4v*rqg2oS*HydW@J;pwBKG6^K^il*di9oCEQbctQk!55Zg+Sw)2ml z$R%PPkCV9CH{`675^tQPwm2P@nak+ZBOeW4jM&{z9}W`$>;@rCV9%B zR`psN>U$xKt;&VBb*XGTWz-a<=UnQd%2ODtdeRiox_8+@;RdRuGI~lMpPWsI098@D zy6pL{M-I6Q*7VY2G9^6jDN(BwpT8zVFNWd0-ha3AOV5X2Qd@Cs+RN;aX9ss!szXYJT| zzf@JM;29g|{DZD&M78yalxkqPG$ZBIi<->bIhW(|0}Kqy&B6)3h}lZLq8E>I?)w)% z%&6=nGzWS{@PapASoEgSXY%zFGPD1(I1UE8S2yrS>$R(J8@eV`FcfiRiLD*eTywEq zkbp4fq^y?afY*1SW5&gREisa1C+koekKOZn0zT8F1}n|P zl+#qjImZb?292K@V+Vu(Aoew99Ic^J8=@9r(r@Ewm`7j*e zmzx((SvfA1Mb2GEcgqU1wC!9jZYtfKY^wLQ9E?Fkf(x5J_3fc{Y$SA3mbsBpMSDn{ z{{eYM1vY46z4K0k(P2U173_{^5;1f(_Eto%49>hy-9YzSZr>I7!u)bpZ8O= zB7gr-OM?Y;gnkZ+2;U?OnXN6KN$o^PUh=BSiAds$DuLWu~r@QsE0qWjI64ud=wNi_U>YVDO4oyG;!A=V=)a6SQ*{B{^#qa4q` zR=Zbj;(!oNp(!}23PJCVp*KL?;5>LlYYACM`i*_ZFNXjsW!DmdqR(e;exl$FpT#?h zD$8Ijs2e?>=1OVjmdn`0Ut$N*5OB^mu=bnNz269!f$@Ju#FKqB)c2ge$$r!}4S9>- zvU}YgYc+9cP2aK`c*_o{hgfZMQm%jDUgXbxy1VY$dQ$NHcp%bMDMN8_rH|f5BY0z3 zfo{G4dlj^~j`4pN*;;rdfMnw@E*u$Uu?vi}k&Elz?q~i|ha%3YQF?LN3t&!NH^0Nz zDt1yZZ`r@O|H4*8M6y79^?S+@WbOX>Rtsj8_~CnIsro4Rx4qIv-=bdR>G-=P4k>9( zw^cl0=A?ZNYWMmE`(IVh?Gf%pjr`7q)d7i}A>)Uwxi^=Iv4IMyi7^-z(HdsI#Mp`m zL!$zkoAJnZyzIBGx6zgAR3KW+>U$7Q|Ru)E*&*!!YU{H%Ja%XP!0!}=Gf`l@^@Ca*nMfZw`F^dc8G zP}<{_x`>=^d|GneJ9`{ngGakui%{p8FqR|{2rF6+bp$>ysZk5{NsUAm9y#1+Cre6 z{APKdead#b*bciHQ4p#;cPiK^W{PfopYx=#>$LP?`3e5`eX#N!;ia-_CbD`kTCFPM za@B^!&b~6i*qcvHSx$Vd4UtGJ-pnesW?cFnaCd%g@-?NU4EU?n5#1M;CGN13-ow; zfGYb&=Aj-ozy3WGPTa$9`A6W0acA1Oz`(v>d;q0q@*U171w zUhp>elc7cMCASkO_zPZ_nO?aFOiiw`M!*+5Lf1kOnEIXCO2R%%PV-)U9}1xy1O`}E z6mTooKYsh*k^w$=LYsVh!9I9q`@a?6kR9_a=7p${g=}s&5p$5;^N|)jafEDGEql`f zD1a2wW~tVEK_sm+&FlA|dZ)|LSzK^JE|HY6|?>ofHrxpq!U ztvF=!WHBWU*j#5WVyQS}eO2cK3X*}U_v14*Si$?NGNV6gpG@_$=dmWbfR#o9GYObl zJQfXcXPEkQ3mTq)l}V;MW8A*eAq~*y@wM;H2^SEu@(KBX3V(q!(D1hVJ|-JGnRe&<)FjI7;)r1@Dth>l56 zM%2DzBe8FFIcm9uIv<_Hc6si=KlP+hK(Lt>wcJMiG8dAuaH;vMg?QQ)2Nw}>SAJ(d znJ{))P@k8&qh>B2l}9atCooyc-B2Go60j@2)EAGIIo)PDNB@HE5ugtpYghDzd`B>h z05p^eo)Iui<@^)J1TF`auxwec5E8BjC-ccv;CqnXF!3kLYWS!OTnko2k05uoByv( zBL||QU>L+*K|aE&6`km)CgEDYYRjiGhr|ZkiaT)np66s68fJjDQ>tP>MJ#g&km;iXQH-KqadFDbQ;=E?F;cW z2so()efB0ELO0R10#$&GA|_VQVdZqMhPjmFOZk97B4>q^>Hl~Q?H}87|5Bk4t^Bu! z*^J;z`NID+oFd`($FzN{@3`g1|(Rt<6fZ1xo*>?QQ_0_}ZR7Q+1ljRQ#IJ?`Ud-fO-_(t@s$++;*`$ZfQE<9pIH{KBWxqWBEKo&Cf!_H)4AYT4|6kRnWmnZl~6na28;kvFX!5P$Xto+ibgi^J0=IHZ~7Vu6l)~ z(Fbd24VcC^f-j<4bf^<%b{f8!X^ZhFNjj-2tPjtIsb?yp56^ckz~jZ&4tRE|G)C5_ z{T0Gk%zp#QCgN59y|~=PFkq%4_^HXf73#>CTdJSlaVtEPHC(}pX_kk2_b9D|_!}8I z6y;>I5@@V2oaX`1-b33iGXKuqi`Yu4$wA1VdsS?sCKG9PnYvkbs7r%q-Ix{wsCt*m zEA)G(cX1=Nb>c=R=U?H6CXH!_c09X?#&ewZdg8xwH0)Go{Jkg_CuI#f-DFku%2r;k zlB_^JB$!NxPQQK8tybVsx7Nf02{2EIO zsEw8D(uYRM>L&5nCXI8_R43}{nisL0hlS^zs`=Ns4e!KE^vYaaq!o(Q$I4Z~Guq0U zz+{~2lUL|lrn^Y>Khoi+zRx8WtmYf$_OC#lyy7$l=h=^f!Uz>pBPn`6Ok3`Ze3K|h zT!Yp9Tk7mZ@pGm+OoeiMd8wBSrg?`?ozoQ(8W7*b{_UWBF#)wvSJna=p$vQ`r&@tl zQZv?*f1C2cFaZjEa~qvq)Y*ufFOqv#-&%GY@{3X=!}T>hocJ$_Aay%U?_uyvIWRyQ zc$GP365XwyM@XEj$B=Q$)Jf5f5Lg)VKK~F7o#$@;{Q(WqTAX(eC2bRw6Zj`zfIMma zbrKil)E;8ViK&qbg40piTXl0*ky95#23k94HaBg4zAd9A?MU8`1=`iUX+-z^^Doa3?I?H#F)> zAta)%Dc33P?N}PpwQtRtp+Ce>)Ut5rlUcM5t2>6oPK}K5PG=#k2R|I~UME(Zen=e? zb9lq*-Z>wKQeW%Ehjz_Au7r-)h`0+YN|sY|d@=Jk6Vu2p*D|lT!_F(R4+Ks!I zn=_CBYzI>hF6tNSv`&;G6|flhqr=ylxO*Rj+45Lmd~<~Q+DT?8+F=QIMb zG*igzLTyMn;e>%8509K8sl{d9lVl}r%GNjV)7WQ8@g1@iAAq?9nY8s?)=m@V>6nsp zs6&$ONmH~Hi#in?%D2$gEi-I)qT0Tf2K{r(sD!msduWFQF9LwtENYi<|3~-IjDdeE z8CM;ZWXlUDGhQ#3mH9kWXIk`Kk}?mjd$fhpAamyTv6O=pjytP0CBip?kI59%r`Q(#)x*PU-XF6LqtjVvB+4^6vG`+B>j`u;4)| z{h7n?4vGYg`O~8;t5pN*K*z|R{k$T4x^*|!s*ceCF+h6vd{?CP&oXLeZqCLvs2Xb7 ze?62nBBP@^#Umz2KB{-8W*zY`$-Sw}%zYPeZ+~IWH;vbP7$0rUR$y+AO*6H8$)}gN zrQ+7UT6)TLiSu6zPiZUB7?)A^4q~Bk#vX=I=nnrU_@n4}VsNf(KD5}OMO_xh%Gsi! z*KJn}Twhzfx8fS$vixPHt*T4)UXY?YKeqc8S?(r%!}vhYwx|`b!;62eue1N#{2CkL zfxddebOXA`|VT#w@&P z$+ak%_!l+rLh}u%T@4$-F`hkaPM-?Z6aD*?+nRYw}?$1i2)Y^uei{72EZN$_I=~NYf=biw85O zW{j@XmE2A!gPOtfY?)4ccz#A1F%GGu|JZ3GRZUxqsBy92OuTI z%NKr1&laLfti|3AFjrD3mnvH@7GoEPSE%^o@0yU-QX~8eB~|zTm>F<2^5|CUN+dz|Kf2%ZW)X70gz8*4*izeG;>0(sb~2zs ztUZxuwD{StXRT+igcP4(#KLrTGQ7xH(`6Z7q_ePUQLMu#qtpuvgva%4!dmv;)bFyXPD_7Bayaj@zX=-nwvQ$gHmOo1WPyVem}mk zR__*>V%-&)jv#!UJi{(JHu7ox{TD)*RXjq}kL(cfTl<}XZ+@I<&T;0y<3Q6GW|ZEs zh$B%%vs!EDR+LS_)ewqeJ~C}3bSQ<%e~ue6M|v`Br7Wql4H!7)LX6+n0TtCm<1};r z?2AU>_$R4D4bjpfd;a*==f{qMq9k+vLq@2$+z9WV4%}NmlT~BlKfQ!I3Mh=R7BZw0 zrMqoNnc)c4$5H)}me%$vGSgN{*AM%`JZY|WWXLs!N*1rZl0ld(g-s=Of}T<Xr&%FuFf8MW z1g#{K&hfI>J-L45y8kRSW=rc;;v0jAr!yg6h!DNofJ)b{!qMIw%t)eM)=3)W;*D-s3!e7Bz@5=j+5!J1GU?=Mk$XMpf+S&T|RB z_@jUv#JK4WMMMbYa6;uft;$rYLk6jt^vX1v?ylrTzE_mv51Jwox3LMU7gDP<2JJ@4 z$J!3I=eZ3ObEv3?%i~vlcX%!YOD0-Wh0GenEuR?tArr+9nP84Mu8Zz)DUe>>mT9vkZ|^ImO^JUr3DKMa zC%nq@g)iJ>it$F%en!}2r6QA6p_U<&v6+{a^F|d{=*zs@fFMnj0(&S@A-a$dD3y ze0U%Q9U7~4PY*MHPz;rUmjAIgZ%sjT`8C+5UT)|qGUMgYSIPT5mAcNtS}Wcf&wXsu zvPrs?_`B?VK3=8eEhJj)6-9OoFp1QJB>G*!p@2HPRE&q)lceH_2&$oiqyw|gAGQh< zGA~lV>exY~!g%byyq&p(xAo!e*QBjh2;WMY#@neADy?J2+}|~)Q+jW>cuwXk+DaUT zW~16iXDFrnh0Hh&6Yf%QdSXGctM@Y|X+)R2<0ypI4enPO)(S9_?wI4nzS7C z_`!E)2ggCt7w?+e5?DH4a_psgfbF7L`ijz%{q`|+D}d{XpfA$!T|hrdNs z)pBXfr4wi;MD+;Q6LiyCOLbhj42Yqa^gccG&`%>H0|j|jcrli-=ek8M^W-tsIHi_= zHBH)I5b@4jn0sEO0cA%u-t+!|c=Wg^dmq=m8tk4%0VkcYqvn9m=mB$LemgGlBD4Ld zsYsO6CkT`7)R?_cE5q7%&#g}OAwTIz>ABjrxle8YpqOdNv}Dg6S*3`F=irVL7Qd0n zfWsyw5n<9zI)0DQ!MSo06eq4{Rsd46<69}u+k6W^7MtUM#<{i!?Kxe(QPyys+gyXdg z#jhGqxMr)&i)A26>x;ffHIWn`v-FuPrqL7V1sG*H(FI@qkZtkw`aK6lJ9Mwr26)OS zZb|TNr@09lF;^Wfnky(FC`95Yb4}4DL3}^_Ys|(!IBHucg}nyP#T%pSgQE>z6gz>>XIw)|3&7z%XI zh>=DWz4yqq#t|o$^wUmVe2vtxmmvN{y3!s;2cn1}jr|*Q-3}2)7I$pP;=2GdWURQ{ z?Z1na08bWdM4Of(PRvBg^m@8kW4ga>$4o;sS{*~eiy!K^2Z#O;*`mn3CsFXy)s;VtJXhe~kKzs0R&S0NN_H9&w-AD3C{`l4 zPNWGBA&w;G@9%O;))UJvML|t6*Nq5vYg}Jo<&VrxQ}!eA=G&~45qEzqXTg>t)fC8? zt@xIYY3pP5S7=QfKL~)55)!3Ci&Y*HMhUa05egkM5=YH1u%UnjkSicf)gM|eF#d&E}|yD#M@>J}Y3Lw&)5qA*TIDP-+7AuLYc-1BgBL3_#f z%mVKj{T>D;mb~|kvyI%OCsA^{M3F4l#ruEUI3YpeI*<(mrO^o^A%wkM0}_z4WHZU| zYJ66;+Tny?-S`CCE`D!3fec`+{V(7{g?O*@On_@Szfj)g&RLfA0JuGbc>(ww`20^u z@~=fukb7UCcmuq#I0HOWXu9)D?ZIl_O~t$6OD75zkg|P7-eZgYK|tI@Bh$s=4cIn) z2>)DvSE6kMY6tcp7r^`|Kay-V%5v>rU?>KVeq)`o(zM^LR(>>~8kX@~aEAltw};=_dO8;ZO<- zE5=gGqz*$ATWO^i0dIelYQyIzDxXFs&P-MM+%;eaJa$5qN?fo?DN3=BGgSf@P`g&L ze?^C*3dY$Zu*Cep%c&^!UT6@*-dfmY65i%YZN{2cAoa7z0xw09mWyI~ht?Y?K5N1O&$$zM$|+ z6Gz7Yz<~P9VGcrWy4-nQWVl&wZ(e@k)q0$WM|4@wrP7=xVo>=%0q6=B_2r){^)k-> z!RYWpfu(y{y$dzt@l~~Of55?mKlHpiIRBW|^1)|^5oWz--{1L%x+vUzKWWFG>i3Pd zzrMssxOaN)zdL+;_%Y;SK}Pjqes z<78j)Z?D7bUjNtj9PTj#&p0&TXZ9K0qXnMXVf=~{2!3sal~?1drhaUBC+`UVgV)}l z_xQ2Go_zbgAN>U{zdQ8V`|rczx@d44jsEhYI;VdX8@QZYW<2=Dj2vNoUeXRvSl&2% zp{7Fy>Z6(mN!3SvexdxRWKri&{CSUEI2kCe zgUf%v(d(&mAOa5TjFRBLCm)o*=sD@sHhWw|J{Uw_4$t489ej$9-=D-^4@Tna*_Y(( z=|o7u^>aEoIlOI131Z~MMdaaSaezFs@USNN3)!GBE z^d~oq3&oyoMLyL<#_`~ z<)~ECq4TO<(wNvOJ9EUg=+u?#l5*W*~dm2`5_yrbiAU@6E`TR^ZbG^rQY)y=x;LV~rSj@E*L zRiv(3!-9pRj@AJt0tO74^3`G`AAT29=ExbG)KwTDxg+NglAfcog(v8j70i6HNziuF zuF89kCW&_&T6jtHgr(_ClHG<50kPYbF;eb?`-i*-Ajw<>L!5dVgD`g$PKSTjb9EL0 zte(-NxCa4N${dZw01IobN`ruE{lVD z=D8?rU>ukP^$toAG_fF41G0bm(e1<1IG|;5H43RUyfg`Dok|t~0|rgj^t7DAVz$@h zVRMY5VUSW^W#HtVhC_j!G>+23fGst8<;cx8Vpm~7z@8gNqcOln8?JC52v^4tNJpqM zJ>B9nCS_%(Tx<%?nUt3byK#)d7706YZeZ-{6bUqMZ64%!IkDP{kuBHDA=#7Ls9Bx>U#wOj}f!d zA>3HGDI0=}5j!PAXfYaAF67}#J`N3j)Nx52H6Is5A-%x^X?aKOdPD>)I6m!{@w8kO z)!c)Zb+sH_8c&rm@G);GX9h#>wl1w#YT zmuu|&O71A!IhLNI<9@LS#!f~$zOKso)bmsJq%rqf`n0w6WB#PE zWIQtF@p{=AUa3={x_X8@OM?ama+Zhm4%WOEGT|WmzCp;>S-jnf1CDl>S=h;TE4QhL z?S{MuVHDc+ayjoLM|m8!;C)K8#l&_lN!Y^mw+I`tiA)&jv?Z*iaE$6WDkEmomoUua$KX%UD8rkDkRNDYzl>pSxcQVAzjuAPL+xrcL%WQ z{3}<-Nv9p9WuE&w2TH%^tGN&Qq+B<1&kwPy;l7AGmGwvBYFr?~RWcZmE4Ry1ZCf9! z(CC7JY`LrF)9Y!_zyO1cxvO{JLC)MY;UN3IL3e*8LY=0y#fZa=wp&@u$yOt`t%3%aaw^SOPveV!aLq>xgus_R<4M++>|R4CU#dy z1k-T71Co!62RQ;NuKb#U0Yt!n`ydTlSa0M(0|y@rBLX%IX=0Gx*AHc9B%#jb>gpNJ zBn^KW*cGNcq<6j83z=|`ecxc*ltt?=JSFiCGY&c0WoMBmJIq`+iyfA{Iij<@Tu&Vn zX2iTXrd)2DW6s15jzN!U_-Y;aFBFT_iDBUwtWFsV#$I)p*tVFf4i(!HYasOq+cDifdt8yuXSKS>g5}74|#vkLenPd)Uz!{+;8c2E9=61tC0gc?QOMV z=+nMu6`Wa7r>Si<<7>3hE<3{^O?H^MEW{2=&cm;Trks@3a@9JX`z6b`85`Zo|#`=;ZU0Sbrp)MkmRq58y6|$^(22} z&>)r9QoGWh7@_-=3L|F==#aRsia%Y)sFTsOlr5e^vTDR;=^WBjEp^uJAwks&UZf)U zXE(=E$G~w7FFNv6w)n)=xceG}tK@zjSXC%(&$E!8dp`{t*ow$QdKX?VWWu*zfUoG~ zJ=n^$eH#X(^}8&YFCop}X=;26Y5#vNOSihkZevCZGH$cO-{k>$wxi;ENg`iokU~!F zs2pgJNjqBZ;sMijUNP~UoIPdB-JCvYY#p6HW$W_kJ!Q_F4Ft>TX0{4UhpH3sd@Xl* z8c6vke@0fPo(7r4gGg#<$vEoD$(TJ1sgLv*BjzVS=70{eyL7Yd_jkfQ=Q|fp!mhytcG7@ zYxq1V)v<@_yL>3r!HMoKn4`L0yjs+Yy~E4jP!6ZV;0XcRUfn gy`^3K^xt?BJM+fBcPs2a|K|$-0L-8AYvk+-0OImk!vFvP diff --git a/airbyte-integrations/connectors/source-prestashop/integration_tests/future_state.json b/airbyte-integrations/connectors/source-prestashop/integration_tests/future_state.json index 50efede45ad5..436fc6fde395 100644 --- a/airbyte-integrations/connectors/source-prestashop/integration_tests/future_state.json +++ b/airbyte-integrations/connectors/source-prestashop/integration_tests/future_state.json @@ -17,6 +17,9 @@ "customer_threads": { "date_upd": "2121-06-16 14:13:26" }, + "groups": { + "date_upd": "2121-06-16 14:13:26" + }, "customers": { "date_upd": "2121-06-16 14:13:26" }, diff --git a/airbyte-integrations/connectors/source-prestashop/setup.py b/airbyte-integrations/connectors/source-prestashop/setup.py index 3a4be1fa4b0c..933ed04edaac 100644 --- a/airbyte-integrations/connectors/source-prestashop/setup.py +++ b/airbyte-integrations/connectors/source-prestashop/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk", + "airbyte-cdk~=0.2", ] TEST_REQUIREMENTS = [ diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/carts.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/carts.json index 68699e2d52c6..de20450a1c8a 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/carts.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/carts.json @@ -66,28 +66,26 @@ "properties": { "cart_rows": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id_product": { - "type": "string" - }, - "id_product_attribute": { - "type": "string" - }, - "id_address_delivery": { - "type": "string" - }, - "id_customization": { - "type": "string" - }, - "quantity": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id_product": { + "type": "string" + }, + "id_product_attribute": { + "type": "string" + }, + "id_address_delivery": { + "type": "string" + }, + "id_customization": { + "type": "string" + }, + "quantity": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/categories.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/categories.json index 1964c497430f..98f3a643df65 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/categories.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/categories.json @@ -57,16 +57,14 @@ "properties": { "categories": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/combinations.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/combinations.json index ecf26557c489..a51729d46244 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/combinations.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/combinations.json @@ -60,37 +60,33 @@ "type": ["null", "string"] }, "available_date": { - "type": "string", - "format": "date-time" + "type": ["null", "string"], + "format": "date" }, "associations": { "type": "object", "properties": { "product_option_values": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } }, "images": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/configurations.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/configurations.json index 53b9ac44d1eb..d567cecdfdb1 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/configurations.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/configurations.json @@ -12,10 +12,10 @@ "type": "string" }, "id_shop_group": { - "type": "string" + "type": ["null", "string"] }, "id_shop": { - "type": "string" + "type": ["null", "string"] }, "date_add": { "type": "string", diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customer_threads.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customer_threads.json index 98612768ed6b..8cd2eafdf9b7 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customer_threads.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customer_threads.json @@ -45,16 +45,14 @@ "properties": { "customer_messages": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customers.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customers.json index 9d8ac9c26f31..d94aad307f81 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customers.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/customers.json @@ -109,16 +109,14 @@ "properties": { "groups": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/employees.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/employees.json index 36d74573019b..862e09e825ed 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/employees.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/employees.json @@ -12,20 +12,20 @@ "type": "string" }, "stats_date_from": { - "type": "string", - "format": "date-time" + "type": ["null", "string"], + "format": "date" }, "stats_date_to": { - "type": "string", - "format": "date-time" + "type": ["null", "string"], + "format": "date" }, "stats_compare_from": { - "type": "string", - "format": "date-time" + "type": ["null", "string"], + "format": "date" }, "stats_compare_to": { - "type": "string", - "format": "date-time" + "type": ["null", "string"], + "format": "date" }, "passwd": { "type": "string" @@ -52,10 +52,10 @@ "type": "string" }, "bo_theme": { - "type": "string" + "type": ["null", "string"] }, "bo_css": { - "type": "string" + "type": ["null", "string"] }, "bo_width": { "type": "string" @@ -82,7 +82,8 @@ "type": ["null", "string"] }, "reset_password_validity": { - "type": "string" + "type": ["null", "string"], + "format": "date-time" } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/manufacturers.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/manufacturers.json index ba9548cb2dcd..53329b0784be 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/manufacturers.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/manufacturers.json @@ -42,16 +42,14 @@ "properties": { "addresses": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/orders.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/orders.json index ca1526a9a4c0..4d3e4fca254d 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/orders.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/orders.json @@ -147,52 +147,50 @@ "properties": { "order_rows": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "product_id": { - "type": "string" - }, - "product_attribute_id": { - "type": "string" - }, - "product_quantity": { - "type": "string" - }, - "product_name": { - "type": "string" - }, - "product_reference": { - "type": "string" - }, - "product_ean13": { - "type": "string" - }, - "product_isbn": { - "type": "string" - }, - "product_upc": { - "type": "string" - }, - "product_price": { - "type": "string" - }, - "id_customization": { - "type": "string" - }, - "unit_price_tax_incl": { - "type": "string" - }, - "unit_price_tax_excl": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "product_id": { + "type": "string" + }, + "product_attribute_id": { + "type": "string" + }, + "product_quantity": { + "type": "string" + }, + "product_name": { + "type": "string" + }, + "product_reference": { + "type": "string" + }, + "product_ean13": { + "type": "string" + }, + "product_isbn": { + "type": "string" + }, + "product_upc": { + "type": "string" + }, + "product_price": { + "type": "string" + }, + "id_customization": { + "type": "string" + }, + "unit_price_tax_incl": { + "type": "string" + }, + "unit_price_tax_excl": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/product_options.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/product_options.json index a2c202bb88fa..f99b6adf7d2b 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/product_options.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/product_options.json @@ -25,16 +25,14 @@ "properties": { "product_option_values": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/products.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/products.json index a431cc8b19a7..6db529bb0ef3 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/products.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/schemas/products.json @@ -219,54 +219,39 @@ "properties": { "categories": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } } - ] + } }, "images": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" } - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": ["id"] } - ] + } }, "stock_availables": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "id_product_attribute": { - "type": "string" - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "id_product_attribute": { + "type": "string" } } - ] + } } } } diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/source.py b/airbyte-integrations/connectors/source-prestashop/source_prestashop/source.py index 653b0912bb73..9be6af3ed7ae 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/source.py +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/source.py @@ -2,6 +2,7 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +import re from base64 import b64encode from typing import Any, List, Mapping, Tuple @@ -71,6 +72,12 @@ class SourcePrestaShop(AbstractSource): + def _validate_and_transform(self, config: Mapping[str, Any]): + if not config.get("_allow_http"): + if re.match(r"^http://", config["url"], re.I): + raise Exception(f"Invalid url: {config['url']}, only https scheme is allowed") + return config + @staticmethod def get_authenticator(config: Mapping[str, Any]): token = b64encode(bytes(config["access_key"] + ":", "utf-8")).decode("ascii") @@ -78,12 +85,14 @@ def get_authenticator(config: Mapping[str, Any]): return authenticator def check_connection(self, logger, config) -> Tuple[bool, any]: + config = self._validate_and_transform(config) authenticator = self.get_authenticator(config) shops = Shops(authenticator=authenticator, url=config["url"]).read_records(sync_mode=SyncMode.full_refresh) next(shops) return True, None def streams(self, config: Mapping[str, Any]) -> List[Stream]: + config = self._validate_and_transform(config) authenticator = self.get_authenticator(config) stream_classes = [ Addresses, diff --git a/airbyte-integrations/connectors/source-prestashop/source_prestashop/spec.json b/airbyte-integrations/connectors/source-prestashop/source_prestashop/spec.json index b32d67f0dbe2..96fd0815e416 100644 --- a/airbyte-integrations/connectors/source-prestashop/source_prestashop/spec.json +++ b/airbyte-integrations/connectors/source-prestashop/source_prestashop/spec.json @@ -5,7 +5,6 @@ "title": "PrestaShop Spec", "type": "object", "required": ["url", "access_key"], - "additionalProperties": false, "properties": { "url": { "type": "string", diff --git a/docs/integrations/sources/presta-shop.md b/docs/integrations/sources/presta-shop.md index a04c203fc3ae..90d9c3d647bb 100644 --- a/docs/integrations/sources/presta-shop.md +++ b/docs/integrations/sources/presta-shop.md @@ -91,5 +91,6 @@ By default, the webservice feature is disabled on PrestaShop and needs to be [sw | Version | Date | Pull Request | Subject | | :--- | :--- | :--- | :--- | +| 0.2.0 | 2022-10-31 | [\#18599](https://github.com/airbytehq/airbyte/pull/18599) | Only https scheme is allowed | | 0.1.0 | 2021-07-02 | [\#4465](https://github.com/airbytehq/airbyte/pull/4465) | Initial implementation | From 0c183334bfa8f4e3c49a2f7b807829c3f1a95da7 Mon Sep 17 00:00:00 2001 From: Anne <102554163+alovew@users.noreply.github.com> Date: Mon, 31 Oct 2022 12:58:39 -0700 Subject: [PATCH 447/498] add nonBreakingChangePreference and notifySchemaChanges (#18636) * add nonBreakingChangePreference and notifySchemaChanges --- airbyte-api/src/main/openapi/config.yaml | 27 +++ .../main/resources/types/StandardSync.yaml | 7 + .../config/persistence/DbConverter.java | 6 +- .../airbyte/config/persistence/MockData.java | 25 ++- .../server/converters/ApiPojoConverters.java | 5 +- .../WebBackendConnectionsHandler.java | 6 +- .../WebBackendConnectionsHandlerTest.java | 4 +- .../test-utils/mock-data/mockConnection.json | 4 +- airbyte-webapp/src/test-utils/testutils.tsx | 2 + .../api/generated-api-html/index.html | 161 +++++++++++------- .../examples/airbyte.local/openapi.yaml | 14 ++ 11 files changed, 183 insertions(+), 78 deletions(-) diff --git a/airbyte-api/src/main/openapi/config.yaml b/airbyte-api/src/main/openapi/config.yaml index 5681bbdbcc70..69adc4af4cb6 100644 --- a/airbyte-api/src/main/openapi/config.yaml +++ b/airbyte-api/src/main/openapi/config.yaml @@ -3266,6 +3266,10 @@ components: format: uuid geography: $ref: "#/components/schemas/Geography" + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + $ref: "#/components/schemas/NonBreakingChangesPreference" WebBackendConnectionCreate: type: object required: @@ -3367,6 +3371,10 @@ components: format: uuid geography: $ref: "#/components/schemas/Geography" + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + $ref: "#/components/schemas/NonBreakingChangesPreference" WebBackendConnectionUpdate: type: object description: Used to apply a patch-style update to a connection, which means that null properties remain unchanged @@ -3411,6 +3419,10 @@ components: format: uuid geography: $ref: "#/components/schemas/Geography" + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + $ref: "#/components/schemas/NonBreakingChangesPreference" ConnectionRead: type: object required: @@ -3463,6 +3475,10 @@ components: $ref: "#/components/schemas/Geography" breakingChange: type: boolean + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + $ref: "#/components/schemas/NonBreakingChangesPreference" SchemaChange: enum: - no_change @@ -4713,6 +4729,8 @@ components: - destination - isSyncing - schemaChange + - notifySchemaChanges + - nonBreakingChangesPreference properties: connectionId: $ref: "#/components/schemas/ConnectionId" @@ -4771,6 +4789,15 @@ components: $ref: "#/components/schemas/Geography" schemaChange: $ref: "#/components/schemas/SchemaChange" + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + $ref: "#/components/schemas/NonBreakingChangesPreference" + NonBreakingChangesPreference: + enum: + - ignore + - disable + type: string WebBackendConnectionReadList: type: object required: diff --git a/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml b/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml index 979144028714..2fabb184b4ea 100644 --- a/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml +++ b/airbyte-config/config-models/src/main/resources/types/StandardSync.yaml @@ -120,3 +120,10 @@ properties: "$ref": Geography.yaml breakingChange: type: boolean + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + type: string + enum: + - ignore + - disable diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java index 96a705dddb90..44c3c160da8c 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DbConverter.java @@ -32,6 +32,7 @@ import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardSourceDefinition.SourceType; import io.airbyte.config.StandardSync; +import io.airbyte.config.StandardSync.NonBreakingChangesPreference; import io.airbyte.config.StandardSync.ScheduleType; import io.airbyte.config.StandardSync.Status; import io.airbyte.config.StandardWorkspace; @@ -79,7 +80,10 @@ public static StandardSync buildStandardSync(final Record record, final List standardSyncs() { .withStatus(Status.ACTIVE) .withSchedule(schedule) .withGeography(Geography.AUTO) - .withBreakingChange(false); + .withBreakingChange(false) + .withNonBreakingChangesPreference(NonBreakingChangesPreference.IGNORE) + .withNotifySchemaChanges(true); final StandardSync standardSync2 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) @@ -496,7 +499,9 @@ public static List standardSyncs() { .withStatus(Status.ACTIVE) .withSchedule(schedule) .withGeography(Geography.AUTO) - .withBreakingChange(false); + .withBreakingChange(false) + .withNonBreakingChangesPreference(NonBreakingChangesPreference.IGNORE) + .withNotifySchemaChanges(true); final StandardSync standardSync3 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_1, OPERATION_ID_2)) @@ -513,7 +518,9 @@ public static List standardSyncs() { .withStatus(Status.ACTIVE) .withSchedule(schedule) .withGeography(Geography.AUTO) - .withBreakingChange(false); + .withBreakingChange(false) + .withNonBreakingChangesPreference(NonBreakingChangesPreference.IGNORE) + .withNotifySchemaChanges(true); final StandardSync standardSync4 = new StandardSync() .withOperationIds(Collections.emptyList()) @@ -530,7 +537,9 @@ public static List standardSyncs() { .withStatus(Status.DEPRECATED) .withSchedule(schedule) .withGeography(Geography.AUTO) - .withBreakingChange(false); + .withBreakingChange(false) + .withNonBreakingChangesPreference(NonBreakingChangesPreference.IGNORE) + .withNotifySchemaChanges(true); final StandardSync standardSync5 = new StandardSync() .withOperationIds(Arrays.asList(OPERATION_ID_3)) @@ -547,7 +556,9 @@ public static List standardSyncs() { .withStatus(Status.ACTIVE) .withSchedule(schedule) .withGeography(Geography.AUTO) - .withBreakingChange(false); + .withBreakingChange(false) + .withNonBreakingChangesPreference(NonBreakingChangesPreference.IGNORE) + .withNotifySchemaChanges(true); final StandardSync standardSync6 = new StandardSync() .withOperationIds(Arrays.asList()) @@ -564,7 +575,9 @@ public static List standardSyncs() { .withStatus(Status.DEPRECATED) .withSchedule(schedule) .withGeography(Geography.AUTO) - .withBreakingChange(false); + .withBreakingChange(false) + .withNonBreakingChangesPreference(NonBreakingChangesPreference.IGNORE) + .withNotifySchemaChanges(true); return Arrays.asList(standardSync1, standardSync2, standardSync3, standardSync4, standardSync5, standardSync6); } diff --git a/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java b/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java index 9583d790c1d9..da5df2f211b4 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java +++ b/airbyte-server/src/main/java/io/airbyte/server/converters/ApiPojoConverters.java @@ -14,6 +14,7 @@ import io.airbyte.api.model.generated.Geography; import io.airbyte.api.model.generated.JobType; import io.airbyte.api.model.generated.JobTypeResourceLimit; +import io.airbyte.api.model.generated.NonBreakingChangesPreference; import io.airbyte.api.model.generated.ResourceRequirements; import io.airbyte.commons.enums.Enums; import io.airbyte.config.BasicSchedule; @@ -94,7 +95,9 @@ public static ConnectionRead internalToConnectionRead(final StandardSync standar .syncCatalog(CatalogConverter.toApi(standardSync.getCatalog())) .sourceCatalogId(standardSync.getSourceCatalogId()) .breakingChange(standardSync.getBreakingChange()) - .geography(Enums.convertTo(standardSync.getGeography(), Geography.class)); + .geography(Enums.convertTo(standardSync.getGeography(), Geography.class)) + .nonBreakingChangesPreference(Enums.convertTo(standardSync.getNonBreakingChangesPreference(), NonBreakingChangesPreference.class)) + .notifySchemaChanges(standardSync.getNotifySchemaChanges()); if (standardSync.getResourceRequirements() != null) { connectionRead.resourceRequirements(resourceRequirementsToApi(standardSync.getResourceRequirements())); diff --git a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java index c18977a9e492..612a1bbae5eb 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java +++ b/airbyte-server/src/main/java/io/airbyte/server/handlers/WebBackendConnectionsHandler.java @@ -297,7 +297,9 @@ private static WebBackendConnectionRead getWebBackendConnectionRead(final Connec .destination(destination) .operations(operations.getOperations()) .resourceRequirements(connectionRead.getResourceRequirements()) - .geography(connectionRead.getGeography()); + .geography(connectionRead.getGeography()) + .notifySchemaChanges(connectionRead.getNotifySchemaChanges()) + .nonBreakingChangesPreference(connectionRead.getNonBreakingChangesPreference()); } // todo (cgardens) - This logic is a headache to follow it stems from the internal data model not @@ -656,6 +658,8 @@ protected static ConnectionUpdate toConnectionPatch(final WebBackendConnectionUp connectionPatch.resourceRequirements(webBackendConnectionPatch.getResourceRequirements()); connectionPatch.sourceCatalogId(webBackendConnectionPatch.getSourceCatalogId()); connectionPatch.geography(webBackendConnectionPatch.getGeography()); + connectionPatch.notifySchemaChanges(webBackendConnectionPatch.getNotifySchemaChanges()); + connectionPatch.nonBreakingChangesPreference(webBackendConnectionPatch.getNonBreakingChangesPreference()); connectionPatch.operationIds(finalOperationIds); diff --git a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java index 646c09ad0b6b..a311d987e954 100644 --- a/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java +++ b/airbyte-server/src/test/java/io/airbyte/server/handlers/WebBackendConnectionsHandlerTest.java @@ -538,7 +538,7 @@ void testForConnectionCreateCompleteness() { final Set handledMethods = Set.of("name", "namespaceDefinition", "namespaceFormat", "prefix", "sourceId", "destinationId", "operationIds", "addOperationIdsItem", "removeOperationIdsItem", "syncCatalog", "schedule", "scheduleType", "scheduleData", - "status", "resourceRequirements", "sourceCatalogId", "geography"); + "status", "resourceRequirements", "sourceCatalogId", "geography", "nonBreakingChangesPreference", "notifySchemaChanges"); final Set methods = Arrays.stream(ConnectionCreate.class.getMethods()) .filter(method -> method.getReturnType() == ConnectionCreate.class) @@ -560,7 +560,7 @@ void testForConnectionPatchCompleteness() { final Set handledMethods = Set.of("schedule", "connectionId", "syncCatalog", "namespaceDefinition", "namespaceFormat", "prefix", "status", "operationIds", "addOperationIdsItem", "removeOperationIdsItem", "resourceRequirements", "name", - "sourceCatalogId", "scheduleType", "scheduleData", "geography"); + "sourceCatalogId", "scheduleType", "scheduleData", "geography", "notifySchemaChanges", "nonBreakingChangesPreference"); final Set methods = Arrays.stream(ConnectionUpdate.class.getMethods()) .filter(method -> method.getReturnType() == ConnectionUpdate.class) diff --git a/airbyte-webapp/src/test-utils/mock-data/mockConnection.json b/airbyte-webapp/src/test-utils/mock-data/mockConnection.json index 0f582582606e..a4cd6916599f 100644 --- a/airbyte-webapp/src/test-utils/mock-data/mockConnection.json +++ b/airbyte-webapp/src/test-utils/mock-data/mockConnection.json @@ -346,5 +346,7 @@ "latestSyncJobStatus": "succeeded", "isSyncing": false, "catalogId": "bf31d1df-d7ba-4bae-b1ec-dac617b4f70c", - "schemaChange": "no_change" + "schemaChange": "no_change", + "notifySchemaChanges": true, + "nonBreakingChangesPreference": "ignore" } diff --git a/airbyte-webapp/src/test-utils/testutils.tsx b/airbyte-webapp/src/test-utils/testutils.tsx index 52a6fea80154..15c4d9ce839c 100644 --- a/airbyte-webapp/src/test-utils/testutils.tsx +++ b/airbyte-webapp/src/test-utils/testutils.tsx @@ -122,4 +122,6 @@ export const mockConnection: WebBackendConnectionRead = { catalogId: "", isSyncing: false, schemaChange: "no_change", + notifySchemaChanges: true, + nonBreakingChangesPreference: "ignore", }; diff --git a/docs/reference/api/generated-api-html/index.html b/docs/reference/api/generated-api-html/index.html index 48dd1c63f7be..92bd602330f3 100644 --- a/docs/reference/api/generated-api-html/index.html +++ b/docs/reference/api/generated-api-html/index.html @@ -494,6 +494,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -658,6 +659,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -778,6 +780,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -841,6 +844,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -962,6 +966,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1025,6 +1030,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1321,6 +1327,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1384,6 +1391,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -1679,6 +1687,7 @@

    Example data

    "timeUnit" : "minutes" }, "breakingChange" : true, + "notifySchemaChanges" : true, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -8483,16 +8492,6 @@

    Example data

    } ] } ] }, - "resourceRequirements" : { - "cpu_limit" : "cpu_limit", - "memory_request" : "memory_request", - "memory_limit" : "memory_limit", - "cpu_request" : "cpu_request" - }, - "schedule" : { - "units" : 0, - "timeUnit" : "minutes" - }, "operations" : [ { "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", @@ -8535,6 +8534,28 @@

    Example data

    "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } ], "catalogId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "notifySchemaChanges" : true, + "namespaceFormat" : "${SOURCE_NAMESPACE}", + "scheduleData" : { + "cron" : { + "cronExpression" : "cronExpression", + "cronTimeZone" : "cronTimeZone" + }, + "basicSchedule" : { + "units" : 6, + "timeUnit" : "minutes" + } + }, + "resourceRequirements" : { + "cpu_limit" : "cpu_limit", + "memory_request" : "memory_request", + "memory_limit" : "memory_limit", + "cpu_request" : "cpu_request" + }, + "schedule" : { + "units" : 0, + "timeUnit" : "minutes" + }, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -8570,18 +8591,7 @@

    Example data

    } ] }, "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", - "namespaceFormat" : "${SOURCE_NAMESPACE}", - "operationIds" : [ null, null ], - "scheduleData" : { - "cron" : { - "cronExpression" : "cronExpression", - "cronTimeZone" : "cronTimeZone" - }, - "basicSchedule" : { - "units" : 6, - "timeUnit" : "minutes" - } - } + "operationIds" : [ null, null ] }

    Produces

    @@ -8708,16 +8718,6 @@

    Example data

    } ] } ] }, - "resourceRequirements" : { - "cpu_limit" : "cpu_limit", - "memory_request" : "memory_request", - "memory_limit" : "memory_limit", - "cpu_request" : "cpu_request" - }, - "schedule" : { - "units" : 0, - "timeUnit" : "minutes" - }, "operations" : [ { "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", @@ -8760,6 +8760,28 @@

    Example data

    "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } ], "catalogId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "notifySchemaChanges" : true, + "namespaceFormat" : "${SOURCE_NAMESPACE}", + "scheduleData" : { + "cron" : { + "cronExpression" : "cronExpression", + "cronTimeZone" : "cronTimeZone" + }, + "basicSchedule" : { + "units" : 6, + "timeUnit" : "minutes" + } + }, + "resourceRequirements" : { + "cpu_limit" : "cpu_limit", + "memory_request" : "memory_request", + "memory_limit" : "memory_limit", + "cpu_request" : "cpu_request" + }, + "schedule" : { + "units" : 0, + "timeUnit" : "minutes" + }, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -8795,18 +8817,7 @@

    Example data

    } ] }, "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", - "namespaceFormat" : "${SOURCE_NAMESPACE}", - "operationIds" : [ null, null ], - "scheduleData" : { - "cron" : { - "cronExpression" : "cronExpression", - "cronTimeZone" : "cronTimeZone" - }, - "basicSchedule" : { - "units" : 6, - "timeUnit" : "minutes" - } - } + "operationIds" : [ null, null ] }

    Produces

    @@ -9181,16 +9192,6 @@

    Example data

    } ] } ] }, - "resourceRequirements" : { - "cpu_limit" : "cpu_limit", - "memory_request" : "memory_request", - "memory_limit" : "memory_limit", - "cpu_request" : "cpu_request" - }, - "schedule" : { - "units" : 0, - "timeUnit" : "minutes" - }, "operations" : [ { "name" : "name", "operationId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", @@ -9233,6 +9234,28 @@

    Example data

    "workspaceId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91" } ], "catalogId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", + "notifySchemaChanges" : true, + "namespaceFormat" : "${SOURCE_NAMESPACE}", + "scheduleData" : { + "cron" : { + "cronExpression" : "cronExpression", + "cronTimeZone" : "cronTimeZone" + }, + "basicSchedule" : { + "units" : 6, + "timeUnit" : "minutes" + } + }, + "resourceRequirements" : { + "cpu_limit" : "cpu_limit", + "memory_request" : "memory_request", + "memory_limit" : "memory_limit", + "cpu_request" : "cpu_request" + }, + "schedule" : { + "units" : 0, + "timeUnit" : "minutes" + }, "name" : "name", "syncCatalog" : { "streams" : [ { @@ -9268,18 +9291,7 @@

    Example data

    } ] }, "connectionId" : "046b6c7f-0b8a-43b9-b35d-6489e6daee91", - "namespaceFormat" : "${SOURCE_NAMESPACE}", - "operationIds" : [ null, null ], - "scheduleData" : { - "cron" : { - "cronExpression" : "cronExpression", - "cronTimeZone" : "cronTimeZone" - }, - "basicSchedule" : { - "units" : 6, - "timeUnit" : "minutes" - } - } + "operationIds" : [ null, null ] }

    Produces

    @@ -10056,6 +10068,7 @@

    Table of Contents

  • LogType -
  • LogsRequestBody -
  • NamespaceDefinitionType -
  • +
  • NonBreakingChangesPreference -
  • NotFoundKnownExceptionInfo -
  • Notification -
  • NotificationRead -
  • @@ -10370,6 +10383,8 @@

    ConnectionCreate - resourceRequirements (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    geography (optional)
    +
    notifySchemaChanges (optional)
    +
    nonBreakingChangesPreference (optional)
    UUID format: uuid
    geography (optional)
    breakingChange
    +
    notifySchemaChanges (optional)
    +
    nonBreakingChangesPreference (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    geography (optional)
    +
    notifySchemaChanges (optional)
    +
    nonBreakingChangesPreference (optional)
    @@ -11020,6 +11039,12 @@

    NamespaceDefinitionType -

    +

    NotFoundKnownExceptionInfo - Up

    @@ -11589,6 +11614,8 @@

    WebBackendConnectionRead - <
    catalogDiff (optional)
    geography (optional)
    schemaChange
    +
    notifySchemaChanges
    +
    nonBreakingChangesPreference

    @@ -11625,6 +11652,8 @@

    WebBackendConnectionUpdate
    operations (optional)
    sourceCatalogId (optional)
    UUID format: uuid
    geography (optional)
    +
    notifySchemaChanges (optional)
    +
    nonBreakingChangesPreference (optional)

    diff --git a/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml b/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml index 19943b678639..99d10f2ad9c9 100644 --- a/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml +++ b/tools/openapi2jsonschema/examples/airbyte.local/openapi.yaml @@ -2694,6 +2694,13 @@ components: - non_breaking - breaking type: string + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + enum: + - ignore + - disable + type: string required: - connectionId - name @@ -2756,6 +2763,13 @@ components: $ref: "#/components/schemas/AirbyteCatalog" skipReset: type: boolean + notifySchemaChanges: + type: boolean + nonBreakingChangesPreference: + enum: + - ignore + - disable + type: string required: - connectionId - syncCatalog From cb785bc18b8729af0f3c9992e9357d7c375eabfe Mon Sep 17 00:00:00 2001 From: Topher Lubaway Date: Mon, 31 Oct 2022 16:58:40 -0500 Subject: [PATCH 448/498] Changes docs deploy failures to show up in #docs (#18738) --- .github/workflows/deploy-docs-site.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/deploy-docs-site.yml b/.github/workflows/deploy-docs-site.yml index 2313bfdb5e08..a66be41b4c86 100644 --- a/.github/workflows/deploy-docs-site.yml +++ b/.github/workflows/deploy-docs-site.yml @@ -40,7 +40,7 @@ jobs: env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN_AIRBYTE_TEAM }} with: - # 'C03BEADRPNY' channel => '#oss-master-build-failure' + # 'C032Y32T065' channel => '#docs' args: >- {\"channel\":\"C03BEADRPNY\", \"blocks\":[ {\"type\":\"divider\"}, @@ -48,4 +48,3 @@ jobs: {\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"_merged by_: *${{ github.actor }}* \n\"}}, {\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\" :octavia-shocked: :octavia-shocked: \n\"}}, {\"type\":\"divider\"}]} - From ae1a499a97b42359d5babf7427f4134b9b538b21 Mon Sep 17 00:00:00 2001 From: Digambar Tupurwadi Date: Tue, 1 Nov 2022 06:43:13 +0530 Subject: [PATCH 449/498] Green house docs (#18314) * corrected the documentation for FreshDesk Connector * corrected the documentation for Greenhouse Connector --- docs/integrations/sources/greenhouse.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/integrations/sources/greenhouse.md b/docs/integrations/sources/greenhouse.md index 81a0da3a46e4..0d96146776ab 100644 --- a/docs/integrations/sources/greenhouse.md +++ b/docs/integrations/sources/greenhouse.md @@ -6,13 +6,12 @@ This page contains the setup guide and reference information for the Greenhouse Please follow the [Greenhouse documentation for generating an API key](https://developers.greenhouse.io/harvest.html#authentication). -## Setup guide -## Step 1: Set up the Greenhouse connector in Airbyte +## Set up the Greenhouse connector in Airbyte ### For Airbyte Cloud: -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Source**. In the top-right corner, click **+new source**. +1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. +2. In the left navigation bar, click **Source**. In the top-right corner, click **+New source**. 3. On the Set up the source page, enter the name for the Greenhouse connector and select **Greenhouse** from the Source type dropdown. 4. Enter your `api_key` 5. Click **Set up source** @@ -49,7 +48,7 @@ The Greenhouse source connector supports the following [sync modes](https://docs * [Job Posts](https://developers.greenhouse.io/harvest.html#get-list-job-posts) * [Job Stages](https://developers.greenhouse.io/harvest.html#get-list-job-stages) * [Jobs](https://developers.greenhouse.io/harvest.html#get-list-jobs) -* [Jobs Openings](https://developers.greenhouse.io/harvest.html#get-list-job-openings) +* [Job Openings](https://developers.greenhouse.io/harvest.html#get-list-job-openings) * [Jobs Stages](https://developers.greenhouse.io/harvest.html#get-list-job-stages-for-job) * [Offers](https://developers.greenhouse.io/harvest.html#get-list-offers) * [Rejection Reasons](https://developers.greenhouse.io/harvest.html#get-list-rejection-reasons) @@ -58,8 +57,8 @@ The Greenhouse source connector supports the following [sync modes](https://docs * [Users](https://developers.greenhouse.io/harvest.html#get-list-users) ## Setting permissions for API Keys -You can specify which API endpoints your API keys have access to from the Greenhouse Dev Center. This will allow you to permit or deny access to each endpoint individually. Any API keys created before January 18th, 2017 will have full permissions to all API endpoints that existed at that time, but any new API keys created after that point will need to be explicitly granted the required endpoint permissions. -To add or remove endpoint permissions on an API key, go to the Dev Center in Greenhouse, click “API Credential Management,” then click “Manage Permissions” next to your Harvest API Key. From there, check or uncheck permissions for any endpoints. +You can specify which API endpoints your API keys have access to from the Greenhouse Dev Center. This will allow you to permit or deny access to each endpoint individually. Any API keys created before January 18th, 2017 will have full permissions to all API endpoints that existed at that time, but any new API keys created after that point will need the required endpoint permissions to be explicitly granted. +To add or remove endpoint permissions on an API key, go to the Dev Center in Greenhouse, click “API Credential Management”, then click “Manage Permissions” next to your Harvest API Key. From there, check or uncheck permissions for any endpoints. **Important Note**: Users with Harvest API keys may access all the data in the endpoint. Access to data in Harvest is binary: everything or nothing. Harvest API keys should be given to internal developers with this understanding and to third parties with caution. Each key should only be allowed to access the endpoints it absolutely needs. See more on this [here](https://developers.greenhouse.io/harvest.html#authentication). From 5ce7ffd162a586056e54a920580408da146c069c Mon Sep 17 00:00:00 2001 From: Ryan Fu Date: Mon, 31 Oct 2022 19:23:19 -0700 Subject: [PATCH 450/498] Resurfaces hidden cloud connectors within UI (#18748) --- airbyte-webapp/src/core/domain/connector/constants.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/airbyte-webapp/src/core/domain/connector/constants.ts b/airbyte-webapp/src/core/domain/connector/constants.ts index aba8be639b46..774b35b9581e 100644 --- a/airbyte-webapp/src/core/domain/connector/constants.ts +++ b/airbyte-webapp/src/core/domain/connector/constants.ts @@ -20,13 +20,11 @@ export const getExcludedConnectorIds = (workspaceId: string) => ? [ "707456df-6f4f-4ced-b5c6-03f73bcad1c5", // hide Cassandra Destination https://github.com/airbytehq/airbyte-cloud/issues/2606 "8ccd8909-4e99-4141-b48d-4984b70b2d89", // hide DynamoDB Destination https://github.com/airbytehq/airbyte-cloud/issues/2608 - "68f351a7-2745-4bef-ad7f-996b8e51bb8c", // hide ElasticSearch Destination https://github.com/airbytehq/airbyte-cloud/issues/2594 "9f760101-60ae-462f-9ee6-b7a9dafd454d", // hide Kafka Destination https://github.com/airbytehq/airbyte-cloud/issues/2610 "294a4790-429b-40ae-9516-49826b9702e1", // hide MariaDB Destination https://github.com/airbytehq/airbyte-cloud/issues/2611 "8b746512-8c2e-6ac1-4adc-b59faafd473c", // hide MongoDB Destination https://github.com/airbytehq/airbyte-cloud/issues/2612 "f3802bc4-5406-4752-9e8d-01e504ca8194", // hide MQTT Destination https://github.com/airbytehq/airbyte-cloud/issues/2613 "2340cbba-358e-11ec-8d3d-0242ac130203", // hide Pular Destination https://github.com/airbytehq/airbyte-cloud/issues/2614 - "d4d3fef9-e319-45c2-881a-bd02ce44cc9f", // hide Redis Destination https://github.com/airbytehq/airbyte-cloud/issues/2593 "2c9d93a7-9a17-4789-9de9-f46f0097eb70", // hide Rockset Destination https://github.com/airbytehq/airbyte-cloud/issues/2615 "2470e835-feaf-4db6-96f3-70fd645acc77", // Salesforce Singer "3dc6f384-cd6b-4be3-ad16-a41450899bf0", // hide Scylla Destination https://github.com/airbytehq/airbyte-cloud/issues/2617 From 04d84ce5f432f60ba0b8281f2da22133d0a39a23 Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Tue, 1 Nov 2022 12:54:07 +0200 Subject: [PATCH 451/498] Update helpers functions in subcharts (#18757) * Update helpers functions in subcharts; Refactor component.imageTag function call * fix typo in _helpers.tpl --- charts/airbyte-bootloader/templates/_helpers.tpl | 6 +++--- charts/airbyte-cron/templates/_helpers.tpl | 6 +++--- charts/airbyte-metrics/templates/_helpers.tpl | 6 +++--- charts/airbyte-server/templates/_helpers.tpl | 6 +++--- charts/airbyte-webapp/templates/_helpers.tpl | 6 +++--- charts/airbyte-worker/templates/_helpers.tpl | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/charts/airbyte-bootloader/templates/_helpers.tpl b/charts/airbyte-bootloader/templates/_helpers.tpl index 80f5cc7ebb29..be2497a57d8b 100644 --- a/charts/airbyte-bootloader/templates/_helpers.tpl +++ b/charts/airbyte-bootloader/templates/_helpers.tpl @@ -75,10 +75,10 @@ Define imageTag */}} {{- define "bootloader.imageTag" -}} -{{- if ((.Values.global.image).tag) }} - {{- printf "%s" .Values.global.image.tag }} -{{- else if .Values.image.tag }} +{{- if .Values.image.tag }} {{- printf "%s" .Values.image.tag }} +{{- else if ((.Values.global.image).tag) }} + {{- printf "%s" .Values.global.image.tag }} {{- else }} {{- printf "%s" .Chart.AppVersion }} {{- end }} diff --git a/charts/airbyte-cron/templates/_helpers.tpl b/charts/airbyte-cron/templates/_helpers.tpl index 6a7715b9023c..8c18f34c0e75 100644 --- a/charts/airbyte-cron/templates/_helpers.tpl +++ b/charts/airbyte-cron/templates/_helpers.tpl @@ -63,10 +63,10 @@ Define imageTag */}} {{- define "cron.imageTag" -}} -{{- if ((.Values.global.image).tag) }} - {{- printf "%s" .Values.global.image.tag }} -{{- else if .Values.image.tag }} +{{- if .Values.image.tag }} {{- printf "%s" .Values.image.tag }} +{{- else if ((.Values.global.image).tag) }} + {{- printf "%s" .Values.global.image.tag }} {{- else }} {{- printf "%s" .Chart.AppVersion }} {{- end }} diff --git a/charts/airbyte-metrics/templates/_helpers.tpl b/charts/airbyte-metrics/templates/_helpers.tpl index 623e3d56e9b0..21a584c89908 100644 --- a/charts/airbyte-metrics/templates/_helpers.tpl +++ b/charts/airbyte-metrics/templates/_helpers.tpl @@ -43,10 +43,10 @@ Define imageTag */}} {{- define "metrics.imageTag" -}} -{{- if ((.Values.global.image).tag) }} - {{- printf "%s" .Values.global.image.tag }} -{{- else if .Values.image.tag }} +{{- if .Values.image.tag }} {{- printf "%s" .Values.image.tag }} +{{- else if ((.Values.global.image).tag) }} + {{- printf "%s" .Values.global.image.tag }} {{- else }} {{- printf "%s" .Chart.AppVersion }} {{- end }} diff --git a/charts/airbyte-server/templates/_helpers.tpl b/charts/airbyte-server/templates/_helpers.tpl index 4dff8d4e06d4..e73a70e89a8b 100644 --- a/charts/airbyte-server/templates/_helpers.tpl +++ b/charts/airbyte-server/templates/_helpers.tpl @@ -63,10 +63,10 @@ Define imageTag */}} {{- define "server.imageTag" -}} -{{- if ((.Values.global.image).tag) }} - {{- printf "%s" .Values.global.image.tag }} -{{- else if .Values.image.tag }} +{{- if .Values.image.tag }} {{- printf "%s" .Values.image.tag }} +{{- else if ((.Values.global.image).tag) }} + {{- printf "%s" .Values.global.image.tag }} {{- else }} {{- printf "%s" .Chart.AppVersion }} {{- end }} diff --git a/charts/airbyte-webapp/templates/_helpers.tpl b/charts/airbyte-webapp/templates/_helpers.tpl index ef7494503204..01b8f68a7bae 100644 --- a/charts/airbyte-webapp/templates/_helpers.tpl +++ b/charts/airbyte-webapp/templates/_helpers.tpl @@ -64,10 +64,10 @@ Define imageTag */}} {{- define "webapp.imageTag" -}} -{{- if ((.Values.global.image).tag) }} - {{- printf "%s" .Values.global.image.tag }} -{{- else if .Values.image.tag }} +{{- if .Values.image.tag }} {{- printf "%s" .Values.image.tag }} +{{- else if ((.Values.global.image).tag) }} + {{- printf "%s" .Values.global.image.tag }} {{- else }} {{- printf "%s" .Chart.AppVersion }} {{- end }} diff --git a/charts/airbyte-worker/templates/_helpers.tpl b/charts/airbyte-worker/templates/_helpers.tpl index 1d991cf17e78..76691fe5b233 100644 --- a/charts/airbyte-worker/templates/_helpers.tpl +++ b/charts/airbyte-worker/templates/_helpers.tpl @@ -63,10 +63,10 @@ Define imageTag */}} {{- define "worker.imageTag" -}} -{{- if ((.Values.global.image).tag) }} - {{- printf "%s" .Values.global.image.tag }} -{{- else if .Values.image.tag }} +{{- if .Values.image.tag }} {{- printf "%s" .Values.image.tag }} +{{- else if ((.Values.global.image).tag) }} + {{- printf "%s" .Values.global.image.tag }} {{- else }} {{- printf "%s" .Chart.AppVersion }} {{- end }} From 3741014ad5f77e125776a65406536fb807b646b4 Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Tue, 1 Nov 2022 15:31:15 +0200 Subject: [PATCH 452/498] Update chart.yaml to reference right subchart (#18718) * Update chart.yaml to reference right subchart * Update airbyte-cron chart name --- charts/airbyte-cron/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 7 +++++-- charts/airbyte/Chart.yaml | 16 ++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/charts/airbyte-cron/Chart.yaml b/charts/airbyte-cron/Chart.yaml index 91831ee02a09..89490ec4236b 100644 --- a/charts/airbyte-cron/Chart.yaml +++ b/charts/airbyte-cron/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -name: airbyte-cron +name: cron description: Helm chart to deploy airbyte-cron # A chart can be either an 'application' or a 'library' chart. diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index 1cc2f56e434d..5bfe7f73a16d 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -23,5 +23,8 @@ dependencies: - name: metrics repository: https://airbytehq.github.io/helm-charts/ version: 0.40.33 -digest: sha256:2b6db6ac5a15f3bc2e53c7453ef704f4a6f64ee276ff60a2d7f7d0947fe0a2df -generated: "2022-10-24T14:01:11.885475748Z" +- name: airbyte-cron + repository: https://airbytehq.github.io/helm-charts/ + version: 0.40.36 +digest: sha256:4bf1839b99570f8c175bf147e3eae2deec2b1dc68a5191e47eb452d5e2c5859e +generated: "2022-10-31T18:20:32.211489+02:00" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 1d49814040b4..14f260918ce0 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -33,33 +33,33 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 - condition: cron.enabled name: cron repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.33 + version: 0.40.36 From 093a2941a13a469552b2b137dc97c5ef2c7116c3 Mon Sep 17 00:00:00 2001 From: Yiyang Li Date: Tue, 1 Nov 2022 06:36:53 -0700 Subject: [PATCH 453/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Destination:=20Hea?= =?UTF-8?q?p=20Analytics=20[python=20cdk]=20(#18530)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🎉 New Desination: Heap Analytics [python cdk] - implement a heap client to load data via the server-side API: https://developers.heap.io/reference/server-side-apis-overview - the connector supports a generic data source, and the api_type determines the output schema. The output schema is dynamic. - users pick the columns that will be loaded to the destination - Consequently, each configured catalog only includes one stream * add a bootstrap to illustrate the connector * add destination dest def * run format all files * correct unit test * auto-bump connector version Co-authored-by: Vincent Koc Co-authored-by: marcosmarxm Co-authored-by: Octavia Squidington III --- .../seed/destination_definitions.yaml | 6 + .../resources/seed/destination_specs.yaml | 142 ++++++++ .../destination-heap-analytics/.dockerignore | 5 + .../destination-heap-analytics/Dockerfile | 38 +++ .../destination-heap-analytics/README.md | 180 ++++++++++ .../destination-heap-analytics/bootstramp.md | 101 ++++++ .../destination-heap-analytics/build.gradle | 8 + .../destination_heap_analytics/__init__.py | 8 + .../destination_heap_analytics/client.py | 51 +++ .../destination_heap_analytics/destination.py | 76 +++++ .../destination_heap_analytics/spec.json | 144 ++++++++ .../destination_heap_analytics/utils.py | 85 +++++ .../integration_tests/integration_test.py | 149 ++++++++ .../integration_tests/invalid_config.json | 10 + .../destination-heap-analytics/main.py | 11 + .../requirements.txt | 1 + .../sample_files/config-aap.json | 9 + .../sample_files/config-aup.json | 9 + .../sample_files/config-events.json | 11 + .../sample_files/configured_catalog.json | 15 + .../sample_files/messages.jsonl | 50 +++ .../destination-heap-analytics/setup.py | 28 ++ .../unit_tests/test_client.py | 54 +++ .../unit_tests/test_parse_json.py | 224 +++++++++++++ .../unit_tests/test_utils.py | 106 ++++++ .../unit_tests/unit_test.py | 7 + ...shKeyMongoDbDestinationAcceptanceTest.java | 1 + .../SshMongoDbDestinationAcceptanceTest.java | 7 +- ...swordMongoDbDestinationAcceptanceTest.java | 1 + .../source_auth0/schemas/users.json | 6 +- .../source-auth0/source_auth0/spec.yaml | 4 +- .../integration_tests/sample_config.json | 7 +- .../integration_tests/spec.json | 26 +- .../source_lever_hiring/spec.json | 26 +- .../source-salesforce/unit_tests/api_test.py | 6 +- .../integration_tests/configured_catalog.json | 76 ++--- .../integration_tests/sample_state.json | 4 +- .../source_zoom/schemas/meeting_polls.json | 170 +++++----- .../schemas/meeting_registrants.json | 8 +- .../meeting_registration_questions.json | 4 +- .../source_zoom/schemas/meetings.json | 43 +-- .../source_zoom/schemas/users.json | 6 +- .../source-zoom/source_zoom/zoom.yaml | 86 ++--- docs/integrations/README.md | 1 + .../destinations/heap-analytics.md | 317 ++++++++++++++++++ 45 files changed, 2042 insertions(+), 285 deletions(-) create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/.dockerignore create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/Dockerfile create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/README.md create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/bootstramp.md create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/build.gradle create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/__init__.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/client.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/destination.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/spec.json create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/utils.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/integration_tests/integration_test.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/main.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/requirements.txt create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aap.json create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aup.json create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-events.json create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/sample_files/configured_catalog.json create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/sample_files/messages.jsonl create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/setup.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_client.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_parse_json.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_utils.py create mode 100644 airbyte-integrations/connectors/destination-heap-analytics/unit_tests/unit_test.py create mode 100644 docs/integrations/destinations/heap-analytics.md diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 94f2bbb7f3af..3ff0328318f0 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -137,6 +137,12 @@ documentationUrl: https://docs.airbyte.com/integrations/destinations/pubsub icon: googlepubsub.svg releaseStage: alpha +- name: Heap Analytics + destinationDefinitionId: f8e68742-407a-4a3c-99ad-dfd42ae2cba8 + dockerRepository: airbyte/destination-heap-analytics + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/destinations/heap-analytics + releaseStage: alpha - name: Kafka destinationDefinitionId: 9f760101-60ae-462f-9ee6-b7a9dafd454d dockerRepository: airbyte/destination-kafka diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index 35f7df968225..b27586cd2f2f 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -2431,6 +2431,148 @@ supportsDBT: false supported_destination_sync_modes: - "append" +- dockerImage: "airbyte/destination-heap-analytics:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/destinations/heap-analytics" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Heap Analytics Destination Spec" + type: "object" + required: + - "base_url" + - "app_id" + - "api" + additionalProperties: true + properties: + app_id: + order: 0 + type: "string" + title: "App Id" + description: "The Environment Id of your Main Profudction project, read\ + \ the doc to learn more." + default: "11" + base_url: + order: 1 + type: "string" + title: "Base URL" + description: "The Base URL for Heap Analytics" + default: "https://heapanalytics.com" + examples: + - "https://heapanalytics.com" + api: + order: 2 + type: "object" + title: "API Type" + additionalProperties: true + oneOf: + - order: 0 + type: "object" + title: "Track Events" + required: + - "api_type" + - "property_columns" + - "event_column" + - "identity_column" + properties: + api_type: + order: 0 + type: "string" + const: "track" + property_columns: + order: 1 + type: "string" + title: "Property Columns" + default: "*" + description: "Please list all columns populated to the properties\ + \ attribute, split by comma(,). It's case sensitive." + examples: + - "subject,variation" + event_column: + order: 2 + type: "string" + title: "Event Column" + description: "Please pick the column populated to the event attribute.\ + \ It's case sensitive." + examples: + - "order_name" + identity_column: + order: 3 + type: "string" + title: "Identity Column" + description: "Please pick the column populated to the identity attribute." + examples: + - "email" + timestamp_column: + order: 4 + type: "string" + title: "Identity Column" + description: "Please pick the column populated to the (optional) timestamp\ + \ attribute. time_now() will be used if missing." + examples: + - "updated_at" + - order: 1 + type: "object" + title: "Add User Properties" + required: + - "api_type" + - "property_columns" + - "identity_column" + properties: + api_type: + order: 0 + type: "string" + const: "add_user_properties" + property_columns: + order: 1 + type: "string" + title: "Property Columns" + default: "*" + description: "Please list all columns populated to the properties\ + \ attribute, split by comma(,). It's case sensitive." + examples: + - "age,language,profession" + identity_column: + order: 3 + type: "string" + title: "Identity Column" + description: "Please pick the column populated to the identity attribute." + examples: + - "user_id" + - order: 2 + type: "object" + title: "Add Account Properties" + required: + - "api_type" + - "property_columns" + - "account_id_column" + properties: + api_type: + order: 0 + type: "string" + const: "add_account_properties" + property_columns: + order: 1 + type: "string" + title: "Property Columns" + default: "*" + description: "Please list all columns populated to the properties\ + \ attribute, split by comma(,). It's case sensitive." + examples: + - "is_in_good_standing,revenue_potential,account_hq,subscription" + account_id_column: + order: 3 + type: "string" + title: "Account ID Column" + description: "Please pick the column populated to the account_id attribute." + examples: + - "company_name" + supportsIncremental: true + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: + - "append" + - "append_dedup" - dockerImage: "airbyte/destination-kafka:0.1.10" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/kafka" diff --git a/airbyte-integrations/connectors/destination-heap-analytics/.dockerignore b/airbyte-integrations/connectors/destination-heap-analytics/.dockerignore new file mode 100644 index 000000000000..0db1b78f4b2c --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/.dockerignore @@ -0,0 +1,5 @@ +* +!Dockerfile +!main.py +!destination_heap_analytics +!setup.py diff --git a/airbyte-integrations/connectors/destination-heap-analytics/Dockerfile b/airbyte-integrations/connectors/destination-heap-analytics/Dockerfile new file mode 100644 index 000000000000..8e0bf4685849 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.11-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY destination_heap_analytics ./destination_heap_analytics + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/destination-heap-analytics diff --git a/airbyte-integrations/connectors/destination-heap-analytics/README.md b/airbyte-integrations/connectors/destination-heap-analytics/README.md new file mode 100644 index 000000000000..8d380c441fe4 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/README.md @@ -0,0 +1,180 @@ +# Heap Analytics Destination + +This is the repository for the Heap Analytics destination connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/destinations/heap-analytics). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.7.0` + +#### Build & Activate Virtual Environment and install dependencies + +From this connector directory, create a virtualenv: +``` +python -m venv .venv +``` + +This will generate a virtual environment for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +From the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:destination-heap-analytics:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/destinations/heap-analytics) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `destination_heap_analytics/spec.json` file. +Note that the `secrets` directory is gitignored by default, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the app id in Lastpass under the secret name `destination heap-analytics app id` and replace the app_id under the `sample_files/config-*.json` + +### Locally running the connector + +#### Server-Side API - Track + +Use [this API](https://developers.heap.io/reference/track-1) to send custom events to Heap server-side. + +```bash +python main.py spec +python main.py check --config sample_files/config-events.json +cat sample_files/messages.jsonl | python main.py write --config sample_files/config-events.json --catalog sample_files/configured_catalog.json +``` + +#### Server-Side API - Add User Properties + +[This API](https://developers.heap.io/reference/add-user-properties) allows you to attach custom properties to any identified users from your servers, such as Sign Up Date (in ISO8601 format), Total # Transactions Completed, or Total Dollars Spent. + +```bash +python main.py spec +python main.py check --config sample_files/config-aup.json +cat sample_files/messages.jsonl | python main.py write --config sample_files/config-aup.json --catalog sample_files/configured_catalog.json +``` + +#### Server-Side API - Add Account Properties + +[This API](https://developers.heap.io/reference/add-account-properties) allows you to attach custom account properties to users. An account ID or use of our Salesforce integration is required for this to work. + +```bash +python main.py spec +python main.py check --config sample_files/config-aap.json +cat sample_files/messages.jsonl | python main.py write --config sample_files/config-aap.json --catalog sample_files/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build + +First, make sure you build the latest Docker image: + +```bash +docker build . -t airbyte/destination-heap-analytics:dev +``` + +You can also build the connector image via Gradle: + +```bash +./gradlew :airbyte-integrations:connectors:destination-heap-analytics:airbyteDocker +``` + +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run + +Then run any of the connector commands as follows: +Spec command + +```bash +docker run --rm airbyte/destination-heap-analytics:dev spec +``` + +Check command + +```bash +docker run --rm -v $(pwd)/sample_files:/sample_files airbyte/destination-heap-analytics:dev check --config /sample_files/config-events.json +docker run --rm -v $(pwd)/sample_files:/sample_files airbyte/destination-heap-analytics:dev check --config /sample_files/config-aap.json +docker run --rm -v $(pwd)/sample_files:/sample_files airbyte/destination-heap-analytics:dev check --config /sample_files/config-aup.json +``` + +Write command +```bash +# messages.jsonl is a file containing line-separated JSON representing AirbyteMessages +cat sample_files/messages.jsonl | docker run --rm -v $(pwd)/sample_files:/sample_files airbyte/destination-heap-analytics:dev write --config /sample_files/config-events.json --catalog /sample_files/configured_catalog.json +cat sample_files/messages.jsonl | docker run --rm -v $(pwd)/sample_files:/sample_files airbyte/destination-heap-analytics:dev write --config /sample_files/config-aup.json --catalog /sample_files/configured_catalog.json +cat sample_files/messages.jsonl | docker run --rm -v $(pwd)/sample_files:/sample_files airbyte/destination-heap-analytics:dev write --config /sample_files/config-aap.json --catalog /sample_files/configured_catalog.json +``` + +## Testing + +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: + +``` +pip install .[tests] +``` + +### Unit Tests +To run unit tests locally, from the connector directory run: + +``` +python -m pytest unit_tests +``` + +### Integration Tests + +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all destination connectors) and custom integration tests (which are specific to this connector). + +#### Custom Integration tests + +Place custom tests inside `integration_tests/` folder, then, from the connector root, run + +```bash +python -m pytest integration_tests +``` + +### Using gradle to run tests + +All commands should be run from airbyte project root. +To run unit tests: + +```bash +./gradlew :airbyte-integrations:connectors:destination-heap-analytics:unitTest +``` + +To run acceptance and custom integration tests: +```bash +./gradlew :airbyte-integrations:connectors:destination-heap-analytics:integrationTest +``` + +## Dependency Management + +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: + +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector + +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +2. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +3. Create a Pull Request. +4. Pat yourself on the back for being an awesome contributor. +5. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/destination-heap-analytics/bootstramp.md b/airbyte-integrations/connectors/destination-heap-analytics/bootstramp.md new file mode 100644 index 000000000000..64abc30333b4 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/bootstramp.md @@ -0,0 +1,101 @@ +# Heap Analytics Destination + +[Heap](https://heap.io) is a product analytics tool that help you collect and analyze understand customers' behavior data in your web apps or mobile apps.Every single click, swipe, tag, pageview and fill will be tracked. It's also called [Auto Capture](https://heap.io/platform/autocapture) + +Other than that, developers can write codes to "manually" track an event -- using a JavaScript SDK or a http request. Today, there is a 3rd way, you can import a large set of data via the open source E(t)L platform -- Airbyte. + +## Support any types of data source + +Airbyte loads data to heap through the [server-side API](https://developers.heap.io/reference/server-side-apis-overview). As long as the data is transformed correctly, and the output includes all required properties, data will be successfully loaded. The api is always on! + +All types of data source are supported, but you have to specify where the required properties are extracted from. + +Let's use [track events](https://developers.heap.io/reference/track-1) as an example. +The following sample data is an user fetched [Auth0's API](https://auth0.com/docs/api/management/v2#!/Users/get_users). + +```json +[{ + "blocked": false, + "created_at": "2022-10-21T04:09:54.622Z", + "email": "evalyn_shields@hotmail.com", + "email_verified": false, + "family_name": "Brakus", + "given_name": "Camden", + "identities": { + "user_id": "0a12757f-4b19-4e93-969e-c3a2e98fe82b", + "connection": "Username-Password-Authentication", + "provider": "auth0", + "isSocial": false + }, + "name": "Jordan Yost", + "nickname": "Elroy", + "updated_at": "2022-10-21T04:09:54.622Z", + "user_id": "auth0|0a12757f-4b19-4e93-969e-c3a2e98fe82b" +}] +``` + +According to [the track API](https://developers.heap.io/reference/track-1), the following attributes are required in the request body. + +- app_id: The id of your project or app +- identity: An identity, typically corresponding to an existing user. +- event: The name of the server-side event. +- properties: An object with key-value properties you want associated with the event. +- timestamp: (optional), the datetime in ISO8601. e.g. "2017-03-10T22:21:56+00:00". Defaults to the current time if not provided. + +To transform the data, you need to configure the following 4 fields when you create the connector: + +- Identity Column: The attribute name from the source data populated to identity. +- event_column: The attribute name from the source data populated to event. +- Timestamp Column: The attribute name from the source data populated to timestamp. This field is optional. It will be the current time if not provided. +- Property Columns: The attribute names from the source data populated to object properties. If you want to pick multiple attributes, split the names by comma(`,`). If you want to pick ALL attributes, simply put asterisk(`*`). + +So, if you want to load the following data: + +```json +{ + "identity": "evalyn_shields@hotmail.com", + "event": "Username-Password-Authentication", + "timestamp": "2022-10-21T04:09:54.622Z", + "properties": { + "blocked": false, + "created_at": "2022-10-21T04:09:54.622Z", + "name": "Jordan Yost" + } +} +``` + +Here's how you may configure the connector: + +```json +{ + "app_id": "11", + "base_url": "https://heapanalytics.com", + "api": { + "api_type": "track", + "property_columns": "blocked,created_at,name", + "event_column": "identities.connection", + "identity_column": "email", + "timestamp_column": "updated_at" + } +} +``` + +Notice, the event property comes from a property `connection` embedded in an object `identities`, that's why you set `event_column` `identities.connection`. It's called dot notation -- write the name of the object, followed by a dot (.), followed by the name of the property. + +Similarly, if you want to load a user or an account, there are other set of required properties. To learn more, please refer to the [ReadMe.md](/docs/integrations/destinations/heap-analytics.md). + +## Liminations + +Though The destination connector supports a generic schema. There are a few limitations. + +### Performance + +Heap offers a bulk api that allows you to load multiple rows of data. However, it's not implemented in the first version. So every row is a http post request to Heap, it's not efficient. Please submit your request and we will enhance it for you. + +### Only one schema is supported in a connector + +Because the configuration of the destination connector includes the details of the transformation, a connector only works for one schema. For example, there are 4 tables in a postgres database -- products, orders, users, logs. If you want to import all tables to heap, you may create 4 different connectors. Each connector includes a transformation setting suitable for the corresponding table schema. + +### Unable to join 2 streams + +If you understand the section above, you may realize there's no way to merge data from 2 streams. Still the postgres example above, the table `products` contains the details(also called metadata) for a given product id. The table `orders` users product id as a foreign key to reference the table `products`. In a SQL console, You can use an `inner join` to combine these 2 table. However, the destination connector is unable to merge them for you. Instead, you may pre-process the data by creating a view in postgres first, and configure Airbyte to load the view, the view that joins these 2 tables. diff --git a/airbyte-integrations/connectors/destination-heap-analytics/build.gradle b/airbyte-integrations/connectors/destination-heap-analytics/build.gradle new file mode 100644 index 000000000000..4eb911066d2e --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' +} + +airbytePython { + moduleDirectory 'destination_heap_analytics' +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/__init__.py b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/__init__.py new file mode 100644 index 000000000000..5eab928daf60 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .destination import DestinationHeapAnalytics + +__all__ = ["DestinationHeapAnalytics"] diff --git a/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/client.py b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/client.py new file mode 100644 index 000000000000..f8fcfe3de1d3 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/client.py @@ -0,0 +1,51 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import logging +from typing import Any, Mapping +from urllib import parse + +import pendulum +import requests +from destination_heap_analytics.utils import datetime_to_string + +HEADERS = {"Content_Type": "application/json"} + +logger = logging.getLogger("airbyte") + + +class HeapClient: + api_type = "" + api_endpoint = "" + check_endpoint = "" + + def __init__(self, base_url: str, app_id: str, api: Mapping[str, str]): + self.api_type = api.get("api_type") + self.app_id = app_id + self.api_endpoint = parse.urljoin(base_url, f"api/{self.api_type}") + self.check_endpoint = parse.urljoin(base_url, "api/track") + + def check(self): + """ + send a payload to the track endpoint + """ + return self._request( + url=self.check_endpoint, + json={ + "identity": "admin@heap.io", + "idempotency_key": "airbyte-preflight-check", + "event": "Airbyte Preflight Check", + "timestamp": datetime_to_string(pendulum.now("UTC")), + }, + ) + + def write(self, json: Mapping[str, Any]): + return self._request(url=self.api_endpoint, json=json) + + def _request(self, url: str, json: Mapping[str, Any] = {}) -> requests.Response: + logger.debug(json) + response = requests.post(url=url, headers=HEADERS, json={"app_id": self.app_id, **(json or {})}) + logger.debug(response.status_code) + response.raise_for_status() + return response diff --git a/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/destination.py b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/destination.py new file mode 100644 index 000000000000..93987c3ee605 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/destination.py @@ -0,0 +1,76 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import logging +from typing import Any, Dict, Iterable, Mapping + +from airbyte_cdk import AirbyteLogger +from airbyte_cdk.destinations import Destination +from airbyte_cdk.models import AirbyteConnectionStatus, AirbyteLogMessage, AirbyteMessage, ConfiguredAirbyteCatalog, Level, Status, Type +from destination_heap_analytics.client import HeapClient +from destination_heap_analytics.utils import flatten_json, parse_aap_json, parse_aup_json, parse_event_json +from requests import HTTPError + +logger = logging.getLogger("airbyte") + + +class DestinationHeapAnalytics(Destination): + def parse_and_validate_json(self, data: Dict[str, any], api: Mapping[str, str]): + flatten = flatten_json(data) + api_type = api.get("api_type") + if api_type == "track": + return parse_event_json(data=flatten, **api) + elif api_type == "add_user_properties": + return parse_aup_json(data=flatten, **api) + elif api_type == "add_account_properties": + return parse_aap_json(data=flatten, **api) + else: + return None + + def write( + self, config: Mapping[str, Any], configured_catalog: ConfiguredAirbyteCatalog, input_messages: Iterable[AirbyteMessage] + ) -> Iterable[AirbyteMessage]: + messages_count = 0 + records_count = 0 + loaded_count = 0 + api = config.get("api") + api["property_columns"] = api.get("property_columns").split(",") + client = HeapClient(**config) + for message in input_messages: + messages_count = messages_count + 1 + if message.type == Type.STATE: + yield message + elif message.type == Type.RECORD: + record = message.record + data = record.data + records_count = records_count + 1 + validated = self.parse_and_validate_json(data=data, api=api) + if validated: + try: + client.write(validated) + loaded_count = loaded_count + 1 + except HTTPError as ex: + logger.warn(f"experienced an error at the {records_count}th row, error: {ex}") + else: + logger.warn(f"data is invalid, skip writing the {records_count}th row") + else: + yield message + resultMessage = AirbyteMessage( + type=Type.LOG, + log=AirbyteLogMessage( + level=Level.INFO, message=f"Total Messages: {messages_count}. Total Records: {records_count}. Total loaded: {loaded_count}." + ), + ) + yield resultMessage + + def check(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> AirbyteConnectionStatus: + try: + client = HeapClient(**config) + logger.info(f"Checking connection for app_id: {client.app_id}, api_endpoint: {client.api_endpoint}") + client.check() + except Exception as e: + return AirbyteConnectionStatus(status=Status.FAILED, message=f"An exception occurred: {repr(e)}") + else: + return AirbyteConnectionStatus(status=Status.SUCCEEDED) diff --git a/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/spec.json b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/spec.json new file mode 100644 index 000000000000..55a2b5fad144 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/spec.json @@ -0,0 +1,144 @@ +{ + "documentationUrl": "https://docs.airbyte.com/integrations/destinations/heap-analytics", + "supported_destination_sync_modes": ["append", "append_dedup"], + "supportsIncremental": true, + "supportsDBT": false, + "supportsNormalization": false, + "connectionSpecification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Heap Analytics Destination Spec", + "type": "object", + "required": ["base_url", "app_id", "api"], + "additionalProperties": true, + "properties": { + "app_id": { + "order": 0, + "type": "string", + "title": "App Id", + "description": "The Environment Id of your Main Profudction project, read the doc to learn more.", + "default": "11" + }, + "base_url": { + "order": 1, + "type": "string", + "title": "Base URL", + "description": "The Base URL for Heap Analytics", + "default": "https://heapanalytics.com", + "examples": ["https://heapanalytics.com"] + }, + "api": { + "order": 2, + "type": "object", + "title": "API Type", + "additionalProperties": true, + "oneOf": [ + { + "order": 0, + "type": "object", + "title": "Track Events", + "required": [ + "api_type", + "property_columns", + "event_column", + "identity_column" + ], + "properties": { + "api_type": { + "order": 0, + "type": "string", + "const": "track" + }, + "property_columns": { + "order": 1, + "type": "string", + "title": "Property Columns", + "default": "*", + "description": "Please list all columns populated to the properties attribute, split by comma(,). It's case sensitive.", + "examples": ["subject,variation"] + }, + "event_column": { + "order": 2, + "type": "string", + "title": "Event Column", + "description": "Please pick the column populated to the event attribute. It's case sensitive.", + "examples": ["order_name"] + }, + "identity_column": { + "order": 3, + "type": "string", + "title": "Identity Column", + "description": "Please pick the column populated to the identity attribute.", + "examples": ["email"] + }, + "timestamp_column": { + "order": 4, + "type": "string", + "title": "Identity Column", + "description": "Please pick the column populated to the (optional) timestamp attribute. time_now() will be used if missing.", + "examples": ["updated_at"] + } + } + }, + { + "order": 1, + "type": "object", + "title": "Add User Properties", + "required": ["api_type", "property_columns", "identity_column"], + "properties": { + "api_type": { + "order": 0, + "type": "string", + "const": "add_user_properties" + }, + "property_columns": { + "order": 1, + "type": "string", + "title": "Property Columns", + "default": "*", + "description": "Please list all columns populated to the properties attribute, split by comma(,). It's case sensitive.", + "examples": ["age,language,profession"] + }, + "identity_column": { + "order": 3, + "type": "string", + "title": "Identity Column", + "description": "Please pick the column populated to the identity attribute.", + "examples": ["user_id"] + } + } + }, + { + "order": 2, + "type": "object", + "title": "Add Account Properties", + "required": ["api_type", "property_columns", "account_id_column"], + "properties": { + "api_type": { + "order": 0, + "type": "string", + "const": "add_account_properties" + }, + "property_columns": { + "order": 1, + "type": "string", + "title": "Property Columns", + "default": "*", + "description": "Please list all columns populated to the properties attribute, split by comma(,). It's case sensitive.", + "examples": [ + "is_in_good_standing,revenue_potential,account_hq,subscription" + ] + }, + "account_id_column": { + "order": 3, + "type": "string", + "title": "Account ID Column", + "description": "Please pick the column populated to the account_id attribute.", + "examples": ["company_name"] + } + } + } + ] + } + } + } +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/utils.py b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/utils.py new file mode 100644 index 000000000000..9d3a76165d69 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/destination_heap_analytics/utils.py @@ -0,0 +1,85 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import datetime +from typing import Any, Dict, List, Mapping + +import pendulum + + +def datetime_to_string(date: datetime.datetime) -> str: + return date.to_iso8601_string() + + +def flatten_json(obj: Dict[str, Any]) -> Dict[str, Any]: + out = {} + + def flatten(x: Dict[str, Any], prefix=""): + if type(x) is dict: + for a in x: + flatten(x[a], prefix + a + ".") + elif type(x) is list: + i = 0 + for a in x: + flatten(a, prefix + str(i) + ".") + i += 1 + else: + out[prefix[:-1]] = x + + flatten(obj) + return out + + +def parse_property_json(data: Dict[str, any], property_columns: List[str]) -> Mapping[str, Any]: + if len(property_columns) == 1 and property_columns[0] == "*": + return {**(data or {})} + else: + properties = {} + for column in property_columns: + if column in data and data[column] is not None: + properties[column] = data[column] + return properties + + +def parse_event_json( + data: Dict[str, any], property_columns: List[str], event_column: str, identity_column: str, timestamp_column: str = None, **kwargs +) -> Mapping[str, Any]: + timestamp = data.get(timestamp_column) if data.get(timestamp_column) else datetime_to_string(pendulum.now("UTC")) + event = data.get(event_column) + identity = data.get(identity_column) + if timestamp and event and identity: + properties = parse_property_json(data=data, property_columns=property_columns) + return { + "identity": identity, + "event": event, + "timestamp": timestamp, + "properties": properties, + } + else: + return None + + +def parse_aup_json(data: Dict[str, any], property_columns: List[str], identity_column: str, **kwargs) -> Mapping[str, Any]: + identity = data.get(identity_column) + if identity: + properties = parse_property_json(data=data, property_columns=property_columns) + return { + "identity": identity, + "properties": properties, + } + else: + return None + + +def parse_aap_json(data: Dict[str, any], property_columns: List[str], account_id_column: str, **kwargs) -> Mapping[str, Any]: + account_id = data.get(account_id_column) + if account_id: + properties = parse_property_json(data=data, property_columns=property_columns) + return { + "account_id": account_id, + "properties": properties, + } + else: + return None diff --git a/airbyte-integrations/connectors/destination-heap-analytics/integration_tests/integration_test.py b/airbyte-integrations/connectors/destination-heap-analytics/integration_tests/integration_test.py new file mode 100644 index 000000000000..407058071aa3 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/integration_tests/integration_test.py @@ -0,0 +1,149 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import sys +from datetime import datetime +from io import StringIO +from json import load +from typing import Any, Dict, List +from unittest.mock import MagicMock + +from airbyte_cdk.models import AirbyteMessage, AirbyteRecordMessage, AirbyteStateMessage, AirbyteStateType, Level, Status, Type +from airbyte_cdk.models.airbyte_protocol import ConfiguredAirbyteCatalog +from destination_heap_analytics.destination import DestinationHeapAnalytics +from pytest import fixture + + +class CaptureStdOut(list): + """ + Captures the stdout messages into the variable list, that could be validated later. + """ + + def __enter__(self): + self._stdout = sys.stdout + sys.stdout = self._stringio = StringIO() + return self + + def __exit__(self, *args): + self.extend(self._stringio.getvalue().splitlines()) + del self._stringio + sys.stdout = self._stdout + + +@fixture(scope="module") +def config_events() -> Dict[str, str]: + with open( + "sample_files/config-events.json", + ) as f: + yield load(f) + + +@fixture(scope="module") +def configured_catalog() -> Dict[str, str]: + with open( + "sample_files/configured_catalog.json", + ) as f: + yield load(f) + + +@fixture(scope="module") +def config_aap() -> Dict[str, str]: + with open( + "sample_files/config-aap.json", + ) as f: + yield load(f) + + +@fixture(scope="module") +def config_aup() -> Dict[str, str]: + with open( + "sample_files/config-aup.json", + ) as f: + yield load(f) + + +@fixture(scope="module") +def invalid_config() -> Dict[str, str]: + with open( + "integration_tests/invalid_config.json", + ) as f: + yield load(f) + + +@fixture +def airbyte_state_message(): + return AirbyteMessage( + type=Type.STATE, + state=AirbyteStateMessage( + type=AirbyteStateType.STREAM, + data={}, + ), + ) + + +@fixture +def airbyte_messages(airbyte_state_message): + return [ + airbyte_state_message, + AirbyteMessage( + type=Type.RECORD, + record=AirbyteRecordMessage( + stream="users", + data={ + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "email_verified": False, + "family_name": "Blanda", + "given_name": "Bradly", + "identities": { + "user_id": "4ce74b28-bc00-4bbf-8a01-712dae975291", + "connection": "Username-Password-Authentication", + "provider": "auth0", + "isSocial": False, + }, + "name": "Hope Rodriguez", + "nickname": "Terrence", + "updated_at": "2022-10-21T04:08:58.994Z", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + emitted_at=int(datetime.now().timestamp()) * 1000, + ), + ), + airbyte_state_message, + ] + + +def test_check_fails(invalid_config): + destination = DestinationHeapAnalytics() + status = destination.check(logger=MagicMock(), config=invalid_config) + assert status.status == Status.FAILED + + +def test_check_succeeds(config_events, config_aap, config_aup): + destination = DestinationHeapAnalytics() + for config in [config_events, config_aap, config_aup]: + status = destination.check(logger=MagicMock(), config=config) + assert status.status == Status.SUCCEEDED + + +def test_write( + config_events: Dict[str, Any], + config_aap: Dict[str, Any], + config_aup: Dict[str, Any], + configured_catalog: ConfiguredAirbyteCatalog, + airbyte_messages: List[AirbyteMessage], + airbyte_state_message: AirbyteStateMessage, +): + destination = DestinationHeapAnalytics() + + for config in [config_events, config_aap, config_aup]: + generator = destination.write(config, configured_catalog, airbyte_messages) + result = list(generator) + assert len(result) == 3 + assert result[0] == airbyte_state_message + assert result[1] == airbyte_state_message + assert result[2].type == Type.LOG + assert result[2].log.level == Level.INFO + assert result[2].log.message == "Total Messages: 3. Total Records: 1. Total loaded: 1." diff --git a/airbyte-integrations/connectors/destination-heap-analytics/integration_tests/invalid_config.json b/airbyte-integrations/connectors/destination-heap-analytics/integration_tests/invalid_config.json new file mode 100644 index 000000000000..5ad762a6beb1 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/integration_tests/invalid_config.json @@ -0,0 +1,10 @@ +{ + "app_id": "11", + "base_url": "https://www.heapanalytics.com", + "api": { + "api_type": "track", + "property_columns": "*", + "event_column": "event", + "identity_column": "email" + } +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/main.py b/airbyte-integrations/connectors/destination-heap-analytics/main.py new file mode 100644 index 000000000000..cf506f8e7738 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/main.py @@ -0,0 +1,11 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from destination_heap_analytics import DestinationHeapAnalytics + +if __name__ == "__main__": + DestinationHeapAnalytics().run(sys.argv[1:]) diff --git a/airbyte-integrations/connectors/destination-heap-analytics/requirements.txt b/airbyte-integrations/connectors/destination-heap-analytics/requirements.txt new file mode 100644 index 000000000000..d6e1198b1ab1 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/requirements.txt @@ -0,0 +1 @@ +-e . diff --git a/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aap.json b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aap.json new file mode 100644 index 000000000000..ae6f0d1ad5bd --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aap.json @@ -0,0 +1,9 @@ +{ + "app_id": "11", + "base_url": "https://heapanalytics.com", + "api": { + "api_type": "add_account_properties", + "property_columns": "family_name,email_verified,blocked", + "account_id_column": "identities.user_id" + } +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aup.json b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aup.json new file mode 100644 index 000000000000..6ad9ad0b0335 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-aup.json @@ -0,0 +1,9 @@ +{ + "app_id": "11", + "base_url": "https://heapanalytics.com", + "api": { + "api_type": "add_user_properties", + "property_columns": "identities_connection,identities_provider,created_at,updated_at,name", + "identity_column": "user_id" + } +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-events.json b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-events.json new file mode 100644 index 000000000000..b2e9e87fc233 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/config-events.json @@ -0,0 +1,11 @@ +{ + "app_id": "11", + "base_url": "https://heapanalytics.com", + "api": { + "api_type": "track", + "property_columns": "blocked,created_at,updated_at,name", + "event_column": "identities.connection", + "identity_column": "email", + "timestamp_column": "updated_at" + } +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/sample_files/configured_catalog.json b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/configured_catalog.json new file mode 100644 index 000000000000..cac1ca9af2e3 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/configured_catalog.json @@ -0,0 +1,15 @@ +{ + "streams": [ + { + "stream": { + "name": "users", + "json_schema": {}, + "supported_sync_modes": ["full_refresh", "incremental"] + }, + "sync_mode": "incremental", + "destination_sync_mode": "overwrite", + "cursor_field": ["updated_at"], + "primary_key": [["user_id"]] + } + ] +} diff --git a/airbyte-integrations/connectors/destination-heap-analytics/sample_files/messages.jsonl b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/messages.jsonl new file mode 100644 index 000000000000..c89edac727f8 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/sample_files/messages.jsonl @@ -0,0 +1,50 @@ +{"type": "RECORD", "record": {"stream": "users", "data": {"created_at": "2022-10-19T21:44:49.226Z", "email": "abcd@email.com", "email_verified": false, "identities": {"connection": "Username-Password-Authentication", "user_id": "63506fd15615e6a1bdb54ebb", "provider": "auth0", "isSocial": false}, "name": "abcd@email.com", "nickname": "abcd", "picture": "https://s.gravatar.com/avatar/0c50c2e0d77c79a9852e31e715038a03?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fab.png", "updated_at": "2022-10-19T21:44:49.226Z", "user_id": "auth0|63506fd15615e6a1bdb54ebb"}, "emitted_at": 1666743597616}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:08:53.393Z", "email": "nedra14@hotmail.com", "email_verified": false, "family_name": "Tillman", "given_name": "Jacinto", "identities": {"user_id": "815ff3c3-84fa-4f63-b959-ac2d11efc63c", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Lola Conn", "nickname": "Kenyatta", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:08:53.393Z", "user_id": "auth0|815ff3c3-84fa-4f63-b959-ac2d11efc63c", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597620}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:08:56.729Z", "email": "myrtice.maggio@yahoo.com", "email_verified": false, "family_name": "Thompson", "given_name": "Greg", "identities": {"user_id": "d9b32ba6-f330-4d31-a062-21edc7dcd47b", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Marilyn Goldner", "nickname": "Alysa", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:08:56.729Z", "user_id": "auth0|d9b32ba6-f330-4d31-a062-21edc7dcd47b", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597621}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:08:57.575Z", "email": "jace2@gmail.com", "email_verified": false, "family_name": "Bahringer", "given_name": "Carey", "identities": {"user_id": "69cccde7-2ec8-4206-9c60-37cfbbf76b89", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Dr. Jay Donnelly", "nickname": "Hiram", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:08:57.575Z", "user_id": "auth0|69cccde7-2ec8-4206-9c60-37cfbbf76b89", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597621}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:08:58.333Z", "email": "thelma.rohan@yahoo.com", "email_verified": false, "family_name": "Sauer", "given_name": "Estel", "identities": {"user_id": "3b0e855c-3ca7-4ef0-ba04-1ad03e9925f3", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Garry Rolfson", "nickname": "Celestine", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:08:58.333Z", "user_id": "auth0|3b0e855c-3ca7-4ef0-ba04-1ad03e9925f3", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597621}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:08:58.994Z", "email": "beryl_becker95@yahoo.com", "email_verified": false, "family_name": "Blanda", "given_name": "Bradly", "identities": {"user_id": "4ce74b28-bc00-4bbf-8a01-712dae975291", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Hope Rodriguez", "nickname": "Terrence", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:08:58.994Z", "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597621}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:08:59.720Z", "email": "rubye_spinka86@yahoo.com", "email_verified": false, "family_name": "Purdy", "given_name": "Florida", "identities": {"user_id": "98831d6c-3cd6-4594-9245-b103ca89cace", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Felipe Corwin PhD", "nickname": "Lilyan", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:08:59.720Z", "user_id": "auth0|98831d6c-3cd6-4594-9245-b103ca89cace", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597621}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:08.993Z", "email": "daniella.ondricka67@yahoo.com", "email_verified": false, "family_name": "Grimes", "given_name": "Ladarius", "identities": {"user_id": "78fefc1c-9971-4f83-8199-fea13d77dd77", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Pam Carroll", "nickname": "Jabari", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:08.993Z", "user_id": "auth0|78fefc1c-9971-4f83-8199-fea13d77dd77", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597622}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:10.060Z", "email": "neva62@gmail.com", "email_verified": false, "family_name": "Nolan", "given_name": "Garnett", "identities": {"user_id": "bc0fd79d-e3a9-4204-8ab5-9983e40fd126", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Kelvin Goldner", "nickname": "Alexandrea", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:10.060Z", "user_id": "auth0|bc0fd79d-e3a9-4204-8ab5-9983e40fd126", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597622}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:13.399Z", "email": "brycen60@hotmail.com", "email_verified": false, "family_name": "Weimann", "given_name": "Marcella", "identities": {"user_id": "af1fc04e-ff8c-4ed3-9aca-54f0dfd6fd44", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Olivia Rice", "nickname": "Cortney", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:13.399Z", "user_id": "auth0|af1fc04e-ff8c-4ed3-9aca-54f0dfd6fd44", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597622}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:14.323Z", "email": "pierce43@yahoo.com", "email_verified": false, "family_name": "Vandervort", "given_name": "Hilbert", "identities": {"user_id": "702b3afc-d551-4c90-81bd-f792bae32b3b", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Flora Parisian", "nickname": "Aglae", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:14.323Z", "user_id": "auth0|702b3afc-d551-4c90-81bd-f792bae32b3b", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597622}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:15.072Z", "email": "rosemary.kautzer@hotmail.com", "email_verified": false, "family_name": "Robel", "given_name": "Coty", "identities": {"user_id": "24e149ff-04f5-457a-9936-64e8a6cb6d06", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Laurie Metz", "nickname": "Harrison", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:15.072Z", "user_id": "auth0|24e149ff-04f5-457a-9936-64e8a6cb6d06", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597622}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:51.891Z", "email": "otho.ward@hotmail.com", "email_verified": false, "family_name": "Funk", "given_name": "Hazle", "identities": {"user_id": "73da3042-0713-423a-bd3c-a45838269230", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Nora Kerluke", "nickname": "Herminio", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:51.891Z", "user_id": "auth0|73da3042-0713-423a-bd3c-a45838269230", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597623}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:54.068Z", "email": "jamel15@yahoo.com", "email_verified": false, "family_name": "Kunze", "given_name": "Maria", "identities": {"user_id": "c7723c91-9d12-41c2-a539-a0908d46092f", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Nichole Von", "nickname": "Mikayla", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:54.068Z", "user_id": "auth0|c7723c91-9d12-41c2-a539-a0908d46092f", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597623}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:54.622Z", "email": "evalyn_shields@hotmail.com", "email_verified": false, "family_name": "Brakus", "given_name": "Camden", "identities": {"user_id": "0a12757f-4b19-4e93-969e-c3a2e98fe82b", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Jordan Yost", "nickname": "Elroy", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:54.622Z", "user_id": "auth0|0a12757f-4b19-4e93-969e-c3a2e98fe82b", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597623}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:55.448Z", "email": "shayna74@gmail.com", "email_verified": false, "family_name": "Klocko", "given_name": "Bulah", "identities": {"user_id": "88abf12b-8a2b-473d-a735-4ca07353378e", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Ms. Marsha Kiehn", "nickname": "Garret", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:55.448Z", "user_id": "auth0|88abf12b-8a2b-473d-a735-4ca07353378e", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597624}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:56.062Z", "email": "alexandrea23@yahoo.com", "email_verified": false, "family_name": "Wehner", "given_name": "Carmine", "identities": {"user_id": "681d25e1-92b9-4997-a1ed-058c71089b03", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Erika Konopelski", "nickname": "Sunny", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:56.062Z", "user_id": "auth0|681d25e1-92b9-4997-a1ed-058c71089b03", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597624}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:56.711Z", "email": "zita.hoeger@hotmail.com", "email_verified": false, "family_name": "Simonis", "given_name": "Estel", "identities": {"user_id": "9c9c0239-a4de-42ee-8169-4fd13db69266", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Ginger Kiehn", "nickname": "Prudence", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:56.711Z", "user_id": "auth0|9c9c0239-a4de-42ee-8169-4fd13db69266", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597624}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:57.377Z", "email": "barrett.collins@gmail.com", "email_verified": false, "family_name": "Carter", "given_name": "Mabelle", "identities": {"user_id": "395305e9-08ce-465f-844b-f968a33bdaa3", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Genevieve Dietrich", "nickname": "Xavier", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:57.377Z", "user_id": "auth0|395305e9-08ce-465f-844b-f968a33bdaa3", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597624}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:57.818Z", "email": "marlen42@yahoo.com", "email_verified": false, "family_name": "Mante", "given_name": "Destini", "identities": {"user_id": "455f21be-922d-4a0f-be9b-5c578358ef59", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Jeanne O'Connell II", "nickname": "Cheyanne", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:57.818Z", "user_id": "auth0|455f21be-922d-4a0f-be9b-5c578358ef59", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597624}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:58.402Z", "email": "glennie_runolfsson1@hotmail.com", "email_verified": false, "family_name": "Muller", "given_name": "Gideon", "identities": {"user_id": "7d3cbf2a-cf1b-406b-b6dc-9c0c46365ef4", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Stewart Schumm", "nickname": "Esmeralda", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:58.402Z", "user_id": "auth0|7d3cbf2a-cf1b-406b-b6dc-9c0c46365ef4", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597625}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:58.874Z", "email": "jany93@gmail.com", "email_verified": false, "family_name": "Donnelly", "given_name": "Kennedi", "identities": {"user_id": "9a35cf40-1a2e-4bf2-bfdf-30c7a7db9039", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Darla Schneider", "nickname": "Olen", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:58.874Z", "user_id": "auth0|9a35cf40-1a2e-4bf2-bfdf-30c7a7db9039", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597625}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:09:59.328Z", "email": "marielle.murazik8@hotmail.com", "email_verified": false, "family_name": "Gutkowski", "given_name": "Alysha", "identities": {"user_id": "26d8952b-2e1e-4b79-b2aa-e363f062701a", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Lynn Crooks", "nickname": "Noe", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:09:59.328Z", "user_id": "auth0|26d8952b-2e1e-4b79-b2aa-e363f062701a", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597625}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:00.175Z", "email": "vergie17@hotmail.com", "email_verified": false, "family_name": "Jones", "given_name": "Gail", "identities": {"user_id": "1110bc0d-d59e-409b-b84a-448dc6c7d6bb", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Edith Pagac", "nickname": "Ignacio", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:00.175Z", "user_id": "auth0|1110bc0d-d59e-409b-b84a-448dc6c7d6bb", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597625}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:01.180Z", "email": "lulu_ullrich@hotmail.com", "email_verified": false, "family_name": "Lesch", "given_name": "Dejon", "identities": {"user_id": "24d08805-c399-431d-a54c-416f6416e341", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Emanuel Hilpert", "nickname": "Kraig", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:01.180Z", "user_id": "auth0|24d08805-c399-431d-a54c-416f6416e341", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597625}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:01.889Z", "email": "lew.hudson76@hotmail.com", "email_verified": false, "family_name": "Roberts", "given_name": "Jackie", "identities": {"user_id": "42797566-e687-4dfc-b5c5-da5e246fcea7", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Francis Hammes V", "nickname": "Irwin", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:01.889Z", "user_id": "auth0|42797566-e687-4dfc-b5c5-da5e246fcea7", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597626}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:03.177Z", "email": "kelli.abbott86@yahoo.com", "email_verified": false, "family_name": "Crooks", "given_name": "Bessie", "identities": {"user_id": "fc9fcb3d-8b1d-496a-9461-e9c9d549601b", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Miss Jessie Pfannerstill", "nickname": "Alvah", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:03.177Z", "user_id": "auth0|fc9fcb3d-8b1d-496a-9461-e9c9d549601b", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597626}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:04.059Z", "email": "kenna_champlin@yahoo.com", "email_verified": false, "family_name": "Torp", "given_name": "Bill", "identities": {"user_id": "8ef15327-eecb-48da-b167-3ef38f7dfdba", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Angel Koepp", "nickname": "Jaron", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:04.059Z", "user_id": "auth0|8ef15327-eecb-48da-b167-3ef38f7dfdba", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597626}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:04.795Z", "email": "merl.harvey33@yahoo.com", "email_verified": false, "family_name": "Muller", "given_name": "Emelia", "identities": {"user_id": "c5819fd9-24c0-4599-9bd3-63e3fbca74a6", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Mrs. Tiffany Carroll", "nickname": "Kamron", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:04.795Z", "user_id": "auth0|c5819fd9-24c0-4599-9bd3-63e3fbca74a6", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597626}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:05.479Z", "email": "berneice48@hotmail.com", "email_verified": false, "family_name": "Heller", "given_name": "Hortense", "identities": {"user_id": "1be8c604-5cca-4c91-9f3d-efca5ca38485", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Betty Powlowski", "nickname": "Sandra", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:05.479Z", "user_id": "auth0|1be8c604-5cca-4c91-9f3d-efca5ca38485", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597626}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:06.068Z", "email": "ethyl_hoppe77@yahoo.com", "email_verified": false, "family_name": "Medhurst", "given_name": "Kaelyn", "identities": {"user_id": "ef66586b-43bb-4b75-840d-0619c9e847bd", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Ismael Jast", "nickname": "Hortense", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:06.068Z", "user_id": "auth0|ef66586b-43bb-4b75-840d-0619c9e847bd", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597626}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:06.716Z", "email": "marilyne88@hotmail.com", "email_verified": false, "family_name": "Rolfson", "given_name": "Frederic", "identities": {"user_id": "70072c5b-d0d4-4603-8b67-ec7f5fe84a50", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Noel Hagenes", "nickname": "Cooper", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:06.716Z", "user_id": "auth0|70072c5b-d0d4-4603-8b67-ec7f5fe84a50", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597627}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:07.327Z", "email": "margie.legros10@yahoo.com", "email_verified": false, "family_name": "Greenfelder", "given_name": "Ricky", "identities": {"user_id": "bef87c6b-ebbd-4963-8039-68768214b0ba", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Paulette Leannon", "nickname": "Destiny", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:07.327Z", "user_id": "auth0|bef87c6b-ebbd-4963-8039-68768214b0ba", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597627}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:07.902Z", "email": "einar_graham@hotmail.com", "email_verified": false, "family_name": "Weissnat", "given_name": "Jessyca", "identities": {"user_id": "f542feea-6a8e-4718-8486-bd42cfbd263e", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Samantha Ortiz", "nickname": "Ryann", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:07.902Z", "user_id": "auth0|f542feea-6a8e-4718-8486-bd42cfbd263e", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597627}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:08.453Z", "email": "katrina23@hotmail.com", "email_verified": false, "family_name": "Hauck", "given_name": "Santos", "identities": {"user_id": "ca975aaa-6840-4f1a-8a6a-5bab7b6f7ddd", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Cecelia Runolfsdottir", "nickname": "Harley", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:08.453Z", "user_id": "auth0|ca975aaa-6840-4f1a-8a6a-5bab7b6f7ddd", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597627}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:09.028Z", "email": "natalia.moore57@gmail.com", "email_verified": false, "family_name": "Walker", "given_name": "Wyman", "identities": {"user_id": "c60e0dc6-844c-444a-9642-7463e6503584", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Lyle Pouros", "nickname": "Michaela", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:09.028Z", "user_id": "auth0|c60e0dc6-844c-444a-9642-7463e6503584", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597628}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:21.677Z", "email": "libbie.grant83@hotmail.com", "email_verified": false, "family_name": "Daniel", "given_name": "Bradford", "identities": {"user_id": "8bf58e36-ca07-4c50-8906-7d329ae4bff8", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Cory Brekke", "nickname": "Jeanne", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:21.677Z", "user_id": "auth0|8bf58e36-ca07-4c50-8906-7d329ae4bff8", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597628}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:23.060Z", "email": "easter11@gmail.com", "email_verified": false, "family_name": "Heaney", "given_name": "Cassidy", "identities": {"user_id": "7b9ab6fe-f1a4-45fe-a0a7-6752f968e4d1", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Dan Hudson", "nickname": "Florine", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:23.060Z", "user_id": "auth0|7b9ab6fe-f1a4-45fe-a0a7-6752f968e4d1", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597628}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:23.807Z", "email": "felicity.johnson89@hotmail.com", "email_verified": false, "family_name": "Waters", "given_name": "Isaiah", "identities": {"user_id": "a251175c-a56a-447e-bcff-c3fb50b7f718", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Donnie Klein", "nickname": "Daphney", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:23.807Z", "user_id": "auth0|a251175c-a56a-447e-bcff-c3fb50b7f718", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597628}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:24.491Z", "email": "alexandra18@gmail.com", "email_verified": false, "family_name": "Simonis", "given_name": "Jaeden", "identities": {"user_id": "d5994287-2f57-4b63-8808-2b001fb1ad4a", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Leticia Spencer", "nickname": "Gabrielle", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:24.491Z", "user_id": "auth0|d5994287-2f57-4b63-8808-2b001fb1ad4a", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597628}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:25.112Z", "email": "obie79@gmail.com", "email_verified": false, "family_name": "Skiles", "given_name": "Ernestina", "identities": {"user_id": "8cf3818b-9c63-47d8-97a2-1daee603e864", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Ronald Yundt", "nickname": "Okey", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:25.112Z", "user_id": "auth0|8cf3818b-9c63-47d8-97a2-1daee603e864", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597628}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:25.655Z", "email": "alexandria.brekke@hotmail.com", "email_verified": false, "family_name": "Bergstrom", "given_name": "Royce", "identities": {"user_id": "dec1828b-f438-4140-a80e-9f3c551b3287", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Johnnie Kuphal", "nickname": "Peggie", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:25.655Z", "user_id": "auth0|dec1828b-f438-4140-a80e-9f3c551b3287", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597629}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:26.228Z", "email": "deanna.breitenberg19@gmail.com", "email_verified": false, "family_name": "Weber", "given_name": "Santiago", "identities": {"user_id": "267902a9-d6a3-4206-a272-bbbf05c5dbef", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Ismael Bogan III", "nickname": "Armando", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:26.228Z", "user_id": "auth0|267902a9-d6a3-4206-a272-bbbf05c5dbef", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597629}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:26.826Z", "email": "kris.ratke@gmail.com", "email_verified": false, "family_name": "Turcotte", "given_name": "Quentin", "identities": {"user_id": "c54f2299-2c1a-4db7-b904-92235c90465d", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Brendan Stark DVM", "nickname": "Tiara", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:26.826Z", "user_id": "auth0|c54f2299-2c1a-4db7-b904-92235c90465d", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597629}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:28.314Z", "email": "christy38@hotmail.com", "email_verified": false, "family_name": "Paucek", "given_name": "Ara", "identities": {"user_id": "4b6f14da-75c1-4fdc-9b42-a890930051d8", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Cynthia Herman", "nickname": "Andreanne", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:28.314Z", "user_id": "auth0|4b6f14da-75c1-4fdc-9b42-a890930051d8", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597629}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:28.857Z", "email": "kirstin.crist62@gmail.com", "email_verified": false, "family_name": "Jacobi", "given_name": "Asia", "identities": {"user_id": "63349601-2daa-4fea-9a8c-6d5904d9f52d", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Geneva Block", "nickname": "Walter", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:28.857Z", "user_id": "auth0|63349601-2daa-4fea-9a8c-6d5904d9f52d", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597629}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:29.365Z", "email": "trevor.cummings68@yahoo.com", "email_verified": false, "family_name": "Crona", "given_name": "Lucious", "identities": {"user_id": "561580da-457b-4fe9-993d-14315d914f91", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Myra Jones", "nickname": "Chaz", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:29.365Z", "user_id": "auth0|561580da-457b-4fe9-993d-14315d914f91", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597629}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:29.809Z", "email": "toy88@gmail.com", "email_verified": false, "family_name": "Leannon", "given_name": "Desiree", "identities": {"user_id": "374793eb-62fa-4818-9fd0-ff1fbc53c522", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Kellie Champlin", "nickname": "Dennis", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:29.810Z", "user_id": "auth0|374793eb-62fa-4818-9fd0-ff1fbc53c522", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597630}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-21T04:10:30.240Z", "email": "jenifer.huel11@yahoo.com", "email_verified": false, "family_name": "Sauer", "given_name": "Jordane", "identities": {"user_id": "64738f61-34af-495a-a22e-1f5085fa9a97", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Catherine Hayes", "nickname": "Trystan", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-21T04:10:30.240Z", "user_id": "auth0|64738f61-34af-495a-a22e-1f5085fa9a97", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597630}} +{"type": "RECORD", "record": {"stream": "users", "data": {"blocked": false, "created_at": "2022-10-23T04:14:20.799Z", "email": "ebony_aufderhar12@yahoo.com", "email_verified": false, "family_name": "Price", "given_name": "Antonietta", "identities": {"user_id": "b1616e22-34af-4e19-812e-6c8c91fe2192", "connection": "Username-Password-Authentication", "provider": "auth0", "isSocial": false}, "name": "Israel Cole", "nickname": "Lue", "picture": "https://secure.gravatar.com/avatar/15626c5e0c749cb912f9d1ad48dba440?s=480&r=pg&d=https%3A%2F%2Fssl.gstatic.com%2Fs2%2Fprofiles%2Fimages%2Fsilhouette80.png", "updated_at": "2022-10-23T04:14:20.799Z", "user_id": "auth0|b1616e22-34af-4e19-812e-6c8c91fe2192", "user_metadata": {}, "app_metadata": {}}, "emitted_at": 1666743597630}} \ No newline at end of file diff --git a/airbyte-integrations/connectors/destination-heap-analytics/setup.py b/airbyte-integrations/connectors/destination-heap-analytics/setup.py new file mode 100644 index 000000000000..fd5c3f35477e --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/setup.py @@ -0,0 +1,28 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.2", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", +] + +setup( + name="destination_heap_analytics", + description="Destination implementation for Heap Analytics.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_client.py b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_client.py new file mode 100644 index 000000000000..60ad79895a23 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_client.py @@ -0,0 +1,54 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from json import load +from typing import Dict + +from destination_heap_analytics.client import HeapClient +from pytest import fixture + + +@fixture(scope="module") +def config_events() -> Dict[str, str]: + with open( + "sample_files/config-events.json", + ) as f: + yield load(f) + + +@fixture(scope="module") +def config_aap() -> Dict[str, str]: + with open( + "sample_files/config-aap.json", + ) as f: + yield load(f) + + +@fixture(scope="module") +def config_aup() -> Dict[str, str]: + with open( + "sample_files/config-aup.json", + ) as f: + yield load(f) + + +class TestHeapClient: + def test_constructor(self, config_events, config_aup, config_aap): + client = HeapClient(**config_events) + assert client.app_id == "11" + assert client.api_type == "track" + assert client.check_endpoint == "https://heapanalytics.com/api/track" + assert client.api_endpoint == "https://heapanalytics.com/api/track" + + client = HeapClient(**config_aup) + assert client.app_id == "11" + assert client.api_type == "add_user_properties" + assert client.check_endpoint == "https://heapanalytics.com/api/track" + assert client.api_endpoint == "https://heapanalytics.com/api/add_user_properties" + + client = HeapClient(**config_aap) + assert client.app_id == "11" + assert client.api_type == "add_account_properties" + assert client.check_endpoint == "https://heapanalytics.com/api/track" + assert client.api_endpoint == "https://heapanalytics.com/api/add_account_properties" diff --git a/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_parse_json.py b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_parse_json.py new file mode 100644 index 000000000000..726367afd294 --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_parse_json.py @@ -0,0 +1,224 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pendulum +from destination_heap_analytics.utils import parse_aap_json, parse_aup_json, parse_event_json, parse_property_json + +user = { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "email_verified": False, + "family_name": "Blanda", + "given_name": "Bradly", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", +} + + +class TestParsePropertyJson: + data = { + "user_id": "4ce74b28-bc00-4bbf-8a01-712dae975291", + "connection": "Username-Password-Authentication", + "provider": "auth0", + "isSocial": False, + } + + def test_parse_all_properties(self): + columns = "*".split(",") + assert parse_property_json(data=self.data, property_columns=columns) == self.data + + def test_parse_selective_properties(self): + columns = "user_id,provider,isSocial".split(",") + assert parse_property_json(data=self.data, property_columns=columns) == { + "user_id": "4ce74b28-bc00-4bbf-8a01-712dae975291", + "provider": "auth0", + "isSocial": False, + } + + def test_parse_missing_properties(self): + columns = "uSeR_iD,identity_provider,isAuthenticated".split(",") + assert parse_property_json(data=self.data, property_columns=columns) == {} + + +class TestParseEventJson: + def test_parse_all_properties(self): + columns = "*".split(",") + assert parse_event_json( + data=user, property_columns=columns, event_column="family_name", identity_column="email", timestamp_column="created_at" + ) == { + "event": "Blanda", + "identity": "beryl_becker95@yahoo.com", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "email_verified": False, + "family_name": "Blanda", + "given_name": "Bradly", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + "timestamp": "2022-10-21T04:08:58.994Z", + } + + def test_parse_selective_properties(self): + columns = "blocked,email,created_at,user_id".split(",") + assert parse_event_json( + data=user, property_columns=columns, event_column="family_name", identity_column="email", timestamp_column="created_at" + ) == { + "event": "Blanda", + "identity": "beryl_becker95@yahoo.com", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + "timestamp": "2022-10-21T04:08:58.994Z", + } + + def test_parse_missing_properties(self): + columns = "uSeR_iD,identity_provider,isAuthenticated".split(",") + assert parse_event_json( + data=user, property_columns=columns, event_column="family_name", identity_column="email", timestamp_column="created_at" + ) == { + "event": "Blanda", + "identity": "beryl_becker95@yahoo.com", + "properties": {}, + "timestamp": "2022-10-21T04:08:58.994Z", + } + + def test_parse_missing_identity(self): + columns = "*".split(",") + assert ( + parse_event_json( + data=user, property_columns=columns, event_column="family_name", identity_column="UsEr_id", timestamp_column="created_at" + ) + is None + ) + + def test_parse_missing_event(self): + columns = "*".split(",") + assert ( + parse_event_json( + data=user, property_columns=columns, event_column="order_name", identity_column="email", timestamp_column="created_at" + ) + is None + ) + + def test_parse_missing_timestamp(self): + known = pendulum.datetime(2023, 5, 21, 12) + pendulum.set_test_now(known) + columns = "*".split(",") + assert parse_event_json( + data=user, property_columns=columns, event_column="family_name", identity_column="email", timestamp_column="updated_at" + ) == { + "event": "Blanda", + "identity": "beryl_becker95@yahoo.com", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "email_verified": False, + "family_name": "Blanda", + "given_name": "Bradly", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + "timestamp": "2023-05-21T12:00:00Z", + } + pendulum.set_test_now() + + +class TestParseAupJson: + def test_parse_all_properties(self): + columns = "*".split(",") + assert parse_aup_json(data=user, property_columns=columns, identity_column="user_id",) == { + "identity": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "email_verified": False, + "family_name": "Blanda", + "given_name": "Bradly", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + } + + def test_parse_selective_properties(self): + columns = "blocked,email,created_at,user_id".split(",") + assert parse_aup_json(data=user, property_columns=columns, identity_column="user_id",) == { + "identity": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + } + + def test_parse_missing_properties(self): + columns = "uSeR_iD,identity_provider,isAuthenticated".split(",") + assert parse_aup_json(data=user, property_columns=columns, identity_column="user_id",) == { + "identity": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + "properties": {}, + } + + def test_parse_missing_account_id(self): + columns = "*".split(",") + assert ( + parse_aup_json( + data=user, + property_columns=columns, + identity_column="UsEr_id", + ) + is None + ) + + +class TestParseAapJson: + def test_parse_all_properties(self): + columns = "*".split(",") + assert parse_aap_json(data=user, property_columns=columns, account_id_column="user_id",) == { + "account_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "email_verified": False, + "family_name": "Blanda", + "given_name": "Bradly", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + } + + def test_parse_selective_properties(self): + columns = "blocked,email,created_at,user_id".split(",") + assert parse_aap_json(data=user, property_columns=columns, account_id_column="user_id",) == { + "account_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + "properties": { + "blocked": False, + "created_at": "2022-10-21T04:08:58.994Z", + "email": "beryl_becker95@yahoo.com", + "user_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + }, + } + + def test_parse_missing_properties(self): + columns = "uSeR_iD,identity_provider,isAuthenticated".split(",") + assert parse_aap_json(data=user, property_columns=columns, account_id_column="user_id",) == { + "account_id": "auth0|4ce74b28-bc00-4bbf-8a01-712dae975291", + "properties": {}, + } + + def test_parse_missing_account_id(self): + columns = "*".split(",") + assert ( + parse_aap_json( + data=user, + property_columns=columns, + account_id_column="UsEr_id", + ) + is None + ) diff --git a/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_utils.py b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_utils.py new file mode 100644 index 000000000000..3b29bcb66ffb --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/test_utils.py @@ -0,0 +1,106 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import pendulum +from destination_heap_analytics.utils import datetime_to_string, flatten_json + + +class TestDatetimeToString: + def test_min_date_time_to_string(self): + assert datetime_to_string(pendulum.DateTime.min) == "0001-01-01T00:00:00Z" + + def test_valid_date_time_to_string(self): + in_utc = pendulum.datetime(2022, 10, 26, 3, 6, 59) + assert datetime_to_string(in_utc) == "2022-10-26T03:06:59Z" + + +class TestFlattenJson: + def test_flatten_none(self): + assert flatten_json({"myUndefined": None}) == {"myUndefined": None} + assert flatten_json({"myNull": None}) == {"myNull": None} + + def test_flatten_number(self): + assert flatten_json({"myNumber": 1}) == {"myNumber": 1} + + def test_flatten_string(self): + assert flatten_json({"myString": "1"}) == {"myString": "1"} + + def test_flatten_boolean(self): + assert flatten_json({"myTrue": True}) == {"myTrue": True} + assert flatten_json({"myFalse": False}) == {"myFalse": False} + + def test_flatten_array_of_nulls(self): + assert flatten_json({"myNulls": [None, 1, None, 3]}) == {"myNulls.0": None, "myNulls.1": 1, "myNulls.2": None, "myNulls.3": 3} + + def test_flatten_array_of_numbers(self): + assert flatten_json({"myNumbers": [1, 2, 3, 4]}) == {"myNumbers.0": 1, "myNumbers.1": 2, "myNumbers.2": 3, "myNumbers.3": 4} + + def test_flatten_array_of_strings(self): + assert flatten_json({"myStrings": ["a", "1", "b", "2"]}) == { + "myStrings.0": "a", + "myStrings.1": "1", + "myStrings.2": "b", + "myStrings.3": "2", + } + + def test_flatten_array_of_booleans(self): + assert flatten_json({"myBools": [True, False, True, False]}) == { + "myBools.0": True, + "myBools.1": False, + "myBools.2": True, + "myBools.3": False, + } + + def test_flatten_a_complex_object(self): + embeded_object = { + "firstName": "John", + "middleName": "", + "lastName": "Green", + "car": { + "make": "Honda", + "model": "Civic", + "year": None, + "revisions": [ + {"miles": 10150, "code": "REV01", "changes": 0, "firstTime": True}, + { + "miles": 20021, + "code": "REV02", + "firstTime": False, + "changes": [ + {"type": "asthetic", "desc": "Left tire cap", "price": 123.45}, + {"type": "mechanic", "desc": "Engine pressure regulator", "engineer": None}, + ], + }, + ], + }, + "visits": [{"date": "2015-01-01", "dealer": "DEAL-001", "useCoupon": True}, {"date": "2015-03-01", "dealer": "DEAL-002"}], + } + assert flatten_json(embeded_object) == ( + { + "car.make": "Honda", + "car.model": "Civic", + "car.revisions.0.changes": 0, + "car.revisions.0.code": "REV01", + "car.revisions.0.miles": 10150, + "car.revisions.0.firstTime": True, + "car.revisions.1.changes.0.desc": "Left tire cap", + "car.revisions.1.changes.0.price": 123.45, + "car.revisions.1.changes.0.type": "asthetic", + "car.revisions.1.changes.1.desc": "Engine pressure regulator", + "car.revisions.1.changes.1.engineer": None, + "car.revisions.1.changes.1.type": "mechanic", + "car.revisions.1.firstTime": False, + "car.revisions.1.code": "REV02", + "car.revisions.1.miles": 20021, + "car.year": None, + "firstName": "John", + "lastName": "Green", + "middleName": "", + "visits.0.date": "2015-01-01", + "visits.0.dealer": "DEAL-001", + "visits.0.useCoupon": True, + "visits.1.date": "2015-03-01", + "visits.1.dealer": "DEAL-002", + } + ) diff --git a/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/unit_test.py b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/unit_test.py new file mode 100644 index 000000000000..dddaea0060fa --- /dev/null +++ b/airbyte-integrations/connectors/destination-heap-analytics/unit_tests/unit_test.py @@ -0,0 +1,7 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +def test_example_method(): + assert True diff --git a/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshKeyMongoDbDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshKeyMongoDbDestinationAcceptanceTest.java index 9a9e5d9889ce..83df20bc4aed 100644 --- a/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshKeyMongoDbDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshKeyMongoDbDestinationAcceptanceTest.java @@ -13,4 +13,5 @@ public class SshKeyMongoDbDestinationAcceptanceTest extends SshMongoDbDestinatio public SshTunnel.TunnelMethod getTunnelMethod() { return TunnelMethod.SSH_KEY_AUTH; } + } diff --git a/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshMongoDbDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshMongoDbDestinationAcceptanceTest.java index 3acae92257cc..9b530b3e2645 100644 --- a/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshMongoDbDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshMongoDbDestinationAcceptanceTest.java @@ -64,9 +64,9 @@ protected JsonNode getFailCheckConfig() throws Exception { @Override protected List retrieveRecords(final TestDestinationEnv testEnv, - final String streamName, - final String namespace, - final JsonNode streamSchema) { + final String streamName, + final String namespace, + final JsonNode streamSchema) { final MongoDatabase database = getMongoDatabase(HostPortResolver.resolveIpAddress(container), container.getExposedPorts().get(0), DATABASE_NAME); final var collection = database.getOrCreateNewCollection(namingResolver.getRawTableName(streamName)); @@ -79,7 +79,6 @@ protected List retrieveRecords(final TestDestinationEnv testEnv, return result; } - @Override protected void tearDown(final TestDestinationEnv testEnv) { container.stop(); diff --git a/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshPasswordMongoDbDestinationAcceptanceTest.java b/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshPasswordMongoDbDestinationAcceptanceTest.java index 379afd47200b..d5cac88b2795 100644 --- a/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshPasswordMongoDbDestinationAcceptanceTest.java +++ b/airbyte-integrations/connectors/destination-mongodb/src/test-integration/java/io/airbyte/integrations/destination/mongodb/SshPasswordMongoDbDestinationAcceptanceTest.java @@ -12,4 +12,5 @@ public class SshPasswordMongoDbDestinationAcceptanceTest extends SshMongoDbDesti public SshTunnel.TunnelMethod getTunnelMethod() { return SshTunnel.TunnelMethod.SSH_PASSWORD_AUTH; } + } diff --git a/airbyte-integrations/connectors/source-auth0/source_auth0/schemas/users.json b/airbyte-integrations/connectors/source-auth0/source_auth0/schemas/users.json index 5f1a13dbd812..ddf342e4c6ac 100644 --- a/airbyte-integrations/connectors/source-auth0/source_auth0/schemas/users.json +++ b/airbyte-integrations/connectors/source-auth0/source_auth0/schemas/users.json @@ -6,13 +6,13 @@ "type": ["object", "null"] }, "user_id": { - "type": [ "string", "null"] + "type": ["string", "null"] }, "username": { - "type": [ "string", "null"] + "type": ["string", "null"] }, "email": { - "type": [ "string", "null"] + "type": ["string", "null"] }, "email_verified": { "type": ["boolean", "null"] diff --git a/airbyte-integrations/connectors/source-auth0/source_auth0/spec.yaml b/airbyte-integrations/connectors/source-auth0/source_auth0/spec.yaml index c697881823b5..4476700ddbce 100644 --- a/airbyte-integrations/connectors/source-auth0/source_auth0/spec.yaml +++ b/airbyte-integrations/connectors/source-auth0/source_auth0/spec.yaml @@ -11,7 +11,7 @@ connectionSpecification: base_url: type: string title: Base URL - examples: + examples: - "https://dev-yourOrg.us.auth0.com/" description: The Authentication API is served over HTTPS. All URLs referenced in the documentation have the following base `https://YOUR_DOMAIN` credentials: @@ -76,4 +76,4 @@ connectionSpecification: href="https://auth0.com/docs/secure/tokens/access-tokens/get-management-api-access-tokens-for-testing">API Access Token The access token used to call the Auth0 Management API Token. It's a JWT that contains specific grant permissions knowns as scopes. type: string - airbyte_secret: true \ No newline at end of file + airbyte_secret: true diff --git a/airbyte-integrations/connectors/source-lever-hiring/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-lever-hiring/integration_tests/sample_config.json index e5ac0a8d0b19..9f5ab92954d3 100644 --- a/airbyte-integrations/connectors/source-lever-hiring/integration_tests/sample_config.json +++ b/airbyte-integrations/connectors/source-lever-hiring/integration_tests/sample_config.json @@ -1,7 +1,8 @@ -{ "credentials" : { +{ + "credentials": { "api_key": "c1adBeHUJKqyPM0X01tshKl4Pwh1LyPdmlorXjfmoDE0lVxZl", "auth_type": "Api Key" - }, + }, "start_date": "2021-07-12T00:00:00Z", "environment": "Sandbox" -} \ No newline at end of file +} diff --git a/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json b/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json index 59b326343ee4..38de89f53345 100644 --- a/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json +++ b/airbyte-integrations/connectors/source-lever-hiring/integration_tests/spec.json @@ -47,19 +47,19 @@ "type": "object", "title": "Authenticate via Lever (Api Key)", "required": ["api_key"], - "properties": { - "auth_type": { - "type": "string", - "const": "Api Key", - "order": 0 - }, - "api_key": { - "title": "Api key", - "type": "string", - "description": "The Api Key of your Lever Hiring account.", - "airbyte_secret": true, - "order":1 - } + "properties": { + "auth_type": { + "type": "string", + "const": "Api Key", + "order": 0 + }, + "api_key": { + "title": "Api key", + "type": "string", + "description": "The Api Key of your Lever Hiring account.", + "airbyte_secret": true, + "order": 1 + } } } ] diff --git a/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json b/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json index 59b326343ee4..38de89f53345 100644 --- a/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json +++ b/airbyte-integrations/connectors/source-lever-hiring/source_lever_hiring/spec.json @@ -47,19 +47,19 @@ "type": "object", "title": "Authenticate via Lever (Api Key)", "required": ["api_key"], - "properties": { - "auth_type": { - "type": "string", - "const": "Api Key", - "order": 0 - }, - "api_key": { - "title": "Api key", - "type": "string", - "description": "The Api Key of your Lever Hiring account.", - "airbyte_secret": true, - "order":1 - } + "properties": { + "auth_type": { + "type": "string", + "const": "Api Key", + "order": 0 + }, + "api_key": { + "title": "Api key", + "type": "string", + "description": "The Api Key of your Lever Hiring account.", + "airbyte_secret": true, + "order": 1 + } } } ] diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py index 38144ceae4c7..db977d9c13a1 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py @@ -460,9 +460,9 @@ def test_forwarding_sobject_options(stream_config, stream_names, catalog_stream_ catalog = ConfiguredAirbyteCatalog( streams=[ ConfiguredAirbyteStream( - stream=AirbyteStream(name=catalog_stream_name, - supported_sync_modes=[SyncMode.full_refresh], - json_schema={"type": "object"}), + stream=AirbyteStream( + name=catalog_stream_name, supported_sync_modes=[SyncMode.full_refresh], json_schema={"type": "object"} + ), sync_mode=SyncMode.full_refresh, destination_sync_mode=DestinationSyncMode.overwrite, ) diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json index 4be548978f15..ef85d31211ac 100644 --- a/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/configured_catalog.json @@ -4,9 +4,7 @@ "stream": { "name": "users", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -15,9 +13,7 @@ "stream": { "name": "meetings", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -26,9 +22,7 @@ "stream": { "name": "meeting_registrants", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -37,9 +31,7 @@ "stream": { "name": "meeting_polls", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -48,9 +40,7 @@ "stream": { "name": "meeting_poll_results", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -59,9 +49,7 @@ "stream": { "name": "meeting_registration_questions", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -70,9 +58,7 @@ "stream": { "name": "webinars", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -81,9 +67,7 @@ "stream": { "name": "webinar_panelists", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -92,9 +76,7 @@ "stream": { "name": "webinar_registrants", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -103,9 +85,7 @@ "stream": { "name": "webinar_absentees", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -114,9 +94,7 @@ "stream": { "name": "webinar_polls", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -125,9 +103,7 @@ "stream": { "name": "webinar_poll_results", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -136,9 +112,7 @@ "stream": { "name": "webinar_registration_questions", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -147,9 +121,7 @@ "stream": { "name": "webinar_tracking_sources", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -158,9 +130,7 @@ "stream": { "name": "webinar_qna_results", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -169,9 +139,7 @@ "stream": { "name": "report_meetings", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -180,9 +148,7 @@ "stream": { "name": "report_meeting_participants", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -191,9 +157,7 @@ "stream": { "name": "report_webinars", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" @@ -202,9 +166,7 @@ "stream": { "name": "report_webinar_participants", "json_schema": {}, - "supported_sync_modes": [ - "full_refresh" - ] + "supported_sync_modes": ["full_refresh"] }, "sync_mode": "full_refresh", "destination_sync_mode": "overwrite" diff --git a/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json b/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json index 0151c6fc660e..0967ef424bce 100644 --- a/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json +++ b/airbyte-integrations/connectors/source-zoom/integration_tests/sample_state.json @@ -1,3 +1 @@ -{ - -} +{} diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json index 989fddd5626f..8b80e1d1c774 100644 --- a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_polls.json @@ -1,96 +1,96 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "id": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "meeting_id": { + "type": "number" + }, + "status": { + "type": "string" + }, + "anonymous": { + "type": "boolean" + }, + "poll_type": { + "type": "number" + }, + "questions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "answer_max_character": { + "type": "number" + }, + "answer_min_character": { + "type": "number" + }, + "answer_required": { + "type": "boolean" + }, + "answers": { + "type": "array", + "items": { + "type": "string" + } + }, + "case_sensitive": { + "type": "boolean" + }, + "name": { "type": "string" - }, - "meeting_id": { + }, + "prompts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "prompt_question": { + "type": "string" + }, + "prompt_right_answers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [] + } + }, + "rating_max_label": { + "type": "string" + }, + "rating_max_value": { "type": "number" - }, - "status": { + }, + "rating_min_label": { "type": "string" - }, - "anonymous": { - "type": "boolean" - }, - "poll_type": { + }, + "rating_min_value": { "type": "number" - }, - "questions": { + }, + "right_answers": { "type": "array", "items": { - "type": "object", - "properties": { - "answer_max_character": { - "type": "number" - }, - "answer_min_character": { - "type": "number" - }, - "answer_required": { - "type": "boolean" - }, - "answers": { - "type": "array", - "items": { - "type": "string" - } - }, - "case_sensitive": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "prompts": { - "type": "array", - "items": { - "type": "object", - "properties": { - "prompt_question": { - "type": "string" - }, - "prompt_right_answers": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [] - } - }, - "rating_max_label": { - "type": "string" - }, - "rating_max_value": { - "type": "number" - }, - "rating_min_label": { - "type": "string" - }, - "rating_min_value": { - "type": "number" - }, - "right_answers": { - "type": "array", - "items": { - "type": "string" - } - }, - "show_as_dropdown": { - "type": "boolean" - }, - "type": { - "type": "string" - } - }, - "required": [] + "type": "string" } - }, - "title": { + }, + "show_as_dropdown": { + "type": "boolean" + }, + "type": { "type": "string" - } + } + }, + "required": [] + } + }, + "title": { + "type": "string" } + } } diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json index c5dc946fb693..b7870278d810 100644 --- a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registrants.json @@ -1,5 +1,5 @@ { - "$schema":"http://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "meeting_id": { "type": "number" @@ -31,8 +31,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] } }, "email": { @@ -81,6 +80,5 @@ "type": "string" } }, - "required": [ - ] + "required": [] } diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json index aa396bf02ef2..0bb4a101de5e 100644 --- a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meeting_registration_questions.json @@ -3,9 +3,7 @@ "type": "object", "properties": { "meeting_id": { - "type": [ - "string" - ] + "type": ["string"] }, "custom_questions": { "type": "array", diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json index 06c7d95dc1f1..81363abcb984 100644 --- a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/meetings.json @@ -52,8 +52,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] } }, "password": { @@ -93,8 +92,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] }, "settings": { "type": "object", @@ -136,8 +134,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] }, "audio": { "type": "string" @@ -160,8 +157,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] } }, "authentication_name": { @@ -194,13 +190,11 @@ } } }, - "required": [ - ] + "required": [] } } }, - "required": [ - ] + "required": [] }, "calendar_type": { "type": "number" @@ -226,8 +220,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] } }, "email_notification": { @@ -266,8 +259,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] } }, "host_video": { @@ -297,13 +289,11 @@ "type": "string" } }, - "required": [ - ] + "required": [] } } }, - "required": [ - ] + "required": [] }, "meeting_authentication": { "type": "boolean" @@ -351,8 +341,7 @@ "type": "number" } }, - "required": [ - ] + "required": [] }, "watermark": { "type": "boolean" @@ -361,8 +350,7 @@ "type": "boolean" } }, - "required": [ - ] + "required": [] }, "start_time": { "type": "string" @@ -394,15 +382,12 @@ "type": "boolean" } }, - "required": [ - ] + "required": [] } }, "type": { "type": "number" } }, - "required": [ - ] - + "required": [] } diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json index 8e2bdef40615..e6936c92b6a1 100644 --- a/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/schemas/users.json @@ -19,8 +19,7 @@ "type": "string" } }, - "required": [ - ] + "required": [] } }, "dept": { @@ -81,6 +80,5 @@ "type": "number" } }, - "required": [ - ] + "required": [] } diff --git a/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml b/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml index 77fae4e527d3..e958ba97fa03 100644 --- a/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml +++ b/airbyte-integrations/connectors/source-zoom/source_zoom/zoom.yaml @@ -23,7 +23,6 @@ definitions: inject_into: "request_parameter" url_base: "*ref(definitions.requester.url_base)" - retriever: requester: $ref: "*ref(definitions.requester)" @@ -31,11 +30,10 @@ definitions: schema_loader: type: JsonSchema file_path: "./source_zoom/schemas/{{ options['name'] }}.json" - users_stream: schema_loader: - $ref: "*ref(definitions.schema_loader)" + $ref: "*ref(definitions.schema_loader)" retriever: paginator: $ref: "*ref(definitions.zoom_paginator)" @@ -49,7 +47,6 @@ definitions: primary_key: "id" path: "/users" - meetings_list_tmp_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -58,7 +55,7 @@ definitions: primary_key: "id" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -74,8 +71,6 @@ definitions: parent_key: "id" stream_slice_field: "parent_id" - - meetings_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -100,7 +95,6 @@ definitions: parent_key: "id" stream_slice_field: "parent_id" - meeting_registrants_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -109,7 +103,7 @@ definitions: primary_key: "id" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -139,7 +133,6 @@ definitions: - path: ["meeting_id"] value: "{{ stream_slice.parent_id }}" - meeting_polls_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -163,7 +156,7 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - - http_codes: [400] + - http_codes: [400] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -178,7 +171,6 @@ definitions: - path: ["meeting_id"] value: "{{ stream_slice.parent_id }}" - meeting_poll_results_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -202,7 +194,7 @@ definitions: response_filters: # 400 error is thrown for meetings created an year ago # 404 error is thrown if the meeting has not enabled polls (from observation, not written in docs) - - http_codes: [400,404] + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -217,7 +209,6 @@ definitions: - path: ["meeting_uuid"] value: "{{ stream_slice.parent_id }}" - meeting_registration_questions_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -229,7 +220,7 @@ definitions: record_selector: extractor: type: DpathExtractor - field_pointer: [] + field_pointer: [] $ref: "*ref(definitions.retriever)" requester: $ref: "*ref(definitions.requester)" @@ -255,7 +246,6 @@ definitions: - path: ["meeting_id"] value: "{{ stream_slice.parent_id }}" - webinars_list_tmp_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -264,7 +254,7 @@ definitions: primary_key: "id" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -312,9 +302,9 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - # When parent stream throws error; then ideally we should have an empty array, and no /webinars/{id} should be called. But somehow we're calling it right now with None. :( - # More context: https://github.com/airbytehq/airbyte/issues/18046 - - http_codes: [400,404] + # When parent stream throws error; then ideally we should have an empty array, and no /webinars/{id} should be called. But somehow we're calling it right now with None. :( + # More context: https://github.com/airbytehq/airbyte/issues/18046 + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -346,8 +336,8 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - # Same problem as "webinars_stream" for 404! and we get 400 error if the account isn't PRO. - - http_codes: [400,404] + # Same problem as "webinars_stream" for 404! and we get 400 error if the account isn't PRO. + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -362,7 +352,6 @@ definitions: - path: ["webinar_id"] value: "{{ stream_slice.parent_id }}" - webinar_registrants_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -370,7 +359,7 @@ definitions: name: "webinar_registrants" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -385,8 +374,8 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. - - http_codes: [400,404] + # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -401,7 +390,6 @@ definitions: - path: ["webinar_id"] value: "{{ stream_slice.parent_id }}" - webinar_absentees_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -410,7 +398,7 @@ definitions: primary_key: "id" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -425,8 +413,8 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. - - http_codes: [400,404] + # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -441,7 +429,6 @@ definitions: - path: ["webinar_uuid"] value: "{{ stream_slice.parent_uuid }}" - webinar_polls_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -464,8 +451,8 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. - - http_codes: [400,404] + # Same problem as "webinars_stream" for 404! 400 is for non PRO accounts. + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -480,8 +467,6 @@ definitions: - path: ["webinar_id"] value: "{{ stream_slice.parent_id }}" - - webinar_poll_results_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -503,7 +488,7 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - - http_codes: [404] + - http_codes: [404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -518,7 +503,6 @@ definitions: - path: ["webinar_uuid"] value: "{{ stream_slice.parent_id }}" - webinar_registration_questions_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -530,7 +514,7 @@ definitions: record_selector: extractor: type: DpathExtractor - field_pointer: [] + field_pointer: [] $ref: "*ref(definitions.retriever)" requester: $ref: "*ref(definitions.requester)" @@ -541,7 +525,7 @@ definitions: - type: DefaultErrorHandler response_filters: # the docs says 404 code, but that's incorrect (from observation); - - http_codes: [400] + - http_codes: [400] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -556,8 +540,6 @@ definitions: - path: ["webinar_id"] value: "{{ stream_slice.parent_id }}" - - webinar_tracking_sources_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -570,7 +552,7 @@ definitions: record_selector: extractor: type: DpathExtractor - field_pointer: ["tracking_sources"] + field_pointer: ["tracking_sources"] $ref: "*ref(definitions.retriever)" requester: $ref: "*ref(definitions.requester)" @@ -595,7 +577,6 @@ definitions: - path: ["webinar_id"] value: "{{ stream_slice.parent_id }}" - webinar_qna_results_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -607,7 +588,7 @@ definitions: record_selector: extractor: type: DpathExtractor - field_pointer: ["questions"] + field_pointer: ["questions"] $ref: "*ref(definitions.retriever)" requester: $ref: "*ref(definitions.requester)" @@ -617,7 +598,7 @@ definitions: error_handlers: - type: DefaultErrorHandler response_filters: - - http_codes: [400,404] + - http_codes: [400, 404] action: IGNORE - type: DefaultErrorHandler stream_slicer: @@ -632,8 +613,6 @@ definitions: - path: ["webinar_uuid"] value: "{{ stream_slice.parent_id }}" - - report_meetings_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -671,10 +650,6 @@ definitions: - path: ["meeting_uuid"] value: "{{ stream_slice.parent_id }}" - - - - report_meeting_participants_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -683,7 +658,7 @@ definitions: primary_key: "id" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -712,7 +687,6 @@ definitions: - path: ["meeting_uuid"] value: "{{ stream_slice.parent_id }}" - report_webinars_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -749,8 +723,6 @@ definitions: - path: ["webinar_uuid"] value: "{{ stream_slice.parent_id }}" - - report_webinar_participants_stream: schema_loader: $ref: "*ref(definitions.schema_loader)" @@ -758,7 +730,7 @@ definitions: name: "report_webinar_participants" retriever: paginator: - $ref: "*ref(definitions.zoom_paginator)" + $ref: "*ref(definitions.zoom_paginator)" record_selector: extractor: type: DpathExtractor @@ -787,7 +759,6 @@ definitions: - path: ["webinar_uuid"] value: "{{ stream_slice.parent_id }}" - streams: - "*ref(definitions.users_stream)" - "*ref(definitions.meetings_stream)" @@ -809,7 +780,6 @@ streams: - "*ref(definitions.report_webinars_stream)" - "*ref(definitions.report_webinar_participants_stream)" - check: stream_names: - "users" diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 77f40b0718f3..e14054264f7a 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -221,6 +221,7 @@ For more information about the grading system, see [Product Release Stages](http | [Google Cloud Storage (GCS)](destinations/gcs.md) | Beta | Yes | | [Google Pubsub](destinations/pubsub.md) | Alpha | Yes | | [Google Sheets](destinations/google-sheets.md) | Alpha | Yes | +| [Heap Analytics](destinations/heap-analytics.md) | Alpha | No | | [Kafka](destinations/kafka.md) | Alpha | No | | [Keen](destinations/keen.md) | Alpha | No | | [Kinesis](destinations/kinesis.md) | Alpha | No | diff --git a/docs/integrations/destinations/heap-analytics.md b/docs/integrations/destinations/heap-analytics.md new file mode 100644 index 000000000000..e7f448f1c29c --- /dev/null +++ b/docs/integrations/destinations/heap-analytics.md @@ -0,0 +1,317 @@ +# Heap Analytics + +[Heap Analytics](https://heap.io) is the only digital insights platform that gives you complete understanding of your customers’ digital journeys, so you can quickly improve conversion, retention, and customer delight. + +Every action a user takes is [autocaptured](https://heap.io/platform/capture). See them live, analyze later, or let our [Illuminate](https://heap.io/platform/illuminate) functionality automatically generate insights. + +The Destination Connector here helps you load data to Heap, so that you could leverage the powerful analytics tool. + +## Prerequisites + +- Heap Analytics Account +- App Id, also called Environment Id + +## Step 1: Set up Heap Analytics + +### Heap Analytics Account + +#### If you don't have an Account + +[Sign up](https://heapanalytics.com/signup) and create your Heap Analytics Account. + +### Understand Projects and Environments in Heap + +Your Heap account is structured into a series of projects and environments. **Projects** in Heap can be thought of as blank slates and are completely independent of another. **Environments** are subsets of projects that share definitions (defined events, segments, and reports), but do not share any data. More info can be found from [this doc](https://help.heap.io/data-management/data-management-features/projects-environments/). + +## Step 2: Prepare the environment Id + +You can get the environment ID from the Heap Analytics dashboard. Choose **Account --> Manage --> Projects**. + +## Step 3: Set up the destination connector in Airbyte + +1. In the left navigation bar, click **Destinations**. In the top-right corner, click **+ new destination**. +2. On the destination setup page, select **Heap Analytics** from the Destination type dropdown and enter a name for this connector. +3. Fill in the environment Id to the field app_id +4. Pick the right API Type, we will cover more details next. + +### API Type Overview + +Airbyte will load data to Heap by the [server-side APIs](https://developers.heap.io/reference/server-side-apis-overview). There are 3 API types + +- [Track Events](https://developers.heap.io/reference/track-1) +- [Add User Properties](https://developers.heap.io/reference/add-user-properties) +- [Add Account Properties](https://developers.heap.io/reference/add-account-properties) + +The destination connector supports all types of schemas of source data. However, each configured catalog, or the connector instance, can load one stream only. +The API type and the configuration determine the output stream. The transformation is run in memory that parses the source data to the schema compatible to the Server-Side API. +Since there are 3 APIs, there are 3 different output schemas. + +## Step 4: Configure the transformation for an API Type + +### Track Events + +Use [this API](https://developers.heap.io/reference/track-1) to send custom events to Heap server-side. + +The following is the sample cURL command: + +```bash +curl \ + -X POST https://heapanalytics.com/api/track\ + -H "Content-Type: application/json" \ + -d '{ + "app_id": "11", + "identity": "alice@example.com", + "event": "Send Transactional Email", + "timestamp": "2017-03-10T22:21:56+00:00", + "properties": { + "subject": "Welcome to My App!", + "variation": "A" + } + }' +``` + +There are 4 properties in the request body. + +- identity: An identity, typically corresponding to an existing user. +- event: The name of the server-side event. +- properties: An object with key-value properties you want associated with the event. +- timestamp: (optional), the datetime in ISO8601. e.g. "2017-03-10T22:21:56+00:00". Defaults to the current time if not provided. + +For `Add User Properties`, You need to configure the following 4 fields in airbyte. + +- Identity Column: The attribute name from the source data populated to identity. +- event_column: The attribute name from the source data populated to event. +- Timestamp Column: The attribute name from the source data populated to timestamp. This field is optional. It will be the current time if not provided. +- Property Columns: The attribute names from the source data populated to object properties. If you want to pick multiple attributes, split the names by comma(`,`). If you want to pick ALL attributes, simply put asterisk(`*`). + +Note that, if you want to reference an attribute name in an object or an embedded object. You can the daisy-chained (`.`) connections. Let's use an example to illustrate it. + +The data source is a json. + +```json +{ + "blocked": false, + "created_at": "2022-10-21T04:09:54.622Z", + "email": "evalyn_shields@hotmail.com", + "email_verified": false, + "family_name": "Brakus", + "given_name": "Camden", + "identities": { + "user_id": "0a12757f-4b19-4e93-969e-c3a2e98fe82b", + "connection": "Username-Password-Authentication", + "provider": "auth0", + "isSocial": false + }, + "name": "Jordan Yost", + "nickname": "Elroy", + "updated_at": "2022-10-21T04:09:54.622Z", + "user_id": "auth0|0a12757f-4b19-4e93-969e-c3a2e98fe82b" +} +``` + +If you configure the connector like this: + +```json +{ + "property_columns": "blocked,created_at,name", + "event_column": "identities.connection", + "identity_column": "email", + "timestamp_column": "updated_at" +} +``` + +The final data will be transformed to + +```json +{ + "identity": "evalyn_shields@hotmail.com", + "event": "Username-Password-Authentication", + "timestamp": "2022-10-21T04:09:54.622Z", + "properties": { + "blocked": false, + "created_at": "2022-10-21T04:09:54.622Z", + "name": "Jordan Yost" + } +} +``` + +### Add User Properties + +[This API](https://developers.heap.io/reference/add-user-properties) allows you to attach custom properties to any identified users from your servers. + +The following is the sample cURL command: + +```bash +curl \ + -X POST https://heapanalytics.com/api/add_user_properties\ + -H "Content-Type: application/json" \ + -d '{ + "app_id": "11", + "identity": "bob@example.com", + "properties": { + "age": "25", + "language": "English", + "profession": "Scientist", + "email": "bob2@example2.com" + } + }' +``` + +There are 2 properties in the request body. + +- identity: An identity, typically corresponding to an existing user. +- properties: An object with key-value properties you want associated with the event. + +For `Add User Properties`, You need to configure the following 2 fields in airbyte. + +- Identity Column: The attribute name from the source data populated to identity. +- property_columns: The attribute names from the source data populated to object properties. If you want to pick multiple attributes, split the names by comma(`,`). If you want to pick ALL attributes, simply put asterisk(`*`). + +Note that, if you want to reference an attribute name in an object or an embedded object. You can the daisy-chained (`.`) connections. Let's use an example to illustrate it. + +The source data is a json + +```json +{ + "blocked": false, + "created_at": "2022-10-21T04:09:59.328Z", + "email": "marielle.murazik8@hotmail.com", + "email_verified": false, + "family_name": "Gutkowski", + "given_name": "Alysha", + "identities": { + "user_id": "26d8952b-2e1e-4b79-b2aa-e363f062701a", + "connection": "Username-Password-Authentication", + "provider": "auth0", + "isSocial": false + }, + "name": "Lynn Crooks", + "nickname": "Noe", + "updated_at": "2022-10-21T04:09:59.328Z", + "user_id": "auth0|26d8952b-2e1e-4b79-b2aa-e363f062701a", +} +``` + +If you configure the connector like this: +```json +{ + "property_columns": "identities_provider,created_at,nickname", + "identity_column": "user_id" +} +``` + +The final data will be transformed to + +```json +{ + "identity": "auth0|26d8952b-2e1e-4b79-b2aa-e363f062701a", + "properties": { + "identities_provider": "auth0", + "created_at": "2022-10-21T04:09:59.328Z", + "nickname": "Neo" + } +} +``` + +### Add Account Properties + +[This API](https://developers.heap.io/reference/add-account-properties) allows you to attach custom account properties to users. + +The following is the sample cURL command: + +```bash +curl \ + -X POST https://heapanalytics.com/api/add_account_properties\ + -H "Content-Type: application/json" \ + -d '{ + "app_id": "123456789", + "account_id": "Fake Company", + "properties": { + "is_in_good_standing": "true", + "revenue_potential": "123456", + "account_hq": "United Kingdom", + "subscription": "Monthly" + } + }' +``` + +There are 2 properties in the request body. + +- account_id: Used for single account updates only. An ID for this account. +- properties: Used for single account updates only. An object with key-value properties you want associated with the account. + +For `Add Account Properties`, you need to configure the following 2 fields in airbyte. + +- Account ID Column: The attribute name from the source data populated to identity. +- Property Columns: The attribute names from the source data populated to object properties. If you want to pick multiple attributes, split the names by comma(`,`). If you want to pick ALL attributes, simply put asterisk(`*`). + +Note that, if you want to reference an attribute name in an object or an embedded object. You can the daisy-chained (`.`) connections. Let's use an example to illustrate it. + +The source data is a json + +```json +{ + "blocked": false, + "created_at": "2022-10-21T04:08:53.393Z", + "email": "nedra14@hotmail.com", + "email_verified": false, + "family_name": "Tillman", + "given_name": "Jacinto", + "identities": { + "user_id": "815ff3c3-84fa-4f63-b959-ac2d11efc63c", + "connection": "Username-Password-Authentication", + "provider": "auth0", + "isSocial": false + }, + "name": "Lola Conn", + "nickname": "Kenyatta", + "updated_at": "2022-10-21T04:08:53.393Z", + "user_id": "auth0|815ff3c3-84fa-4f63-b959-ac2d11efc63c" +} +``` + +If you configure the connector like this: + +```json +{ + "property_columns": "family_name,email_verified,blocked", + "account_id_column": "identities.user_id" +} +``` + +The final data will be transformed to + +```json +{ + "account_id": "815ff3c3-84fa-4f63-b959-ac2d11efc63c", + "properties": { + "family_name": "Tillman", + "email_verified": false, + "blocked": false + } +} +``` + +### Features & Supported sync modes + +| Feature | Supported?\(Yes/No\) | +| :----------------------------- | :------------------- | +| Ful-Refresh Overwrite | Yes | +| Ful-Refresh Append | Yes | +| Incremental Append | Yes | +| Incremental Append-Deduplicate | Yes | + +### Rate Limiting & Performance Considerations +Currently, the API Client for Heap Analytics sends one http request per row of source data. It slows down the performance if you have massive data to load to Heap. There is a bulk API offered under Heap, but it's not implemented in the first version. + +Please kindly provide your feedback, I am happy to cache the transformed data in memory and load them to the bulk API. + +## Future improvements: + +- Implement the [Bulk API](https://developers.heap.io/reference/bulk-track) that loads multiple rows of data to Heap Analytics. + +## Changelog + +| Version | Date | Pull Request | Subject | +| ------- | ---------- | -------------------------------------------------------- | ----------------------------------- | +| 0.1.0 | 2022-10-26 | [18530](https://github.com/airbytehq/airbyte/pull/18530) | Initial Release | From 77223167bd876dfaae2cf9258233220e2463839e Mon Sep 17 00:00:00 2001 From: Serhii Chvaliuk Date: Tue, 1 Nov 2022 15:48:04 +0200 Subject: [PATCH 454/498] Source Salesforce: add get_error_display_message for ConnectionError (#18753) Signed-off-by: Sergey Chvalyuk --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../connectors/source-salesforce/Dockerfile | 2 +- .../integration_tests/bulk_error_test.py | 4 +- .../source_salesforce/source.py | 83 +++++++++---------- .../source_salesforce/streams.py | 5 ++ .../source-salesforce/unit_tests/api_test.py | 4 +- docs/integrations/sources/salesforce.md | 1 + 8 files changed, 54 insertions(+), 49 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index ec5957656d4a..dace40db9293 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -1073,7 +1073,7 @@ - name: Salesforce sourceDefinitionId: b117307c-14b6-41aa-9422-947e34922962 dockerRepository: airbyte/source-salesforce - dockerImageTag: 1.0.22 + dockerImageTag: 1.0.23 documentationUrl: https://docs.airbyte.com/integrations/sources/salesforce icon: salesforce.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 2788eaf7c4c2..9d52680a839b 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -10698,7 +10698,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-salesforce:1.0.22" +- dockerImage: "airbyte/source-salesforce:1.0.23" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/salesforce" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-salesforce/Dockerfile b/airbyte-integrations/connectors/source-salesforce/Dockerfile index 72ddaf32b2d9..567f40689cff 100644 --- a/airbyte-integrations/connectors/source-salesforce/Dockerfile +++ b/airbyte-integrations/connectors/source-salesforce/Dockerfile @@ -13,5 +13,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=1.0.22 +LABEL io.airbyte.version=1.0.23 LABEL io.airbyte.name=airbyte/source-salesforce diff --git a/airbyte-integrations/connectors/source-salesforce/integration_tests/bulk_error_test.py b/airbyte-integrations/connectors/source-salesforce/integration_tests/bulk_error_test.py index bc75b186d4a7..c7755e638aac 100644 --- a/airbyte-integrations/connectors/source-salesforce/integration_tests/bulk_error_test.py +++ b/airbyte-integrations/connectors/source-salesforce/integration_tests/bulk_error_test.py @@ -33,7 +33,9 @@ def get_stream(input_config: Mapping[str, Any], stream_name: str) -> Stream: stream_cls = type("a", (object,), {"name": stream_name}) configured_stream_cls = type("b", (object,), {"stream": stream_cls()}) catalog_cls = type("c", (object,), {"streams": [configured_stream_cls()]}) - return SourceSalesforce().streams(input_config, catalog_cls())[0] + source = SourceSalesforce() + source.catalog = catalog_cls() + return source.streams(input_config)[0] def get_any_real_stream(input_config: Mapping[str, Any]) -> Stream: diff --git a/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py b/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py index 743d8ac8c4e8..753819c56c14 100644 --- a/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py +++ b/airbyte-integrations/connectors/source-salesforce/source_salesforce/source.py @@ -2,23 +2,33 @@ # Copyright (c) 2022 Airbyte, Inc., all rights reserved. # +import logging from typing import Any, Iterator, List, Mapping, MutableMapping, Optional, Tuple, Union import requests from airbyte_cdk import AirbyteLogger -from airbyte_cdk.models import AirbyteMessage, AirbyteStateMessage, ConfiguredAirbyteCatalog +from airbyte_cdk.models import AirbyteMessage, AirbyteStateMessage, ConfiguredAirbyteCatalog, ConfiguredAirbyteStream from airbyte_cdk.sources import AbstractSource from airbyte_cdk.sources.connector_state_manager import ConnectorStateManager from airbyte_cdk.sources.streams import Stream from airbyte_cdk.sources.streams.http.auth import TokenAuthenticator -from airbyte_cdk.sources.utils.schema_helpers import split_config +from airbyte_cdk.sources.utils.schema_helpers import InternalConfig +from airbyte_cdk.utils.traced_exception import AirbyteTracedException from requests import codes, exceptions # type: ignore[import] from .api import UNSUPPORTED_BULK_API_SALESFORCE_OBJECTS, UNSUPPORTED_FILTERING_STREAMS, Salesforce from .streams import BulkIncrementalSalesforceStream, BulkSalesforceStream, Describe, IncrementalSalesforceStream, SalesforceStream +class AirbyteStopSync(AirbyteTracedException): + pass + + class SourceSalesforce(AbstractSource): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.catalog = None + @staticmethod def _get_sf_object(config: Mapping[str, Any]) -> Salesforce: sf = Salesforce(**config) @@ -94,56 +104,41 @@ def generate_streams( return streams - def streams(self, config: Mapping[str, Any], catalog: ConfiguredAirbyteCatalog = None) -> List[Stream]: + def streams(self, config: Mapping[str, Any]) -> List[Stream]: sf = self._get_sf_object(config) - stream_objects = sf.get_validated_streams(config=config, catalog=catalog) + stream_objects = sf.get_validated_streams(config=config, catalog=self.catalog) streams = self.generate_streams(config, stream_objects, sf) - streams.append(Describe(sf_api=sf, catalog=catalog)) + streams.append(Describe(sf_api=sf, catalog=self.catalog)) return streams def read( self, - logger: AirbyteLogger, + logger: logging.Logger, config: Mapping[str, Any], catalog: ConfiguredAirbyteCatalog, state: Union[List[AirbyteStateMessage], MutableMapping[str, Any]] = None, ) -> Iterator[AirbyteMessage]: - """ - Overwritten to dynamically receive only those streams that are necessary for reading for significant speed gains - (Salesforce has a strict API limit on requests). - """ - config, internal_config = split_config(config) - # get the streams once in case the connector needs to make any queries to generate them - logger.info("Starting generating streams") - stream_instances = {s.name: s for s in self.streams(config, catalog=catalog)} - state_manager = ConnectorStateManager(stream_instance_map=stream_instances, state=state) - logger.info(f"Starting syncing {self.name}") - self._stream_to_instance_map = stream_instances - for configured_stream in catalog.streams: - stream_instance = stream_instances.get(configured_stream.stream.name) - if not stream_instance: - raise KeyError( - f"The requested stream {configured_stream.stream.name} was not found in the source. Available streams: {stream_instances.keys()}" - ) - - try: - yield from self._read_stream( - logger=logger, - stream_instance=stream_instance, - configured_stream=configured_stream, - state_manager=state_manager, - internal_config=internal_config, - ) - except exceptions.HTTPError as error: - error_data = error.response.json()[0] - error_code = error_data.get("errorCode") - if error.response.status_code == codes.FORBIDDEN and error_code == "REQUEST_LIMIT_EXCEEDED": - logger.warn(f"API Call limit is exceeded. Error message: '{error_data.get('message')}'") - break # if got 403 rate limit response, finish the sync with success. - raise error - - except Exception as e: - logger.exception(f"Encountered an exception while reading stream {self.name}") - raise e + # save for use inside streams method + self.catalog = catalog + try: + yield from super().read(logger, config, catalog, state) + except AirbyteStopSync: + logger.info(f"Finished syncing {self.name}") - logger.info(f"Finished syncing {self.name}") + def _read_stream( + self, + logger: logging.Logger, + stream_instance: Stream, + configured_stream: ConfiguredAirbyteStream, + state_manager: ConnectorStateManager, + internal_config: InternalConfig, + ) -> Iterator[AirbyteMessage]: + try: + yield from super()._read_stream(logger, stream_instance, configured_stream, state_manager, internal_config) + except exceptions.HTTPError as error: + error_data = error.response.json()[0] + error_code = error_data.get("errorCode") + if error.response.status_code == codes.FORBIDDEN and error_code == "REQUEST_LIMIT_EXCEEDED": + logger.warn(f"API Call limit is exceeded. Error message: '{error_data.get('message')}'") + raise AirbyteStopSync() # if got 403 rate limit response, finish the sync with success. + raise error diff --git a/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py b/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py index 89d24aba86a3..d141ce57a76e 100644 --- a/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py +++ b/airbyte-integrations/connectors/source-salesforce/source_salesforce/streams.py @@ -130,6 +130,11 @@ def read_records( return raise error + def get_error_display_message(self, exception: BaseException) -> Optional[str]: + if isinstance(exception, exceptions.ConnectionError): + return f"After {self.max_retries} retries the connector has failed with a network error. It looks like Salesforce API experienced temporary instability, please try again later." + return super().get_error_display_message(exception) + class BulkSalesforceStream(SalesforceStream): page_size = 15000 diff --git a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py index db977d9c13a1..1a997e00bb10 100644 --- a/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py +++ b/airbyte-integrations/connectors/source-salesforce/unit_tests/api_test.py @@ -498,7 +498,9 @@ def test_forwarding_sobject_options(stream_config, stream_names, catalog_stream_ ], }, ) - streams = SourceSalesforce().streams(config=stream_config, catalog=catalog) + source = SourceSalesforce() + source.catalog = catalog + streams = source.streams(config=stream_config) expected_names = catalog_stream_names if catalog else stream_names assert not set(expected_names).symmetric_difference(set(stream.name for stream in streams)), "doesn't match excepted streams" diff --git a/docs/integrations/sources/salesforce.md b/docs/integrations/sources/salesforce.md index 900b17d4a174..0c51319d0605 100644 --- a/docs/integrations/sources/salesforce.md +++ b/docs/integrations/sources/salesforce.md @@ -121,6 +121,7 @@ Now that you have set up the Salesforce source connector, check out the followin | Version | Date | Pull Request | Subject | | :------ | :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- | +| 1.0.23 | 2022-11-01 | [18753](https://github.com/airbytehq/airbyte/pull/18753) | Add error_display_message for ConnectionError | | 1.0.22 | 2022-10-12 | [17615](https://github.com/airbytehq/airbyte/pull/17615) | Make paging work, if `cursor_field` is not changed inside one page | | 1.0.21 | 2022-10-10 | [17778](https://github.com/airbytehq/airbyte/pull/17778) | Add `EventWhoRelation` to the list of unsupported Bulk API objects. | | 1.0.20 | 2022-09-30 | [17453](https://github.com/airbytehq/airbyte/pull/17453) | Check objects that are not supported by the Bulk API (v52.0) | From 065fef065ca99f2464e0cf56fa879838fa4e4c8c Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Tue, 1 Nov 2022 15:48:33 +0200 Subject: [PATCH 455/498] fix helm release workflow (#18776) --- .github/workflows/publish-helm-charts.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-helm-charts.yml b/.github/workflows/publish-helm-charts.yml index 24a13ed48504..b3afd70f2da9 100644 --- a/.github/workflows/publish-helm-charts.yml +++ b/.github/workflows/publish-helm-charts.yml @@ -109,7 +109,11 @@ jobs: id: changelog run: | cd ./airbyte/ - echo "changelog=$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no)" >> $GITHUB_OUTPUT + changelog=$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no) + changelog="${changelog//'%'/'%25'}" + changelog="${changelog//$'\n'/'%0A'}" + changelog="${changelog//$'\r'/'%0D'}" + echo "${changelog}" >> $GITHUB_OUTPUT - name: Create Pull Request uses: peter-evans/create-pull-request@v4 From 9ab4d07cf3cc8215060f9d1377fbb4e958868121 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 1 Nov 2022 09:56:15 -0400 Subject: [PATCH 456/498] Update to latest Micronaut release (#18774) --- deps.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps.toml b/deps.toml index 81bcd7798f61..4a4e3745e411 100644 --- a/deps.toml +++ b/deps.toml @@ -10,7 +10,7 @@ slf4j = "1.7.36" lombok = "1.18.24" jooq = "3.13.4" junit-jupiter = "5.9.0" -micronaut = "3.7.2" +micronaut = "3.7.3" micronaut-test = "3.7.0" postgresql = "42.3.5" connectors-testcontainers = "1.15.3" @@ -119,7 +119,7 @@ micronaut-jdbc-hikari = { module = "io.micronaut.sql:micronaut-jdbc-hikari" } micronaut-jooq = { module = "io.micronaut.sql:micronaut-jooq" } micronaut-management = { module = "io.micronaut:micronaut-management" } micronaut-runtime = { module = "io.micronaut:micronaut-runtime" } -micronaut-security = { module = "io.micronaut.security:micronaut-security", version = "3.8.0" } +micronaut-security = { module = "io.micronaut.security:micronaut-security", version = "3.8.1" } micronaut-test-core = { module = "io.micronaut.test:micronaut-test-core", version.ref = "micronaut-test" } micronaut-test-junit5 = { module = "io.micronaut.test:micronaut-test-junit5", version.ref = "micronaut-test" } micronaut-validation = { module = "io.micronaut:micronaut-validation" } From 4cb42a9efb168b70476bfd0c872ecba7a290499d Mon Sep 17 00:00:00 2001 From: Kyryl Skobylko Date: Tue, 1 Nov 2022 17:07:39 +0200 Subject: [PATCH 457/498] fix cron resources spec (#18778) --- charts/airbyte-cron/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/airbyte-cron/templates/deployment.yaml b/charts/airbyte-cron/templates/deployment.yaml index ca55dce92fec..5778de3d4dca 100644 --- a/charts/airbyte-cron/templates/deployment.yaml +++ b/charts/airbyte-cron/templates/deployment.yaml @@ -141,5 +141,5 @@ spec: {{- end }} {{- if .Values.resources }} - resources: {{- toYaml .Values.resources | nindent 10 }} + resources: {{- toYaml .Values.resources | nindent 12 }} {{- end }} \ No newline at end of file From 4e12c1399b219b6a8b26ee66c53a4de46a85ff1f Mon Sep 17 00:00:00 2001 From: Volodymyr Pochtar Date: Tue, 1 Nov 2022 17:27:44 +0200 Subject: [PATCH 458/498] ci: use oldgithub actions notation for output in publish-helm-charts worklow (#18780) --- .github/workflows/publish-helm-charts.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/publish-helm-charts.yml b/.github/workflows/publish-helm-charts.yml index b3afd70f2da9..d53feac9adf6 100644 --- a/.github/workflows/publish-helm-charts.yml +++ b/.github/workflows/publish-helm-charts.yml @@ -109,11 +109,7 @@ jobs: id: changelog run: | cd ./airbyte/ - changelog=$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no) - changelog="${changelog//'%'/'%25'}" - changelog="${changelog//$'\n'/'%0A'}" - changelog="${changelog//$'\r'/'%0D'}" - echo "${changelog}" >> $GITHUB_OUTPUT + echo "::set-output name=changelog::$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no)" - name: Create Pull Request uses: peter-evans/create-pull-request@v4 From 385ab7e6d2563c7eb34c3e1ca8d977dfe2d6dd30 Mon Sep 17 00:00:00 2001 From: Ella Rohm-Ensing Date: Tue, 1 Nov 2022 11:37:01 -0400 Subject: [PATCH 459/498] improve connector dependency github action (#18480) * Use more specific variable name * Write affected sources and destinations to files * Use Markdown template to format results * Update workflow to edit existing comment instead of making a new one * Check whether changed_files.txt is empty before proceeding * move template to .github/ * Use different quotes for true * Move logic about continuing back to after dependency report because of ignores * Use correct filename in conditional * Use if to avoid early exit * Create folder for comment templates and use constant for file path * Add handling for non-source non-destination info, pull out markdown listing --- .../connector_dependency_template.md | 25 ++++++++++ .../report-connectors-dependency.yml | 29 ++++++----- tools/bin/ci_check_dependency.py | 48 +++++++++++++++++-- 3 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 .github/comment_templates/connector_dependency_template.md diff --git a/.github/comment_templates/connector_dependency_template.md b/.github/comment_templates/connector_dependency_template.md new file mode 100644 index 000000000000..7fb4b13e95dd --- /dev/null +++ b/.github/comment_templates/connector_dependency_template.md @@ -0,0 +1,25 @@ + + +NOTE ⚠️ Changes in this PR affect the following connectors. Make sure to run corresponding integration tests: + +
    + +Sources ({num_sources}) + + + +{sources} + +
    + +
    + +Destinations ({num_destinations}) + + + +{destinations} + +
    + +{others} diff --git a/.github/workflows/report-connectors-dependency.yml b/.github/workflows/report-connectors-dependency.yml index 99fec32edbc4..046bb3685c2f 100644 --- a/.github/workflows/report-connectors-dependency.yml +++ b/.github/workflows/report-connectors-dependency.yml @@ -11,27 +11,32 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 # OR "2" -> To retrieve the preceding commit. - - name: Extract git-diff changed files run: | git diff --name-only remotes/origin/master...HEAD -- airbyte-integrations/ > ./changed_files.txt - id: extract_changed_files - name: Install dependencies run: | python -m pip install --upgrade pip - name: Create dependency report - run: | - output=$(python ./tools/bin/ci_check_dependency.py ./changed_files.txt) - output="${output//'%'/'%25'}" - output="${output//$'\n'/'%0A'}" - output="${output//$'\r'/'%0D'}" - echo "changed_files_report=$output" >> $GITHUB_OUTPUT id: dependency_report + run: | + python ./tools/bin/ci_check_dependency.py ./changed_files.txt + if [[ -f comment_body.md ]]; then + echo "comment=true" >> $GITHUB_OUTPUT + fi + - name: Find existing comment for connector dependencies + if: steps.dependency_report.outputs.comment == 'true' + uses: peter-evans/find-comment@v2 + id: find_comment + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "report-connectors-dependency.yml" - name: Comment report in PR - if: steps.dependency_report.outputs.changed_files_report != '' + if: steps.dependency_report.outputs.comment == 'true' uses: peter-evans/create-or-update-comment@v2 with: issue-number: ${{ github.event.pull_request.number }} - body: | - **NOTE** :warning: Changes in this PR affect the following connectors. Make sure to run corresponding integration tests: - ${{steps.dependency_report.outputs.changed_files_report}} + comment-id: ${{ steps.find_comment.outputs.comment-id }} + edit-mode: "replace" + body-file: "comment_body.md" diff --git a/tools/bin/ci_check_dependency.py b/tools/bin/ci_check_dependency.py index df983e65c311..6462af3de8c1 100644 --- a/tools/bin/ci_check_dependency.py +++ b/tools/bin/ci_check_dependency.py @@ -11,6 +11,7 @@ "/integration_tests/", "/unit_tests/", # Common "acceptance-test-config.yml", "acceptance-test-docker.sh", ".md", ".dockerignore", ".gitignore", "requirements.txt"] +COMMENT_TEMPLATE_PATH = ".github/comment_templates/connector_dependency_template.md" def main(): @@ -32,7 +33,10 @@ def main(): # Try to find dependency in build.gradle file depended_connectors = list(set(get_depended_connectors(changed_modules, all_build_gradle_files))) - write_report(depended_connectors) + + # Create comment body to post on pull request + if depended_connectors: + write_report(depended_connectors) def get_changed_files(path): @@ -73,19 +77,53 @@ def find_file(name, path): return os.path.join(root, name) -def get_depended_connectors(changed_connectors, all_build_gradle_files): +def get_depended_connectors(changed_modules, all_build_gradle_files): depended_connectors = [] - for changed_connector in changed_connectors: + for changed_module in changed_modules: for connector, gradle_file in all_build_gradle_files.items(): with open(gradle_file) as file: - if changed_connector in file.read(): + if changed_module in file.read(): depended_connectors.append(connector) return depended_connectors +def as_bulleted_markdown_list(items): + text = "" + for item in items: + text += f"- {item}\n" + return text + + def write_report(depended_connectors): + affected_sources = [] + affected_destinations = [] + affected_others = [] for depended_connector in depended_connectors: - print("- " + depended_connector) + if depended_connector.startswith("source"): + affected_sources.append(depended_connector) + elif depended_connector.startswith("destination"): + affected_destinations.append(depended_connector) + else: + affected_others.append(depended_connector) + + with open(COMMENT_TEMPLATE_PATH, "r") as f: + template = f.read() + + others_md = "" + if affected_others: + others_md += "The following were also affected:\n" + others_md += as_bulleted_markdown_list(affected_others) + + comment = template.format( + sources=as_bulleted_markdown_list(affected_sources), + destinations=as_bulleted_markdown_list(affected_destinations), + others=others_md, + num_sources=len(affected_sources), + num_destinations=len(affected_destinations) + ) + + with open("comment_body.md", "w") as f: + f.write(comment) if __name__ == "__main__": From 8cf546483d8097839a3dcf0cea38e86a2b5553c6 Mon Sep 17 00:00:00 2001 From: Greg Solovyev Date: Tue, 1 Nov 2022 05:52:02 -1000 Subject: [PATCH 460/498] =?UTF-8?q?=F0=9F=90=9B=20Add=20a=20drop=20table?= =?UTF-8?q?=20hook=20to=20drop=20scd=20tables=20in=20case=20of=20overwrite?= =?UTF-8?q?=20sync=20(#18015)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a drop table hook to drop scd tables in case of overwrite sync * Add an integration test for dropping SCD table on overwrite * skip new test for Oracle and TiDB * Add normalization run after initial reset * Bump normalization version --- .../bases/base-normalization/Dockerfile | 2 +- .../integration_tests/dbt_integration_test.py | 2 +- .../data_input/test_drop_scd_catalog.json | 46 +++++ .../test_drop_scd_catalog_incremental.json | 46 +++++ .../test_drop_scd_catalog_reset.json | 46 +++++ .../data_input/test_drop_scd_messages.txt | 5 + .../test_scd_reset_messages_incremental.txt | 6 + .../dbt_data_tests/test_check_row_counts.sql | 2 + .../test_drop_scd_overwrite.py | 161 ++++++++++++++++++ .../integration_tests/test_ephemeral.py | 29 +--- .../integration_tests/test_normalization.py | 57 ++----- .../integration_tests/utils.py | 87 ++++++++++ .../transform_catalog/stream_processor.py | 35 +++- 13 files changed, 446 insertions(+), 78 deletions(-) create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog.json create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_incremental.json create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_reset.json create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_messages.txt create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_scd_reset_messages_incremental.txt create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/dbt_test_config/dbt_data_tests/test_check_row_counts.sql create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/test_drop_scd_overwrite.py create mode 100644 airbyte-integrations/bases/base-normalization/integration_tests/utils.py diff --git a/airbyte-integrations/bases/base-normalization/Dockerfile b/airbyte-integrations/bases/base-normalization/Dockerfile index 8e375a1b8de3..c30c4e43478f 100644 --- a/airbyte-integrations/bases/base-normalization/Dockerfile +++ b/airbyte-integrations/bases/base-normalization/Dockerfile @@ -28,5 +28,5 @@ WORKDIR /airbyte ENV AIRBYTE_ENTRYPOINT "/airbyte/entrypoint.sh" ENTRYPOINT ["/airbyte/entrypoint.sh"] -LABEL io.airbyte.version=0.2.23 +LABEL io.airbyte.version=0.2.24 LABEL io.airbyte.name=airbyte/normalization diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/dbt_integration_test.py b/airbyte-integrations/bases/base-normalization/integration_tests/dbt_integration_test.py index 80726e45e779..7cb25ea39ad9 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/dbt_integration_test.py +++ b/airbyte-integrations/bases/base-normalization/integration_tests/dbt_integration_test.py @@ -690,7 +690,7 @@ def clean_tmp_tables( schemas_to_remove[destination.value] = [] # based on test_type select path to source files - if test_type == "ephemeral": + if test_type == "ephemeral" or test_type == "test_reset_scd_overwrite": if not tmp_folders: raise TypeError("`tmp_folders` arg is not provided.") for folder in tmp_folders: diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog.json b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog.json new file mode 100644 index 000000000000..37d6c7d9a939 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog.json @@ -0,0 +1,46 @@ +{ + "streams": [ + { + "stream": { + "name": "stream_test_scd_drop", + "json_schema": { + "type": ["null", "object"], + "properties": { + "id": { + "type": "integer" + }, + "date": { + "type": "string", + "format": "date" + }, + "timestamp_col": { + "type": "string", + "format": "date-time" + }, + "datetime_to_string": { + "type": "string", + "format": "date-time", + "airbyte_type": "timestamp_with_timezone" + }, + "string_to_dt": { + "type": "string" + }, + "number_to_int": { + "type": "number" + }, + "int_to_number": { + "type": "integer" + } + } + }, + "supported_sync_modes": ["incremental"], + "source_defined_cursor": true, + "default_cursor_field": [] + }, + "sync_mode": "incremental", + "cursor_field": ["date"], + "destination_sync_mode": "append_dedup", + "primary_key": [["id"]] + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_incremental.json b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_incremental.json new file mode 100644 index 000000000000..04b78b4b435f --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_incremental.json @@ -0,0 +1,46 @@ +{ + "streams": [ + { + "stream": { + "name": "stream_test_scd_drop", + "json_schema": { + "type": ["null", "object"], + "properties": { + "id": { + "type": "integer" + }, + "date": { + "type": "string", + "format": "date" + }, + "timestamp_col": { + "type": "string", + "format": "date-time" + }, + "datetime_to_string": { + "type": "string" + }, + "string_to_dt": { + "type": "string", + "format": "date-time", + "airbyte_type": "timestamp_with_timezone" + }, + "number_to_int": { + "type": "integer" + }, + "int_to_number": { + "type": "number" + } + } + }, + "supported_sync_modes": ["incremental"], + "source_defined_cursor": true, + "default_cursor_field": [] + }, + "sync_mode": "incremental", + "cursor_field": ["date"], + "destination_sync_mode": "append_dedup", + "primary_key": [["id"]] + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_reset.json b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_reset.json new file mode 100644 index 000000000000..9a76b76cda8b --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_catalog_reset.json @@ -0,0 +1,46 @@ +{ + "streams": [ + { + "stream": { + "name": "stream_test_scd_drop", + "json_schema": { + "type": ["null", "object"], + "properties": { + "id": { + "type": "integer" + }, + "date": { + "type": "string", + "format": "date" + }, + "timestamp_col": { + "type": "string", + "format": "date-time" + }, + "datetime_to_string": { + "type": "string" + }, + "string_to_dt": { + "type": "string", + "format": "date-time", + "airbyte_type": "timestamp_with_timezone" + }, + "number_to_int": { + "type": "integer" + }, + "int_to_number": { + "type": "number" + } + } + }, + "supported_sync_modes": ["incremental"], + "source_defined_cursor": true, + "default_cursor_field": [] + }, + "sync_mode": "incremental", + "cursor_field": ["date"], + "destination_sync_mode": "overwrite", + "primary_key": [["id"]] + } + ] +} diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_messages.txt b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_messages.txt new file mode 100644 index 000000000000..e35685cb629a --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_drop_scd_messages.txt @@ -0,0 +1,5 @@ +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637589000, "data": { "id": 1, "date": "2022-08-29", "timestamp_col": "2020-08-29T00:00:00.000000-0000", "datetime_to_string":"2022-10-01T01:04:04-04:00", "string_to_dt":"2022-11-01T02:03:04-07:00", "number_to_int": 1, "int_to_number": 10}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637689100, "data": { "id": 2, "date": "2022-08-30", "timestamp_col": "2020-08-30T00:00:00.000-00", "datetime_to_string":"2022-10-02T01:04:04-04:00", "string_to_dt":"2022-11-02T03:04:05-07:00", "number_to_int": 10, "int_to_number": 11}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637789200, "data": { "id": 3, "date": "2022-08-31", "timestamp_col": "2020-08-31T00:00:00+00", "datetime_to_string":"2022-10-03T01:04:04-04:00", "string_to_dt":"2022-11-03T03:04:06-07:00", "number_to_int": 11, "int_to_number": 12}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637889300, "data": { "id": 4, "date": "2022-09-01", "timestamp_col": "2020-08-31T00:00:00+0000", "datetime_to_string":"2022-10-04T01:04:04-04:00", "string_to_dt":"2022-11-04T03:04:07-07:00", "number_to_int": 111, "int_to_number": 133}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637989400, "data": { "id": 5, "date": "2022-09-02", "timestamp_col": "2020-09-01T00:00:00Z", "datetime_to_string":"2022-10-05T01:04:04-04:00", "string_to_dt":"2022-11-05T03:04:08-12:00", "number_to_int": 1010, "int_to_number": 1300}}} diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_scd_reset_messages_incremental.txt b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_scd_reset_messages_incremental.txt new file mode 100644 index 000000000000..492efbaea0ae --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/data_input/test_scd_reset_messages_incremental.txt @@ -0,0 +1,6 @@ +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637589000, "data": { "id": 1, "date": "2022-08-29", "timestamp_col": "2020-08-29T00:00:00.000000-0000", "datetime_to_string":"2022-10-01T01:04:04-04:00", "string_to_dt":"2022-11-01T02:03:04-07:00", "number_to_int": 1, "int_to_number": 10}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637689100, "data": { "id": 2, "date": "2022-08-30", "timestamp_col": "2020-08-30T00:00:00.000-00", "datetime_to_string":"2022-10-02T01:04:04-04:00", "string_to_dt":"2022-11-02T03:04:05-07:00", "number_to_int": 10, "int_to_number": 11}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637789200, "data": { "id": 3, "date": "2022-08-31", "timestamp_col": "2020-08-31T00:00:00+00", "datetime_to_string":"2022-10-03T01:04:04-04:00", "string_to_dt":"2022-11-03T03:04:06-07:00", "number_to_int": 11, "int_to_number": 12}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637889300, "data": { "id": 4, "date": "2022-09-01", "timestamp_col": "2020-08-31T00:00:00+0000", "datetime_to_string":"2022-10-04T01:04:04-04:00", "string_to_dt":"2022-11-04T03:04:07-07:00", "number_to_int": 111, "int_to_number": 133}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637989400, "data": { "id": 5, "date": "2022-09-02", "timestamp_col": "2020-09-01T00:00:00Z", "datetime_to_string":"2022-10-05T01:04:04-04:00", "string_to_dt":"2022-11-05T03:04:08-12:00", "number_to_int": 1010, "int_to_number": 1300}}} +{"type": "RECORD", "record": {"stream": "stream_test_scd_drop", "emitted_at": 1602637989400, "data": { "id": 6, "date": "2022-09-03", "timestamp_col": "2020-09-01T00:00:00Z", "datetime_to_string":"this is a string, not a datetime value", "string_to_dt":"2022-11-05T03:04:08-12:00", "number_to_int": 1010, "int_to_number": 1300.25}}} diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/dbt_test_config/dbt_data_tests/test_check_row_counts.sql b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/dbt_test_config/dbt_data_tests/test_check_row_counts.sql new file mode 100644 index 000000000000..5b8755db9ec6 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/resources/test_reset_scd_overwrite/dbt_test_config/dbt_data_tests/test_check_row_counts.sql @@ -0,0 +1,2 @@ +select * from {{ ref('test_scd_drop_row_counts') }} +where row_count != expected_count diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/test_drop_scd_overwrite.py b/airbyte-integrations/bases/base-normalization/integration_tests/test_drop_scd_overwrite.py new file mode 100644 index 000000000000..4f082f528c05 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/test_drop_scd_overwrite.py @@ -0,0 +1,161 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import json +import os +import pathlib +import shutil + +import pytest +from integration_tests.dbt_integration_test import DbtIntegrationTest +from integration_tests.utils import generate_dbt_models, run_destination_process, setup_test_dir +from normalization import DestinationType + +temporary_folders = set() +dbt_test_utils = DbtIntegrationTest() + + +@pytest.fixture(scope="module", autouse=True) +def before_all_tests(request): + destinations_to_test = dbt_test_utils.get_test_targets() + # set clean-up args to clean target destination after the test + clean_up_args = { + "destination_type": [d for d in DestinationType if d.value in destinations_to_test], + "test_type": "test_reset_scd_overwrite", + "tmp_folders": temporary_folders, + } + dbt_test_utils.set_target_schema("test_reset_scd_overwrite") + dbt_test_utils.change_current_test_dir(request) + dbt_test_utils.setup_db(destinations_to_test) + os.environ["PATH"] = os.path.abspath("../.venv/bin/") + ":" + os.environ["PATH"] + yield + dbt_test_utils.clean_tmp_tables(**clean_up_args) + dbt_test_utils.tear_down_db() + for folder in temporary_folders: + print(f"Deleting temporary test folder {folder}") + shutil.rmtree(folder, ignore_errors=True) + + +@pytest.fixture +def setup_test_path(request): + dbt_test_utils.change_current_test_dir(request) + print(f"Running from: {pathlib.Path().absolute()}") + print(f"Current PATH is: {os.environ['PATH']}") + yield + os.chdir(request.config.invocation_dir) + + +@pytest.mark.parametrize("destination_type", list(DestinationType)) +def test_reset_scd_on_overwrite(destination_type: DestinationType, setup_test_path): + if destination_type.value not in dbt_test_utils.get_test_targets(): + pytest.skip(f"Destinations {destination_type} is not in NORMALIZATION_TEST_TARGET env variable") + + if destination_type.value in [DestinationType.ORACLE.value, DestinationType.TIDB.value]: + # Oracle and TiDB do not support incremental syncs with schema changes yet + pytest.skip(f"{destination_type} does not support incremental sync with schema change yet") + elif destination_type.value == DestinationType.REDSHIFT.value: + # set unique schema for Redshift test + dbt_test_utils.set_target_schema(dbt_test_utils.generate_random_string("test_reset_scd_")) + + test_resource_name = "test_reset_scd_overwrite" + # Select target schema + target_schema = dbt_test_utils.target_schema + + try: + print(f"Testing resetting SCD tables on overwrite with {destination_type} in schema {target_schema}") + run_reset_scd_on_overwrite_test(destination_type, test_resource_name) + finally: + dbt_test_utils.set_target_schema(target_schema) + + +def run_reset_scd_on_overwrite_test(destination_type: DestinationType, test_resource_name: str): + # Generate DBT profile yaml + integration_type = destination_type.value + test_root_dir = setup_test_dir(integration_type, temporary_folders) + destination_config = dbt_test_utils.generate_profile_yaml_file(destination_type, test_root_dir) + test_directory = os.path.join(test_root_dir, "models/generated") + shutil.rmtree(test_directory, ignore_errors=True) + + # Generate config file for the destination + config_file = os.path.join(test_root_dir, "destination_config.json") + with open(config_file, "w") as f: + f.write(json.dumps(destination_config)) + + # make sure DBT dependencies are installed + dbt_test_utils.dbt_check(destination_type, test_root_dir) + + # Generate catalog for an initial reset/cleanup (pre-test) + original_catalog_file = os.path.join("resources", test_resource_name, "data_input", "test_drop_scd_catalog.json") + dbt_test_utils.copy_replace( + original_catalog_file, + os.path.join(test_root_dir, "initial_reset_catalog.json"), + pattern='"destination_sync_mode": ".*"', + replace_value='"destination_sync_mode": "overwrite"', + ) + + # Force a reset in destination raw tables to remove any data left over from previous test runs + assert run_destination_process(destination_type, test_root_dir, "", "initial_reset_catalog.json", dbt_test_utils) + # generate models from catalog + generate_dbt_models(destination_type, test_resource_name, test_root_dir, "models", "test_drop_scd_catalog_reset.json", dbt_test_utils) + + # Run dbt process to normalize data from the first sync + dbt_test_utils.dbt_run(destination_type, test_root_dir, force_full_refresh=True) + + # Remove models generated in previous step to avoid DBT compilation errors + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_incremental") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_views") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_ctes") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_tables") + shutil.rmtree(test_directory, ignore_errors=True) + + # Run the first sync to create raw tables in destinations + dbt_test_utils.copy_replace(original_catalog_file, os.path.join(test_root_dir, "destination_catalog.json")) + message_file = os.path.join("resources", test_resource_name, "data_input", "test_drop_scd_messages.txt") + assert run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json", dbt_test_utils) + + # generate models from catalog + generate_dbt_models(destination_type, test_resource_name, test_root_dir, "models", "test_drop_scd_catalog.json", dbt_test_utils) + + # Run dbt process to normalize data from the first sync + dbt_test_utils.dbt_run(destination_type, test_root_dir, force_full_refresh=True) + + # Remove models generated in previous step to avoid DBT compilation errors + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_incremental") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_views") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_ctes") + shutil.rmtree(test_directory, ignore_errors=True) + + # Generate a catalog with modified schema for a reset + reset_catalog_file = os.path.join("resources", test_resource_name, "data_input", "test_drop_scd_catalog_reset.json") + dbt_test_utils.copy_replace(reset_catalog_file, os.path.join(test_root_dir, "reset_catalog.json")) + + # Run a reset + assert run_destination_process(destination_type, test_root_dir, "", "reset_catalog.json", dbt_test_utils) + + # Run dbt process after reset to drop SCD table + generate_dbt_models(destination_type, test_resource_name, test_root_dir, "models", "test_drop_scd_catalog_reset.json", dbt_test_utils) + dbt_test_utils.dbt_run(destination_type, test_root_dir, force_full_refresh=True) + + # Remove models generated in previous step to avoid DBT compilation errors + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_incremental") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_views") + shutil.rmtree(test_directory, ignore_errors=True) + test_directory = os.path.join(test_root_dir, "models/generated/airbyte_ctes") + shutil.rmtree(test_directory, ignore_errors=True) + + # Run another sync with modified catalog + modified_catalog_file = os.path.join("resources", test_resource_name, "data_input", "test_drop_scd_catalog_incremental.json") + dbt_test_utils.copy_replace(modified_catalog_file, os.path.join(test_root_dir, "destination_catalog.json")) + message_file = os.path.join("resources", test_resource_name, "data_input", "test_scd_reset_messages_incremental.txt") + assert run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json", dbt_test_utils) + + # Run dbt process + generate_dbt_models(destination_type, test_resource_name, test_root_dir, "models", "test_drop_scd_catalog_reset.json", dbt_test_utils) + dbt_test_utils.dbt_run(destination_type, test_root_dir) diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py b/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py index 455a81064819..59ec20fc7a36 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py +++ b/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py @@ -7,12 +7,11 @@ import pathlib import re import shutil -import tempfile -from distutils.dir_util import copy_tree from typing import Any, Dict import pytest from integration_tests.dbt_integration_test import DbtIntegrationTest +from integration_tests.utils import setup_test_dir from normalization.destination_type import DestinationType from normalization.transform_catalog import TransformCatalog @@ -108,7 +107,7 @@ def run_test(destination_type: DestinationType, column_count: int, expected_exce print("Testing ephemeral") integration_type = destination_type.value # Create the test folder with dbt project and appropriate destination settings to run integration tests from - test_root_dir = setup_test_dir(integration_type) + test_root_dir = setup_test_dir(integration_type, temporary_folders) destination_config = dbt_test_utils.generate_profile_yaml_file(destination_type, test_root_dir) # generate a catalog and associated dbt models files generate_dbt_models(destination_type, test_root_dir, column_count) @@ -131,30 +130,6 @@ def search_logs_for_pattern(log_file: str, pattern: str): return False -def setup_test_dir(integration_type: str) -> str: - """ - We prepare a clean folder to run the tests from. - """ - test_root_dir = f"{pathlib.Path().joinpath('..', 'build', 'normalization_test_output', integration_type.lower()).resolve()}" - os.makedirs(test_root_dir, exist_ok=True) - test_root_dir = tempfile.mkdtemp(dir=test_root_dir) - temporary_folders.add(test_root_dir) - shutil.rmtree(test_root_dir, ignore_errors=True) - print(f"Setting up test folder {test_root_dir}") - copy_tree("../dbt-project-template", test_root_dir) - if integration_type == DestinationType.MSSQL.value: - copy_tree("../dbt-project-template-mssql", test_root_dir) - elif integration_type == DestinationType.MYSQL.value: - copy_tree("../dbt-project-template-mysql", test_root_dir) - elif integration_type == DestinationType.ORACLE.value: - copy_tree("../dbt-project-template-oracle", test_root_dir) - elif integration_type == DestinationType.SNOWFLAKE.value: - copy_tree("../dbt-project-template-snowflake", test_root_dir) - elif integration_type == DestinationType.TIDB.value: - copy_tree("../dbt-project-template-tidb", test_root_dir) - return test_root_dir - - def setup_input_raw_data(integration_type: str, test_root_dir: str, destination_config: Dict[str, Any]) -> bool: """ This should populate the associated "raw" tables from which normalization is reading from when running dbt CLI. diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/test_normalization.py b/airbyte-integrations/bases/base-normalization/integration_tests/test_normalization.py index bd8b8a7ee167..0163cd128151 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/test_normalization.py +++ b/airbyte-integrations/bases/base-normalization/integration_tests/test_normalization.py @@ -13,6 +13,7 @@ import pytest from integration_tests.dbt_integration_test import DbtIntegrationTest +from integration_tests.utils import generate_dbt_models, run_destination_process from normalization.destination_type import DestinationType from normalization.transform_catalog import TransformCatalog @@ -109,7 +110,7 @@ def run_first_normalization(destination_type: DestinationType, test_resource_nam # Use destination connector to create _airbyte_raw_* tables to use as input for the test assert setup_input_raw_data(destination_type, test_resource_name, test_root_dir, destination_config) # generate models from catalog - generate_dbt_models(destination_type, test_resource_name, test_root_dir, "models", "catalog.json") + generate_dbt_models(destination_type, test_resource_name, test_root_dir, "models", "catalog.json", dbt_test_utils) # Setup test resources and models setup_dbt_test(destination_type, test_resource_name, test_root_dir) dbt_test_utils.dbt_check(destination_type, test_root_dir) @@ -146,7 +147,9 @@ def run_schema_change_normalization(destination_type: DestinationType, test_reso pytest.skip(f"{destination_type} is disabled as it doesnt fully support schema change in incremental yet") setup_schema_change_data(destination_type, test_resource_name, test_root_dir) - generate_dbt_models(destination_type, test_resource_name, test_root_dir, "modified_models", "catalog_schema_change.json") + generate_dbt_models( + destination_type, test_resource_name, test_root_dir, "modified_models", "catalog_schema_change.json", dbt_test_utils + ) setup_dbt_schema_change_test(destination_type, test_resource_name, test_root_dir) dbt_test_utils.dbt_run(destination_type, test_root_dir) normalize_dbt_output(test_root_dir, "build/run/airbyte_utils/modified_models/generated/", "third_output") @@ -234,17 +237,17 @@ def setup_input_raw_data( with open(config_file, "w") as f: f.write(json.dumps(destination_config)) # Force a reset in destination raw tables - assert run_destination_process(destination_type, test_root_dir, "", "reset_catalog.json") + assert run_destination_process(destination_type, test_root_dir, "", "reset_catalog.json", dbt_test_utils) # Run a sync to create raw tables in destinations - return run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json") + return run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json", dbt_test_utils) def setup_incremental_data(destination_type: DestinationType, test_resource_name: str, test_root_dir: str) -> bool: message_file = os.path.join("resources", test_resource_name, "data_input", "messages_incremental.txt") # Force a reset in destination raw tables - assert run_destination_process(destination_type, test_root_dir, "", "reset_catalog.json") + assert run_destination_process(destination_type, test_root_dir, "", "reset_catalog.json", dbt_test_utils) # Run a sync to create raw tables in destinations - return run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json") + return run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json", dbt_test_utils) def setup_schema_change_data(destination_type: DestinationType, test_resource_name: str, test_root_dir: str) -> bool: @@ -270,43 +273,7 @@ def update(config_yaml): dbt_test_utils.update_yaml_file(os.path.join(test_root_dir, "dbt_project.yml"), update) # Run a sync to update raw tables in destinations - return run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json") - - -def run_destination_process(destination_type: DestinationType, test_root_dir: str, message_file: str, catalog_file: str, docker_tag="dev"): - commands = [ - "docker", - "run", - "--rm", - "--init", - "-v", - f"{test_root_dir}:/data", - "--network", - "host", - "-i", - f"airbyte/destination-{destination_type.value.lower()}:{docker_tag}", - "write", - "--config", - "/data/destination_config.json", - "--catalog", - ] - return dbt_test_utils.run_destination_process(message_file, test_root_dir, commands + [f"/data/{catalog_file}"]) - - -def generate_dbt_models(destination_type: DestinationType, test_resource_name: str, test_root_dir: str, output_dir: str, catalog_file: str): - """ - This is the normalization step generating dbt models files from the destination_catalog.json taken as input. - """ - transform_catalog = TransformCatalog() - transform_catalog.config = { - "integration_type": destination_type.value, - "schema": dbt_test_utils.target_schema, - "catalog": [os.path.join("resources", test_resource_name, "data_input", catalog_file)], - "output_path": os.path.join(test_root_dir, output_dir, "generated"), - "json_column": "_airbyte_data", - "profile_config_dir": test_root_dir, - } - transform_catalog.process_catalog() + return run_destination_process(destination_type, test_root_dir, message_file, "destination_catalog.json", dbt_test_utils) def setup_dbt_test(destination_type: DestinationType, test_resource_name: str, test_root_dir: str): @@ -546,10 +513,10 @@ def test_redshift_normalization_migration(tmp_path, setup_test_path): } transform_catalog.process_catalog() - run_destination_process(destination_type, tmp_path, messages_file1, "destination_catalog.json", docker_tag="0.3.29") + run_destination_process(destination_type, tmp_path, messages_file1, "destination_catalog.json", dbt_test_utils, docker_tag="0.3.29") dbt_test_utils.dbt_check(destination_type, tmp_path) dbt_test_utils.dbt_run(destination_type, tmp_path, force_full_refresh=True) - run_destination_process(destination_type, tmp_path, messages_file2, "destination_catalog.json", docker_tag="dev") + run_destination_process(destination_type, tmp_path, messages_file2, "destination_catalog.json", dbt_test_utils, docker_tag="dev") dbt_test_utils.dbt_run(destination_type, tmp_path, force_full_refresh=False) dbt_test(destination_type, tmp_path) # clean-up test tables created for this test diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/utils.py b/airbyte-integrations/bases/base-normalization/integration_tests/utils.py new file mode 100644 index 000000000000..87e3b895d224 --- /dev/null +++ b/airbyte-integrations/bases/base-normalization/integration_tests/utils.py @@ -0,0 +1,87 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import os +import pathlib +import shutil +import tempfile +from distutils.dir_util import copy_tree + +from integration_tests.dbt_integration_test import DbtIntegrationTest +from normalization import DestinationType, TransformCatalog + + +def setup_test_dir(integration_type: str, temporary_folders: set) -> str: + """ + We prepare a clean folder to run the tests from. + """ + test_root_dir = f"{pathlib.Path().joinpath('..', 'build', 'normalization_test_output', integration_type.lower()).resolve()}" + os.makedirs(test_root_dir, exist_ok=True) + test_root_dir = tempfile.mkdtemp(dir=test_root_dir) + temporary_folders.add(test_root_dir) + shutil.rmtree(test_root_dir, ignore_errors=True) + current_path = os.getcwd() + print(f"Setting up test folder {test_root_dir}. Current path {current_path}") + copy_tree("../dbt-project-template", test_root_dir) + if integration_type == DestinationType.MSSQL.value: + copy_tree("../dbt-project-template-mssql", test_root_dir) + elif integration_type == DestinationType.MYSQL.value: + copy_tree("../dbt-project-template-mysql", test_root_dir) + elif integration_type == DestinationType.ORACLE.value: + copy_tree("../dbt-project-template-oracle", test_root_dir) + elif integration_type == DestinationType.SNOWFLAKE.value: + copy_tree("../dbt-project-template-snowflake", test_root_dir) + elif integration_type == DestinationType.TIDB.value: + copy_tree("../dbt-project-template-tidb", test_root_dir) + return test_root_dir + + +def run_destination_process( + destination_type: DestinationType, + test_root_dir: str, + message_file: str, + catalog_file: str, + dbt_test_utils: DbtIntegrationTest, + docker_tag="dev", +): + commands = [ + "docker", + "run", + "--rm", + "--init", + "-v", + f"{test_root_dir}:/data", + "--network", + "host", + "-i", + f"airbyte/destination-{destination_type.value.lower()}:{docker_tag}", + "write", + "--config", + "/data/destination_config.json", + "--catalog", + ] + return dbt_test_utils.run_destination_process(message_file, test_root_dir, commands + [f"/data/{catalog_file}"]) + + +def generate_dbt_models( + destination_type: DestinationType, + test_resource_name: str, + test_root_dir: str, + output_dir: str, + catalog_file: str, + dbt_test_utils: DbtIntegrationTest, +): + """ + This is the normalization step generating dbt models files from the destination_catalog.json taken as input. + """ + transform_catalog = TransformCatalog() + transform_catalog.config = { + "integration_type": destination_type.value, + "schema": dbt_test_utils.target_schema, + "catalog": [os.path.join("resources", test_resource_name, "data_input", catalog_file)], + "output_path": os.path.join(test_root_dir, output_dir, "generated"), + "json_column": "_airbyte_data", + "profile_config_dir": test_root_dir, + } + transform_catalog.process_catalog() diff --git a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/stream_processor.py b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/stream_processor.py index b46d8e390c81..231588f92903 100644 --- a/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/stream_processor.py +++ b/airbyte-integrations/bases/base-normalization/normalization/transform_catalog/stream_processor.py @@ -1140,6 +1140,10 @@ def add_to_outputs( subdir: str = "", partition_by: PartitionScheme = PartitionScheme.DEFAULT, ) -> str: + # Explicit function so that we can have type hints to satisfy the linter + def wrap_in_quotes(s: str) -> str: + return '"' + s + '"' + schema = self.get_schema(is_intermediate) # MySQL table names need to be manually truncated, because it does not do it automatically truncate_name = self.destination_type == DestinationType.MYSQL or self.destination_type == DestinationType.TIDB @@ -1261,14 +1265,36 @@ def add_to_outputs( else: hooks.append(f"drop view {stg_schema}.{stg_table}") - # Explicit function so that we can have type hints to satisfy the linter - def wrap_in_quotes(s: str) -> str: - return '"' + s + '"' - config["post_hook"] = "[" + ",".join(map(wrap_in_quotes, hooks)) + "]" else: # incremental is handled in the SCD SQL already sql = self.add_incremental_clause(sql) + elif self.destination_sync_mode == DestinationSyncMode.overwrite: + if suffix == "" and not is_intermediate: + # drop SCD table after creating the destination table + scd_table_name = self.tables_registry.get_table_name(schema, self.json_path, self.stream_name, "scd", truncate_name) + print(f" Adding drop table hook for {scd_table_name} to {file_name}") + hooks = [ + Template( + """ + {{ '{%' }} + set scd_table_relation = adapter.get_relation( + database=this.database, + schema=this.schema, + identifier='{{ scd_table_name }}' + ) + {{ '%}' }} + {{ '{%' }} + if scd_table_relation is not none + {{ '%}' }} + {{ '{%' }} + do adapter.drop_relation(scd_table_relation) + {{ '%}' }} + {{ '{% endif %}' }} + """ + ).render(scd_table_name=scd_table_name) + ] + config["post_hook"] = "[" + ",".join(map(wrap_in_quotes, hooks)) + "]" template = Template( """ {{ '{{' }} config( @@ -1280,6 +1306,7 @@ def wrap_in_quotes(s: str) -> str: {{ sql }} """ ) + self.sql_outputs[output] = template.render(config=config, sql=sql, tags=self.get_model_tags(is_intermediate)) json_path = self.current_json_path() print(f" Generating {output} from {json_path}") From 6a2ef9738e6a23dc05e9125b04f1993e61781a01 Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Tue, 1 Nov 2022 12:02:15 -0400 Subject: [PATCH 461/498] Move components to `components/common` (#18728) * Move ApiErrorboundary to components/common and cleanup * Move PageViewContainer to common, cleanup imports, remove unused subtitle * Move ConfirmationModal to components/common * Move DeleteBlock to components/common * Move ConnectorIcon and DefaultLogoCatalog to components/common * Move DocumentationPanel to components/common * Move EmtpyResource components to components/common * Move FormChangeTracker to components/common * Move HeadTitle to components/common * Move link to components/common * Move StatusIcon to components/common * Move MainPageWithScroll to components/common * Move Version to components/common * Cleanup imports * Cleanup imports in StatusIcon * Move StatusIcon to components/ui * Fix DeleteBlock mocking in ConnectionSettingsTab --- airbyte-webapp/src/App.tsx | 3 ++- .../src/components/ApiErrorBoundary/index.tsx | 3 --- .../src/components/BaseClearView/index.tsx | 3 --- .../PageViewContainer.tsx | 15 --------------- .../CenteredPageComponents/Subtitle.tsx | 12 ------------ .../CenteredPageComponents/index.tsx | 4 ---- .../ConnectionBlock/ConnectionBlockItem.tsx | 2 +- .../CreateConnection/TryAfterErrorBlock.tsx | 2 +- .../components/DefaultLogoCatalog/index.tsx | 4 ---- .../src/components/DeleteBlock/index.tsx | 3 --- .../components/EmptyResourceBlock/index.tsx | 3 --- .../components/AllConnectionsStatusCell.tsx | 4 ++-- .../EntityTable/components/ConnectorCell.tsx | 2 +- .../EntityTable/components/NameCell.tsx | 6 +++--- .../src/components/HeadTitle/index.tsx | 3 --- .../src/components/Indicator/Indicator.tsx | 4 +--- .../src/components/Indicator/index.tsx | 2 +- .../components/JobItem/components/JobLogs.tsx | 4 ++-- .../components/JobItem/components/MainInfo.tsx | 2 +- airbyte-webapp/src/components/Link/index.tsx | 4 ---- .../components/MainPageWithScroll/index.tsx | 4 ---- .../components/StatusIcon/index.stories.tsx | 18 ------------------ .../src/components/StatusIcon/index.tsx | 4 ---- .../src/components/Version/index.tsx | 3 --- .../ApiErrorBoundary/ApiErrorBoundary.tsx | 15 +++++---------- .../ServerUnavailableView.tsx | 4 +--- .../common/ApiErrorBoundary/index.ts | 1 + .../ConfirmationModal.module.scss | 0 .../ConfirmationModal/ConfirmationModal.tsx | 2 +- .../{ => common}/ConfirmationModal/index.ts | 0 .../ConnectorIcon/ConnectorIcon.tsx | 0 .../{ => common}/ConnectorIcon/index.ts | 0 .../DefaultLogoCatalog/DefaultLogoCatalog.tsx | 4 +--- .../common/DefaultLogoCatalog/index.ts | 1 + .../{ => common}/DeleteBlock/DeleteBlock.tsx | 4 +--- .../src/components/common/DeleteBlock/index.ts | 1 + .../DocumentationPanel.module.scss | 2 +- .../DocumentationPanel/DocumentationPanel.tsx | 0 .../DocumentationPanel/index.ts} | 0 .../EmptyResourceBlock/EmptyResourceBlock.tsx | 6 ++---- .../common/EmptyResourceBlock/index.ts | 1 + .../EmptyResourceListView.module.scss | 2 +- .../EmptyResourceListView.tsx | 0 .../EmptyResourceListView/bowtie-half.svg | 0 .../EmptyResourceListView/index.ts | 0 .../FormChangeTracker/FormChangeTracker.tsx | 0 .../{ => common}/FormChangeTracker/index.ts | 0 .../{ => common}/HeadTitle/HeadTitle.tsx | 4 +--- .../src/components/common/HeadTitle/index.ts | 1 + .../src/components/{ => common}/Link/Link.tsx | 6 ++---- .../src/components/common/Link/index.ts | 1 + .../MainPageWithScroll.module.scss | 2 +- .../MainPageWithScroll/MainPageWithScroll.tsx | 4 +--- .../common/MainPageWithScroll/index.ts | 1 + .../PageViewContainer}/BaseClearView.tsx | 6 ++---- .../PageViewContainer}/PaddedCard.tsx | 4 +--- .../PageViewContainer/PageViewContainer.tsx | 12 ++++++++++++ .../common/PageViewContainer/index.ts | 1 + .../{ => common}/Version/Version.tsx | 6 ++---- .../src/components/common/Version/index.ts | 1 + .../src/components/icons/PauseIcon.tsx | 4 +--- airbyte-webapp/src/components/index.tsx | 6 ++---- .../StatusIcon/CircleLoader.module.scss | 2 +- .../{ => ui}/StatusIcon/CircleLoader.tsx | 0 .../{ => ui}/StatusIcon/StatusIcon.test.tsx | 2 +- .../{ => ui}/StatusIcon/StatusIcon.tsx | 6 ++---- .../components/ui/StatusIcon/index.stories.tsx | 18 ++++++++++++++++++ .../src/components/ui/StatusIcon/index.ts | 1 + .../ConfirmationModalService.tsx | 2 +- .../hooks/services/ConfirmationModal/types.ts | 2 +- airbyte-webapp/src/packages/cloud/App.tsx | 2 +- .../src/packages/cloud/cloudRoutes.tsx | 2 +- .../packages/cloud/views/AcceptEmailInvite.tsx | 2 +- .../cloud/views/auth/LoginPage/LoginPage.tsx | 2 +- .../ResetPasswordPage/ResetPasswordPage.tsx | 2 +- .../cloud/views/auth/SignupPage/SignupPage.tsx | 2 +- .../views/credits/CreditsPage/CreditsPage.tsx | 4 ++-- .../CreditsPage/components/ConnectionCell.tsx | 2 +- .../AllConnectionsPage/AllConnectionsPage.tsx | 4 ++-- .../ConnectionItemPage/ConnectionItemPage.tsx | 2 +- .../ConnectionSettingsTab.test.tsx | 10 ++++++---- .../ConnectionSettingsTab.tsx | 2 +- .../ConnectionItemPage/ConnectionStatusTab.tsx | 6 +++--- .../DbtCloudTransformationsCard.tsx | 2 +- .../CreationFormPage/CreationFormPage.tsx | 2 +- .../CreationFormPage/ExistingEntityForm.tsx | 2 +- .../AllDestinationsPage.tsx | 6 +++--- .../CreateDestinationPage.tsx | 2 +- .../DestinationItemPage.tsx | 6 +++--- .../components/DestinationSettings.tsx | 2 +- .../pages/OnboardingPage/OnboardingPage.tsx | 4 ++-- .../components/ProgressBlock.tsx | 2 +- .../pages/PreferencesPage/PreferencesPage.tsx | 4 ++-- .../src/pages/SettingsPage/SettingsPage.tsx | 4 ++-- .../pages/AccountPage/AccountPage.tsx | 2 +- .../ConfigurationsPage/ConfigurationsPage.tsx | 2 +- .../components/ConnectorsView.tsx | 2 +- .../components/CreateConnectorModal.tsx | 3 ++- .../pages/MetricsPage/MetricsPage.tsx | 2 +- .../NotificationPage/NotificationPage.tsx | 2 +- .../pages/AllSourcesPage/AllSourcesPage.tsx | 6 +++--- .../CreateSourcePage/CreateSourcePage.tsx | 2 +- .../pages/SourceItemPage/SourceItemPage.tsx | 6 +++--- .../components/SourceSettings.tsx | 2 +- airbyte-webapp/src/pages/routes.tsx | 2 +- airbyte-webapp/src/utils/imageUtils.tsx | 2 +- .../ConnectionForm/ConnectionFormFields.tsx | 2 +- .../components/CreateControls.tsx | 2 +- .../src/views/Connection/FormCard.tsx | 2 +- .../TransformationForm/TransformationForm.tsx | 2 +- .../ConnectorDocumentationLayout.tsx | 2 +- .../Connector/ServiceForm/ServiceForm.tsx | 2 +- .../ConnectorServiceTypeControl/utils.tsx | 2 +- .../components/TestingConnectionError.tsx | 2 +- .../components/TestingConnectionSuccess.tsx | 2 +- .../src/views/layout/SideBar/SideBar.tsx | 2 +- 116 files changed, 154 insertions(+), 226 deletions(-) delete mode 100644 airbyte-webapp/src/components/ApiErrorBoundary/index.tsx delete mode 100644 airbyte-webapp/src/components/BaseClearView/index.tsx delete mode 100644 airbyte-webapp/src/components/CenteredPageComponents/PageViewContainer.tsx delete mode 100644 airbyte-webapp/src/components/CenteredPageComponents/Subtitle.tsx delete mode 100644 airbyte-webapp/src/components/CenteredPageComponents/index.tsx delete mode 100644 airbyte-webapp/src/components/DefaultLogoCatalog/index.tsx delete mode 100644 airbyte-webapp/src/components/DeleteBlock/index.tsx delete mode 100644 airbyte-webapp/src/components/EmptyResourceBlock/index.tsx delete mode 100644 airbyte-webapp/src/components/HeadTitle/index.tsx delete mode 100644 airbyte-webapp/src/components/Link/index.tsx delete mode 100644 airbyte-webapp/src/components/MainPageWithScroll/index.tsx delete mode 100644 airbyte-webapp/src/components/StatusIcon/index.stories.tsx delete mode 100644 airbyte-webapp/src/components/StatusIcon/index.tsx delete mode 100644 airbyte-webapp/src/components/Version/index.tsx rename airbyte-webapp/src/components/{ => common}/ApiErrorBoundary/ApiErrorBoundary.tsx (89%) rename airbyte-webapp/src/components/{ApiErrorBoundary/components => common/ApiErrorBoundary}/ServerUnavailableView.tsx (83%) create mode 100644 airbyte-webapp/src/components/common/ApiErrorBoundary/index.ts rename airbyte-webapp/src/components/{ => common}/ConfirmationModal/ConfirmationModal.module.scss (100%) rename airbyte-webapp/src/components/{ => common}/ConfirmationModal/ConfirmationModal.tsx (96%) rename airbyte-webapp/src/components/{ => common}/ConfirmationModal/index.ts (100%) rename airbyte-webapp/src/components/{ => common}/ConnectorIcon/ConnectorIcon.tsx (100%) rename airbyte-webapp/src/components/{ => common}/ConnectorIcon/index.ts (100%) rename airbyte-webapp/src/components/{ => common}/DefaultLogoCatalog/DefaultLogoCatalog.tsx (96%) create mode 100644 airbyte-webapp/src/components/common/DefaultLogoCatalog/index.ts rename airbyte-webapp/src/components/{ => common}/DeleteBlock/DeleteBlock.tsx (95%) create mode 100644 airbyte-webapp/src/components/common/DeleteBlock/index.ts rename airbyte-webapp/src/components/{ => common}/DocumentationPanel/DocumentationPanel.module.scss (83%) rename airbyte-webapp/src/components/{ => common}/DocumentationPanel/DocumentationPanel.tsx (100%) rename airbyte-webapp/src/components/{DocumentationPanel/index.tsx => common/DocumentationPanel/index.ts} (100%) rename airbyte-webapp/src/components/{ => common}/EmptyResourceBlock/EmptyResourceBlock.tsx (86%) create mode 100644 airbyte-webapp/src/components/common/EmptyResourceBlock/index.ts rename airbyte-webapp/src/components/{ => common}/EmptyResourceListView/EmptyResourceListView.module.scss (95%) rename airbyte-webapp/src/components/{ => common}/EmptyResourceListView/EmptyResourceListView.tsx (100%) rename airbyte-webapp/src/components/{ => common}/EmptyResourceListView/bowtie-half.svg (100%) rename airbyte-webapp/src/components/{ => common}/EmptyResourceListView/index.ts (100%) rename airbyte-webapp/src/components/{ => common}/FormChangeTracker/FormChangeTracker.tsx (100%) rename airbyte-webapp/src/components/{ => common}/FormChangeTracker/index.ts (100%) rename airbyte-webapp/src/components/{ => common}/HeadTitle/HeadTitle.tsx (92%) create mode 100644 airbyte-webapp/src/components/common/HeadTitle/index.ts rename airbyte-webapp/src/components/{ => common}/Link/Link.tsx (79%) create mode 100644 airbyte-webapp/src/components/common/Link/index.ts rename airbyte-webapp/src/components/{ => common}/MainPageWithScroll/MainPageWithScroll.module.scss (92%) rename airbyte-webapp/src/components/{ => common}/MainPageWithScroll/MainPageWithScroll.tsx (80%) create mode 100644 airbyte-webapp/src/components/common/MainPageWithScroll/index.ts rename airbyte-webapp/src/components/{BaseClearView => common/PageViewContainer}/BaseClearView.tsx (83%) rename airbyte-webapp/src/components/{CenteredPageComponents => common/PageViewContainer}/PaddedCard.tsx (74%) create mode 100644 airbyte-webapp/src/components/common/PageViewContainer/PageViewContainer.tsx create mode 100644 airbyte-webapp/src/components/common/PageViewContainer/index.ts rename airbyte-webapp/src/components/{ => common}/Version/Version.tsx (84%) create mode 100644 airbyte-webapp/src/components/common/Version/index.ts rename airbyte-webapp/src/components/{ => ui}/StatusIcon/CircleLoader.module.scss (93%) rename airbyte-webapp/src/components/{ => ui}/StatusIcon/CircleLoader.tsx (100%) rename airbyte-webapp/src/components/{ => ui}/StatusIcon/StatusIcon.test.tsx (95%) rename airbyte-webapp/src/components/{ => ui}/StatusIcon/StatusIcon.tsx (93%) create mode 100644 airbyte-webapp/src/components/ui/StatusIcon/index.stories.tsx create mode 100644 airbyte-webapp/src/components/ui/StatusIcon/index.ts diff --git a/airbyte-webapp/src/App.tsx b/airbyte-webapp/src/App.tsx index f353d43dd08f..8806625227e3 100644 --- a/airbyte-webapp/src/App.tsx +++ b/airbyte-webapp/src/App.tsx @@ -3,6 +3,8 @@ import { HelmetProvider } from "react-helmet-async"; import { BrowserRouter as Router } from "react-router-dom"; import { ThemeProvider } from "styled-components"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; + import { ApiServices } from "core/ApiServices"; import { I18nProvider } from "core/i18n"; import { ServicesProvider } from "core/servicesProvider"; @@ -14,7 +16,6 @@ import NotificationService from "hooks/services/Notification"; import { AnalyticsProvider } from "views/common/AnalyticsProvider"; import { StoreProvider } from "views/common/StoreProvider"; -import ApiErrorBoundary from "./components/ApiErrorBoundary"; import LoadingPage from "./components/LoadingPage"; import { Config, diff --git a/airbyte-webapp/src/components/ApiErrorBoundary/index.tsx b/airbyte-webapp/src/components/ApiErrorBoundary/index.tsx deleted file mode 100644 index 9b35c99c9efb..000000000000 --- a/airbyte-webapp/src/components/ApiErrorBoundary/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import ApiErrorBoundary from "./ApiErrorBoundary"; - -export default ApiErrorBoundary; diff --git a/airbyte-webapp/src/components/BaseClearView/index.tsx b/airbyte-webapp/src/components/BaseClearView/index.tsx deleted file mode 100644 index 851f34720c00..000000000000 --- a/airbyte-webapp/src/components/BaseClearView/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import BaseClearView from "./BaseClearView"; - -export default BaseClearView; diff --git a/airbyte-webapp/src/components/CenteredPageComponents/PageViewContainer.tsx b/airbyte-webapp/src/components/CenteredPageComponents/PageViewContainer.tsx deleted file mode 100644 index 78348fd7a681..000000000000 --- a/airbyte-webapp/src/components/CenteredPageComponents/PageViewContainer.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -import BaseClearView from "components/BaseClearView"; - -import PaddedCard from "./PaddedCard"; - -const PageViewContainer: React.FC> = (props) => { - return ( - - {props.children} - - ); -}; - -export default PageViewContainer; diff --git a/airbyte-webapp/src/components/CenteredPageComponents/Subtitle.tsx b/airbyte-webapp/src/components/CenteredPageComponents/Subtitle.tsx deleted file mode 100644 index e9cf6c952a00..000000000000 --- a/airbyte-webapp/src/components/CenteredPageComponents/Subtitle.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import styled from "styled-components"; - -const Subtitle = styled.div` - font-size: 16px; - line-height: 21px; - text-align: center; - letter-spacing: 0.03em; - color: ${({ theme }) => theme.greyColor70}; - padding-bottom: 10px; -`; - -export default Subtitle; diff --git a/airbyte-webapp/src/components/CenteredPageComponents/index.tsx b/airbyte-webapp/src/components/CenteredPageComponents/index.tsx deleted file mode 100644 index 3569e3a632c0..000000000000 --- a/airbyte-webapp/src/components/CenteredPageComponents/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import PageViewContainer from "./PageViewContainer"; -import Subtitle from "./Subtitle"; - -export { PageViewContainer, Subtitle }; diff --git a/airbyte-webapp/src/components/ConnectionBlock/ConnectionBlockItem.tsx b/airbyte-webapp/src/components/ConnectionBlock/ConnectionBlockItem.tsx index c9dbacd9ac2e..1ffda334824c 100644 --- a/airbyte-webapp/src/components/ConnectionBlock/ConnectionBlockItem.tsx +++ b/airbyte-webapp/src/components/ConnectionBlock/ConnectionBlockItem.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; -import { ConnectorIcon } from "components/ConnectorIcon"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; interface IProps { name: string; diff --git a/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx b/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx index 4ebcd84f906e..7f25f3ec2e39 100644 --- a/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx +++ b/airbyte-webapp/src/components/CreateConnection/TryAfterErrorBlock.tsx @@ -1,8 +1,8 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import { StatusIcon } from "components/StatusIcon"; import { Button } from "components/ui/Button"; +import { StatusIcon } from "components/ui/StatusIcon"; import { Text } from "components/ui/Text"; import styles from "./TryAfterErrorBlock.module.scss"; diff --git a/airbyte-webapp/src/components/DefaultLogoCatalog/index.tsx b/airbyte-webapp/src/components/DefaultLogoCatalog/index.tsx deleted file mode 100644 index 452ec9bc5900..000000000000 --- a/airbyte-webapp/src/components/DefaultLogoCatalog/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import DefaultLogoCatalog from "./DefaultLogoCatalog"; - -export * from "./DefaultLogoCatalog"; -export { DefaultLogoCatalog }; diff --git a/airbyte-webapp/src/components/DeleteBlock/index.tsx b/airbyte-webapp/src/components/DeleteBlock/index.tsx deleted file mode 100644 index 5c3aa9c34c11..000000000000 --- a/airbyte-webapp/src/components/DeleteBlock/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import DeleteBlock from "./DeleteBlock"; - -export default DeleteBlock; diff --git a/airbyte-webapp/src/components/EmptyResourceBlock/index.tsx b/airbyte-webapp/src/components/EmptyResourceBlock/index.tsx deleted file mode 100644 index 9453e6f6beac..000000000000 --- a/airbyte-webapp/src/components/EmptyResourceBlock/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import EmptyResourceBlock from "./EmptyResourceBlock"; - -export default EmptyResourceBlock; diff --git a/airbyte-webapp/src/components/EntityTable/components/AllConnectionsStatusCell.tsx b/airbyte-webapp/src/components/EntityTable/components/AllConnectionsStatusCell.tsx index 700f4aa4f2a1..d4aaeb2c3f46 100644 --- a/airbyte-webapp/src/components/EntityTable/components/AllConnectionsStatusCell.tsx +++ b/airbyte-webapp/src/components/EntityTable/components/AllConnectionsStatusCell.tsx @@ -1,8 +1,8 @@ import React, { useMemo } from "react"; import { useIntl } from "react-intl"; -import StatusIcon from "components/StatusIcon"; -import { StatusIconStatus } from "components/StatusIcon/StatusIcon"; +import { StatusIcon } from "components/ui/StatusIcon"; +import { StatusIconStatus } from "components/ui/StatusIcon/StatusIcon"; import { Status } from "../types"; diff --git a/airbyte-webapp/src/components/EntityTable/components/ConnectorCell.tsx b/airbyte-webapp/src/components/EntityTable/components/ConnectorCell.tsx index 6c65dbc56160..c9515c835984 100644 --- a/airbyte-webapp/src/components/EntityTable/components/ConnectorCell.tsx +++ b/airbyte-webapp/src/components/EntityTable/components/ConnectorCell.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; -import { ConnectorIcon } from "components/ConnectorIcon"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; interface IProps { value: string; diff --git a/airbyte-webapp/src/components/EntityTable/components/NameCell.tsx b/airbyte-webapp/src/components/EntityTable/components/NameCell.tsx index 0d6c40136c75..12612928ec95 100644 --- a/airbyte-webapp/src/components/EntityTable/components/NameCell.tsx +++ b/airbyte-webapp/src/components/EntityTable/components/NameCell.tsx @@ -2,9 +2,9 @@ import React, { useMemo } from "react"; import { useIntl } from "react-intl"; import styled from "styled-components"; -import { ConnectorIcon } from "components/ConnectorIcon"; -import StatusIcon from "components/StatusIcon"; -import { StatusIconStatus } from "components/StatusIcon/StatusIcon"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; +import { StatusIcon } from "components/ui/StatusIcon"; +import { StatusIconStatus } from "components/ui/StatusIcon/StatusIcon"; import { Status } from "../types"; diff --git a/airbyte-webapp/src/components/HeadTitle/index.tsx b/airbyte-webapp/src/components/HeadTitle/index.tsx deleted file mode 100644 index 0487dc268001..000000000000 --- a/airbyte-webapp/src/components/HeadTitle/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import HeadTitle from "./HeadTitle"; - -export default HeadTitle; diff --git a/airbyte-webapp/src/components/Indicator/Indicator.tsx b/airbyte-webapp/src/components/Indicator/Indicator.tsx index 5603b5609fae..7332a0017d73 100644 --- a/airbyte-webapp/src/components/Indicator/Indicator.tsx +++ b/airbyte-webapp/src/components/Indicator/Indicator.tsx @@ -1,10 +1,8 @@ import styled from "styled-components"; -const Indicator = styled.div` +export const Indicator = styled.div` height: 10px; width: 10px; border-radius: 50%; background: ${({ theme }) => theme.dangerColor}; `; - -export default Indicator; diff --git a/airbyte-webapp/src/components/Indicator/index.tsx b/airbyte-webapp/src/components/Indicator/index.tsx index 819ae52b983a..1ae3d0b05412 100644 --- a/airbyte-webapp/src/components/Indicator/index.tsx +++ b/airbyte-webapp/src/components/Indicator/index.tsx @@ -1,4 +1,4 @@ -import Indicator from "./Indicator"; +import { Indicator } from "./Indicator"; export default Indicator; export { Indicator }; diff --git a/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx b/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx index 96fd32b4cccb..aa657c11c46b 100644 --- a/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx +++ b/airbyte-webapp/src/components/JobItem/components/JobLogs.tsx @@ -3,8 +3,8 @@ import React, { useState } from "react"; import { FormattedMessage } from "react-intl"; import { useLocation } from "react-router-dom"; -import StatusIcon from "components/StatusIcon"; -import { StatusIconStatus } from "components/StatusIcon/StatusIcon"; +import { StatusIcon } from "components/ui/StatusIcon"; +import { StatusIconStatus } from "components/ui/StatusIcon/StatusIcon"; import { JobsWithJobs } from "pages/ConnectionPage/pages/ConnectionItemPage/JobsList"; import { useGetDebugInfoJob } from "services/job/JobService"; diff --git a/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx b/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx index c79a411bc233..c6d363b3b4db 100644 --- a/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx +++ b/airbyte-webapp/src/components/JobItem/components/MainInfo.tsx @@ -4,8 +4,8 @@ import classNames from "classnames"; import React, { useMemo } from "react"; import { FormattedDateParts, FormattedMessage, FormattedTimeParts } from "react-intl"; -import { StatusIcon } from "components"; import { Cell, Row } from "components/SimpleTableComponents"; +import { StatusIcon } from "components/ui/StatusIcon"; import { AttemptRead, JobStatus, SynchronousJobRead } from "core/request/AirbyteClient"; import { JobsWithJobs } from "pages/ConnectionPage/pages/ConnectionItemPage/JobsList"; diff --git a/airbyte-webapp/src/components/Link/index.tsx b/airbyte-webapp/src/components/Link/index.tsx deleted file mode 100644 index 55d42b86de85..000000000000 --- a/airbyte-webapp/src/components/Link/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import Link from "./Link"; - -export default Link; -export { Link }; diff --git a/airbyte-webapp/src/components/MainPageWithScroll/index.tsx b/airbyte-webapp/src/components/MainPageWithScroll/index.tsx deleted file mode 100644 index 10ec5864f986..000000000000 --- a/airbyte-webapp/src/components/MainPageWithScroll/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import MainPageWithScroll from "./MainPageWithScroll"; - -export default MainPageWithScroll; -export { MainPageWithScroll }; diff --git a/airbyte-webapp/src/components/StatusIcon/index.stories.tsx b/airbyte-webapp/src/components/StatusIcon/index.stories.tsx deleted file mode 100644 index 9a08b674322b..000000000000 --- a/airbyte-webapp/src/components/StatusIcon/index.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { ComponentStory, ComponentMeta } from "@storybook/react"; - -import StatusIconComponent from "./StatusIcon"; - -export default { - title: "Ui/StatusIcon", - component: StatusIconComponent, - argTypes: { - value: { type: { name: "number", required: false } }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ; - -export const StatusIcon = Template.bind({}); -StatusIcon.args = { - status: "success", -}; diff --git a/airbyte-webapp/src/components/StatusIcon/index.tsx b/airbyte-webapp/src/components/StatusIcon/index.tsx deleted file mode 100644 index 1f237064a348..000000000000 --- a/airbyte-webapp/src/components/StatusIcon/index.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import StatusIcon from "./StatusIcon"; - -export default StatusIcon; -export { StatusIcon }; diff --git a/airbyte-webapp/src/components/Version/index.tsx b/airbyte-webapp/src/components/Version/index.tsx deleted file mode 100644 index 093a17131a1a..000000000000 --- a/airbyte-webapp/src/components/Version/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import Version from "./Version"; - -export default Version; diff --git a/airbyte-webapp/src/components/ApiErrorBoundary/ApiErrorBoundary.tsx b/airbyte-webapp/src/components/common/ApiErrorBoundary/ApiErrorBoundary.tsx similarity index 89% rename from airbyte-webapp/src/components/ApiErrorBoundary/ApiErrorBoundary.tsx rename to airbyte-webapp/src/components/common/ApiErrorBoundary/ApiErrorBoundary.tsx index 112098e3b3f4..3ce5d2256169 100644 --- a/airbyte-webapp/src/components/ApiErrorBoundary/ApiErrorBoundary.tsx +++ b/airbyte-webapp/src/components/common/ApiErrorBoundary/ApiErrorBoundary.tsx @@ -10,7 +10,7 @@ import { ErrorOccurredView } from "views/common/ErrorOccurredView"; import { ResourceNotFoundErrorBoundary } from "views/common/ResorceNotFoundErrorBoundary"; import { StartOverErrorView } from "views/common/StartOverErrorView"; -import ServerUnavailableView from "./components/ServerUnavailableView"; +import { ServerUnavailableView } from "./ServerUnavailableView"; interface ApiErrorBoundaryState { errorId?: string; @@ -37,7 +37,7 @@ interface ApiErrorBoundaryProps { const RETRY_DELAY = 2500; -class ApiErrorBoundary extends React.Component< +class ApiErrorBoundaryComponent extends React.Component< React.PropsWithChildren, ApiErrorBoundaryState > { @@ -107,19 +107,14 @@ class ApiErrorBoundary extends React.Component< } } -const ApiErrorBoundaryWithHooks: React.FC> = ({ - children, - ...props -}) => { +export const ApiErrorBoundary: React.FC> = ({ children, ...props }) => { const { reset } = useQueryErrorResetBoundary(); const location = useLocation(); const navigate = useNavigate(); return ( - + {children} - + ); }; - -export default ApiErrorBoundaryWithHooks; diff --git a/airbyte-webapp/src/components/ApiErrorBoundary/components/ServerUnavailableView.tsx b/airbyte-webapp/src/components/common/ApiErrorBoundary/ServerUnavailableView.tsx similarity index 83% rename from airbyte-webapp/src/components/ApiErrorBoundary/components/ServerUnavailableView.tsx rename to airbyte-webapp/src/components/common/ApiErrorBoundary/ServerUnavailableView.tsx index 50cadbac01fd..7553d5e6000c 100644 --- a/airbyte-webapp/src/components/ApiErrorBoundary/components/ServerUnavailableView.tsx +++ b/airbyte-webapp/src/components/common/ApiErrorBoundary/ServerUnavailableView.tsx @@ -8,7 +8,7 @@ interface ServerUnavailableViewProps { retryDelay: number; } -const ServerUnavailableView: React.FC = ({ onRetryClick, retryDelay }) => { +export const ServerUnavailableView: React.FC = ({ onRetryClick, retryDelay }) => { useEffect(() => { const timer: ReturnType = setTimeout(() => { onRetryClick(); @@ -25,5 +25,3 @@ const ServerUnavailableView: React.FC = ({ onRetryCl /> ); }; - -export default ServerUnavailableView; diff --git a/airbyte-webapp/src/components/common/ApiErrorBoundary/index.ts b/airbyte-webapp/src/components/common/ApiErrorBoundary/index.ts new file mode 100644 index 000000000000..0f3fb4d73057 --- /dev/null +++ b/airbyte-webapp/src/components/common/ApiErrorBoundary/index.ts @@ -0,0 +1 @@ +export * from "./ApiErrorBoundary"; diff --git a/airbyte-webapp/src/components/ConfirmationModal/ConfirmationModal.module.scss b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.module.scss similarity index 100% rename from airbyte-webapp/src/components/ConfirmationModal/ConfirmationModal.module.scss rename to airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.module.scss diff --git a/airbyte-webapp/src/components/ConfirmationModal/ConfirmationModal.tsx b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx similarity index 96% rename from airbyte-webapp/src/components/ConfirmationModal/ConfirmationModal.tsx rename to airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx index ff87ee8d3d32..4f09c55badda 100644 --- a/airbyte-webapp/src/components/ConfirmationModal/ConfirmationModal.tsx +++ b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx @@ -5,7 +5,7 @@ import styled from "styled-components"; import { Button } from "components/ui/Button"; import { Modal } from "components/ui/Modal"; -import useLoadingState from "../../hooks/useLoadingState"; +import useLoadingState from "../../../hooks/useLoadingState"; import styles from "./ConfirmationModal.module.scss"; const Content = styled.div` diff --git a/airbyte-webapp/src/components/ConfirmationModal/index.ts b/airbyte-webapp/src/components/common/ConfirmationModal/index.ts similarity index 100% rename from airbyte-webapp/src/components/ConfirmationModal/index.ts rename to airbyte-webapp/src/components/common/ConfirmationModal/index.ts diff --git a/airbyte-webapp/src/components/ConnectorIcon/ConnectorIcon.tsx b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx similarity index 100% rename from airbyte-webapp/src/components/ConnectorIcon/ConnectorIcon.tsx rename to airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx diff --git a/airbyte-webapp/src/components/ConnectorIcon/index.ts b/airbyte-webapp/src/components/common/ConnectorIcon/index.ts similarity index 100% rename from airbyte-webapp/src/components/ConnectorIcon/index.ts rename to airbyte-webapp/src/components/common/ConnectorIcon/index.ts diff --git a/airbyte-webapp/src/components/DefaultLogoCatalog/DefaultLogoCatalog.tsx b/airbyte-webapp/src/components/common/DefaultLogoCatalog/DefaultLogoCatalog.tsx similarity index 96% rename from airbyte-webapp/src/components/DefaultLogoCatalog/DefaultLogoCatalog.tsx rename to airbyte-webapp/src/components/common/DefaultLogoCatalog/DefaultLogoCatalog.tsx index 4819d8d17f92..fc8884c7a29e 100644 --- a/airbyte-webapp/src/components/DefaultLogoCatalog/DefaultLogoCatalog.tsx +++ b/airbyte-webapp/src/components/common/DefaultLogoCatalog/DefaultLogoCatalog.tsx @@ -1,4 +1,4 @@ -const DefaultLogoCatalog = (): JSX.Element => ( +export const DefaultLogoCatalog = (): JSX.Element => ( ( /> ); - -export default DefaultLogoCatalog; diff --git a/airbyte-webapp/src/components/common/DefaultLogoCatalog/index.ts b/airbyte-webapp/src/components/common/DefaultLogoCatalog/index.ts new file mode 100644 index 000000000000..20849f83d17d --- /dev/null +++ b/airbyte-webapp/src/components/common/DefaultLogoCatalog/index.ts @@ -0,0 +1 @@ +export * from "./DefaultLogoCatalog"; diff --git a/airbyte-webapp/src/components/DeleteBlock/DeleteBlock.tsx b/airbyte-webapp/src/components/common/DeleteBlock/DeleteBlock.tsx similarity index 95% rename from airbyte-webapp/src/components/DeleteBlock/DeleteBlock.tsx rename to airbyte-webapp/src/components/common/DeleteBlock/DeleteBlock.tsx index ca8617e08a50..7758c8d2f13a 100644 --- a/airbyte-webapp/src/components/DeleteBlock/DeleteBlock.tsx +++ b/airbyte-webapp/src/components/common/DeleteBlock/DeleteBlock.tsx @@ -30,7 +30,7 @@ const Text = styled.div` white-space: pre-line; `; -const DeleteBlock: React.FC = ({ type, onDelete }) => { +export const DeleteBlock: React.FC = ({ type, onDelete }) => { const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService(); const navigate = useNavigate(); @@ -62,5 +62,3 @@ const DeleteBlock: React.FC = ({ type, onDelete }) => { ); }; - -export default DeleteBlock; diff --git a/airbyte-webapp/src/components/common/DeleteBlock/index.ts b/airbyte-webapp/src/components/common/DeleteBlock/index.ts new file mode 100644 index 000000000000..efdf28e28c23 --- /dev/null +++ b/airbyte-webapp/src/components/common/DeleteBlock/index.ts @@ -0,0 +1 @@ +export * from "./DeleteBlock"; diff --git a/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.module.scss b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.module.scss similarity index 83% rename from airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.module.scss rename to airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.module.scss index 00bd408cd9da..9e3f275e91c9 100644 --- a/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.module.scss +++ b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.module.scss @@ -1,4 +1,4 @@ -@use "../../scss/colors"; +@use "scss/colors"; .container { min-height: 100vh; diff --git a/airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.tsx b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.tsx similarity index 100% rename from airbyte-webapp/src/components/DocumentationPanel/DocumentationPanel.tsx rename to airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.tsx diff --git a/airbyte-webapp/src/components/DocumentationPanel/index.tsx b/airbyte-webapp/src/components/common/DocumentationPanel/index.ts similarity index 100% rename from airbyte-webapp/src/components/DocumentationPanel/index.tsx rename to airbyte-webapp/src/components/common/DocumentationPanel/index.ts diff --git a/airbyte-webapp/src/components/EmptyResourceBlock/EmptyResourceBlock.tsx b/airbyte-webapp/src/components/common/EmptyResourceBlock/EmptyResourceBlock.tsx similarity index 86% rename from airbyte-webapp/src/components/EmptyResourceBlock/EmptyResourceBlock.tsx rename to airbyte-webapp/src/components/common/EmptyResourceBlock/EmptyResourceBlock.tsx index fea79684ef70..9e08f217359c 100644 --- a/airbyte-webapp/src/components/EmptyResourceBlock/EmptyResourceBlock.tsx +++ b/airbyte-webapp/src/components/common/EmptyResourceBlock/EmptyResourceBlock.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; -interface IProps { +interface EmptyResourceBlockProps { text: React.ReactNode; description?: React.ReactNode; } @@ -32,7 +32,7 @@ const Description = styled.div` margin-top: 5px; `; -const EmptyResourceBlock: React.FC = ({ text, description }) => ( +export const EmptyResourceBlock: React.FC = ({ text, description }) => ( cactus @@ -41,5 +41,3 @@ const EmptyResourceBlock: React.FC = ({ text, description }) => ( {description} ); - -export default EmptyResourceBlock; diff --git a/airbyte-webapp/src/components/common/EmptyResourceBlock/index.ts b/airbyte-webapp/src/components/common/EmptyResourceBlock/index.ts new file mode 100644 index 000000000000..42693319a3d4 --- /dev/null +++ b/airbyte-webapp/src/components/common/EmptyResourceBlock/index.ts @@ -0,0 +1 @@ +export * from "./EmptyResourceBlock"; diff --git a/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.module.scss b/airbyte-webapp/src/components/common/EmptyResourceListView/EmptyResourceListView.module.scss similarity index 95% rename from airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.module.scss rename to airbyte-webapp/src/components/common/EmptyResourceListView/EmptyResourceListView.module.scss index 9c40338ba444..85843a3e294b 100644 --- a/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.module.scss +++ b/airbyte-webapp/src/components/common/EmptyResourceListView/EmptyResourceListView.module.scss @@ -1,4 +1,4 @@ -@use "../../scss/colors"; +@use "scss/colors"; .container { display: flex; diff --git a/airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.tsx b/airbyte-webapp/src/components/common/EmptyResourceListView/EmptyResourceListView.tsx similarity index 100% rename from airbyte-webapp/src/components/EmptyResourceListView/EmptyResourceListView.tsx rename to airbyte-webapp/src/components/common/EmptyResourceListView/EmptyResourceListView.tsx diff --git a/airbyte-webapp/src/components/EmptyResourceListView/bowtie-half.svg b/airbyte-webapp/src/components/common/EmptyResourceListView/bowtie-half.svg similarity index 100% rename from airbyte-webapp/src/components/EmptyResourceListView/bowtie-half.svg rename to airbyte-webapp/src/components/common/EmptyResourceListView/bowtie-half.svg diff --git a/airbyte-webapp/src/components/EmptyResourceListView/index.ts b/airbyte-webapp/src/components/common/EmptyResourceListView/index.ts similarity index 100% rename from airbyte-webapp/src/components/EmptyResourceListView/index.ts rename to airbyte-webapp/src/components/common/EmptyResourceListView/index.ts diff --git a/airbyte-webapp/src/components/FormChangeTracker/FormChangeTracker.tsx b/airbyte-webapp/src/components/common/FormChangeTracker/FormChangeTracker.tsx similarity index 100% rename from airbyte-webapp/src/components/FormChangeTracker/FormChangeTracker.tsx rename to airbyte-webapp/src/components/common/FormChangeTracker/FormChangeTracker.tsx diff --git a/airbyte-webapp/src/components/FormChangeTracker/index.ts b/airbyte-webapp/src/components/common/FormChangeTracker/index.ts similarity index 100% rename from airbyte-webapp/src/components/FormChangeTracker/index.ts rename to airbyte-webapp/src/components/common/FormChangeTracker/index.ts diff --git a/airbyte-webapp/src/components/HeadTitle/HeadTitle.tsx b/airbyte-webapp/src/components/common/HeadTitle/HeadTitle.tsx similarity index 92% rename from airbyte-webapp/src/components/HeadTitle/HeadTitle.tsx rename to airbyte-webapp/src/components/common/HeadTitle/HeadTitle.tsx index 5a65b03423dc..2aa5969a852a 100644 --- a/airbyte-webapp/src/components/HeadTitle/HeadTitle.tsx +++ b/airbyte-webapp/src/components/common/HeadTitle/HeadTitle.tsx @@ -28,7 +28,7 @@ interface IProps { * Titles defined by {@link HeadTitleDefinition} will be * chained together with the {@link SEPARATOR}. */ -const HeadTitle: React.FC = ({ titles }) => { +export const HeadTitle: React.FC = ({ titles }) => { const intl = useIntl(); const getTitle = (d: HeadTitleDefinition): string => { @@ -42,5 +42,3 @@ const HeadTitle: React.FC = ({ titles }) => { ); }; - -export default HeadTitle; diff --git a/airbyte-webapp/src/components/common/HeadTitle/index.ts b/airbyte-webapp/src/components/common/HeadTitle/index.ts new file mode 100644 index 000000000000..60ef6d2aa654 --- /dev/null +++ b/airbyte-webapp/src/components/common/HeadTitle/index.ts @@ -0,0 +1 @@ +export * from "./HeadTitle"; diff --git a/airbyte-webapp/src/components/Link/Link.tsx b/airbyte-webapp/src/components/common/Link/Link.tsx similarity index 79% rename from airbyte-webapp/src/components/Link/Link.tsx rename to airbyte-webapp/src/components/common/Link/Link.tsx index 7e249dbe198e..339a4a250e0e 100644 --- a/airbyte-webapp/src/components/Link/Link.tsx +++ b/airbyte-webapp/src/components/common/Link/Link.tsx @@ -4,14 +4,14 @@ import { } from "react-router-dom"; import styled from "styled-components"; -export interface ILinkProps { +export interface LinkProps { bold?: boolean; $clear?: boolean; $light?: boolean; } // TODO: fix typings -const Link = styled(ReactLink)` +export const Link = styled(ReactLink)` color: ${({ theme, $light }) => ($light ? theme.darkGreyColor : theme.primaryColor)}; font-weight: ${({ bold }) => (bold ? "bold" : "normal")}; @@ -21,5 +21,3 @@ const Link = styled(ReactLink)` opacity: 0.8; } `; - -export default Link; diff --git a/airbyte-webapp/src/components/common/Link/index.ts b/airbyte-webapp/src/components/common/Link/index.ts new file mode 100644 index 000000000000..3b40a46d8b6b --- /dev/null +++ b/airbyte-webapp/src/components/common/Link/index.ts @@ -0,0 +1 @@ +export * from "./Link"; diff --git a/airbyte-webapp/src/components/MainPageWithScroll/MainPageWithScroll.module.scss b/airbyte-webapp/src/components/common/MainPageWithScroll/MainPageWithScroll.module.scss similarity index 92% rename from airbyte-webapp/src/components/MainPageWithScroll/MainPageWithScroll.module.scss rename to airbyte-webapp/src/components/common/MainPageWithScroll/MainPageWithScroll.module.scss index 106e7c2a7fd3..22c55a7f45fb 100644 --- a/airbyte-webapp/src/components/MainPageWithScroll/MainPageWithScroll.module.scss +++ b/airbyte-webapp/src/components/common/MainPageWithScroll/MainPageWithScroll.module.scss @@ -1,4 +1,4 @@ -@use "../../scss/variables"; +@use "scss/variables"; .page { overflow-y: hidden; diff --git a/airbyte-webapp/src/components/MainPageWithScroll/MainPageWithScroll.tsx b/airbyte-webapp/src/components/common/MainPageWithScroll/MainPageWithScroll.tsx similarity index 80% rename from airbyte-webapp/src/components/MainPageWithScroll/MainPageWithScroll.tsx rename to airbyte-webapp/src/components/common/MainPageWithScroll/MainPageWithScroll.tsx index 58bcb7fc00ef..047e1e14dbe1 100644 --- a/airbyte-webapp/src/components/MainPageWithScroll/MainPageWithScroll.tsx +++ b/airbyte-webapp/src/components/common/MainPageWithScroll/MainPageWithScroll.tsx @@ -12,7 +12,7 @@ interface MainPageWithScrollProps { children?: React.ReactNode; } -const MainPageWithScroll: React.FC = ({ headTitle, pageTitle, children }) => { +export const MainPageWithScroll: React.FC = ({ headTitle, pageTitle, children }) => { return (
    @@ -25,5 +25,3 @@ const MainPageWithScroll: React.FC = ({ headTitle, page
    ); }; - -export default MainPageWithScroll; diff --git a/airbyte-webapp/src/components/common/MainPageWithScroll/index.ts b/airbyte-webapp/src/components/common/MainPageWithScroll/index.ts new file mode 100644 index 000000000000..70db65c04232 --- /dev/null +++ b/airbyte-webapp/src/components/common/MainPageWithScroll/index.ts @@ -0,0 +1 @@ +export * from "./MainPageWithScroll"; diff --git a/airbyte-webapp/src/components/BaseClearView/BaseClearView.tsx b/airbyte-webapp/src/components/common/PageViewContainer/BaseClearView.tsx similarity index 83% rename from airbyte-webapp/src/components/BaseClearView/BaseClearView.tsx rename to airbyte-webapp/src/components/common/PageViewContainer/BaseClearView.tsx index bd2f29202fb9..0fde4c190fb3 100644 --- a/airbyte-webapp/src/components/BaseClearView/BaseClearView.tsx +++ b/airbyte-webapp/src/components/common/PageViewContainer/BaseClearView.tsx @@ -3,7 +3,7 @@ import { useIntl } from "react-intl"; import { Link } from "react-router-dom"; import styled from "styled-components"; -import Version from "components/Version"; +import { Version } from "../Version"; const Content = styled.div` height: 100%; @@ -28,7 +28,7 @@ const MainInfo = styled.div` flex-direction: column; `; -const BaseClearView: React.FC> = ({ children }) => { +export const BaseClearView: React.FC> = ({ children }) => { const { formatMessage } = useIntl(); return ( @@ -42,5 +42,3 @@ const BaseClearView: React.FC> = ({ children }) ); }; - -export default BaseClearView; diff --git a/airbyte-webapp/src/components/CenteredPageComponents/PaddedCard.tsx b/airbyte-webapp/src/components/common/PageViewContainer/PaddedCard.tsx similarity index 74% rename from airbyte-webapp/src/components/CenteredPageComponents/PaddedCard.tsx rename to airbyte-webapp/src/components/common/PageViewContainer/PaddedCard.tsx index ab9f9cd6d14f..0ab4a0e0dac8 100644 --- a/airbyte-webapp/src/components/CenteredPageComponents/PaddedCard.tsx +++ b/airbyte-webapp/src/components/common/PageViewContainer/PaddedCard.tsx @@ -2,11 +2,9 @@ import styled from "styled-components"; import { Card } from "components/ui/Card"; -const PaddedCard = styled(Card)` +export const PaddedCard = styled(Card)` width: 100%; max-width: 600px; margin-bottom: 8px; padding: 37px 53px 24px 66px; `; - -export default PaddedCard; diff --git a/airbyte-webapp/src/components/common/PageViewContainer/PageViewContainer.tsx b/airbyte-webapp/src/components/common/PageViewContainer/PageViewContainer.tsx new file mode 100644 index 000000000000..e8273a4054e8 --- /dev/null +++ b/airbyte-webapp/src/components/common/PageViewContainer/PageViewContainer.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +import { BaseClearView } from "./BaseClearView"; +import { PaddedCard } from "./PaddedCard"; + +export const PageViewContainer: React.FC> = (props) => { + return ( + + {props.children} + + ); +}; diff --git a/airbyte-webapp/src/components/common/PageViewContainer/index.ts b/airbyte-webapp/src/components/common/PageViewContainer/index.ts new file mode 100644 index 000000000000..bcf3fecf668f --- /dev/null +++ b/airbyte-webapp/src/components/common/PageViewContainer/index.ts @@ -0,0 +1 @@ +export * from "./PageViewContainer"; diff --git a/airbyte-webapp/src/components/Version/Version.tsx b/airbyte-webapp/src/components/common/Version/Version.tsx similarity index 84% rename from airbyte-webapp/src/components/Version/Version.tsx rename to airbyte-webapp/src/components/common/Version/Version.tsx index 8d79ca41a643..55554f0015a3 100644 --- a/airbyte-webapp/src/components/Version/Version.tsx +++ b/airbyte-webapp/src/components/common/Version/Version.tsx @@ -13,12 +13,12 @@ const Content = styled.div<{ primary?: boolean }>` margin-top: 10px; `; -interface IProps { +interface VersionProps { className?: string; primary?: boolean; } -const Version: React.FC = ({ className, primary }) => { +export const Version: React.FC = ({ className, primary }) => { const config = useConfig(); return ( @@ -26,5 +26,3 @@ const Version: React.FC = ({ className, primary }) => { ); }; - -export default Version; diff --git a/airbyte-webapp/src/components/common/Version/index.ts b/airbyte-webapp/src/components/common/Version/index.ts new file mode 100644 index 000000000000..b7c04b0d5ab2 --- /dev/null +++ b/airbyte-webapp/src/components/common/Version/index.ts @@ -0,0 +1 @@ +export * from "./Version"; diff --git a/airbyte-webapp/src/components/icons/PauseIcon.tsx b/airbyte-webapp/src/components/icons/PauseIcon.tsx index 1b08b1417b26..1520bb587d7b 100644 --- a/airbyte-webapp/src/components/icons/PauseIcon.tsx +++ b/airbyte-webapp/src/components/icons/PauseIcon.tsx @@ -3,12 +3,10 @@ interface Props { title?: string; } -const PauseIcon = ({ color = "currentColor", title }: Props): JSX.Element => ( +export const PauseIcon = ({ color = "currentColor", title }: Props): JSX.Element => ( {title && {title}} ); - -export default PauseIcon; diff --git a/airbyte-webapp/src/components/index.tsx b/airbyte-webapp/src/components/index.tsx index 54763971f8c3..647bdc23f0d8 100644 --- a/airbyte-webapp/src/components/index.tsx +++ b/airbyte-webapp/src/components/index.tsx @@ -1,13 +1,11 @@ export * from "./ArrayOfObjectsEditor"; -export * from "./DefaultLogoCatalog"; export * from "./Label"; export * from "./LabeledControl"; export * from "./LabeledInput"; export * from "./LabeledRadioButton"; export * from "./LabeledSwitch"; -export * from "./Link"; +export * from "./common/Link"; export * from "./LoadingPage"; -export * from "./MainPageWithScroll"; +export * from "./common/MainPageWithScroll"; export * from "./SimpleTableComponents"; -export * from "./StatusIcon"; export * from "./ConnectorCard"; diff --git a/airbyte-webapp/src/components/StatusIcon/CircleLoader.module.scss b/airbyte-webapp/src/components/ui/StatusIcon/CircleLoader.module.scss similarity index 93% rename from airbyte-webapp/src/components/StatusIcon/CircleLoader.module.scss rename to airbyte-webapp/src/components/ui/StatusIcon/CircleLoader.module.scss index bb8f014fb00d..23d17402c918 100644 --- a/airbyte-webapp/src/components/StatusIcon/CircleLoader.module.scss +++ b/airbyte-webapp/src/components/ui/StatusIcon/CircleLoader.module.scss @@ -1,4 +1,4 @@ -@use "../../scss/colors"; +@use "scss/colors"; @keyframes spinning { 0% { diff --git a/airbyte-webapp/src/components/StatusIcon/CircleLoader.tsx b/airbyte-webapp/src/components/ui/StatusIcon/CircleLoader.tsx similarity index 100% rename from airbyte-webapp/src/components/StatusIcon/CircleLoader.tsx rename to airbyte-webapp/src/components/ui/StatusIcon/CircleLoader.tsx diff --git a/airbyte-webapp/src/components/StatusIcon/StatusIcon.test.tsx b/airbyte-webapp/src/components/ui/StatusIcon/StatusIcon.test.tsx similarity index 95% rename from airbyte-webapp/src/components/StatusIcon/StatusIcon.test.tsx rename to airbyte-webapp/src/components/ui/StatusIcon/StatusIcon.test.tsx index 28a7dd155337..1fafd4bcb000 100644 --- a/airbyte-webapp/src/components/StatusIcon/StatusIcon.test.tsx +++ b/airbyte-webapp/src/components/ui/StatusIcon/StatusIcon.test.tsx @@ -1,6 +1,6 @@ import { render } from "@testing-library/react"; -import StatusIcon, { StatusIconStatus } from "./StatusIcon"; +import { StatusIcon, StatusIconStatus } from "./StatusIcon"; describe("", () => { it("renders with title and default icon", () => { diff --git a/airbyte-webapp/src/components/StatusIcon/StatusIcon.tsx b/airbyte-webapp/src/components/ui/StatusIcon/StatusIcon.tsx similarity index 93% rename from airbyte-webapp/src/components/StatusIcon/StatusIcon.tsx rename to airbyte-webapp/src/components/ui/StatusIcon/StatusIcon.tsx index 80910eb63c09..de33c7d20f14 100644 --- a/airbyte-webapp/src/components/StatusIcon/StatusIcon.tsx +++ b/airbyte-webapp/src/components/ui/StatusIcon/StatusIcon.tsx @@ -4,8 +4,8 @@ import React from "react"; import styled from "styled-components"; import { MoonIcon } from "components/icons/MoonIcon"; +import { PauseIcon } from "components/icons/PauseIcon"; -import PauseIcon from "../icons/PauseIcon"; import { CircleLoader } from "./CircleLoader"; export type StatusIconStatus = "sleep" | "inactive" | "success" | "warning" | "loading" | "error"; @@ -68,7 +68,7 @@ const Value = styled.span` padding-left: 3px; `; -const StatusIcon: React.FC = ({ title, status = "error", ...props }) => { +export const StatusIcon: React.FC = ({ title, status = "error", ...props }) => { const valueElement = props.value ? {props.value} : null; if (status === "loading") { @@ -93,5 +93,3 @@ const StatusIcon: React.FC = ({ title, status = "error", ...pro ); }; - -export default StatusIcon; diff --git a/airbyte-webapp/src/components/ui/StatusIcon/index.stories.tsx b/airbyte-webapp/src/components/ui/StatusIcon/index.stories.tsx new file mode 100644 index 000000000000..35e41b7ee290 --- /dev/null +++ b/airbyte-webapp/src/components/ui/StatusIcon/index.stories.tsx @@ -0,0 +1,18 @@ +import { ComponentStory, ComponentMeta } from "@storybook/react"; + +import { StatusIcon } from "./StatusIcon"; + +export default { + title: "UI/StatusIcon", + component: StatusIcon, + argTypes: { + value: { type: { name: "number", required: false } }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Primary = Template.bind({}); +Primary.args = { + status: "success", +}; diff --git a/airbyte-webapp/src/components/ui/StatusIcon/index.ts b/airbyte-webapp/src/components/ui/StatusIcon/index.ts new file mode 100644 index 000000000000..3a026ce2e803 --- /dev/null +++ b/airbyte-webapp/src/components/ui/StatusIcon/index.ts @@ -0,0 +1 @@ +export * from "./StatusIcon"; diff --git a/airbyte-webapp/src/hooks/services/ConfirmationModal/ConfirmationModalService.tsx b/airbyte-webapp/src/hooks/services/ConfirmationModal/ConfirmationModalService.tsx index 52b3a50a9b01..a52657089f25 100644 --- a/airbyte-webapp/src/hooks/services/ConfirmationModal/ConfirmationModalService.tsx +++ b/airbyte-webapp/src/hooks/services/ConfirmationModal/ConfirmationModalService.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect, useMemo } from "react"; -import { ConfirmationModal } from "components/ConfirmationModal"; +import { ConfirmationModal } from "components/common/ConfirmationModal"; import useTypesafeReducer from "hooks/useTypesafeReducer"; diff --git a/airbyte-webapp/src/hooks/services/ConfirmationModal/types.ts b/airbyte-webapp/src/hooks/services/ConfirmationModal/types.ts index a7d4aa4619d8..e483c75e52f0 100644 --- a/airbyte-webapp/src/hooks/services/ConfirmationModal/types.ts +++ b/airbyte-webapp/src/hooks/services/ConfirmationModal/types.ts @@ -1,4 +1,4 @@ -import { ConfirmationModalProps } from "components/ConfirmationModal/ConfirmationModal"; +import { ConfirmationModalProps } from "components/common/ConfirmationModal/ConfirmationModal"; export type ConfirmationModalOptions = Omit; diff --git a/airbyte-webapp/src/packages/cloud/App.tsx b/airbyte-webapp/src/packages/cloud/App.tsx index 8e7ef654384b..f67a6ff60663 100644 --- a/airbyte-webapp/src/packages/cloud/App.tsx +++ b/airbyte-webapp/src/packages/cloud/App.tsx @@ -4,7 +4,7 @@ import { HelmetProvider } from "react-helmet-async"; import { BrowserRouter as Router } from "react-router-dom"; import { ThemeProvider } from "styled-components"; -import ApiErrorBoundary from "components/ApiErrorBoundary"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; import LoadingPage from "components/LoadingPage"; import { I18nProvider } from "core/i18n"; diff --git a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx index 03bd329eca6f..ac74dd5f3254 100644 --- a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx +++ b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx @@ -2,7 +2,7 @@ import React, { Suspense, useEffect, useMemo } from "react"; import { Navigate, Route, Routes, useLocation } from "react-router-dom"; import { useEffectOnce } from "react-use"; -import ApiErrorBoundary from "components/ApiErrorBoundary"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; import LoadingPage from "components/LoadingPage"; import { useAnalyticsIdentifyUser, useAnalyticsRegisterValues } from "hooks/services/Analytics/useAnalyticsService"; diff --git a/airbyte-webapp/src/packages/cloud/views/AcceptEmailInvite.tsx b/airbyte-webapp/src/packages/cloud/views/AcceptEmailInvite.tsx index 713b158c6241..616163a28eb4 100644 --- a/airbyte-webapp/src/packages/cloud/views/AcceptEmailInvite.tsx +++ b/airbyte-webapp/src/packages/cloud/views/AcceptEmailInvite.tsx @@ -2,7 +2,7 @@ import { Formik } from "formik"; import { FormattedMessage, useIntl } from "react-intl"; import * as yup from "yup"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { isGdprCountry } from "utils/dataPrivacy"; diff --git a/airbyte-webapp/src/packages/cloud/views/auth/LoginPage/LoginPage.tsx b/airbyte-webapp/src/packages/cloud/views/auth/LoginPage/LoginPage.tsx index 7e54e8344b15..412ede696f78 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/LoginPage/LoginPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/auth/LoginPage/LoginPage.tsx @@ -5,7 +5,7 @@ import { NavigateOptions, To, useNavigate } from "react-router-dom"; import * as yup from "yup"; import { LabeledInput, Link } from "components"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { Button } from "components/ui/Button"; import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics"; diff --git a/airbyte-webapp/src/packages/cloud/views/auth/ResetPasswordPage/ResetPasswordPage.tsx b/airbyte-webapp/src/packages/cloud/views/auth/ResetPasswordPage/ResetPasswordPage.tsx index 936bb9bf13e9..2e477ba4afb8 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/ResetPasswordPage/ResetPasswordPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/auth/ResetPasswordPage/ResetPasswordPage.tsx @@ -4,7 +4,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import * as yup from "yup"; import { LabeledInput, Link } from "components"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { Button } from "components/ui/Button"; import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics"; diff --git a/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/SignupPage.tsx b/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/SignupPage.tsx index 02c6d9782330..aec02663556d 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/SignupPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/auth/SignupPage/SignupPage.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { Heading } from "components/ui/Heading"; import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics"; diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx index 0e46bca05b77..733efafbe672 100644 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/CreditsPage.tsx @@ -1,8 +1,8 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import HeadTitle from "components/HeadTitle"; -import MainPageWithScroll from "components/MainPageWithScroll"; +import { HeadTitle } from "components/common/HeadTitle"; +import { MainPageWithScroll } from "components/common/MainPageWithScroll"; import { PageHeader } from "components/ui/PageHeader"; import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics"; diff --git a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/ConnectionCell.tsx b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/ConnectionCell.tsx index 0fe91e3f32ae..1c268ce8951b 100644 --- a/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/ConnectionCell.tsx +++ b/airbyte-webapp/src/packages/cloud/views/credits/CreditsPage/components/ConnectionCell.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React from "react"; import styled from "styled-components"; -import { ConnectorIcon } from "components/ConnectorIcon"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; interface ConnectionCellProps { sourceDefinitionName: string; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx index 5dc45c5c2ee5..081605bb7ab1 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx @@ -5,8 +5,8 @@ import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; import { LoadingPage, MainPageWithScroll } from "components"; -import { EmptyResourceListView } from "components/EmptyResourceListView"; -import HeadTitle from "components/HeadTitle"; +import { EmptyResourceListView } from "components/common/EmptyResourceListView"; +import { HeadTitle } from "components/common/HeadTitle"; import { Button } from "components/ui/Button"; import { PageHeader } from "components/ui/PageHeader"; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx index 6daba67181b9..6d3b6400ef13 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionItemPage.tsx @@ -2,7 +2,7 @@ import React, { Suspense } from "react"; import { Navigate, Route, Routes, useParams } from "react-router-dom"; import { LoadingPage, MainPageWithScroll } from "components"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { ConnectionStatus } from "core/request/AirbyteClient"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx index 79f0f5b8e56e..079a58226119 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.test.tsx @@ -27,10 +27,12 @@ jest.mock("hooks/services/ConnectionEdit/ConnectionEditService", () => ({ useConnectionEditService: () => ({ connection: mockConnection }), })); -jest.mock("components/DeleteBlock", () => () => { - const MockDeleteBlock = () =>
    Does not actually delete anything
    ; - return ; -}); +jest.mock("components/common/DeleteBlock", () => ({ + DeleteBlock: () => { + const MockDeleteBlock = () =>
    Does not actually delete anything
    ; + return ; + }, +})); describe("", () => { it("only renders connection state when advanced mode is enabled", async () => { diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx index b28518c7cfad..782d63d43c67 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionSettingsTab.tsx @@ -1,6 +1,6 @@ import React from "react"; -import DeleteBlock from "components/DeleteBlock"; +import { DeleteBlock } from "components/common/DeleteBlock"; import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics"; import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService"; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx index 04412bdfac6b..44dafe945438 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionStatusTab.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useState } from "react"; import { FormattedMessage } from "react-intl"; import { Link, useLocation } from "react-router-dom"; -import EmptyResource from "components/EmptyResourceBlock"; +import { EmptyResourceBlock } from "components/common/EmptyResourceBlock"; import { RotateIcon } from "components/icons/RotateIcon"; import { useAttemptLink } from "components/JobItem/attemptLinkUtils"; import { Button } from "components/ui/Button"; @@ -199,7 +199,7 @@ export const ConnectionStatusTab: React.FC = () => { {jobs.length ? ( ) : linkedJobNotFound ? ( - } description={ @@ -208,7 +208,7 @@ export const ConnectionStatusTab: React.FC = () => { } /> ) : ( - } /> + } /> )} {(moreJobPagesAvailable || isJobPageLoading) && ( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 18b04c2ba153..7b764cb90353 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -5,7 +5,7 @@ import { Field, Form, Formik, FieldArray, FieldProps, FormikHelpers } from "form import { Link } from "react-router-dom"; import { array, object, number } from "yup"; -import { FormChangeTracker } from "components/FormChangeTracker"; +import { FormChangeTracker } from "components/common/FormChangeTracker"; import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Input } from "components/ui/Input"; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx index e3f56400758b..5e2a2de2098a 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/CreationFormPage.tsx @@ -4,10 +4,10 @@ import { useLocation, useNavigate } from "react-router-dom"; import { LoadingPage } from "components"; import { CloudInviteUsersHint } from "components/CloudInviteUsersHint"; +import { HeadTitle } from "components/common/HeadTitle"; import ConnectionBlock from "components/ConnectionBlock"; import { FormPageContent } from "components/ConnectorBlocks"; import { CreateConnectionForm } from "components/CreateConnection/CreateConnectionForm"; -import HeadTitle from "components/HeadTitle"; import { PageHeader } from "components/ui/PageHeader"; import { StepsMenu } from "components/ui/StepsMenu"; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ExistingEntityForm.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ExistingEntityForm.tsx index b01fe9c10098..3a1a79934f82 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ExistingEntityForm.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/CreationFormPage/ExistingEntityForm.tsx @@ -4,7 +4,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import styled from "styled-components"; import * as yup from "yup"; -import { ConnectorIcon } from "components/ConnectorIcon"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; import { ControlLabels } from "components/LabeledControl"; import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx index 83bfd27f0883..f8190abc9123 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/AllDestinationsPage/AllDestinationsPage.tsx @@ -4,9 +4,9 @@ import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { EmptyResourceListView } from "components/EmptyResourceListView"; -import HeadTitle from "components/HeadTitle"; -import { MainPageWithScroll } from "components/MainPageWithScroll"; +import { EmptyResourceListView } from "components/common/EmptyResourceListView"; +import { HeadTitle } from "components/common/HeadTitle"; +import { MainPageWithScroll } from "components/common/MainPageWithScroll"; import { Button } from "components/ui/Button"; import { PageHeader } from "components/ui/PageHeader"; diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx index f0e800a652d6..6d86719ede3f 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/CreateDestinationPage.tsx @@ -3,8 +3,8 @@ import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; import { CloudInviteUsersHint } from "components/CloudInviteUsersHint"; +import { HeadTitle } from "components/common/HeadTitle"; import { FormPageContent } from "components/ConnectorBlocks"; -import HeadTitle from "components/HeadTitle"; import { PageHeader } from "components/ui/PageHeader"; import { ConnectionConfiguration } from "core/domain/connection"; diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx index a87d607d2f10..849453db15aa 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx @@ -3,10 +3,10 @@ import { FormattedMessage } from "react-intl"; import { Route, Routes, useNavigate, useParams } from "react-router-dom"; import { LoadingPage } from "components"; -import ApiErrorBoundary from "components/ApiErrorBoundary"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; +import { HeadTitle } from "components/common/HeadTitle"; import { ItemTabs, StepsTypes, TableItemTitle } from "components/ConnectorBlocks"; -import { ConnectorIcon } from "components/ConnectorIcon"; -import HeadTitle from "components/HeadTitle"; import Placeholder, { ResourceTypes } from "components/Placeholder"; import { Breadcrumbs } from "components/ui/Breadcrumbs"; import { DropDownOptionDataItem } from "components/ui/DropDown"; diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/components/DestinationSettings.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/components/DestinationSettings.tsx index 84140aad0f39..018a0b2bfbe6 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/components/DestinationSettings.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/components/DestinationSettings.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import DeleteBlock from "components/DeleteBlock"; +import { DeleteBlock } from "components/common/DeleteBlock"; import { ConnectionConfiguration } from "core/domain/connection"; import { Connector } from "core/domain/connector"; diff --git a/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx b/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx index b45a2faab7c8..d9ad112c49f8 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/OnboardingPage.tsx @@ -3,8 +3,8 @@ import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; import styled from "styled-components"; -import ApiErrorBoundary from "components/ApiErrorBoundary"; -import HeadTitle from "components/HeadTitle"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; +import { HeadTitle } from "components/common/HeadTitle"; import LoadingPage from "components/LoadingPage"; import { Button } from "components/ui/Button"; diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/ProgressBlock.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/ProgressBlock.tsx index 3c5d4109f3ea..c417f0e99868 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/ProgressBlock.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/ProgressBlock.tsx @@ -4,7 +4,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import styled, { keyframes } from "styled-components"; -import Link from "components/Link"; +import { Link } from "components/common/Link"; import { Button } from "components/ui/Button"; import { Heading } from "components/ui/Heading"; diff --git a/airbyte-webapp/src/pages/PreferencesPage/PreferencesPage.tsx b/airbyte-webapp/src/pages/PreferencesPage/PreferencesPage.tsx index 908f4ae72e62..3762c4fbdd58 100644 --- a/airbyte-webapp/src/pages/PreferencesPage/PreferencesPage.tsx +++ b/airbyte-webapp/src/pages/PreferencesPage/PreferencesPage.tsx @@ -1,8 +1,8 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import { PageViewContainer } from "components/CenteredPageComponents"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; +import { PageViewContainer } from "components/common/PageViewContainer"; import { Heading } from "components/ui/Heading"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; diff --git a/airbyte-webapp/src/pages/SettingsPage/SettingsPage.tsx b/airbyte-webapp/src/pages/SettingsPage/SettingsPage.tsx index 930ed1446a8f..733ee6344475 100644 --- a/airbyte-webapp/src/pages/SettingsPage/SettingsPage.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/SettingsPage.tsx @@ -2,9 +2,9 @@ import React, { Suspense } from "react"; import { FormattedMessage } from "react-intl"; import { Navigate, Route, Routes, useLocation, useNavigate } from "react-router-dom"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; +import { MainPageWithScroll } from "components/common/MainPageWithScroll"; import LoadingPage from "components/LoadingPage"; -import MainPageWithScroll from "components/MainPageWithScroll"; import { PageHeader } from "components/ui/PageHeader"; import { SideMenu, CategoryItem, SideMenuItem } from "components/ui/SideMenu"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/AccountPage/AccountPage.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/AccountPage/AccountPage.tsx index 4abc5c2b48b2..3d177eacdde8 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/AccountPage/AccountPage.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/AccountPage/AccountPage.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import useWorkspaceEditor from "pages/SettingsPage/components/useWorkspaceEditor"; import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/ConfigurationsPage/ConfigurationsPage.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/ConfigurationsPage/ConfigurationsPage.tsx index 2582b07307ec..5193948e0892 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/ConfigurationsPage/ConfigurationsPage.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/ConfigurationsPage/ConfigurationsPage.tsx @@ -2,7 +2,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import styled from "styled-components"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { Card } from "components/ui/Card"; import LogsContent from "./components/LogsContent"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/ConnectorsView.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/ConnectorsView.tsx index d09be4d7ae7d..544b10beadc4 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/ConnectorsView.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/ConnectorsView.tsx @@ -2,7 +2,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import { CellProps } from "react-table"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { Table } from "components/ui/Table"; import { Connector, ConnectorDefinition } from "core/domain/connector"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/CreateConnectorModal.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/CreateConnectorModal.tsx index eb0bb7e51245..1ed9f9abe421 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/CreateConnectorModal.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/ConnectorsPage/components/CreateConnectorModal.tsx @@ -4,9 +4,10 @@ import { FormattedMessage, useIntl } from "react-intl"; import styled from "styled-components"; import * as yup from "yup"; -import { LabeledInput, Link, StatusIcon } from "components"; +import { LabeledInput, Link } from "components"; import { Button } from "components/ui/Button"; import { Modal } from "components/ui/Modal"; +import { StatusIcon } from "components/ui/StatusIcon"; import { links } from "utils/links"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/MetricsPage/MetricsPage.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/MetricsPage/MetricsPage.tsx index 957261bd6405..b570964ff6eb 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/MetricsPage/MetricsPage.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/MetricsPage/MetricsPage.tsx @@ -1,7 +1,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx index 10e81d65a9a7..655b56dbe884 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState, useCallback } from "react"; import { FormattedMessage } from "react-intl"; -import HeadTitle from "components/HeadTitle"; +import { HeadTitle } from "components/common/HeadTitle"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; import useWorkspace, { useCurrentWorkspace, WebhookPayload } from "hooks/services/useWorkspace"; diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx index 199dbec04fdb..8ed8075c28df 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/AllSourcesPage/AllSourcesPage.tsx @@ -4,9 +4,9 @@ import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; import { useNavigate } from "react-router-dom"; -import { EmptyResourceListView } from "components/EmptyResourceListView"; -import HeadTitle from "components/HeadTitle"; -import { MainPageWithScroll } from "components/MainPageWithScroll"; +import { EmptyResourceListView } from "components/common/EmptyResourceListView"; +import { HeadTitle } from "components/common/HeadTitle"; +import { MainPageWithScroll } from "components/common/MainPageWithScroll"; import { Button } from "components/ui/Button"; import { PageHeader } from "components/ui/PageHeader"; diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx index a5fc4ddf9947..6ba95a10107b 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/CreateSourcePage.tsx @@ -3,8 +3,8 @@ import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; import { CloudInviteUsersHint } from "components/CloudInviteUsersHint"; +import { HeadTitle } from "components/common/HeadTitle"; import { FormPageContent } from "components/ConnectorBlocks"; -import HeadTitle from "components/HeadTitle"; import { PageHeader } from "components/ui/PageHeader"; import { ConnectionConfiguration } from "core/domain/connection"; diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx index 03949f1f8982..a5ea2816e196 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx @@ -2,10 +2,10 @@ import React, { Suspense, useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { Route, Routes, useNavigate, useParams } from "react-router-dom"; -import ApiErrorBoundary from "components/ApiErrorBoundary"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; +import { HeadTitle } from "components/common/HeadTitle"; import { ItemTabs, StepsTypes, TableItemTitle } from "components/ConnectorBlocks"; -import { ConnectorIcon } from "components/ConnectorIcon"; -import HeadTitle from "components/HeadTitle"; import LoadingPage from "components/LoadingPage"; import Placeholder, { ResourceTypes } from "components/Placeholder"; import { Breadcrumbs } from "components/ui/Breadcrumbs"; diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/components/SourceSettings.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/components/SourceSettings.tsx index 5ec2b21d2c1f..854bd757d015 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/components/SourceSettings.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/components/SourceSettings.tsx @@ -1,7 +1,7 @@ import React, { useEffect } from "react"; import { FormattedMessage } from "react-intl"; -import DeleteBlock from "components/DeleteBlock"; +import { DeleteBlock } from "components/common/DeleteBlock"; import { ConnectionConfiguration } from "core/domain/connection"; import { SourceRead, WebBackendConnectionListItem } from "core/request/AirbyteClient"; diff --git a/airbyte-webapp/src/pages/routes.tsx b/airbyte-webapp/src/pages/routes.tsx index ba76c4c73df1..dd5676e4142e 100644 --- a/airbyte-webapp/src/pages/routes.tsx +++ b/airbyte-webapp/src/pages/routes.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from "react"; import { Navigate, Route, Routes, useLocation } from "react-router-dom"; import { useEffectOnce } from "react-use"; -import ApiErrorBoundary from "components/ApiErrorBoundary"; +import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; import { useAnalyticsIdentifyUser, useAnalyticsRegisterValues } from "hooks/services/Analytics"; import { useApiHealthPoll } from "hooks/services/Health"; diff --git a/airbyte-webapp/src/utils/imageUtils.tsx b/airbyte-webapp/src/utils/imageUtils.tsx index 962a719181f6..412c729c13dd 100644 --- a/airbyte-webapp/src/utils/imageUtils.tsx +++ b/airbyte-webapp/src/utils/imageUtils.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; -import { DefaultLogoCatalog } from "components"; +import { DefaultLogoCatalog } from "components/common/DefaultLogoCatalog"; const IconContainer = styled.img` height: 100%; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx index 798c4faa4f79..ed798f7a2fef 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx @@ -7,7 +7,7 @@ import { FormattedMessage, useIntl } from "react-intl"; import { useUnmount } from "react-use"; import { ControlLabels } from "components"; -import { FormChangeTracker } from "components/FormChangeTracker"; +import { FormChangeTracker } from "components/common/FormChangeTracker"; import { Button } from "components/ui/Button"; import { Heading } from "components/ui/Heading"; import { Input } from "components/ui/Input"; diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/CreateControls.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/CreateControls.tsx index b2c221e4d5fd..0ce9dfcb5d90 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/CreateControls.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/CreateControls.tsx @@ -2,9 +2,9 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import styled from "styled-components"; -import { StatusIcon } from "components/StatusIcon"; import { Button } from "components/ui/Button"; import { Spinner } from "components/ui/Spinner"; +import { StatusIcon } from "components/ui/StatusIcon"; interface CreateControlsProps { isSubmitting: boolean; diff --git a/airbyte-webapp/src/views/Connection/FormCard.tsx b/airbyte-webapp/src/views/Connection/FormCard.tsx index 1df3f5c02954..b89d8903e9e6 100644 --- a/airbyte-webapp/src/views/Connection/FormCard.tsx +++ b/airbyte-webapp/src/views/Connection/FormCard.tsx @@ -3,7 +3,7 @@ import React from "react"; import { useIntl } from "react-intl"; import { useMutation } from "react-query"; -import { FormChangeTracker } from "components/FormChangeTracker"; +import { FormChangeTracker } from "components/common/FormChangeTracker"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { generateMessageFromError } from "utils/errorStatusMessage"; diff --git a/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx b/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx index 389ecff8df23..f4f31d2a06f1 100644 --- a/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx +++ b/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx @@ -4,7 +4,7 @@ import { getIn, useFormik } from "formik"; import React from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import { FormChangeTracker } from "components/FormChangeTracker"; +import { FormChangeTracker } from "components/common/FormChangeTracker"; import { ControlLabels } from "components/LabeledControl"; import { Button } from "components/ui/Button"; import { DropDown } from "components/ui/DropDown"; diff --git a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx index f81ca5a38989..8c081de050c4 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx @@ -9,7 +9,7 @@ import styles from "./ConnectorDocumentationLayout.module.scss"; import { useDocumentationPanelContext } from "./DocumentationPanelContext"; const LazyDocumentationPanel = lazy(() => - import("components/DocumentationPanel").then(({ DocumentationPanel }) => ({ default: DocumentationPanel })) + import("components/common/DocumentationPanel").then(({ DocumentationPanel }) => ({ default: DocumentationPanel })) ); export const ConnectorDocumentationLayout: React.FC> = ({ children }) => { diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx index fa8251f1b293..7d5e160fb132 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx @@ -3,7 +3,7 @@ import { JSONSchema7 } from "json-schema"; import React, { useCallback, useEffect, useMemo } from "react"; import { useDeepCompareEffect } from "react-use"; -import { FormChangeTracker } from "components/FormChangeTracker"; +import { FormChangeTracker } from "components/common/FormChangeTracker"; import { ConnectorDefinition, ConnectorDefinitionSpecification } from "core/domain/connector"; import { isDestinationDefinitionSpecification } from "core/domain/connector/destination"; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx index 8da9b212581c..6c6c05d57191 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl/utils.tsx @@ -1,4 +1,4 @@ -import { ConnectorIcon } from "components/ConnectorIcon"; +import { ConnectorIcon } from "components/common/ConnectorIcon"; import { Connector, ConnectorDefinition } from "core/domain/connector"; import { ReleaseStage } from "core/request/AirbyteClient"; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionError.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionError.tsx index 4a010704ef14..f88f3e9ef602 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionError.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionError.tsx @@ -2,7 +2,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import styled from "styled-components"; -import { StatusIcon } from "components"; +import { StatusIcon } from "components/ui/StatusIcon"; const Error = styled(StatusIcon)` padding-left: 1px; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSuccess.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSuccess.tsx index 7197c75bc3dd..ec07619a3590 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSuccess.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/TestingConnectionSuccess.tsx @@ -2,7 +2,7 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import styled from "styled-components"; -import { StatusIcon } from "components"; +import { StatusIcon } from "components/ui/StatusIcon"; const LoadingContainer = styled.div` font-weight: 600; diff --git a/airbyte-webapp/src/views/layout/SideBar/SideBar.tsx b/airbyte-webapp/src/views/layout/SideBar/SideBar.tsx index 6c033332eb94..3e3cedc817c6 100644 --- a/airbyte-webapp/src/views/layout/SideBar/SideBar.tsx +++ b/airbyte-webapp/src/views/layout/SideBar/SideBar.tsx @@ -7,8 +7,8 @@ import { FormattedMessage } from "react-intl"; import { NavLink, useLocation } from "react-router-dom"; import { Link } from "components"; +import { Version } from "components/common/Version"; import { Text } from "components/ui/Text"; -import Version from "components/Version"; import { useConfig } from "config"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; From 0ee930678b769f02a0be327fe7ec57cb71276d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Bravo?= Date: Tue, 1 Nov 2022 17:18:10 +0100 Subject: [PATCH 462/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20Public?= =?UTF-8?q?=20APIs=20[python=20CDK]=20(#18471)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ airbytehq/connector-contest#91 ] Add Source Public APIs * [ airbytehq/connector-contest#91 ] Add Source Bootstrap.md * [ airbytehq/connector-contest#91 ] Add Source documentation * [ airbytehq/connector-contest#91 ] Add Source Public APIs * [ airbytehq/connector-contest#91 ] Add Source Bootstrap.md * [ airbytehq/connector-contest#91 ] Add Source documentation * [ airbytehq/connector-contest#91 ] Remove uneeded incremental class and tests, updated doc * [ airbytehq/connector-contest#91 ] Remove uneeded incremental class and tests, updated doc * [ airbytehq/connector-contest#91 ] Fix pflake8 offenses * run airbytePythonFormat on source-public-apis, and remove test_incremental_streams.py * fix: generate and add source definitions * - update documentationUrl; - add new lint to eof * auto-bump connector version * correct documentationUrl in source_specs.yaml Co-authored-by: Yiyang Li Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 7 + .../src/main/resources/seed/source_specs.yaml | 12 ++ airbyte-integrations/builds.md | 1 + .../source-public-apis/.dockerignore | 6 + .../connectors/source-public-apis/Dockerfile | 38 +++++ .../connectors/source-public-apis/README.md | 132 ++++++++++++++++++ .../acceptance-test-config.yml | 27 ++++ .../acceptance-test-docker.sh | 16 +++ .../source-public-apis/bootstrap.md | 19 +++ .../source-public-apis/build.gradle | 9 ++ .../integration_tests/__init__.py | 3 + .../integration_tests/acceptance.py | 16 +++ .../integration_tests/configured_catalog.json | 22 +++ .../integration_tests/invalid_config.json | 3 + .../integration_tests/sample_config.json | 1 + .../connectors/source-public-apis/main.py | 13 ++ .../source-public-apis/requirements.txt | 2 + .../sample_files/config.json | 1 + .../sample_files/configured_catalog.json | 22 +++ .../sample_files/invalid_config.json | 3 + .../connectors/source-public-apis/setup.py | 29 ++++ .../source_public_apis/__init__.py | 8 ++ .../schemas/categories.json | 9 ++ .../source_public_apis/schemas/services.json | 27 ++++ .../source_public_apis/source.py | 85 +++++++++++ .../source_public_apis/spec.yaml | 7 + .../source-public-apis/unit_tests/__init__.py | 3 + .../unit_tests/test_source.py | 21 +++ .../unit_tests/test_streams.py | 75 ++++++++++ docs/integrations/README.md | 1 + docs/integrations/sources/public-apis.md | 46 ++++++ 31 files changed, 664 insertions(+) create mode 100644 airbyte-integrations/connectors/source-public-apis/.dockerignore create mode 100644 airbyte-integrations/connectors/source-public-apis/Dockerfile create mode 100644 airbyte-integrations/connectors/source-public-apis/README.md create mode 100644 airbyte-integrations/connectors/source-public-apis/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-public-apis/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-public-apis/bootstrap.md create mode 100644 airbyte-integrations/connectors/source-public-apis/build.gradle create mode 100644 airbyte-integrations/connectors/source-public-apis/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-public-apis/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-public-apis/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-public-apis/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-public-apis/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-public-apis/main.py create mode 100644 airbyte-integrations/connectors/source-public-apis/requirements.txt create mode 100644 airbyte-integrations/connectors/source-public-apis/sample_files/config.json create mode 100644 airbyte-integrations/connectors/source-public-apis/sample_files/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-public-apis/sample_files/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-public-apis/setup.py create mode 100644 airbyte-integrations/connectors/source-public-apis/source_public_apis/__init__.py create mode 100644 airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/categories.json create mode 100644 airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/services.json create mode 100644 airbyte-integrations/connectors/source-public-apis/source_public_apis/source.py create mode 100644 airbyte-integrations/connectors/source-public-apis/source_public_apis/spec.yaml create mode 100644 airbyte-integrations/connectors/source-public-apis/unit_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-public-apis/unit_tests/test_source.py create mode 100644 airbyte-integrations/connectors/source-public-apis/unit_tests/test_streams.py create mode 100644 docs/integrations/sources/public-apis.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index dace40db9293..be57d698655e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -992,6 +992,13 @@ icon: primetric.svg sourceType: api releaseStage: alpha +- name: Public APIs + sourceDefinitionId: a4617b39-3c14-44cd-a2eb-6e720f269235 + dockerRepository: airbyte/source-public-apis + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/public-apis + sourceType: api + releaseStage: alpha - name: Qualaroo sourceDefinitionId: b08e4776-d1de-4e80-ab5c-1e51dad934a2 dockerRepository: airbyte/source-qualaroo diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 9d52680a839b..ca90292a4584 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -9923,6 +9923,18 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-public-apis:0.1.0" + spec: + documentationUrl: "https://docs.airbyte.com/integrations/sources/public-apis" + connectionSpecification: + $schema: "http://json-schema.org/draft-07/schema#" + title: "Public Apis Spec" + type: "object" + required: [] + properties: {} + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-qualaroo:0.1.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/qualaroo" diff --git a/airbyte-integrations/builds.md b/airbyte-integrations/builds.md index ab4c3417064d..4ded50736728 100644 --- a/airbyte-integrations/builds.md +++ b/airbyte-integrations/builds.md @@ -93,6 +93,7 @@ | Posthog | [![source-posthog](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-posthog%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-posthog) | | PrestaShop | [![source-prestashop](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-prestashop%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-prestashop) | | Primetric | [![source-primetric](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-primetric%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-primetric) | +| Public APIs | [![source-public-apis](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-public-apis%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-public-apis) | | CockroachDb | [![source-cockroachdb](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-cockroachdb%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-cockroachdb) | | Confluence | [![source-confluence](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-confluence%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-confluence) | | Qualaroo | [![source-qualaroo](https://img.shields.io/endpoint?url=https%3A%2F%2Fdnsgjos7lj2fu.cloudfront.net%2Ftests%2Fsummary%2Fsource-qualaroo%2Fbadge.json)](https://dnsgjos7lj2fu.cloudfront.net/tests/summary/source-qualaroo) | diff --git a/airbyte-integrations/connectors/source-public-apis/.dockerignore b/airbyte-integrations/connectors/source-public-apis/.dockerignore new file mode 100644 index 000000000000..b50539605105 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_public_apis +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-public-apis/Dockerfile b/airbyte-integrations/connectors/source-public-apis/Dockerfile new file mode 100644 index 000000000000..9dc3b03c8017 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.13-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_public_apis ./source_public_apis + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-public-apis diff --git a/airbyte-integrations/connectors/source-public-apis/README.md b/airbyte-integrations/connectors/source-public-apis/README.md new file mode 100644 index 000000000000..743520456851 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/README.md @@ -0,0 +1,132 @@ +# Public Apis Source + +This is the repository for the Public Apis source connector, written in Python. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/public-apis). + +## Local development + +### Prerequisites +**To iterate on this connector, make sure to complete this prerequisites section.** + +#### Minimum Python version required `= 3.9.0` + +#### Build & Activate Virtual Environment and install dependencies +From this connector directory, create a virtual environment: +``` +python -m venv .venv +``` + +This will generate a virtualenv for this module in `.venv/`. Make sure this venv is active in your +development environment of choice. To activate it from the terminal, run: +``` +source .venv/bin/activate +pip install -r requirements.txt +pip install '.[tests]' +``` +If you are in an IDE, follow your IDE's instructions to activate the virtualenv. + +Note that while we are installing dependencies from `requirements.txt`, you should only edit `setup.py` for your dependencies. `requirements.txt` is +used for editable installs (`pip install -e`) to pull in Python dependencies from the monorepo and will call `setup.py`. +If this is mumbo jumbo to you, don't worry about it, just put your deps in `setup.py` but install using `pip install -r requirements.txt` and everything +should work as you expect. + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-public-apis:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/public-apis) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_public_apis/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source public-apis test creds` +and place them into `secrets/config.json`. + +### Locally running the connector +``` +python main.py spec +python main.py check --config secrets/config.json +python main.py discover --config secrets/config.json +python main.py read --config secrets/config.json --catalog integration_tests/configured_catalog.json +``` + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-public-apis:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-public-apis:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-public-apis:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-public-apis:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-public-apis:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-public-apis:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing +Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. +First install test dependencies into your virtual environment: +``` +pip install .[tests] +``` +### Unit Tests +To run unit tests locally, from the connector directory run: +``` +python -m pytest unit_tests +``` + +### Integration Tests +There are two types of integration tests: Acceptance Tests (Airbyte's test suite for all source connectors) and custom integration tests (which are specific to this connector). +#### Custom Integration tests +Place custom tests inside `integration_tests/` folder, then, from the connector root, run +``` +python -m pytest integration_tests +``` +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. +To run your integration tests with acceptance tests, from the connector root, run +``` +python -m pytest integration_tests -p integration_tests.acceptance +``` +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-public-apis:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-public-apis:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-public-apis/acceptance-test-config.yml b/airbyte-integrations/connectors/source-public-apis/acceptance-test-config.yml new file mode 100644 index 000000000000..ac96606aa111 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/acceptance-test-config.yml @@ -0,0 +1,27 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-public-apis:dev +acceptance_tests: + spec: + tests: + - spec_path: "source_public_apis/spec.yaml" + connection: + tests: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + tests: + - config_path: "secrets/config.json" + basic_read: + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + incremental: + bypass_reason: "This connector does not implement incremental sync" + full_refresh: + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" diff --git a/airbyte-integrations/connectors/source-public-apis/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-public-apis/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-public-apis/bootstrap.md b/airbyte-integrations/connectors/source-public-apis/bootstrap.md new file mode 100644 index 000000000000..57d2529e8494 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/bootstrap.md @@ -0,0 +1,19 @@ +## Streams + +[Public APIs](https://api.publicapis.org/) is a REST API without authentication. Connector has the following streams, and none of them support incremental refresh. + +* [Services](https://api.publicapis.org#get-entries) +* [Categories](https://api.publicapis.org#get-categories) + + +## Pagination + +[Public APIs](https://api.publicapis.org/) uses NO pagination. + +## Properties + +The connector configuration includes NO properties as this is a publi API without authentication. + +## Authentication + +[Public APIs](https://api.publicapis.org/) is a REST API without authentication. \ No newline at end of file diff --git a/airbyte-integrations/connectors/source-public-apis/build.gradle b/airbyte-integrations/connectors/source-public-apis/build.gradle new file mode 100644 index 000000000000..70f478b82a1c --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_public_apis' +} diff --git a/airbyte-integrations/connectors/source-public-apis/integration_tests/__init__.py b/airbyte-integrations/connectors/source-public-apis/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-public-apis/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-public-apis/integration_tests/acceptance.py new file mode 100644 index 000000000000..1302b2f57e10 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/integration_tests/acceptance.py @@ -0,0 +1,16 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + # TODO: setup test dependencies if needed. otherwise remove the TODO comments + yield + # TODO: clean up test dependencies diff --git a/airbyte-integrations/connectors/source-public-apis/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-public-apis/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..414979f92e93 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/integration_tests/configured_catalog.json @@ -0,0 +1,22 @@ +{ + "streams": [ + { + "stream": { + "name": "services", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "categories", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-public-apis/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-public-apis/integration_tests/invalid_config.json new file mode 100644 index 000000000000..5a47475c1995 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/integration_tests/invalid_config.json @@ -0,0 +1,3 @@ +{ + "invalid": "config_key" +} diff --git a/airbyte-integrations/connectors/source-public-apis/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-public-apis/integration_tests/sample_config.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/integration_tests/sample_config.json @@ -0,0 +1 @@ +{} diff --git a/airbyte-integrations/connectors/source-public-apis/main.py b/airbyte-integrations/connectors/source-public-apis/main.py new file mode 100644 index 000000000000..044c327b2da8 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_public_apis import SourcePublicApis + +if __name__ == "__main__": + source = SourcePublicApis() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-public-apis/requirements.txt b/airbyte-integrations/connectors/source-public-apis/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-public-apis/sample_files/config.json b/airbyte-integrations/connectors/source-public-apis/sample_files/config.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/sample_files/config.json @@ -0,0 +1 @@ +{} diff --git a/airbyte-integrations/connectors/source-public-apis/sample_files/configured_catalog.json b/airbyte-integrations/connectors/source-public-apis/sample_files/configured_catalog.json new file mode 100644 index 000000000000..414979f92e93 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/sample_files/configured_catalog.json @@ -0,0 +1,22 @@ +{ + "streams": [ + { + "stream": { + "name": "services", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "categories", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-public-apis/sample_files/invalid_config.json b/airbyte-integrations/connectors/source-public-apis/sample_files/invalid_config.json new file mode 100644 index 000000000000..5a47475c1995 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/sample_files/invalid_config.json @@ -0,0 +1,3 @@ +{ + "invalid": "config_key" +} diff --git a/airbyte-integrations/connectors/source-public-apis/setup.py b/airbyte-integrations/connectors/source-public-apis/setup.py new file mode 100644 index 000000000000..aa18141af671 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.2", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_public_apis", + description="Source implementation for Public Apis.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-public-apis/source_public_apis/__init__.py b/airbyte-integrations/connectors/source-public-apis/source_public_apis/__init__.py new file mode 100644 index 000000000000..cad0a5ef03b7 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/source_public_apis/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourcePublicApis + +__all__ = ["SourcePublicApis"] diff --git a/airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/categories.json b/airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/categories.json new file mode 100644 index 000000000000..a90ed1fede99 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/categories.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "name": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/services.json b/airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/services.json new file mode 100644 index 000000000000..63823694a556 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/source_public_apis/schemas/services.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "API": { + "type": ["null", "string"] + }, + "Description": { + "type": ["null", "string"] + }, + "Auth": { + "type": ["null", "string"] + }, + "HTTPS": { + "type": ["null", "boolean"] + }, + "Cors": { + "type": ["null", "string"] + }, + "Link": { + "type": ["null", "string"] + }, + "Category": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-public-apis/source_public_apis/source.py b/airbyte-integrations/connectors/source-public-apis/source_public_apis/source.py new file mode 100644 index 000000000000..8bd3dde04d93 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/source_public_apis/source.py @@ -0,0 +1,85 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from abc import ABC +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Tuple + +import requests +from airbyte_cdk.sources import AbstractSource +from airbyte_cdk.sources.streams import Stream +from airbyte_cdk.sources.streams.http import HttpStream +from airbyte_cdk.sources.streams.http.auth import NoAuth + + +class PublicApisStream(HttpStream, ABC): + url_base = "https://api.publicapis.org/" + + def next_page_token(self, response: requests.Response) -> Optional[Mapping[str, Any]]: + return None + + def request_params( + self, stream_state: Mapping[str, Any], stream_slice: Mapping[str, any] = None, next_page_token: Mapping[str, Any] = None + ) -> MutableMapping[str, Any]: + return {} + + def parse_response(self, response: requests.Response, **kwargs) -> Iterable[Mapping]: + yield {} + + +class Categories(PublicApisStream): + primary_key = "name" + + def path( + self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None + ) -> str: + return "categories" + + def parse_response( + self, + response: requests.Response, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, + ) -> Iterable[Mapping]: + return [{"name": cat} for cat in response.json()["categories"]] + + +class Services(PublicApisStream): + primary_key = "API" + + def path( + self, stream_state: Mapping[str, Any] = None, stream_slice: Mapping[str, Any] = None, next_page_token: Mapping[str, Any] = None + ) -> str: + return "entries" + + def parse_response( + self, + response: requests.Response, + stream_state: Mapping[str, Any], + stream_slice: Mapping[str, Any] = None, + next_page_token: Mapping[str, Any] = None, + ) -> Iterable[Mapping]: + return response.json()["entries"] + + +# Source +class SourcePublicApis(AbstractSource): + def check_connection(self, logger, config) -> Tuple[bool, any]: + """ + :param config: the user-input config object conforming to the connector's spec.yaml + :param logger: logger object + :return Tuple[bool, any]: (True, None) if the input config can be used to connect to the API successfully, (False, error) otherwise. + """ + if len(config) == 0: + return True, None + else: + return False, None + + def streams(self, config: Mapping[str, Any]) -> List[Stream]: + """ + :param config: A Mapping of the user input configuration as defined in the connector spec. + """ + auth = NoAuth() + return [Services(authenticator=auth), Categories(authenticator=auth)] diff --git a/airbyte-integrations/connectors/source-public-apis/source_public_apis/spec.yaml b/airbyte-integrations/connectors/source-public-apis/source_public_apis/spec.yaml new file mode 100644 index 000000000000..dce7f95cc6c1 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/source_public_apis/spec.yaml @@ -0,0 +1,7 @@ +documentationUrl: https://docs.airbyte.com/integrations/sources/public-apis +connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Public Apis Spec + type: object + required: [] + properties: {} diff --git a/airbyte-integrations/connectors/source-public-apis/unit_tests/__init__.py b/airbyte-integrations/connectors/source-public-apis/unit_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/unit_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-public-apis/unit_tests/test_source.py b/airbyte-integrations/connectors/source-public-apis/unit_tests/test_source.py new file mode 100644 index 000000000000..3c4a8a060336 --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/unit_tests/test_source.py @@ -0,0 +1,21 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from unittest.mock import MagicMock + +from source_public_apis.source import SourcePublicApis + + +def test_check_connection(mocker): + source = SourcePublicApis() + logger_mock, config_mock = MagicMock(), MagicMock() + assert source.check_connection(logger_mock, config_mock) == (True, None) + + +def test_streams(mocker): + source = SourcePublicApis() + config_mock = MagicMock() + streams = source.streams(config_mock) + expected_streams_number = 2 + assert len(streams) == expected_streams_number diff --git a/airbyte-integrations/connectors/source-public-apis/unit_tests/test_streams.py b/airbyte-integrations/connectors/source-public-apis/unit_tests/test_streams.py new file mode 100644 index 000000000000..97ad3a22469b --- /dev/null +++ b/airbyte-integrations/connectors/source-public-apis/unit_tests/test_streams.py @@ -0,0 +1,75 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from http import HTTPStatus +from unittest.mock import MagicMock + +import pytest +from source_public_apis.source import PublicApisStream + + +@pytest.fixture +def patch_base_class(mocker): + # Mock abstract methods to enable instantiating abstract class + mocker.patch.object(PublicApisStream, "path", "v0/example_endpoint") + mocker.patch.object(PublicApisStream, "primary_key", "test_primary_key") + mocker.patch.object(PublicApisStream, "__abstractmethods__", set()) + + +def test_request_params(patch_base_class): + stream = PublicApisStream() + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_params = {} + assert stream.request_params(**inputs) == expected_params + + +def test_next_page_token(patch_base_class): + stream = PublicApisStream() + inputs = {"response": MagicMock()} + expected_token = None + assert stream.next_page_token(**inputs) == expected_token + + +def test_parse_response(patch_base_class): + stream = PublicApisStream() + inputs = {"response": MagicMock()} + expected_parsed_object = {} + assert next(stream.parse_response(**inputs)) == expected_parsed_object + + +def test_request_headers(patch_base_class): + stream = PublicApisStream() + inputs = {"stream_slice": None, "stream_state": None, "next_page_token": None} + expected_headers = {} + assert stream.request_headers(**inputs) == expected_headers + + +def test_http_method(patch_base_class): + stream = PublicApisStream() + # TODO: replace this with your expected http request method + expected_method = "GET" + assert stream.http_method == expected_method + + +@pytest.mark.parametrize( + ("http_status", "should_retry"), + [ + (HTTPStatus.OK, False), + (HTTPStatus.BAD_REQUEST, False), + (HTTPStatus.TOO_MANY_REQUESTS, True), + (HTTPStatus.INTERNAL_SERVER_ERROR, True), + ], +) +def test_should_retry(patch_base_class, http_status, should_retry): + response_mock = MagicMock() + response_mock.status_code = http_status + stream = PublicApisStream() + assert stream.should_retry(response_mock) == should_retry + + +def test_backoff_time(patch_base_class): + response_mock = MagicMock() + stream = PublicApisStream() + expected_backoff_time = None + assert stream.backoff_time(response_mock) == expected_backoff_time diff --git a/docs/integrations/README.md b/docs/integrations/README.md index e14054264f7a..77d113dc87b2 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -146,6 +146,7 @@ For more information about the grading system, see [Product Release Stages](http | [Postgres](sources/postgres.md) | Generally Available | Yes | | [PostHog](sources/posthog.md) | Alpha | Yes | | [PrestaShop](sources/presta-shop.md) | Alpha | Yes | +| [Public APIs](sources/public-apis.md) | Alpha | Yes | | [Qualaroo](sources/qualaroo.md) | Alpha | Yes | | [QuickBooks](sources/quickbooks.md) | Alpha | No | | [RD Station Marketing](sources/rd-station-marketing.md) | Alpha | No | diff --git a/docs/integrations/sources/public-apis.md b/docs/integrations/sources/public-apis.md new file mode 100644 index 000000000000..c4c9ebaff643 --- /dev/null +++ b/docs/integrations/sources/public-apis.md @@ -0,0 +1,46 @@ +# Public APIs + +## Sync overview + +This source can sync data for the [Public APIs](https://api.publicapis.org/) REST API. It supports only Full Refresh syncs. + +### Output schema + +This Source is capable of syncing the following Streams: + +* [Services](https://api.publicapis.org#get-entries) +* [Categories](https://api.publicapis.org#get-categories) + +### Data type mapping + +| Integration Type | Airbyte Type | Notes | +| :--- | :--- | :--- | +| `string` | `string` | | +| `integer`, `number` | `number` | | +| `boolean` | `boolean` | | + +### Features + +| Feature | Supported?\(Yes/No\) | Notes | +| :--- | :--- | :--- | +| Full Refresh Sync | Yes | | +| Incremental Sync | No | | +| SSL connection | Yes | +| Namespaces | No | | +| Pagination | No | | + +## Getting started + +### Requirements + +There is no requirements to setup this source. + +### Setup guide + +This source requires no setup. + +## Changelog + +| Version | Date | Pull Request | Subject | +| :--- | :--- | :--- | :--- | +| 0.1.0 | 2022-10-28 | [18471](https://github.com/airbytehq/airbyte/pull/18471) | Initial Release | From aff9963a9fefe420f5c65afaed71adfc5325b500 Mon Sep 17 00:00:00 2001 From: Haithem SOUALA Date: Tue, 1 Nov 2022 17:18:37 +0100 Subject: [PATCH 463/498] =?UTF-8?q?=F0=9F=8E=89=20New=20Source:=20Coinmark?= =?UTF-8?q?etcap=20[low-code=20CDK]=20(#18565)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * init commit * add docs * add docs * Delete logs.txt * add items * fix comments * fix comment * fix acceptance test * remove *state.json used for incremental imports test * Add ignored_fields on listing to get the acceptance test pass - the crypto market is VERY volatile, the data change between 2 full imports when the test is run * manually generate source_specs.yaml for coinmarketcap Co-authored-by: Yiyang Li --- .../resources/seed/source_definitions.yaml | 7 ++ .../src/main/resources/seed/source_specs.yaml | 44 ++++++++++ .../source-coinmarketcap/.dockerignore | 6 ++ .../source-coinmarketcap/Dockerfile | 38 ++++++++ .../connectors/source-coinmarketcap/README.md | 79 +++++++++++++++++ .../source-coinmarketcap/__init__.py | 3 + .../acceptance-test-config.yml | 54 ++++++++++++ .../acceptance-test-docker.sh | 16 ++++ .../source-coinmarketcap/build.gradle | 9 ++ .../integration_tests/__init__.py | 3 + .../integration_tests/acceptance.py | 16 ++++ .../integration_tests/configured_catalog.json | 49 +++++++++++ .../integration_tests/invalid_config.json | 4 + .../integration_tests/sample_config.json | 5 ++ .../connectors/source-coinmarketcap/main.py | 13 +++ .../source-coinmarketcap/requirements.txt | 2 + .../connectors/source-coinmarketcap/setup.py | 29 +++++++ .../source_coinmarketcap/__init__.py | 8 ++ .../source_coinmarketcap/coinmarketcap.yaml | 86 +++++++++++++++++++ .../schemas/categories.json | 39 +++++++++ .../schemas/exchange.json | 24 ++++++ .../source_coinmarketcap/schemas/fiat.json | 18 ++++ .../source_coinmarketcap/schemas/listing.json | 60 +++++++++++++ .../source_coinmarketcap/schemas/quotes.json | 5 ++ .../source_coinmarketcap/source.py | 18 ++++ .../source_coinmarketcap/spec.yaml | 40 +++++++++ docs/integrations/README.md | 1 + docs/integrations/sources/coinmarketcap.md | 39 +++++++++ 28 files changed, 715 insertions(+) create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/.dockerignore create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/Dockerfile create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/README.md create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/__init__.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-config.yml create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-docker.sh create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/build.gradle create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/integration_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/integration_tests/acceptance.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/integration_tests/configured_catalog.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/integration_tests/invalid_config.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/integration_tests/sample_config.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/main.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/requirements.txt create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/setup.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/__init__.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/coinmarketcap.yaml create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/categories.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/exchange.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/fiat.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/listing.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/quotes.json create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/source.py create mode 100644 airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml create mode 100644 docs/integrations/sources/coinmarketcap.md diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index be57d698655e..303c592a0a80 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -225,6 +225,13 @@ documentationUrl: https://docs.airbyte.com/integrations/sources/coin-api sourceType: api releaseStage: alpha +- name: CoinMarketCap + sourceDefinitionId: 239463f5-64bb-4d88-b4bd-18ce673fd572 + dockerRepository: airbyte/source-coinmarketcap + dockerImageTag: 0.1.0 + documentationUrl: https://docs.airbyte.com/integrations/sources/coinmarketcap + sourceType: api + releaseStage: alpha - name: Commercetools sourceDefinitionId: 008b2e26-11a3-11ec-82a8-0242ac130003 dockerRepository: airbyte/source-commercetools diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index ca90292a4584..8c82c3ccdcc6 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2286,6 +2286,50 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] +- dockerImage: "airbyte/source-coinmarketcap:0.1.0" + spec: + documentationUrl: https://docs.airbyte.com/integrations/sources/coinmarketcap + connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Coinmarketcap Spec + type: object + required: + - api_key + - data_type + additionalProperties: true + properties: + api_key: + title: API Key + type: string + description: >- + Your API Key. See here. The token is + case sensitive. + airbyte_secret: true + data_type: + title: Data type + type: string + enum: + - latest + - historical + description: >- + /latest: Latest market ticker quotes and averages for cryptocurrencies and exchanges. + /historical: Intervals of historic market data like OHLCV data or data for use in charting libraries. See here. + symbols: + title: Symbol + type: array + items: { + "type": "string" + } + description: Cryptocurrency symbols. (only used for quotes stream) + minItems: 1 + examples: + - AVAX + - BTC + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-commercetools:0.1.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/commercetools" diff --git a/airbyte-integrations/connectors/source-coinmarketcap/.dockerignore b/airbyte-integrations/connectors/source-coinmarketcap/.dockerignore new file mode 100644 index 000000000000..f5ce66b59757 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/.dockerignore @@ -0,0 +1,6 @@ +* +!Dockerfile +!main.py +!source_coinmarketcap +!setup.py +!secrets diff --git a/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile b/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile new file mode 100644 index 000000000000..f0d9c84e18a4 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile @@ -0,0 +1,38 @@ +FROM python:3.9.11-alpine3.15 as base + +# build and load all requirements +FROM base as builder +WORKDIR /airbyte/integration_code + +# upgrade pip to the latest version +RUN apk --no-cache upgrade \ + && pip install --upgrade pip \ + && apk --no-cache add tzdata build-base + + +COPY setup.py ./ +# install necessary packages to a temporary folder +RUN pip install --prefix=/install . + +# build a clean environment +FROM base +WORKDIR /airbyte/integration_code + +# copy all loaded and built libraries to a pure basic image +COPY --from=builder /install /usr/local +# add default timezone settings +COPY --from=builder /usr/share/zoneinfo/Etc/UTC /etc/localtime +RUN echo "Etc/UTC" > /etc/timezone + +# bash is installed for more convenient debugging. +RUN apk --no-cache add bash + +# copy payload code only +COPY main.py ./ +COPY source_coinmarketcap ./source_coinmarketcap + +ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" +ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] + +LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.name=airbyte/source-coinmarketcap diff --git a/airbyte-integrations/connectors/source-coinmarketcap/README.md b/airbyte-integrations/connectors/source-coinmarketcap/README.md new file mode 100644 index 000000000000..b3742a3f61c7 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/README.md @@ -0,0 +1,79 @@ +# Coinmarketcap Source + +This is the repository for the Coinmarketcap configuration based source connector. +For information about how to use this connector within Airbyte, see [the documentation](https://docs.airbyte.io/integrations/sources/coinmarketcap). + +## Local development + +#### Building via Gradle +You can also build the connector in Gradle. This is typically used in CI and not needed for your development workflow. + +To build using Gradle, from the Airbyte repository root, run: +``` +./gradlew :airbyte-integrations:connectors:source-coinmarketcap:build +``` + +#### Create credentials +**If you are a community contributor**, follow the instructions in the [documentation](https://docs.airbyte.io/integrations/sources/coinmarketcap) +to generate the necessary credentials. Then create a file `secrets/config.json` conforming to the `source_coinmarketcap/spec.yaml` file. +Note that any directory named `secrets` is gitignored across the entire Airbyte repo, so there is no danger of accidentally checking in sensitive information. +See `integration_tests/sample_config.json` for a sample config file. + +**If you are an Airbyte core member**, copy the credentials in Lastpass under the secret name `source coinmarketcap test creds` +and place them into `secrets/config.json`. + +### Locally running the connector docker image + +#### Build +First, make sure you build the latest Docker image: +``` +docker build . -t airbyte/source-coinmarketcap:dev +``` + +You can also build the connector image via Gradle: +``` +./gradlew :airbyte-integrations:connectors:source-coinmarketcap:airbyteDocker +``` +When building via Gradle, the docker image name and tag, respectively, are the values of the `io.airbyte.name` and `io.airbyte.version` `LABEL`s in +the Dockerfile. + +#### Run +Then run any of the connector commands as follows: +``` +docker run --rm airbyte/source-coinmarketcap:dev spec +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-coinmarketcap:dev check --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets airbyte/source-coinmarketcap:dev discover --config /secrets/config.json +docker run --rm -v $(pwd)/secrets:/secrets -v $(pwd)/integration_tests:/integration_tests airbyte/source-coinmarketcap:dev read --config /secrets/config.json --catalog /integration_tests/configured_catalog.json +``` +## Testing + +#### Acceptance Tests +Customize `acceptance-test-config.yml` file to configure tests. See [Source Acceptance Tests](https://docs.airbyte.io/connector-development/testing-connectors/source-acceptance-tests-reference) for more information. +If your connector requires to create or destroy resources for use during acceptance tests create fixtures for it and place them inside integration_tests/acceptance.py. + +To run your integration tests with docker + +### Using gradle to run tests +All commands should be run from airbyte project root. +To run unit tests: +``` +./gradlew :airbyte-integrations:connectors:source-coinmarketcap:unitTest +``` +To run acceptance and custom integration tests: +``` +./gradlew :airbyte-integrations:connectors:source-coinmarketcap:integrationTest +``` + +## Dependency Management +All of your dependencies should go in `setup.py`, NOT `requirements.txt`. The requirements file is only used to connect internal Airbyte dependencies in the monorepo for local development. +We split dependencies between two groups, dependencies that are: +* required for your connector to work need to go to `MAIN_REQUIREMENTS` list. +* required for the testing need to go to `TEST_REQUIREMENTS` list + +### Publishing a new version of the connector +You've checked out the repo, implemented a million dollar feature, and you're ready to share your changes with the world. Now what? +1. Make sure your changes are passing unit and integration tests. +1. Bump the connector version in `Dockerfile` -- just increment the value of the `LABEL io.airbyte.version` appropriately (we use [SemVer](https://semver.org/)). +1. Create a Pull Request. +1. Pat yourself on the back for being an awesome contributor. +1. Someone from Airbyte will take a look at your PR and iterate with you to merge it into master. diff --git a/airbyte-integrations/connectors/source-coinmarketcap/__init__.py b/airbyte-integrations/connectors/source-coinmarketcap/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-config.yml b/airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-config.yml new file mode 100644 index 000000000000..0f415a181544 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-config.yml @@ -0,0 +1,54 @@ +# See [Source Acceptance Tests](https://docs.airbyte.com/connector-development/testing-connectors/source-acceptance-tests-reference) +# for more information about how to configure these tests +connector_image: airbyte/source-coinmarketcap:dev +acceptance_tests: + spec: + tests: + - spec_path: "source_coinmarketcap/spec.yaml" + connection: + tests: + - config_path: "secrets/config.json" + status: "succeed" + - config_path: "integration_tests/invalid_config.json" + status: "failed" + discovery: + tests: + - config_path: "secrets/config.json" + basic_read: + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + empty_streams: [] + timeout_seconds: 1800 +# TODO uncomment this block to specify that the tests should assert the connector outputs the records provided in the input file a file +# expect_records: +# path: "integration_tests/expected_records.txt" +# extra_fields: no +# exact_order: no +# extra_records: yes + incremental: + bypass_reason: "This connector does not implement incremental sync" +# TODO uncomment this block this block if your connector implements incremental sync: +# tests: +# - config_path: "secrets/config.json" +# configured_catalog_path: "integration_tests/configured_catalog.json" +# future_state_path: "integration_tests/abnormal_state.json" + full_refresh: + tests: + - config_path: "secrets/config.json" + configured_catalog_path: "integration_tests/configured_catalog.json" + timeout_seconds: 1800 + ignored_fields: + categories: + - "avg_price_change" + - "market_cap" + - "market_cap_change" + - "volume" + - "volume_change" + listing: + - "cmc_rank" + - "last_updated" + - "self_reported_circulating_supply" + - "self_reported_market_cap" + - "tvl_ratio" + - "quote" diff --git a/airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-docker.sh b/airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-docker.sh new file mode 100644 index 000000000000..c51577d10690 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/acceptance-test-docker.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env sh + +# Build latest connector image +docker build . -t $(cat acceptance-test-config.yml | grep "connector_image" | head -n 1 | cut -d: -f2-) + +# Pull latest acctest image +docker pull airbyte/source-acceptance-test:latest + +# Run +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp:/tmp \ + -v $(pwd):/test_input \ + airbyte/source-acceptance-test \ + --acceptance-test-config /test_input + diff --git a/airbyte-integrations/connectors/source-coinmarketcap/build.gradle b/airbyte-integrations/connectors/source-coinmarketcap/build.gradle new file mode 100644 index 000000000000..b63e834f0136 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'airbyte-python' + id 'airbyte-docker' + id 'airbyte-source-acceptance-test' +} + +airbytePython { + moduleDirectory 'source_coinmarketcap' +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/__init__.py b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/__init__.py new file mode 100644 index 000000000000..1100c1c58cf5 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/__init__.py @@ -0,0 +1,3 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# diff --git a/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/acceptance.py b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/acceptance.py new file mode 100644 index 000000000000..1302b2f57e10 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/acceptance.py @@ -0,0 +1,16 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import pytest + +pytest_plugins = ("source_acceptance_test.plugin",) + + +@pytest.fixture(scope="session", autouse=True) +def connector_setup(): + """This fixture is a placeholder for external resources that acceptance test might require.""" + # TODO: setup test dependencies if needed. otherwise remove the TODO comments + yield + # TODO: clean up test dependencies diff --git a/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/configured_catalog.json b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/configured_catalog.json new file mode 100644 index 000000000000..1286423cc99e --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/configured_catalog.json @@ -0,0 +1,49 @@ +{ + "streams": [ + { + "stream": { + "name": "categories", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "listing", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "quotes", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "fiat", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + }, + { + "stream": { + "name": "exchange", + "json_schema": {}, + "supported_sync_modes": ["full_refresh"] + }, + "sync_mode": "full_refresh", + "destination_sync_mode": "overwrite" + } + ] +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/invalid_config.json b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/invalid_config.json new file mode 100644 index 000000000000..213c6d60fee1 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/invalid_config.json @@ -0,0 +1,4 @@ +{ + "api_key": "fail-1111", + "symbols": ["AVAX", "BTC"] +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/sample_config.json b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/sample_config.json new file mode 100644 index 000000000000..bac5ba53f15e --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/integration_tests/sample_config.json @@ -0,0 +1,5 @@ +{ + "api_key": "6k114d1c-2df2-4b04-8504-5531db370799", + "data_type": "latest", + "symbols": ["AVAX", "BTC"] +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/main.py b/airbyte-integrations/connectors/source-coinmarketcap/main.py new file mode 100644 index 000000000000..ed48a5429628 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/main.py @@ -0,0 +1,13 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +import sys + +from airbyte_cdk.entrypoint import launch +from source_coinmarketcap import SourceCoinmarketcap + +if __name__ == "__main__": + source = SourceCoinmarketcap() + launch(source, sys.argv[1:]) diff --git a/airbyte-integrations/connectors/source-coinmarketcap/requirements.txt b/airbyte-integrations/connectors/source-coinmarketcap/requirements.txt new file mode 100644 index 000000000000..0411042aa091 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/requirements.txt @@ -0,0 +1,2 @@ +-e ../../bases/source-acceptance-test +-e . diff --git a/airbyte-integrations/connectors/source-coinmarketcap/setup.py b/airbyte-integrations/connectors/source-coinmarketcap/setup.py new file mode 100644 index 000000000000..5c1ff243fce1 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/setup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from setuptools import find_packages, setup + +MAIN_REQUIREMENTS = [ + "airbyte-cdk~=0.1", +] + +TEST_REQUIREMENTS = [ + "pytest~=6.1", + "pytest-mock~=3.6.1", + "source-acceptance-test", +] + +setup( + name="source_coinmarketcap", + description="Source implementation for Coinmarketcap.", + author="Airbyte", + author_email="contact@airbyte.io", + packages=find_packages(), + install_requires=MAIN_REQUIREMENTS, + package_data={"": ["*.json", "*.yaml", "schemas/*.json", "schemas/shared/*.json"]}, + extras_require={ + "tests": TEST_REQUIREMENTS, + }, +) diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/__init__.py b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/__init__.py new file mode 100644 index 000000000000..2ae84594a678 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/__init__.py @@ -0,0 +1,8 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + + +from .source import SourceCoinmarketcap + +__all__ = ["SourceCoinmarketcap"] diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/coinmarketcap.yaml b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/coinmarketcap.yaml new file mode 100644 index 000000000000..995149b72fa6 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/coinmarketcap.yaml @@ -0,0 +1,86 @@ +version: "0.1.0" + +definitions: + selector: + extractor: + field_pointer: ["data"] + requester: + url_base: "https://pro-api.coinmarketcap.com" + http_method: "GET" + authenticator: + type: ApiKeyAuthenticator + header: "X-CMC_PRO_API_KEY" + api_token: "{{ config['api_key'] }}" + offset_paginator: + type: DefaultPaginator + $options: + url_base: "*ref(definitions.requester.url_base)" + page_size_option: + inject_into: "request_parameter" + field_name: "limit" + pagination_strategy: + type: "OffsetIncrement" + page_size: 1000 + page_token_option: + field_name: "start" + inject_into: "request_parameter" + retriever: + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + $ref: "*ref(definitions.offset_paginator)" + requester: + $ref: "*ref(definitions.requester)" + base_stream: + retriever: + $ref: "*ref(definitions.retriever)" + categories_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "categories" + primary_key: "id" + path: "/v1/cryptocurrency/categories" + listing_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "listing" + primary_key: "id" + path: "/v1/cryptocurrency/listings/{{ config['data_type'] or ''}}" + quotes_stream: + $ref: "*ref(definitions.base_stream)" + retriever: + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + type: NoPagination + requester: + $ref: "*ref(definitions.requester)" + request_options_provider: + request_parameters: + symbol: "{{ ','.join(config.get('symbols', [])) }}" + $options: + name: "quotes" + path: "/v1/cryptocurrency/quotes/{{ config['data_type'] or ''}}" + fiat_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "fiat" + primary_key: "id" + path: "/v1/fiat/map" + exchange_stream: + $ref: "*ref(definitions.base_stream)" + $options: + name: "exchange" + primary_key: "id" + path: "/v1/exchange/map" + +streams: + - "*ref(definitions.categories_stream)" + - "*ref(definitions.listing_stream)" + - "*ref(definitions.quotes_stream)" + - "*ref(definitions.fiat_stream)" + - "*ref(definitions.exchange_stream)" + +check: + stream_names: + - "categories" diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/categories.json b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/categories.json new file mode 100644 index 000000000000..be88bde13836 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/categories.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["null", "string"] + }, + "name": { + "type": ["null", "string"] + }, + "title": { + "type": ["null", "string"] + }, + "description": { + "type": ["null", "string"] + }, + "num_tokens": { + "type": ["null", "integer"] + }, + "avg_price_change": { + "type": ["null", "number"] + }, + "market_cap": { + "type": ["null", "number"] + }, + "market_cap_change": { + "type": ["null", "number"] + }, + "volume": { + "type": ["null", "number"] + }, + "volume_change": { + "type": ["null", "number"] + }, + "last_updated": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/exchange.json b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/exchange.json new file mode 100644 index 000000000000..fa7bc89dc458 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/exchange.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["null", "integer"] + }, + "name": { + "type": ["null", "string"] + }, + "slug": { + "type": ["null", "string"] + }, + "is_active": { + "type": ["null", "integer"] + }, + "first_historical_data": { + "type": ["null", "string"] + }, + "last_historical_data": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/fiat.json b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/fiat.json new file mode 100644 index 000000000000..105ff2a8ef55 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/fiat.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": ["null", "string"] + }, + "sign": { + "type": ["null", "string"] + }, + "symbol": { + "type": ["null", "string"] + } + } +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/listing.json b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/listing.json new file mode 100644 index 000000000000..5750c0eaf914 --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/listing.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "id": { + "type": ["null", "integer"] + }, + "name": { + "type": ["null", "string"] + }, + "symbol": { + "type": ["null", "string"] + }, + "slug": { + "type": ["null", "string"] + }, + "num_market_pairs": { + "type": ["null", "integer"] + }, + "date_added": { + "type": ["null", "string"] + }, + "tags": { + "type": ["null", "array"], + "items": { + "type": "string" + } + }, + "max_supply": { + "type": ["null", "number"] + }, + "circulating_supply": { + "type": ["null", "number"] + }, + "total_supply": { + "type": ["null", "number"] + }, + "platform": { + "type": ["null", "object"] + }, + "cmc_rank": { + "type": ["null", "integer"] + }, + "self_reported_circulating_supply": { + "type": ["null", "number"] + }, + "self_reported_market_cap": { + "type": ["null", "number"] + }, + "tvl_ratio": { + "type": ["null", "number"] + }, + "last_updated": { + "type": ["null", "string"] + }, + "quote": { + "type": ["null", "object"] + } + } +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/quotes.json b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/quotes.json new file mode 100644 index 000000000000..80cafd6fca5c --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/schemas/quotes.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": {} +} diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/source.py b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/source.py new file mode 100644 index 000000000000..a9cc2eae44dc --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/source.py @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from airbyte_cdk.sources.declarative.yaml_declarative_source import YamlDeclarativeSource + +""" +This file provides the necessary constructs to interpret a provided declarative YAML configuration file into +source connector. + +WARNING: Do not modify this file. +""" + + +# Declarative Source +class SourceCoinmarketcap(YamlDeclarativeSource): + def __init__(self): + super().__init__(**{"path_to_yaml": "coinmarketcap.yaml"}) diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml new file mode 100644 index 000000000000..b461cf69da6e --- /dev/null +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml @@ -0,0 +1,40 @@ +documentationUrl: https://docs.airbyte.com/integrations/sources/coinmarketcap +connectionSpecification: + $schema: http://json-schema.org/draft-07/schema# + title: Coinmarketcap Spec + type: object + required: + - api_key + - data_type + additionalProperties: true + properties: + api_key: + title: API Key + type: string + description: >- + Your API Key. See here. The token is + case sensitive. + airbyte_secret: true + data_type: + title: Data type + type: string + enum: + - latest + - historical + description: >- + /latest: Latest market ticker quotes and averages for cryptocurrencies and exchanges. + /historical: Intervals of historic market data like OHLCV data or data for use in charting libraries. See here. + symbols: + title: Symbol + type: array + items: { + "type": "string" + } + description: Cryptocurrency symbols. (only used for quotes stream) + minItems: 1 + examples: + - AVAX + - BTC + diff --git a/docs/integrations/README.md b/docs/integrations/README.md index 77d113dc87b2..e8b1b5712383 100644 --- a/docs/integrations/README.md +++ b/docs/integrations/README.md @@ -44,6 +44,7 @@ For more information about the grading system, see [Product Release Stages](http | [ClickHouse](sources/clickhouse.md) | Alpha | Yes | | [Close.com](sources/close-com.md) | Alpha | Yes | | [CockroachDB](sources/cockroachdb.md) | Alpha | No | +| [Coinmarketcap](sources/coinmarketcap.md) | Alpha | Yes | | [Commercetools](sources/commercetools.md) | Alpha | No | | [Confluence](sources/confluence.md) | Alpha | No | | [ConvertKit](sources/convertkit.md) | Alpha | No | diff --git a/docs/integrations/sources/coinmarketcap.md b/docs/integrations/sources/coinmarketcap.md new file mode 100644 index 000000000000..db336c0fa74e --- /dev/null +++ b/docs/integrations/sources/coinmarketcap.md @@ -0,0 +1,39 @@ +# Coinmarketcap API + +## Sync overview + +This source can sync data from the [Coinmarketcap API](https://coinmarketcap.com/api/documentation/v1/). At present this connector only supports full refresh syncs meaning that each time you use the connector it will sync all available records from scratch. Please use cautiously if you expect your API to have a lot of records. + +## This Source Supports the Following Streams + +* [categories](https://coinmarketcap.com/api/documentation/v1/#operation/getV1CryptocurrencyCategories) +* [listing](https://coinmarketcap.com/api/documentation/v1/#operation/getV1CryptocurrencyListingsLatest) +* [quotes](https://coinmarketcap.com/api/documentation/v1/#operation/getV2CryptocurrencyQuotesLatest) +* [fiat](https://coinmarketcap.com/api/documentation/v1/#tag/fiat) +* [exchange](https://coinmarketcap.com/api/documentation/v1/#tag/exchange) + +### Features + +| Feature | Supported?\(Yes/No\) | Notes | +| :--- | :--- | :--- | +| Full Refresh Sync | Yes | | +| Incremental Sync | No | | + +### Performance considerations + +Coinmarketcap APIs are under rate limits for the number of API calls allowed per API keys per second. If you reach a rate limit, API will return a 429 HTTP error code. See [here](https://coinmarketcap.com/api/documentation/v1/#section/Errors-and-Rate-Limits) + +## Getting started + +### Requirements + +* [API token](https://coinmarketcap.com/api/documentation/v1/#section/Authentication) +* Data Type: + * latest + * historical + +## Changelog + +| Version | Date | Pull Request | Subject | +| :------ | :--------- | :-------------------------------------------------------- | :----------------------------------------- | +| 0.1.0 | 2022-10-29 | [#18565](https://github.com/airbytehq/airbyte/pull/18565) | 🎉 New Source: Coinmarketcap API [low-code CDK] | \ No newline at end of file From 354db219dae497c0ff44e158689cfa21a71e77b4 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 1 Nov 2022 12:31:52 -0400 Subject: [PATCH 464/498] Filter Temporal exit errors from traces (#18777) * Filter Temporal exit errors from traces * Remove unnecessary PMD suppression * Formatting --- .../workers/ApplicationInitializer.java | 12 +- .../StorageObjectGetInterceptor.java | 2 +- .../tracing/TemporalSdkInterceptor.java | 82 ++++++++ .../StorageObjectGetInterceptorTest.java | 176 ------------------ .../io/airbyte/workers/tracing/DummySpan.java | 138 ++++++++++++++ .../StorageObjectGetInterceptorTest.java | 46 +++++ .../tracing/TemporalSdkInterceptorTest.java | 95 ++++++++++ 7 files changed, 373 insertions(+), 178 deletions(-) rename airbyte-workers/src/main/java/io/airbyte/workers/{ => tracing}/StorageObjectGetInterceptor.java (97%) create mode 100644 airbyte-workers/src/main/java/io/airbyte/workers/tracing/TemporalSdkInterceptor.java delete mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/tracing/DummySpan.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/tracing/StorageObjectGetInterceptorTest.java create mode 100644 airbyte-workers/src/test/java/io/airbyte/workers/tracing/TemporalSdkInterceptorTest.java diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java b/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java index c6f6f4c9c4f0..1c8b4c82c45c 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/ApplicationInitializer.java @@ -4,6 +4,8 @@ package io.airbyte.workers; +import datadog.trace.api.GlobalTracer; +import datadog.trace.api.Tracer; import io.airbyte.commons.temporal.TemporalInitializationUtils; import io.airbyte.commons.temporal.TemporalJobType; import io.airbyte.commons.temporal.TemporalUtils; @@ -24,6 +26,8 @@ import io.airbyte.workers.temporal.spec.SpecWorkflowImpl; import io.airbyte.workers.temporal.support.TemporalProxyHelper; import io.airbyte.workers.temporal.sync.SyncWorkflowImpl; +import io.airbyte.workers.tracing.StorageObjectGetInterceptor; +import io.airbyte.workers.tracing.TemporalSdkInterceptor; import io.micronaut.context.annotation.Requires; import io.micronaut.context.annotation.Value; import io.micronaut.context.env.Environment; @@ -133,7 +137,7 @@ public class ApplicationInitializer implements ApplicationEventListener onTraceComplete(final Collection trace) { + final var filtered = new ArrayList(); + + trace.forEach(t -> { + final var tags = t.getTags(); + + // if no tags, then keep the span and move on to the next one + if (CollectionUtils.isEmpty(tags)) { + filtered.add(t); + return; + } + + if (isExitTrace(t)) { + t.setError(false); + } + + filtered.add(t); + }); + + return filtered; + } + + @Override + public int priority() { + return 0; + } + + /** + * Test whether the provided {@link MutableSpan} contains a Temporal workflow exit error. + * + * @param trace The {@link MutableSpan} to be tested. + * @return {@code true} if the {@link MutableSpan} contains a Temporal workflow exit error or + * {@code false} otherwise. + */ + @VisibleForTesting + boolean isExitTrace(final MutableSpan trace) { + if (trace == null) + return false; + + return trace.isError() && + EXIT_ERROR_MESSAGE.equals(trace.getTags().getOrDefault(ERROR_MESSAGE_TAG_KEY, "")) && + CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME.equals(trace.getResourceName()); + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java deleted file mode 100644 index fbe4271762c7..000000000000 --- a/airbyte-workers/src/test/java/io/airbyte/workers/StorageObjectGetInterceptorTest.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.workers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import datadog.trace.api.interceptor.MutableSpan; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class StorageObjectGetInterceptorTest { - - private static class DummySpan implements MutableSpan { - - @SuppressWarnings("PMD") - private final Map tags = new HashMap<>(); - private boolean error = false; - - @Override - public long getStartTime() { - return 0; - } - - @Override - public long getDurationNano() { - return 0; - } - - @Override - public CharSequence getOperationName() { - return null; - } - - @Override - public MutableSpan setOperationName(final CharSequence serviceName) { - return null; - } - - @Override - public String getServiceName() { - return null; - } - - @Override - public MutableSpan setServiceName(final String serviceName) { - return null; - } - - @Override - public CharSequence getResourceName() { - return null; - } - - @Override - public MutableSpan setResourceName(final CharSequence resourceName) { - return null; - } - - @Override - public Integer getSamplingPriority() { - return null; - } - - @Override - public MutableSpan setSamplingPriority(final int newPriority) { - return null; - } - - @Override - public String getSpanType() { - return null; - } - - @Override - public MutableSpan setSpanType(final CharSequence type) { - return null; - } - - @Override - public Map getTags() { - return tags; - } - - @Override - public MutableSpan setTag(final String tag, final String value) { - tags.put(tag, value); - return this; - } - - @Override - public MutableSpan setTag(final String tag, final boolean value) { - tags.put(tag, value); - return this; - } - - @Override - public MutableSpan setTag(final String tag, final Number value) { - tags.put(tag, value); - return this; - } - - @Override - public MutableSpan setMetric(final CharSequence metric, final int value) { - return null; - } - - @Override - public MutableSpan setMetric(final CharSequence metric, final long value) { - return null; - } - - @Override - public MutableSpan setMetric(final CharSequence metric, final double value) { - return null; - } - - @Override - public boolean isError() { - return error; - } - - @Override - public MutableSpan setError(final boolean value) { - error = value; - return this; - } - - @Override - public MutableSpan getRootSpan() { - return null; - } - - @Override - public MutableSpan getLocalRootSpan() { - return null; - } - - } - - @Test - void testOnTraceComplete() { - final var simple = new DummySpan(); - - final var unmodifiedError = new DummySpan(); - unmodifiedError.setError(true); - unmodifiedError.setTag("unmodified", true); - - final var statusCodeError = new DummySpan(); - statusCodeError.setError(true); - statusCodeError.setTag("peer.hostname", "storage.googleapis.com"); - statusCodeError.setTag("http.status_code", 404); - - final var errorMsgError = new DummySpan(); - errorMsgError.setError(true); - errorMsgError.setTag("peer.hostname", "storage.googleapis.com"); - errorMsgError.setTag("error.msg", "404 Not Found and is still missing!"); - - final var spans = List.of( - simple, unmodifiedError, statusCodeError, errorMsgError); - - final var interceptor = new StorageObjectGetInterceptor(); - final var actual = interceptor.onTraceComplete(spans); - - assertEquals(spans, actual); - assertTrue(unmodifiedError.isError()); - assertFalse(statusCodeError.isError()); - assertFalse(errorMsgError.isError()); - } - -} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/tracing/DummySpan.java b/airbyte-workers/src/test/java/io/airbyte/workers/tracing/DummySpan.java new file mode 100644 index 000000000000..bb954c1f5d63 --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/tracing/DummySpan.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.tracing; + +import datadog.trace.api.interceptor.MutableSpan; +import java.util.HashMap; +import java.util.Map; + +class DummySpan implements MutableSpan { + + private final Map tags = new HashMap<>(); + private boolean error = false; + + private String resourceName = null; + + @Override + public long getStartTime() { + return 0; + } + + @Override + public long getDurationNano() { + return 0; + } + + @Override + public CharSequence getOperationName() { + return null; + } + + @Override + public MutableSpan setOperationName(final CharSequence serviceName) { + return null; + } + + @Override + public String getServiceName() { + return null; + } + + @Override + public MutableSpan setServiceName(final String serviceName) { + return null; + } + + @Override + public CharSequence getResourceName() { + return resourceName; + } + + @Override + public MutableSpan setResourceName(final CharSequence resourceName) { + this.resourceName = resourceName.toString(); + return this; + } + + @Override + public Integer getSamplingPriority() { + return null; + } + + @Override + public MutableSpan setSamplingPriority(final int newPriority) { + return null; + } + + @Override + public String getSpanType() { + return null; + } + + @Override + public MutableSpan setSpanType(final CharSequence type) { + return null; + } + + @Override + public Map getTags() { + return tags; + } + + @Override + public MutableSpan setTag(final String tag, final String value) { + tags.put(tag, value); + return this; + } + + @Override + public MutableSpan setTag(final String tag, final boolean value) { + tags.put(tag, value); + return this; + } + + @Override + public MutableSpan setTag(final String tag, final Number value) { + tags.put(tag, value); + return this; + } + + @Override + public MutableSpan setMetric(final CharSequence metric, final int value) { + return null; + } + + @Override + public MutableSpan setMetric(final CharSequence metric, final long value) { + return null; + } + + @Override + public MutableSpan setMetric(final CharSequence metric, final double value) { + return null; + } + + @Override + public boolean isError() { + return error; + } + + @Override + public MutableSpan setError(final boolean value) { + error = value; + return this; + } + + @Override + public MutableSpan getRootSpan() { + return null; + } + + @Override + public MutableSpan getLocalRootSpan() { + return null; + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/tracing/StorageObjectGetInterceptorTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/tracing/StorageObjectGetInterceptorTest.java new file mode 100644 index 000000000000..b2320256e14e --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/tracing/StorageObjectGetInterceptorTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.tracing; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class StorageObjectGetInterceptorTest { + + @Test + void testOnTraceComplete() { + final var simple = new DummySpan(); + + final var unmodifiedError = new DummySpan(); + unmodifiedError.setError(true); + unmodifiedError.setTag("unmodified", true); + + final var statusCodeError = new DummySpan(); + statusCodeError.setError(true); + statusCodeError.setTag("peer.hostname", "storage.googleapis.com"); + statusCodeError.setTag("http.status_code", 404); + + final var errorMsgError = new DummySpan(); + errorMsgError.setError(true); + errorMsgError.setTag("peer.hostname", "storage.googleapis.com"); + errorMsgError.setTag("error.msg", "404 Not Found and is still missing!"); + + final var spans = List.of( + simple, unmodifiedError, statusCodeError, errorMsgError); + + final var interceptor = new StorageObjectGetInterceptor(); + final var actual = interceptor.onTraceComplete(spans); + + assertEquals(spans, actual); + assertTrue(unmodifiedError.isError()); + assertFalse(statusCodeError.isError()); + assertFalse(errorMsgError.isError()); + } + +} diff --git a/airbyte-workers/src/test/java/io/airbyte/workers/tracing/TemporalSdkInterceptorTest.java b/airbyte-workers/src/test/java/io/airbyte/workers/tracing/TemporalSdkInterceptorTest.java new file mode 100644 index 000000000000..8b95a4aaf04c --- /dev/null +++ b/airbyte-workers/src/test/java/io/airbyte/workers/tracing/TemporalSdkInterceptorTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.workers.tracing; + +import static io.airbyte.workers.tracing.TemporalSdkInterceptor.CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME; +import static io.airbyte.workers.tracing.TemporalSdkInterceptor.ERROR_MESSAGE_TAG_KEY; +import static io.airbyte.workers.tracing.TemporalSdkInterceptor.EXIT_ERROR_MESSAGE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * Test suite for the {@link TemporalSdkInterceptor} class. + */ +class TemporalSdkInterceptorTest { + + @Test + void testOnTraceComplete() { + final var simple = new DummySpan(); + + final var noError = new DummySpan(); + noError.setError(false); + noError.setResourceName(CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME); + noError.setTag("tag", "value"); + + final var otherError = new DummySpan(); + otherError.setError(true); + otherError.setResourceName(CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME); + otherError.setTag("error.message", "some other error"); + + final var temporalExitMsgError = new DummySpan(); + temporalExitMsgError.setError(true); + temporalExitMsgError.setTag(ERROR_MESSAGE_TAG_KEY, EXIT_ERROR_MESSAGE); + temporalExitMsgError.setResourceName(CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME); + + final var temporalExitMsgOtherResourceError = new DummySpan(); + temporalExitMsgOtherResourceError.setError(true); + temporalExitMsgOtherResourceError.setTag(ERROR_MESSAGE_TAG_KEY, EXIT_ERROR_MESSAGE); + temporalExitMsgOtherResourceError.setResourceName("OtherResource.run"); + + final var spans = List.of( + simple, noError, otherError, temporalExitMsgError, temporalExitMsgOtherResourceError); + + final var interceptor = new TemporalSdkInterceptor(); + final var actual = interceptor.onTraceComplete(spans); + + assertEquals(spans, actual); + assertFalse(simple.isError()); + assertFalse(noError.isError()); + assertTrue(otherError.isError()); + assertFalse(temporalExitMsgError.isError()); + assertTrue(temporalExitMsgOtherResourceError.isError()); + } + + @Test + void testIsExitTrace() { + final var interceptor = new TemporalSdkInterceptor(); + + assertEquals(false, interceptor.isExitTrace(null)); + assertEquals(false, interceptor.isExitTrace(new DummySpan())); + + final var temporalTrace = new DummySpan(); + temporalTrace.setResourceName(CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME); + assertEquals(false, interceptor.isExitTrace(temporalTrace)); + + final var temporalTraceWithError = new DummySpan(); + temporalTraceWithError.setError(true); + temporalTraceWithError.setResourceName(CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME); + assertEquals(false, interceptor.isExitTrace(temporalTraceWithError)); + + final var temporalTraceWithExitError = new DummySpan(); + temporalTraceWithExitError.setError(true); + temporalTraceWithExitError.setTag(ERROR_MESSAGE_TAG_KEY, EXIT_ERROR_MESSAGE); + temporalTraceWithExitError.setResourceName(CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME); + assertEquals(true, interceptor.isExitTrace(temporalTraceWithExitError)); + + final var otherTemporalTraceWithExitError = new DummySpan(); + otherTemporalTraceWithExitError.setError(true); + otherTemporalTraceWithExitError.setTag(ERROR_MESSAGE_TAG_KEY, EXIT_ERROR_MESSAGE); + otherTemporalTraceWithExitError.setResourceName("OtherResource"); + assertEquals(false, interceptor.isExitTrace(otherTemporalTraceWithExitError)); + } + + @Test + void testPriority() { + final var interceptor = new TemporalSdkInterceptor(); + assertEquals(0, interceptor.priority()); + } + +} From 978128afc272c428b3cec14438c570371b402a35 Mon Sep 17 00:00:00 2001 From: "Sherif A. Nada" Date: Tue, 1 Nov 2022 10:21:16 -0700 Subject: [PATCH 465/498] Fix adjust.md header --- docs/integrations/sources/adjust.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/sources/adjust.md b/docs/integrations/sources/adjust.md index 25bd213ec2e2..6d96d7df3681 100644 --- a/docs/integrations/sources/adjust.md +++ b/docs/integrations/sources/adjust.md @@ -1,4 +1,4 @@ -# Airbyte Source Connector for Adjust +# Adjust This is a setup guide for the Adjust source connector which ingests data from the reports API. From e7ae961a26b561940f102bb6402632665cea1503 Mon Sep 17 00:00:00 2001 From: Mark Berger Date: Tue, 1 Nov 2022 19:26:09 +0200 Subject: [PATCH 466/498] Mark/update notification settings design (#18159) * Update Notification Settings design - Removed styled-components - Added new behavior 'Test' and 'Submit' buttons --- .../icons}/DocsIcon.tsx | 4 +- .../src/components/icons/PlayIcon.tsx | 12 + airbyte-webapp/src/locales/en.json | 16 +- .../cloud/views/layout/SideBar/SideBar.tsx | 2 +- .../NotificationPage/NotificationPage.tsx | 61 +-- .../components/WebHookForm.module.scss | 168 ++++++++ .../components/WebHookForm.tsx | 373 +++++++++++------- .../NotificationPage/components/help.png | Bin 0 -> 31497 bytes airbyte-webapp/src/utils/links.ts | 2 + .../src/views/layout/SideBar/SideBar.tsx | 2 +- 10 files changed, 441 insertions(+), 199 deletions(-) rename airbyte-webapp/src/{views/layout/SideBar/components => components/icons}/DocsIcon.tsx (82%) create mode 100644 airbyte-webapp/src/components/icons/PlayIcon.tsx create mode 100644 airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.module.scss create mode 100644 airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/help.png diff --git a/airbyte-webapp/src/views/layout/SideBar/components/DocsIcon.tsx b/airbyte-webapp/src/components/icons/DocsIcon.tsx similarity index 82% rename from airbyte-webapp/src/views/layout/SideBar/components/DocsIcon.tsx rename to airbyte-webapp/src/components/icons/DocsIcon.tsx index 3aef838cf6ac..9fcd7481a760 100644 --- a/airbyte-webapp/src/views/layout/SideBar/components/DocsIcon.tsx +++ b/airbyte-webapp/src/components/icons/DocsIcon.tsx @@ -1,4 +1,4 @@ -const DocsIcon = ({ color = "currentColor" }: { color?: string }): JSX.Element => ( +export const DocsIcon = ({ color = "currentColor" }: { color?: string }): JSX.Element => ( ); - -export default DocsIcon; diff --git a/airbyte-webapp/src/components/icons/PlayIcon.tsx b/airbyte-webapp/src/components/icons/PlayIcon.tsx new file mode 100644 index 000000000000..0eda66dc6b6e --- /dev/null +++ b/airbyte-webapp/src/components/icons/PlayIcon.tsx @@ -0,0 +1,12 @@ +interface Props { + color?: string; +} + +export const PlayIcon = ({ color = "currentColor" }: Props): JSX.Element => ( + + + +); diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 8a5af3249a25..e6f728bcd7c1 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -483,16 +483,22 @@ "admin.customImage": "custom", "settings.accountSettings": "Account Settings", - "settings.changeSaved": "change saved!", "settings.webhook": "Connection status Webhook", + "settings.webhook.test.passed": "Webhook test passed!", + "settings.webhook.test.failed": "Webhook test failed. Please verify that the webhook URL is valid.", + "settings.webhook.save.failed": "Cannot save changes because the webhook test failed. Please verify that the webhook URL is valid.", "settings.webhookTitle": "Connection status Webhook URL", - "settings.webhookDescriprion": "Get notifications the way you want (for instance, on Slack, or other).", - "settings.webhookTestText": "Testing the Webhook will send a “Hello World”. ", - "settings.sendOnSuccess": "Send notifications when sync succeeds.", - "settings.sendOnFailure": "Send notifications when sync fails.", + "settings.webhookTestText": "Testing the Webhook will send a “Hello World”.", + "settings.syncNotifications.label": "Notify me:", + "settings.sendOnSuccess": "When Sync succeeds", + "settings.sendOnFailure": "When Sync fails", "settings.yourWebhook": "Your Webhook URL", "settings.test": "Test", "settings.notifications": "Notifications", + "settings.notificationGuide.button": "Need help with Webhook URL?", + "settings.notificationGuide.title": "How to get notification the way you want with your webhook URL?", + "settings.notificationGuide.link.configuration": "Configure Sync notifications", + "settings.notificationGuide.link.slackConfiguration": "Configure a Slack Notifications Webhook", "settings.metrics": "Metrics", "settings.notificationSettings": "Notification Settings", "settings.metricsSettings": "Metrics Settings", diff --git a/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx b/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx index c3818e7f7165..6696a666a3ec 100644 --- a/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx +++ b/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx @@ -8,6 +8,7 @@ import { NavLink } from "react-router-dom"; import { Link } from "components"; import { CreditsIcon } from "components/icons/CreditsIcon"; +import { DocsIcon } from "components/icons/DocsIcon"; import { Text } from "components/ui/Text"; import { useExperiment } from "hooks/services/Experiment"; @@ -21,7 +22,6 @@ import { links } from "utils/links"; import ChatIcon from "views/layout/SideBar/components/ChatIcon"; import ConnectionsIcon from "views/layout/SideBar/components/ConnectionsIcon"; import DestinationIcon from "views/layout/SideBar/components/DestinationIcon"; -import DocsIcon from "views/layout/SideBar/components/DocsIcon"; import OnboardingIcon from "views/layout/SideBar/components/OnboardingIcon"; import RecipesIcon from "views/layout/SideBar/components/RecipesIcon"; import SettingsIcon from "views/layout/SideBar/components/SettingsIcon"; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx index 655b56dbe884..bfce0d09b269 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/NotificationPage.tsx @@ -1,60 +1,17 @@ -import React, { useMemo, useState, useCallback } from "react"; -import { FormattedMessage } from "react-intl"; +import React, { useMemo } from "react"; import { HeadTitle } from "components/common/HeadTitle"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; -import useWorkspace, { useCurrentWorkspace, WebhookPayload } from "hooks/services/useWorkspace"; +import { useCurrentWorkspace } from "hooks/services/useWorkspace"; -import { Content, SettingsCard } from "../SettingsComponents"; -import WebHookForm from "./components/WebHookForm"; - -function useAsyncWithTimeout(f: (data: K) => Promise) { - const [errorMessage, setErrorMessage] = useState(null); - const [successMessage, setSuccessMessage] = useState(null); - const call = useCallback( - async (data: K) => { - setSuccessMessage(null); - setErrorMessage(null); - try { - await f(data); - setSuccessMessage(); - - setTimeout(() => { - setSuccessMessage(null); - }, 2000); - } catch (e) { - setErrorMessage(); - - setTimeout(() => { - setErrorMessage(null); - }, 2000); - } - }, - [f] - ); - - return { - call, - successMessage, - errorMessage, - }; -} +import { WebHookForm } from "./components/WebHookForm"; const NotificationPage: React.FC = () => { useTrackPage(PageTrackingCodes.SETTINGS_NOTIFICATION); - const { updateWebhook, testWebhook } = useWorkspace(); const workspace = useCurrentWorkspace(); - - const { - call: onSubmitWebhook, - errorMessage, - successMessage, - } = useAsyncWithTimeout(async (data: WebhookPayload) => updateWebhook(data)); - const firstNotification = workspace.notifications?.[0]; - const initialValues = useMemo( () => ({ webhook: firstNotification?.slackConfiguration?.webhook, @@ -67,17 +24,7 @@ const NotificationPage: React.FC = () => { return ( <> - }> - - - - + ); }; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.module.scss b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.module.scss new file mode 100644 index 000000000000..ee7ed998a5c5 --- /dev/null +++ b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.module.scss @@ -0,0 +1,168 @@ +@use "scss/colors"; +@use "scss/variables"; + +.webhookGuide { + opacity: 0; + visibility: hidden; + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + height: 0; + max-height: 0; + padding: 0 variables.$spacing-xl; + margin-bottom: 0; + background-color: colors.$grey-50; + border-radius: variables.$border-radius-xs; + transition: variables.$transition-out; + + &.active { + opacity: 1; + visibility: visible; + height: 150px; + max-height: 100%; + margin-bottom: variables.$spacing-xl; + } + + .webhookGuideTitle { + display: flex; + justify-content: space-between; + padding-bottom: variables.$spacing-lg; + + .crossIcon { + font-size: 22px; + } + } + + ul { + list-style-type: none; + list-style-position: inside; + margin: 0; + padding: 0; + + li + li { + padding-top: variables.$spacing-md; + } + } + + .webhookGuideLink { + display: inline-flex; + align-items: center; + padding: variables.$spacing-sm 0; + color: colors.$blue; + + .text { + color: colors.$blue; + padding-left: variables.$spacing-md; + } + } + + .webhookGuideImg { + position: absolute; + right: 60px; + bottom: -20px; + width: 106px; + height: 125px; + } +} + +.webhookUrlLabelRow { + position: relative; + margin-bottom: variables.$spacing-md; + + .webhookUrlLabelCell { + flex: auto; + width: 100%; + + label { + padding-bottom: 0; + } + } + + .webhookGuideButtonCell { + position: relative; + flex: auto; + padding: 0; + + .webhookGuideButton { + color: colors.$blue; + text-decoration: underline; + padding-left: 0; + padding-right: 0; + white-space: nowrap; + margin-right: 45px; + + &:hover { + color: colors.$blue; + } + } + + .webhookGuideButtonImg { + position: absolute; + bottom: 6px; + right: 0; + width: 34px; + height: 40px; + } + } +} + +.webhookUrlRow { + margin-bottom: variables.$spacing-xl; + + .webhookUrlInputCell { + position: relative; + flex: auto; + width: 100%; + + .webhookErrorMessage { + position: absolute; + bottom: -16px; + left: 0; + color: colors.$red-600; + } + } + + .testButtonCell { + flex: auto; + padding: 0; + + .testButton { + white-space: nowrap; + } + } +} + +.notificationSettingsLabelRow { + margin-bottom: variables.$spacing-md; + + .notificationSettingsLabelCell { + label { + padding-bottom: 0; + } + } +} + +.notificationSettingsRow { + margin-bottom: variables.$spacing-md; + + .notificationSettingsCell { + position: relative; + display: flex; + + .sendOnFailure { + margin-right: variables.$spacing-xl; + } + } +} + +.tooltip { + transform: translate(0, -#{variables.$spacing-md}); + white-space: nowrap; +} + +.action { + display: flex; + justify-content: flex-end; + margin-top: variables.$spacing-xl; +} diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.tsx b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.tsx index c09f9f97a717..c841292dae98 100644 --- a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.tsx +++ b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/WebHookForm.tsx @@ -1,51 +1,42 @@ +import { faXmark } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classNames from "classnames"; import { Field, FieldProps, Form, Formik } from "formik"; -import React from "react"; +import React, { useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; -import styled from "styled-components"; import * as yup from "yup"; import { Label, LabeledSwitch } from "components"; +import { DocsIcon } from "components/icons/DocsIcon"; +import { PlayIcon } from "components/icons/PlayIcon"; import { Row, Cell } from "components/SimpleTableComponents"; import { Button } from "components/ui/Button"; +import { Heading } from "components/ui/Heading"; import { Input } from "components/ui/Input"; +import { Text } from "components/ui/Text"; +import { Tooltip } from "components/ui/Tooltip"; -import { WebhookPayload } from "hooks/services/useWorkspace"; -import { equal } from "utils/objects"; +import useWorkspace, { WebhookPayload } from "hooks/services/useWorkspace"; +import { links } from "utils/links"; -const Text = styled.div` - font-style: normal; - font-weight: normal; - font-size: 13px; - line-height: 150%; - padding-bottom: 5px; -`; +import { useNotificationService } from "../../../../../hooks/services/Notification"; +import { Content, SettingsCard } from "../../SettingsComponents"; +import help from "./help.png"; +import styles from "./WebHookForm.module.scss"; -const InputRow = styled(Row)` - height: auto; - margin-bottom: 40px; -`; - -const Message = styled(Text)` - margin: -40px 0 21px; - padding: 0; - color: ${({ theme }) => theme.greyColor40}; -`; - -const FeedbackCell = styled(Cell)` - &:last-child { - text-align: left; - } - padding-left: 11px; -`; +const enum WebhookAction { + Test = "test", + Save = "save", +} -const Success = styled.div` - font-size: 13px; - color: ${({ theme }) => theme.successColor}; -`; +interface FormActionType { + [WebhookAction.Test]: boolean; + [WebhookAction.Save]: boolean; +} -const Error = styled(Success)` - color: ${({ theme }) => theme.dangerColor}; -`; +interface WebHookFormProps { + webhook: WebhookPayload; +} const webhookValidationSchema = yup.object().shape({ webhook: yup.string().url("form.url.error"), @@ -53,43 +44,63 @@ const webhookValidationSchema = yup.object().shape({ sendOnFailure: yup.boolean(), }); -interface WebHookFormProps { - webhook: WebhookPayload; - successMessage?: React.ReactNode; - errorMessage?: React.ReactNode; - onSubmit: (data: WebhookPayload) => void; - onTest: (data: WebhookPayload) => void; -} - -const WebHookForm: React.FC = ({ webhook, onSubmit, successMessage, errorMessage, onTest }) => { +export const WebHookForm: React.FC = ({ webhook }) => { + const [webhookViewGuide, setWebhookViewGuide] = useState(false); + const [formAction, setFormAction] = useState({ test: false, save: false }); + const { registerNotification, unregisterAllNotifications } = useNotificationService(); + const { updateWebhook, testWebhook } = useWorkspace(); const { formatMessage } = useIntl(); - const feedBackBlock = (dirty: boolean, isSubmitting: boolean, webhook?: string) => { - if (successMessage) { - return {successMessage}; - } - - if (errorMessage) { - return {errorMessage}; + const webhookAction = async (action: WebhookAction, data: WebhookPayload) => { + unregisterAllNotifications(); + setFormAction((value) => ({ ...value, [action]: true })); + if (action === WebhookAction.Test) { + switch (await testWebhookAction(data)) { + case true: { + registerNotification({ + id: "settings.webhook.test.passed", + title: formatMessage({ id: "settings.webhook.test.passed" }), + isError: false, + }); + break; + } + case false: { + registerNotification({ + id: "settings.webhook.test.failed", + title: formatMessage({ id: "settings.webhook.test.failed" }), + isError: true, + }); + break; + } + } } - - if (dirty) { - return ( - - ); + if (action === WebhookAction.Save) { + switch (await testWebhookAction(data)) { + case true: { + await updateWebhook(data); + break; + } + case false: { + registerNotification({ + id: "settings.webhook.save.failed", + title: formatMessage({ id: "settings.webhook.save.failed" }), + isError: true, + }); + break; + } + } } + setFormAction((value) => ({ ...value, [action]: false })); + }; - if (webhook) { - return ( - - ); + const testWebhookAction = async (data: WebhookPayload): Promise => { + try { + // TODO: Temporary solution. The current implementation of the back-end requires at least one selected trigger). Should be removed after back-end fixes + const payload = { ...data, sendOnSuccess: true }; + return (await testWebhook(payload))?.status === "succeeded"; + } catch (e) { + return false; } - - return null; }; return ( @@ -99,76 +110,174 @@ const WebHookForm: React.FC = ({ webhook, onSubmit, successMes validateOnBlur validateOnChange={false} validationSchema={webhookValidationSchema} - onSubmit={(values: WebhookPayload) => { - if (equal(webhook, values)) { - onTest(values); - } else { - onSubmit(values); - } - }} + onSubmit={(values: WebhookPayload) => webhookAction(WebhookAction.Save, values)} > - {({ isSubmitting, initialValues, dirty, errors }) => ( + {({ dirty, errors, values }) => ( - - - - - - - - {({ field, meta }: FieldProps) => ( - - )} - - - {feedBackBlock(dirty, isSubmitting, initialValues.webhook)} - - {initialValues.webhook ? ( - - - - ) : null} - - - - {({ field }: FieldProps) => ( - } - /> - )} - - - - - {({ field }: FieldProps) => ( - } - /> - )} - - - + }> + +
    +
    + + + +
    + +
    +
    + + {formatMessage({ +
    + + + + + + {!webhookViewGuide && ( + <> + + {formatMessage({ + + )} + + + + + + {({ field, meta }: FieldProps) => ( + + )} + + {!!errors.webhook && ( + + + + )} + + + webhookAction(WebhookAction.Test, values)} + > + + + } + > + + + + + + + + + + + + + {({ field }: FieldProps) => ( + } + /> + )} + + + {({ field }: FieldProps) => ( + } + /> + )} + + + +
    +
    +
    + +
    )} ); }; - -export default WebHookForm; diff --git a/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/help.png b/airbyte-webapp/src/pages/SettingsPage/pages/NotificationPage/components/help.png new file mode 100644 index 0000000000000000000000000000000000000000..29a435549f155442e5b116d237d43bb316a897f0 GIT binary patch literal 31497 zcmV)GK)%0;P)+AI?jxW3rXAn1=0a!mvmUdPQQdLfsk~7Y$Sc_t5bE(sZ+P#>+Wow&T`pu*la%PSGg6_~A*mvV06uV1Pp>BTD!bQMh=Atrc1ODC zRiVEt+^d5=cfdg^hXB6!r0iak+^doPmSNutgMG1kErWwZ>mLI6o)gr-(;;Z+X4rj= zytk#D{xaYXS}lJ_Y72t6>|RUV%kJsdm=X2%#A7De6OV1OM~^G*@#7a>o$j>&_Wek+ zN9B+P-+5t8-3&on@06x?K!p>JX@b{`s)q^g@6qELV0?Ya;%95a_XeMed)eIoH4c83 ze{V7{FK_O$Pps+i&u-0&tL!UVy5RY)PV>ZbE8vM|I{-iL0cm%yHuqWrfz$7oatPr4 z7XD!%(_zA~O)#l(sy*iLk{La`9&rqr1FCyt@d4mx1a6)~Zo)G` zjC)X91bFV=XW_5^!v7%r2De$z%z zpYIsde0JWNSc;JWM1kj_A89Z$5wwJo$n%F0=1Ar^O(TCbW56A>aT!;Btnt1Ekl&nY!DoaSd$~Ep9#dJfH=T zgw|x=4784BRQrzN+Yx??=QR5G^u&z--F@zMBgG3FDyE}z8T`+0Tg^^rcHFTI0?_;Y zqk)t|0FS~PyS3I1&YJ;OzUL}vnowrmYr7G+2pFad1~e@Z`eA6hiGUN+k-@4k7qown z0Mx+QMKcSP3CDz7{Q%!*)JcHOatP z4^}GfH4O%lHb2+_j(}bSHR#COox%907q{B+hm}(G%78>6fGQhN3_Upz8HG@#H};^L5W2=#k$+2sL@VoHJ>A0T>GVn@)TK5PC$jShgOj$i{P zHv&)|MF{4Wsg3Gz+|Ou~NV+s{H|l)&B~lkm47>Z08_f0{TcL_@9uG>*p0Fq7KnFP9 z&~HL;0g50cnRWR$;fnI-t-?ll>UByPM*sk6%!D_ya%SUG^H|}mkaBeInILXUK-Zl{ zQw<%M(fl|{Kw2jnO+$3-wJzC4rG8MxR<)uC3@qDL`j^svC4o1%b+w#Kt%#G0P=l&`r6|G zsU~GDOp!%xLn08eqVYz*OSL?ABpyMm=z0V3_pxiJYe*OewNrUsf{g%B9F>xeNu8U! zN5(gSf5$ceaL1(HJbGlkxnSaSXQ(&A6VG+jGD1u4bp-^vQh~iB2P(iz?sXjm-Y4?b zH#FNXe(*;y?lt3CBYTem=s<@RHL5kaY~YSy1!kb|B}2asy$7#FI2Y+TrHwNMGMgSt zQx&5+jZJL;=GQ{AZ93}UQRfR8)Lh2PqWfqn)@DR1f6->iEQNh z!S4+r!b#s}Nv8ufG?+q%1RC`qUXSxi8H@yv8C4JWJaPlnkiEvs4>W+6Ah-b&aDV(g zSHYjYwKYF*u|`GExR5>(OLpMCEFc=vf97w(!oXtol8z*`rzxjcL}sB!_7 zWk6D3#VDCrl! z2Trqd6a|TtC{rDfqlo+)kbsThHk#BR7Or0Kp&I~5({T+%cP6-e3NF+(2oo3?gF+nmB^YGN_1~3rE-fWSxPh z=?Q_2ld^&q&D3uo&Yxr&!(imdqp=F+3>~H%JTFsUY@{a4mknEm2;SVP5ru)K+F)aU zk?WIDgs%g_u|RuYARZ^D_Nt$1lSXjQJ)OR^=Q~#--mLqdY~O1j-`@c~*S$WkH+;x} z`%loIWOx{mfKBQi5pWw%Pn9vJ_p!yNrZkrRtTsn~v6YRS+RDG7s`dRqMeUS)TTmUFp=Z-hqWQkw2b9gh$H)rzKs-N`shGvz^L2@X z2s1bF+xx1A`0>jM?1l?*e+D?tzWJ)&@G<8tdEeB}+o&rbf>clniAI#++@zY$7L7JO zrs@E~)j=JfH8yKfC_H!`86ag((h=iN;yIM3C(*h_I#OtZ^6W6Ea^@X5=t;}rA2qUB zrF9vwWN;^=Q;!zLjwRQJH}%MqT~u>2nDM8A8GV?u--iHf*+_=Cx;J=!2Xgmwo)%4tVm}74Sk=r}_PZO8{)t z=$Q74L&u57HNk{qCfnCHmSOzi4SXHpd^A}iaTUgU7ww5iSJHc{C>CxnoeSpJi9N0y zw!H%I{NrGs>HtXwv$1E{fBA8Xx$)kGP(>>4H5qoHKFj_HaEyK}hX8UfSf=6M|9p7_ z0sSL4Jy&HKV(454ZG^_;0r0`!Y*W4+NBAH_!VRI4m6# zUFW`I;6uEi9v$rYu8LXoyQ|>FKYYKpeC(=%pg(KERQQvN=h>s`8~mN{zJ8UK%N>Bu z3!385{LiqJ_V8ba-fGYNAebS;YM+0r1NvjX1N+h%x?gncm)&)I{yWoZ&G2t?ubHru zvOfa64MOUx7kpS>{l81$==zcXT!Tn{Wm0TvH7Ua@aZDSdcmaD>CK6byVHXmttavjZ z(h>Q!o-~kw4i+WtQz@t%>JRfJh17(-*E(ST&yB4P*gZ8}(^(VTtI_{$IN@lhA3NG_ zudH6{|8Cs42|8Cl4=e6}5<1+g%YB|Q@iOPOn_=Jf#^$6GiGnckT9XkBl>mO1POI}e z;k-To&a&F!7_~XXkrdXhub9t$cUrC7@;MN)Li;WI8^Bv2Of&O>zV3hj^m2RjVI=_o z7&XC`B-&D#SP?Zckjs$Xhbdg)2R7Po0&=-q{2-|l#fYMn$z3V+04Q=5Q{1E{GbFzw z+Vb+|F8IOCt!DA>u1*eAe)Cx{{asU_`Gap2K=)VLesCkq`|1x|2YB9>k-O}I+4gN` zH%I;tlr9^9&ezA}ODvNtbVlj0XT1~5*zx^q!&*mK9{;_%c6USva_c~Dp05t_84wDF z_Dl9BfR~^U%$f7YZK!!7P@~ZWtTjPp<%wqlh4zO?VbC|8QDo6<-qz*Iovm5F%HFYZ zv7-SSfNvL$8e>L|g(K@5u{fqVYeJdWiZTE}rNx@)WDx$&;q}laKZjC6?luMgZ4WMn ztA09Ds0adi)|HpRZ1>t(P*)QT^s^eL!e`ku8*<{qCHUcEPI}Cm%rsd)_)F10W zeo6nawRycWFs}o<F=UdXG z-aJZX-Q?mP<1D`DDx?SDbC66d$$($o*SQRCy0^{T?m*RFZW}ml*}Eu-Dv5`WP;Iqw z$(jlP*p=x2B>-tx8vN!DZD#&YXUe${*sY)c;2r_C7Njw)4&)1eaFhQ${esz;!(;OT z$s^7jK&_~YiKCvEvo7tw7Dw5*2H=6~{aZ^~!2Iux^Xw09YORJ*BsP=#bKLy};F$Kh zoIfC-7JRN#!oi@m9&2q-D$}OM6TbKSR=E(-MfKfZ%{EU~F>p#J9OYg|`@dtyj`2Kq zmD6?z3efSZn|v8Sf4De{qbu;(t#LGPqp1DV)sxNc@bUct;EC5H@a_(Kq`T>Ao}n+V zz&x0%K%rQWpapAI&=df@5nuaBi+!MTS#mFShT9_0XME`IJ1)?qzbKYc9%(o|S65;_%uC zeYg(yhd|HgK!xHzb@{j8%+geyKO!Kep3jD26y?7qPVDzza@*DBpMLlOcxK%r{#Xd) z&&_!+EdJjw!N)&xDNH%%G#D|Wpn|*>JSJ#CGt?b>i~HRezdy>d-`$U)XFr<-Cp44Q zY4m>t;*2owb2hZU2Rq#toRNhe_6WF#pX|VWTJ7)gI))tXI)T&fJJvw`Ylr*Jz2sMS zz%y%~fm>Ew18+R#QaJ3jr_u&owSEP>C7^ExsJ3Q)m z*TEcZtY&bIpw|t(9dEE}@DeD)@+hVz>4XI@InxkTXHRZ73j1^Ev!f%hM0q4yK6o+A zb=1WD zzz2#QZuzZW-#u_=BLQA>QUCWx+Op{m7lnGMhbI4Opes!OqL-=|hS#wJ_wVQ+J zVK#5wXx@0rB|*J$es}a2r`2O|mkbT7R=8tjD8K!!t3K(d((7SY1Z{#l?s>#(@A|`E z*Z&{lbfBTm4YK0}r^$yVnjF1JQzlNgnDPohn%l-}0w#=X7ooVn+c$$Ggx?p>SFR*l4rjqqSaV zi*p({ZzGl@V-LCe-Pe9R)9{Vr;UD>SuTpndb#QAzO*!T*P(SVnSoFVN^v7QO`}wfg z(IDT1T#Vo_V~j@`Hi+H7m_GW0o96k=IHEM~q(Oyw)Ap@cy8EpV@^;>)2^W zo$cuujvk8~WkW@P#vZyWEl8{(V=kV{)lnojj~X1s(G_^$2BIGQkfTp>bmf>BC7%A~ zS>FDtA}!EMsq4Y4!O^hvSA-Y#iRs_YM~|$}Xb225PJ#x{lm{ZJZk0>TVR{75^>=+M z;kKupa(v${8F_}gQ)Bwx&uhQ&P(KQN=DFA8kF34}rZ!z+aHcs8I_%a=I4hzaX;UZV zM+Z>#=0W$Is(ZdLb;9wSsL4LhL1$BCpF^BukAbkJgH#FNCTD%!* zl)fIU0H38lpl>+|+(g5Yy%LgZos-t)00n-BAe;ZQnR1(V+ABd1x!;1oaGe5;Rusow z;eHoDGSLriRKa@x=l;qv`FVe-2?82vNE-!bs!65tIP9{Z79Se<`&<=Z`r1#LA@7cK z#OstiJQEJrQVfFGBLI=bP3yrtac93;01X%cp)TUUoy~dgf@-@VxMx@UVlV={2~<6k zi(Icgvc3W623i~a*}(;C1vH`50bLPka{mCFx5ToW34N!lOsDDPz$b+;+#xMM9nWvWt7ZLuKhWgHlwnd*y2%1G{Q@-=`lj#=8zB5`5n2(=B?as_cqNv4<8Pi~vu1 z;s@W_n)aFgD>^76^o#oMEyM^VmzSyT>=RX?{%;7 z+88?)%AF4_HdbZbQG^61-U5#>{RKFO)BBRjs%PNptFMM#mI6J~S{yqq(m%2%;^YMr za>FSpix&b2Ahm2FflqjQ%u$tB)`RV>37FF|dk1OwG zPA8+;l9Bp=Ga ztNY;NN1On9#;WSn&5Xl%MsNy;|+f52 zUlY>;8pk))av4t59|jY^Tl9xFoi*LELB_5I@i>{l7>P-;OKr?KC&TUAeV|iNz#+fp=%xT1_yriPuo6RC?bL+rR1dnqO;UxN}X11mL)?3XQ(O_pf|g zg(!2Z=;ij_{1U*^y?9kzWYb8sFL(UX_?mMbTKLUi0eBfyue<1tEyCaEUn3TItfFef zs4-%hOC6`@yH>@6_oBfuy>({=HTle$Gi!nbw=ew9e@wRNz|!WeU7_>{4dta{nnV-r zE`a+ZI0{XhdY-?A_N6yMM@NTmGyX-7QO8}sbgqqC0?2EKrc!HP>%wj6T(crfXQO8& z^`L2%kA*lzkX(qyWJz(QAntRwGh0@#sO~m=&EaI16BnA`czEM6P1Sal8#-73K8fEn z9y1A!!ddcEbt=x03V(8@ECg)ka7LxXVpyE{cVB?mxSRPK^XJd^;J^lq8#m5t_iCD5 zYjA8I(=Kc`7NcZ?slAU8rKHM1>@AtpPhWf@D%p)$hfy?)ye5)X!He0lC&?Ikw(k8i zkAkV?HpHoLmfXa6jx(x9H4*hCd^1=8Ug9_JK7S59`{)@_?|4E3$WG*zlDi71Av;AW z?ABy~y(FWC*Mp5|23QwSG?AX)SxcIB1%Uk&?E>t3-g852%g}A`<*wjW_+Uo{}~JbPe>Hb z;ARI&p{f#CO;6Yi<(+NNgt*%)Th`0*`r@>Wdr8ChHgas*(iLa=Q|H8_GXB0VnzAcJ ze)=mrGs%K0W{n;(PPTE8ApgP2zhWoxx}eC%yF($NAk+<*!#kh2-CAXG#E3DpTj)#{ z>D`&tqnh6684Lg~@f&RDn7~D)LMwz%@n4WkLXL{XDyaZexiVf>UqHBQFKNT6eGT(Q zY8LtnbsXHVHVUrGwKadL^Fj8cU>m*u!29yD?24daTO8^&uO5wa8pRTmhn2tU{MoWEPGIOwFgJToa&c@k=dbriw0reP zEOtas#RJz!;Ul0VEJJGwdC=z^p*8|&phxG0N9k&j&AOq{Jxn}am-yFG#(xHt20y)F zDz6$AC3;Ul#dpMqZ0Sbjp;LGscjkn$4EO!Ru0~_dpqqu+HkF%FZa4K4F9u)*+n4PriAm{0OJA3`C z!O^{W7R}F;ggDv&GVhA-S- z-o6I7L@)71Icky7@Fjs9YsD2PmNx&!YX@>SQ4qo3I6T%U*MzVIFBO%UYEZ~eDj8ikOaVcnIT1-DMuJ%`## zE<*4X#z2NzrVxf*gHqlD#H-6S*}Yh;v)z8hS@?VoYvZbOM~*Am8cp5j059pBV$eyRav@SijZ0+=Dxda-neZS@gBM6uU|1rBJSdf2nCp)85mb?oc(Nm@8 z%*bK&cILb0!@vJ5fM@m>zU#d*eQ8=ee;dtO!t6gwVL1u=h0e%LBu+-w4Hr4MaRJ;^H?pW6p~GG0i8%*+x6Vm9b6zb@}yIcp^5-``_Bi*Wx*`-x%T% zF@j0kY|}3b2f3(2Y$Hb=6Svyli%`bnD3~_ti9j^?~mwNPe>`JH8t}(L!o9kRUJ!Km*cBN zVTXZ<)7lKkbVEThPinyTY-kRp8mOFUm|LMXs2qEC0+M|V@I?HD=Ay^Bm_3Wk@#8Et z6Q-%<91W!C7B4ns-g{B2ec->!{s=gcxOpcE>afJzj9t@)dPqMq4C($s=g~;=X}}>e z5&@8tmw~)020@N8n?N>qFBHM=P0*vmqZxvG-ba>dC6jp#uFEO&VlMO&D#;67(R1RJ zIaq8s4g~y9Q!%eQ2>okaHb&q12i1ZNnRL<@FLU(|2A|t9iC08Rr-ovd5lZ z728A6O&%Bgv){C}tCo>DhyZR(v@tT8OB`;3%(-O4lKFT1DS#CG$>O+&$9w;@R?99! zq#Av>-S(vLx;H3jkexemMx4oF>`$q_xf#@2h%eT)y z8mI}rUV1U{IQb5+X-n-I`1`u}W|>}48s7xxP(~eVGT+&)3WU&?U}$)cAdi-M)FA#o z;M~0-bt7TL&C(5OGxF(g} zXmcM12q=W_;M9gGX4AHf@Yqv< zLtcN=z5W1u+RMO#;GXfJzY}o0>-<@8`GudC&S}^q1k@`bc4E9Xj`CTBa!X!{F|NbD zT;Z+q9M&N3^5;Q*{Ezd0>LZN!lU<3-V!OY+;5y$iwbsVb=SwZu!Ko)q3HRPa z8lq>*Z`!uO3>z}i3P^!+Q)snlu0BScBNPV$n#&nPMy`yboOGUT+q{QN7za}tJjR2a z%iyku7FPrKlA7FQfCRIPuEICvH?;WS4`OO;kmywi!xtNoAUqY+^e0(mAbf~|fVt+z zdG;quTjiP&1R;XC=Tc_TwTOlwOUZ?i=ly?6u~?04vk?8neq;|+3}?Tfv6*Mg%QIT5 z#wgE20Ez$Y3tbiGVQGc-D$8H98xwusoKU8NSQT1C3rB!enqhYZ=}6i6;om)Fjy0MlDLbiRoPH?C<@GSm)!M#1l!YN? zbf~ZoW+x)Od)ql5Gbf)g#SR-X!g&@pnwTaWztihDKg zYk-&B%d0)?`E6g?V9AJqjNL{2z%tdbX}F|HrQ$bdZEg$B|3ao(7z_ynPLsnouAS+D zu)Bf)!iW2=H_g^VZ1PY&ukH?0xAbt!TAhBOAF8xn<#33Rku3e4g+L1D>PV;1lM6IA zQgle{7{1pk809FC1NB1h`*t0{k19?<*k9guHFg^Nt~bpBzc5vxOZI^$m${C#!Cdr) z7CUlyT(Am%hgM>4g>r)|#!L6Z*yK```i98EGvH;?y?3egKg(W=7o;5q0S8CHbQ*sb z+P=5}=h*e^wYf}z63qhY+9BqrXpP3e=V9H~vR>Ds3&@ zoqhUrr_ny_hUIY*wDFNiUMQ1?U`U=`z?yn^S(Z*5&_lO?6x4o1U5o;|hE<((|S=&`Hn>KH>6OJX{ICu`mgR1{Ed9=trjy4jx zdaa^o65PpylJaXYf!c?HLeg&cCV$_TOtpAUI}8$lcS6wQ@Qu$_>`{j`gxnqdkjd~Q z;0hP#8_5ry|Ki4V?qQ(iKaxr`WMsm;u)g90KX@lc4Nr?VqUPJYtqUs8bUf9-gqj$Y)wG9*%<0)`D8!Ou~l=wrwB_$_>&es%lRh68v7!QzmW!H|nm;Wx`L+)5e|Ka=4h zH`oUU$cV!0!=x*}A8^V__$}VhU*CQ;dI0?Ij&K(LCoXTZ&6m!{71zu^|6qo>8G|Dn zRHEZsD~q&>R^h0djSoaLrlp5PbRn55nnn0&4-S7Vr=a8E|B-7dK<#mn4&l#3qmSXMS5&}cX^yuA(bAy?At+8CuaRfKixTkLc~v&m z4!KVNVg&oY8Ldbc?hAKp$!l5lTnE&U4!IxuSjrIAqxbT?FK+Pk!qf!-8#P=BXJbks zPlBJj(&sir+*Vc`hjuQ(sV?AvK5#>T!Z7#U4=us`Y-kwYB%TEPxlN548=C<9^t7=n zTF*eFBU-DQRJq4DCh~69p3HCYZ^mU2B6Z01WCA`r(Hp@ZU887U?kpyq#0`agxN`*w zG{9q+Q;>lvqKVha`kKonQ(%rS(FbDq-sn!6*avZ6xug2NLy?m4R<}>f-;b;2@2|L5 zBfc379K7AVu!Y{@+q`w79ZhSYBgi37BWUbN5$(vTo4KwURwp)=z5n)=tzGu+m5a?! zZ=Pm8F=GMpa34ZPV6!$#X7C#cB*jIP3~3TZhk6T2&1P3 zu^;lBk0!UsdmCwMxC!BUL+1hNG~oHZk7yc5DVJ90K=%+gK2TX!D{|Y;ZwAYR2aL8f z;~R{Kz59Y$Of`5=mtma^BhW?(kIX6=)dg^TLCeF(Hu$PhQzy5$@N)|eY32w^YidR# zNJ<*Wd4h&v??XI2JuUogHyJtt8XJ?OIH!bxW@&XbeuzR96z8?J$9)lPE8Yarr8NB8 z091|v7(N$iWH(ZIA2Ax+XpmvmUGD)JKgqfK<~jP-Abp&~s1gpwLd;dqBB(PqwE5n! z|Bv@)#>6g9kN>*R@u)K!MY;T8GkOM0mHgB;ax?N%&O;~jw#q##7eNhKEEfZVLnP_S zXn=auQeurg~x$#S#o^W|A4{D5WbrUx58 zGY=IGtm}C2Z?z}HM!;(kjSH%+O3Q1i;*Uz;P6I(8Io66b$+->s^3kCF^=(&+hHGkS z@@1W|hB*tmJpX3qQrmcJlTEZWUR#~m8Ikhvl?EFm_eOEvW72f-UPxMn&_Ei2O&Aq( zx&}RgMQiHR&{fatlmF6KD?rx>WZMlE4K9`fY8-qh7$LKot0|C@%SkX{+2s{eY5ff- zQKeeaY9fVzkr<82MMo+y-dxf)#hT@j!wP#8btoENJQ^nMBF!Q2@^B5|_%U79N-#u> zJ^7{5q8K?un_`&@Sgmv#yS~02{_JDT9@x{TPxruXY-~(!I^O61zJ8AR^30_~qhqHO zE)$)4(qz>a<(gy)9<^B=gY*-~EFEh=@eL5Kiwf@a@ZJc03eOoj-AG&m+=wOlVz2e(K5Wg>@TO0WO#BF-PFa744yWAQOx;txrK2ib@%DTgn?! zzh&FWU#Bt{lTm=5VLXRcJYl($9KB>UYd0>_9|qH;twY6`u#AhI#P766c=Dpzuh^@XgiGYlMTcK^XHafVMM8xNZow zj@@kv1hMatMkT}8ep#z z-6p1To>iz(4h_dljS2v<6D#GE!~nd9i2mZ%bsH<*zN&FK%k+oA1n_oHGzIx2`mXcn z$q;<``xS>fxlr~gQ!b0}_E1|H!Vw}LHUcE|Jw1>-GtspLCO$Y&oZAGih*`b11Aex2 zo?Hm$n$sCW{))2)uqY@R(MShIIvWGMJD4wHL)+y#UfI%Rw>Wp{3tgQC0~vU{7rIsn zsNZ+RbZBmFuBxqz($|uK*tR2HTMDHbD2xx$Z5cWr8`q>LJXa5+QFsuc6R!|x2&AcfJ-_-L1bI0RD#JumMD(#9$XGc!$_qB1pEWkKKv|60eSoQqj_=FK8-S*uzE*FVB>o!95Qz zuBE*f7B|$|abF2OxlZ~EU;5}$nAlLZ$_3@y%Mmb$oK+?j5XZC1FibTs4VT_nQtX?x zk+qO4oni`T*4oYAbJ9@%`h)3k&qF=uxZ=XAbLYvZiXvov=v9DNFBoa z5N=l?HP4CD2?}hNB*mC`cRrAuCn^o2{N?ekvYSv75jyVY|D(wT>#7=+4~Ar` zqRitr_dK*1^L_PNFCK3;7dILMSrUnO_!C~I_&Xb;3}zc2PSVwJdmg#AAq^tsePzf5 z8Ae^Cv{`DB7X>wHOG}IQL-*#**d6h!7dyWF*MIfb@cJVr_PIYMp#AZ*dBKChMmY0g z`b_sYI7OKflBV@eb4yDGhMy)GkMytPw}UcXK?mG`xV)9;VCea#_t|ZC?qgd6+!d$- zZh*n5!BNoFgK4yI`LtI1f2Pe%=?N>=I4q_!?-PBVi0FRieQFfI2kn!Yq>hJ8vil?| zSLM83Zn$H<`TnoxKwp9y9r<*B4aHi5S{geHu2}r&%17bSi5L6Vs9~dE+2glW{mu0& zZ-Q0ppMo!aWT~A_UFl#l_J8%VURXkP$jrx@}KafhH|ST@?NoX3jWQBgkX zV9}sY>81JG1!Z`s=hQTmHusuY?Tf(*@D`wQ5~vmDf2PyB+DPmA`(U!fMa3QzqqTuX zJKl63d;pbHR1K)&5sxDTYqW%;f;=3*8-IQ6(pIBADt!rp9r-o_ebS^!{&Owvjey3{ zVA--|uz2xeueA}(8rZdD^Q)WTXC1$Q>m0xx?Sl)OT7a%s+;3ugmJ`{1E7v>%-~L@2Y}(Ncb3U-hPb#qrwY(IFp5B%i zjaCO4d*r5Qu{<W|Nq~YI-e?Z;)zx|%m-j!wjHZCEaa?naEkjoIY z_|>27w0;g{Pb1HZkZB?vT6-Ji5#V3ZU5%4)!DOKC|9Upu`*8IVc?i-52iX2<;{M7Due=DiI1Y}$hHMyI z@}^mC1Z)-htW3w?s^g9wTN&M@bnhr#062S-dUPBSEhM}~8I)m?G<{@4(iFT_tbDi$ zs!Epq{})U%>#8SKwLuN;Ef)fVTUvm)ILiDvofb6_JsgR&X$|Vd#7HIsII9t8!{NX(COP_}sTFUucn=>xYD#`4aa!a?BC1Cj_*;{!uVGSTsA{EPIXzgql- zSJpXh{V1$<9+A6OcfdMa1_1({d&}fzd+C%pz;s0JiAfDYp&mrJ)O94+nP_UhNBTJA zqIDjWobrY&(-1xf^Y)xQGFgf}uQQk?5iaq5{k_xPHEuy0^r5}!xPxBGpyq6T@gqy& zH0RzD9<2D9f8QPP%K;w^dZrH)$qyXTPzAMAeX(|qr68>Oto`I5lqk|0{FhP+q&!1- zaz2mw4-YSaTkdUx)oapdW5Z#u^B_l_J=*i`0C)uSCdbRyxxX*GvOeYK4%8=%Z}P>5 z9JqtWDHl%@B-f_go=Mp_1|*~%)fDsrmqhk}&*VzA_O;!4Oo`B)0oc=}o=>>NPWrq?v5b0|JLTy0lasE9Y)Tp*v8D+E|o&@XvNi$67 z+`z{wf)Juxj*l6wYDmf!GjEn-8Bi{c&^SNgJ8*8uRGv7_Ll9vOrrx z2jI~`6(}-BP5mSKg5btPyYDQT2{ig5E+P2&d3Etqth=FccSzQ0mwQx#U2TGyP7jtR;}QD{4>ESw$3Hk8%1k@8aXGmWPj1%EXyFwz^94Hfm?4(=*0U)6?>U!d=8Rd^w9a&EvNRL79 zs52z0+r~a?cpouHGj(i}j((s|#eo{8B3wE)j;MZ+G=;fTTH}M7;DGD<%=_;7y@4BN ze0>3SQua51D{FBOTD*0&P%6=vhh)4G%renFQhU zhK{9vBDxRv&HOW051QKr>2DH_q&^#~zNgZ}Sx}Qfn_ATXxffv@hu!;>L}8X%^nS2WYI3L&l4q%|Wlfd+rOIiQBN>xwdnpH! zYucm0EF($errNO8v)E$^JBS@OX$8>^elOR+ote%>TG?+PjSPD9lTj`niQ8c4gV00Y zTmrvQoj`GZ;1SZ$oYx!fxH|jsZ$A&c$UOJD9(GaoSAgSCb%T3d;a(%2c=}G`__#gk zbyMOE&FGo)1LGlDX=w#0BM;Jo4tQ`4@>m9ww$TG(V@W!dkY}aQ0Ob4(98*<;N0*;` zBlZM{amh6ZjnDx)H>m5CvTj7J0EE&2Uz=r`LFY~kW=+lLJ{kJGKW3JUd_0;7rulZz zHSx8`akn_d^y3>pZx;2QT8v+Q6?Q}RSAe77QorIly#Mj*&1sF(d=Nx}_u=NUW=VN8 ziF_A*!ZLHk7^{tDC~~x z&j3fk%ICP|-xU0C@574?`Nu)?TwUPADge^>p18_P+%H)jjWwqT0m%eUf%weH;}N{Q z8L$tW4XZfX6X=+_;3fgCqBfwtJ^)f5k&hsr7?AU@zdFh122k07J(+`eo&0qh)sdk< zF|tV2{IThlY6>G9Kiqel$u09sj-V^h6jKxmg43dUm+T)kHPoAm>y8mc2YV z0XMf06Tt|W-Bi0gv z<8Tj(!q=#%sT(FOD^E=dC6vtjRzjtpCyw24z8 zM1FJo)!D*dejJcK_VU~?zoZTJf*eQyN5PvV?n5+WUhG;0lTVw@L#w#<1f8r2jnv@| z+>(Z7l_h12iUxW@oki`+oELy7jpqG8_{jvc+9+diOXs1kB1=`ro+LmO&0er0K!G^4 znMVQzVgT{HM4KV$IYPH&c|mHkB#>F^REFt`ktX=RND=?_zb`dQ@A($=BJB{s-FFGD zU6cb2;3()mEP@<0Ie#COx6#kkzmfHL#5h#i(y*Rp1s4rEEi#Eh_K6x<^4L{7#E6t} zK2seFAcBWe5llRmp>Or51PY{}f=seM0lwf#0^RWwH;aj*lo;1087b)Yg@fO4`_=Go zKbp~7=pAJqgc&)z6rt>|9Ebo%L69R|K~&c>$ekt!r4boVI>MQ9RaT5jGxGCPA$L#; zFyZ@I@njIclk=JAhllj!Cb~#K!IeHnN}-Cs_SnujHJQ)~WxQI|(NrfjJ$#5D$Bag_ zjcxBHt-i&%0IzXT$tCUcOdpiw1MbzUYTr|0;J}s=gcMu+3&)Rdf7qV)_U zK^UG9V{wHBHSb<)NyKPo#K2}CLzRLTG)>| ztE`Wc70#napQK)+Y9p9f?TZ_PfBXVlMy;3(AxHn;emDdAFeK5x-3EJEYil{s0gi&R zfG|O;89!hi`Cqk!`XIC%hh_r-7%G<>q8~j0ssLRL)sWy{9gx3c5T#mM&mvN&hT<$A zUFMmG46C`ZtY;rv63fqloDZZh5@~ByhzC^QD0TvUW$N;go;!CXTzhjXYHjF6+9A-9 z3JkCus1!vDj+K~8>?58SCjNa#n;B9!5>7gPN+!xrr@+SCjYwORf1BJIYEVxSl4RT` zunO0wFL^eLbOQ1`h}KufO#x3^4G5R#?LJzVM*}$EpJ)>8YCxy4LZLUB=&9j0fhka= zs0qA1?6kFU?mxKefoq_Kpr*zg?kiycwYHXn0pKhc@$|Xm$PXWQ;(Gu1aE89}93%iU z3dGbA^jfRl>jE*N2YM=(;@SX4fxe&%i-xXfWI0gUkwBflN9sWAm_mUh00WTBR!Z6z zJ9z5Oj1ERn*X7fbfBn0Egzqh$Y0yhmMG(xmRy=a;cG!nvVBiHQLz(`T=M?b-c77oxKZtc(gFR~5G7E&{BTEEKG(8Tiojc?MQKAau@?Z6$;p}Np^Z_3;#D+5 z0XC78c7rwvl=4nR{{XbxQ2o_C#um<}&mqf=06?)|56A_}dCfg57uz4*Fvrx0NaDs; z*vV;Sds7Ai;0RdsW;D6iRQkI&gx5#JPS8&8J9=qTE6}1>$jd)6V*#AnP>$ZR2x6H4 z0Wnl9VpFaxfXywHM6)T_HQxsSy~vaJ;2|bi$&@uy=t%f8UKon#NURmzuXai85d7KgFi2%j1H25O zB5ZkQeuQNaMq+6M`pXT|Kw+g7xhA6_>H3MCxy18VCw*au#bos~TSa71J?1Z`OEZAaw0r#4K5BgQqNo6ek4 zDq~4xgUfao_tjcb0nEF?Hf`y`l4r*Kwd*&oGV8iJVYA!z;>K0*)S3?HZ^>ek3PdX7 z!=30apL^wU302nwb&7`|29jCuJv40GU#O7ezL1Mf-;9D6Lq-kqZEElv&N!5g!oM$Y zYXLvqXMbPSc^>L)x50p{s9z0U8q%BWF2Iq7v_dotdkA_dnoge%=TB<3ejtohKBilA0$EJ7E7}S*zyR z6$l0+@{@hy=G$G_)pasU5Xu^tt9^NDX{vjb;jM3)1!G2xv)xwmIXI{y^HO{yjX;c_ zt&L!2&6g`}Wf;~5WZogDNSin6+!i-ln8U}Gtj|Fqt@adYeFXgKHOm1XWG%`wdgNH< zz?M57mkg$Jr2!EL00V9$e@AV%(IcXias*qC&r!2m`aLi$k#o}da&-<>0@>2&6M{LA z7?Ls?;5o4#;)2C9-Ti3iw#|e67}Q#JMT6JW+6d~)rp-mIZ6()Q-AL9X!XFC_@t%iR zN=&D9B>UrE(mwoAJbT8ZsjRX?p{=ZC5%jn;^y+6+;|Owz>DAMYiLSCDlA5 z?GUNkGT4jRetTPX2H*(j1wcGi3%c3fcgcJ^`LxTRj*2rf_hgNmC^QRP6OUbKUfa|S z33dX+XEyThE^@2U2Rb+UjMpjg2D9m0xMUP^vEommgMI$*64+2cZr($eHn{f#e5z z6jS;WUok+cLiKk2l~o$J+Ws2wIVd+B#LlNRJoh|(h-#gn*vSA=j-P-$?SMv|CR~I2 zd+$Sw;hWdY@VleIf5OpCFmhx)zW2x9;#`7Hty$qcR=s$u@QZc`G^XZGgGKrS@MZ{n zETJS9PnqSw-D@W{ydwJii=s^1$qD0dhdVKUvZbw{zh zB5~43W4j=L9C zgIasa?C?C)L#NbBoAG*xh4Y9jz*c|oP#ESDf(len-KJU#Yf6LB3Q^A3gOl1=zPAU3 z!IdG3w1rN@-)Rs8`ekY8=k@_D#}}!FQN~>oMAS`XP!+hkN%kvHBv1-Mj-OH#iJ*d& zoOi2lyFQMk`y-3ss?PcR`U*H&1COp+RxL!28(Luyh3B>O2Jouj##GZ|$4`=B(9b@h ziK&(rYT|i(_`Mpd<$>{MweY7z?ecW|tqOPrkAMZOc-Rer53=Eb1&B$5Zr$`b1A&e4R5^7QuvFt1CNgq=z#R0(Q3m9 zsBphb`EQN(lPhD88_U`XqW01QL29sttzP?*KUf|zaA)IDAiU3%#^v!{?1%#OFf^hB z1G$b$OW0I1^6$Fyn0LZmQ>{uo}%$6;9Woj^9IU!dTLK&$rO3b%Rj5*TonEsPp%@ z=lb&t8!G1K?XC9q6$^_^sX(Z1P=P@r)zW24?sXj~&W+&y3#Yk{JE{qy>IS3UOW|_? zX$~>845Eci!Hp-hBq~krGoVy8X?&0e?7c92055ToR-qlaRl~XI@vx-KvmSe|Nzm8Lfs)F$%o_@H!QXAhy+01#k1UQ_)D}Gu zq@zF>TCGi=bCG)|Oh|nWAGs86m5~$=|HAr;{nzhLtH#Z7dRkR3Gmui$;Bz6&Mi=?e zyT1u%oOFpb26U*x&=~6hmvN$h!PrxZNo#=A@GCUH`^M`;dsubCj46YZY_I{GCTeY& zkiaXe0g$Gm5haiGNfvR+^b_ZifWGrSaVnia7Zf^K9ze^Qkgm}@>O2pJxhSe!xAoL4 zQyMbvPt)ZxDp5g8d+-_yvS=1x3I7flfN}LW{=)FJ%ZQLmo1t)DY~j8KuN038d~6fX zp>7imd|bk3~@{5VAP;ndr(^B`8!DdQ(%gd$m|qSl$r*2%7>} zBvi~5ku2{E0@6hL`TLg(&_Iq3qUD*htNR+y=~wPzqmR5B-n$ZmM55uiIa0wjS3EU~ zRh)9RtZ4N$4P0+D<7YaTRGQ7wsOfsA#X7KJDs>`=^o#xAMPLOD%p+N*YK+*>KDqP*fG;S1`JQqska6R0hOgYuzly$Tp%$z@c{^7gr4HgBrUY?l(N@0s z9Q~x~@FpTNXz5%RT(67AVM1$EJAob~fE+7XnU^^>t0A?%4A|HXOMJ!BiUqmv*wOH{ zYAAPKTKg|;(_r(~jXcK3(I*0(vjs(``rwe27XZTII^z%k*t5n$@Ne3^(k^2EfU znpaCEO&mEn^HSM2kB8Jg{1uNbhMVrV+JC@Ya^6Ws2ul>eBs>!OxG)$okwA-*ZxC?jtIcaAr_k_E$;!* zz1^DpNdwy-fo%JO#%_Pioolrl9iDPv*%Fjg-Dvi-@Z*cAs`OcdMK96)LRY8xub+L; zFUfIOeItDMvhShcl^EMb0h{j;Smm#0k_SXfED!v2U&FP;l^P&+784BEFfIkO#YJIb znFujF@|;5cF)t_wjywz5q-V`kj=p103gaS6M?$&e*Nhqucl7j;Y~pUloAzp9HaZydz{u@CFjkYJ9We}8Pshu zFm$8C-v|pYz25l@hllSUS^XgV>yPFF#G8q;fgG?J;PA1HK3(_qr(NcA!SQq^chu(9 zW$poOmU9FYZ6k# z3iMc|CnS}SsR-K-{5(nf=T?ptMlSB!9L>$*aS;nyT|B;D$SPIHl)xS9hf zX@)?92Q+U5n^}fJ3hagFd>qKtB~zb*LLd!p0u@o2J;5eoEpL^_frmo!z|in-t>f3( z_J`!0!yT|+^GN1^?Et=w?$r*bc*nZCH!AG&%isSPHF$CX7-V?qm9?<;#fRa+C$5K^ z@4VVP=3G`Ck1RIbTOYBbhUKmkj1K2;)PBgO@WNx~Be?O~omPL=lZYISWX{d*MNexC zC6LkDnZEk_Pi~k4xBlVVzOhF*?S17Hi-19CnFGr|z}l6UJIvH#Qk+f5_cFGT1Z7^7pEa`O07etUaOz&Fn2$24NQ35 z6u;TGGh;qU`(`rS>Lt4U?|C!;6Wh-&){UFiK<6`eYAud;8;hb=U;w4+jGl3?ZA6bj z;8=BGIKn|JSFSI=Bm!CchBOuaB_3!UN48>^fxq)%d@Xib8hJcZwJt1`u5=F3i0G!F$#ia7z zIfRaOOPz^6bX1~Gt+`LyJ#ESyI;+yc>(blP;+8Luur8r0(LFo2R(-O6dHh90h3 zOv!)EcYrjrHPP4VU3kG`5DcO`=3KBvA40>iZ@qd%4qHyhVcXHx|9xd*H*}rT?ccB4 z_&of=Y3{#m`%Bmrd2!TUvZ$li}0~b5OGtaGn zUoC6(hecoW5%mq?H?o#GQATZ~QHTGN_e_Dd-Kb_`<6=+wYOZGF={ zzhp{+|15!6+^3>;Ezt{YiAnS~xw8oJzis;q`0N$);E1D-f>)33_Iw|y(2mizaP>p2 z4Tg14g05MQt!_uJfVrb%VbvM?Cp+$s69@8On~2Zd*(xT}DY#_pNo173@6Qw67SbRD z(YlSD=376WA-xd!KF%vejw{Ba5u-T#xQ-nrkVGyeb(XX-cBb`Rp%TIGI~JPrU*Bw5 zTl4vF#9?j@p%doa*4J?n&k$!{a=n(EM*fpaQ(;BARA7mo&4BT61bBr`D4@V$>oYCw z!qwQovOD4fGTkrOEO&nD{8_hQI{+Jg&S0}cN)9KdTOP7xh<7*3fd#(5c%~R;NFh!+ z{tP(v_%mV4tD9h5*Yj}u152HOvYG2|^fmdlQe|5ig6nfCgq!L?{@36Bgg@8CXTJ+3 zpM0)2Vo`9ExZQQA<&WLv+VHA5Z0yNiQ(_yRUb6zWZ0r4byw14>wU>#tAi3SdSSN!s zhTUVDG3cW95WWNa<*(c4S)4x!VQBZA_r6D#m?;j7eEx9Ypv8fskKrplA5*x!q$9*p z1@7o*19wawz5UN=Z67vbLs|iwo`&p%LbQ|&a9r@pI>X;oxm_g~z&V}>aC~~$&6;`R zW0hs*7dN-UyImwRr~$eW-v7Ei&vx9CW!=wKKO%Z4wy$)<|Fe*fywF|zQ1jGt9p;V| zZN4`blX4Jvw{L9s03*RS^I-S5T06wwmmpa09_)`k^&sG@H+iw^S$HfBR%l_glR_}? z`cwaijcDGa{ojF@<$?9^oK+$A?Arzz73I6T?rU@RZ6O}9)4e|8Ue~%;)1#}}U7VsB zlwX?j&t*iQ3Rp7o8losR+K?Wg#~t0|H8@f(v|Ua)VJg~MWG{;S0qh^orAt5R)5Uo#(|xAaA?ppY84U|E4#vXhBf#jZy^_h=HsU1h6xe*%{#x1z-090o)feYAoYh zO$oq4%Du%H1W>bp0hESkka%jh-uc&mvGgl^+^<6%X#w4ZWRQ9`z%>j(Pq4@#jgk`7 z*rQ{plfCZfcpFwcyvP}xQ$u_rnp(yxPnu94PA{g>gLn3slHz;Y_rgYI8&v64ziMxV zzxjHjdHkt%%o_pNvBGF@qie7J?m8IrP#wJXzBDm$^R`X!{a=33^aI=o7_8mh3VWZM zRUm+UG6VolgV3=s%2vGob_n-nHtol4M3-ubcS8Q{^Jlur&mT2*jNC#5U>@>W*g#}Z zhJA_7IW=y6ed|24eq)8(oTQzi#wFuDaBZDX#bD&*Fs2vup3Xy7r}JQq94p5~>H{E= z)j(+=HCko;O)_9DVmd@QlOUG=ZB`ZL^)P^<-GsQ9>hfX(kWyULHVqDQ0gNxV{DX7g z5B1y8r#$+}=!7YnYB$~UE9gZK$O|DnxC-n$X@{Ovh_Am|XTt~fVg2#Erts04q>_{& z7hs?finmfePf+M$6^b(VA2BNt4O;qmQI)pg%y(bdh(1Pq)69P0L?}Z6r(Eo*Jd55i z7b6%a2ghfP$HknIoEyV*3xZ2r9KS}=FX9r6D4gM#gYw7hJJD{!3iLAF2(`(htK%%T z9b>?|+g?39_>o_l9Fs=SXN|7C8qy9ygU*J*S$3dUlUz#)l&cHr$tg~Qd*CYX1NoV) zQDz)_A@b-f@)>>r1(NE1?4qN*sSVP5R3CRUAsLHTXCrqlMI=EPx*U=98aLzgC~^aj z7(O=9O;4{~VcC~$c$QY^q+D$vw^n6E>iqyfbUpNXe+Nv8esCP75#)=j?R?w)?ef0c zxz}EHVRZCaqrckHSn#6rNc_j5e=7d8i;g)B2HBjwos@EXYhc=WvxGzE`jM)k*@VcZ z2Lx3BU@-bT63e85c>80k+RdFSM0z)yfn8gU(QrXPv-Uq zXM?oBmfh&V3+FNgkq&Cb)gZ*nCOPqBV^~TZz!ZuqGGGQKO0_^w1p-}}2?Zc_JbqWN z)C1jY=x;{P2uV8~r;(1?4*Rd*L;bYgaONqOAqXUtLO_cNNCYy^TPav(qE~`}WogZK z_p;V`O}MKdxGU;(iBXKMOHcVZkObm>WP>o2wIF(=*bp1T{^y59n&aiEHOnn~dDO@w znb44nezsClgYS+eOs*q1&rCHl7_FCt|e%-{% ziYM+V-TB}xT@S6g54AM994nx#{sTnHpg@Mk<&on`!FVG|Kp}WTE<$UfUpiWKp(FT3 zX2A-6JjZ=;V}-SMC-hv}x}-la{%oWJmU2xbbZ#TOLe(usxRJ>zJphQQuHJwQfaQGs z+$e94aPGn+6Wta(CziH`o-&}A=udUoc8Ho6Ibf6XBbUgRT%qp$Qfk~`d#8ev2j|Oi8du~BhaAoHrFqkhu`5Y_@+`g|5V+ek;6S5w~9K*nwV@% z8i~tIqw(M;AWL_Pi z+V%rkN}vFaPxhpe$D6Ea!;#AH#DOD{{$B<)X&vA(Zzpr1Q*3UO&!D30F;8{FTjJ zJ&wIEB!ffJA1Uh(CpDDOZHjM$ZyRVPlM0Y9fVeh`$s{1cN;dorUBa~A`E|zKyej9% zlLZ#VcTITUJo_dRTcu$QMD2;6ak%fM z#8ONzzW?FH$X9D=?;hTplNzQ*=n(~>J`@wSXk_6|#B1?zC*&by!^WO8;+eI}#h|49 zyrz!W-lYTu@5!Z8 zzYP2k2~|>p!5F6AG?ZtgIXUqu&hg_ZZSdmyiY3=zwOQvqyf@`@=LF{sff{0x73JiE zRN3|6b#g`K!A+PfMUG!=u96D;%$mDl*!E>6v+HoaDs&Gy9d=};Oh%}%8`}^WwI_66 zI{MRVm&0I?LjbQW_#Efz4d+d2@!@$7m=K)FX(o&qSfbK1H8bDPB%xf6n{)AI^=NQ$H4@$hpsSKf;op8*87Kd0Rc zL$?0`Ox+f{z3z07GXQW`u{5dKpD!oFyM_;Zz~vAJuPr4gXwUN|eONprHUKwcBab~P ze(1+iZxr#P2X1)!Dv*&Eyb{(v7N)-8Kb6Ayao=n8hI1x0<7{;!TH4SoGYZGsn3-tF zzsH43*_%UYCBaXQ8>$(ZZ#uKwV_RMKN>gW_i)IA}?Ad6xpx66^8t5Kyck&^CSC<~z z8-33wHEwlQy1?c4%g%>W}0fn2nF$#d+m?f{KX9wczjhmZdzES4S4<%e;7Gp z9K7?w*66yUcg#(6{tJ6F{979%oIZjXXaxK3L2+<-gVPAB$&ei#QVPz7KoT7-+085T z5yQt;JH#P?4-zRsL3{gI^8n$g2F+;6i)_ib@EdP&a=^?b8W-K<8HE}Lk1&&&V<>AatPo(r3A&a*mGye3JacI*yu|}u|V}+ z$kz;^Itgd@NiYaOKmi$x^DP}cz{7kU^vOc!D(Ow1{_s-CVbH zf~{ffn2*PmjZrE{+`)(r6j7n{tS5AQp< zm9>t{5#uIQvoj~b0LvkOr_uri?fK^)z8OZ3sAtU_YVkpg9I{8n+j$u!Bt_qJBD?{U zkRD&P3_pHfeccKL-hJHo$uQ&XS6K!tX>ecS&~n&aoOPgyz7bcU6gU=Ny|@vdc~0Uk zJc~&-U|KY4@}lg3VKfhJW3-ZIVS+;I^I2QY)Mvu z@Z#Mdz>@;F%B!a+L|^jENWKQJ_O>yQ4bJzC?+{GwVT&&>T=3d7!YBXa<`5ap;=D(z z^Fv|?9=HSO633FCVu`s>!WGsm*}A>UsCq99>jyBxF)daoIW$e?0pI`+6~dG<5xxpp2*7Yo zt0Y6AR=e+^CA!_Z{1k${jBj4pxWR54cB%W}Cb#D>chaGfopm&RJ)wT?_T(H8DpLRZ}PNKNj4B&iv=O1-b>FYfAC=zZtp0?c>MB~)c}nI_YD<`nob5U0VNbi!1qA+uqCdDgd=oaL&2mL{ogype?%4EG+uXXR0>hoJlS42}euN z9Z0@*$qLMH0a#3e8ETCAv=Oy*?%` zS;#=o`MPuJ>@<2<0+p$E_wmGrGSs;510n}Az>%8F)f>=j^2W28tyKkix>+4tzwlMg znmNx!tA?a2_EH!XrUwEZg8PO!@a)>2>CjWB%!UuXXMqPLf!Pa9Z;SO-s10?*bLl(Z zxtLc=B$YfZ9^6c)AkYWQfq7zeg6T!jQ<5E!&Tz^v-xo`(Rp?nmRAflP0C;YsdsPHS zughQnOWQ!Uc zrCon`;1RF!!%B4dCU>FRL))yH$);$O+&8!FEoaZF#=}ttgr*#Blg0$1Cj2|3lM~@1ed8`{j zFxkq zWiGCc7Q7O=QY#8^?xaqikunUv-nT3)OPUOGPS!ikT@BnAo&1acdoO~U6*E%`;kYKy z3QlNB@=M4|17iF#)i+W$jd$bDDTd#=4Hf%9>OGkcwI$K)4?Xk^Q%92^P=<7`f>GOV zbSK&_BM@%KBr;HK#`}2*u9#h2_PQ27*Z|G}9g%Y;&Ggiyn<~Mo)q}mDHuB#EBQ7oq zYlR1MJ~kiU2hzWH&5RziWf0s~I%9I!kb2?BoOVm0Z%LS9$QzK9TOdC>Xj<<7Qa(x@ z6&vDXbmwXG^S8IQn!^34Mo(B0RljBH3q5wg^x086ZV&Hy;zK6w_LC~`-1V<1*|)1) zb|pBH?p2rJ!3OXaP=SV#!^b)8{bisb9E9&i7g%aM<%&jxVK}%A>CuG;YGJWAD3erF z@8kZ+dl%UeL+gFg6_}V=2*5LZ0#L|+6>>x%<#QyT+1LwycHRU$EzOX}!@DOb!HcCG zdN1R^sRUluzx0A%*9gmI>m2bq`+^ysKju4Dp2yBbRE;ZZnW^W^GE|LA3vSq}TKr%F zI6m+X>yOjlaTOdsuEc~jlq4enDIf`dq z!{|rrVC?dABE+!nhmfjRzDNE#;@7_84j)#ZAA9tq0oURO6TnMQD4=s8>Mq?wf2|b#&E7G!2Sq|baJRr-l7k80W&Po77hJ)752JXkTDUe&BA|b0_%~V9 z1^ z@N8xpQD_+zn`JVpnBUymnicGUR_LGbxgMUh|0`EC9zRF-d(oE;G=vN|Suz`iy#gFTp&*R-s&o1dldgEF zJycfCUz}(t>_BfmTjgU=-4y2Z5IRau2M9rvEKZOh-XVn=n5WakW_?WdtccOc>8n`O z^X`;;SD=5|n*}{BUW?UrMC)cXwZH3a(20urr(&!R`4C z&h&X;;{&kuxNwt-^EK=87p(RGNl6V^BEXHDxE5!1nGSnF4x~U&38E%wfEY-q`Y@f* zi|XTLcYCp>$r^7-y#!E2?5{2|(g4VS_O`L1M=V8)7{b z;9v>;mKU!IUKlGmAt6u{juSHZAb5M6&)eQ|=lmk?ExXqS2woB{7uT*nSCclN;23K0 zQ$KKq=i;^pdR`R+m)6td>kCwh(r8aSVX8BFmlfN!SEjS=uK+JWtl3v}`R=}C>((x9 z%*ix)@=$~-R9Wz>Xpc&kMJ;aQXl=K?+SS8=GTdLn@BEVH{O~!6i|LWg$sGwoF{rQc za3USW6f+o=-yS2YzKvMS^XI>vUCpRt;Uu#%#MnSP1phaJUMJ2V@Ta-ga;L@X5#;Z= z;C(%hh*>vAVhPr8&P=Xd_h2S8Oe^NYpp;-Q$o@i6N)R;20tnitliuUDGVFqz>4~S7 z1=}oy+u0>&Gv)JE1$_7kc{;%cAH*mkhWUFKPgFI^lb9VlNDJEtY!g#l!?NE@H8S8? z`~b?(CBRtO2*JQ5cca#pcJXg!_lTnQL&^|1Hx?G;dt9dPDnJG&0{ohr7WDXWo{f@H z%?j9-6SDX^&N}U~Y8|fxdqD;>z{?PBb|<~?eGBXnpQ(jso?9UiKJDlBpDD2_3pHdz zhF&!4GUCrtEf#2o`-mivVd0+p7s^_g90A1+R96>{YB@DFJr>g+?-~rJlt7MNIRfa+ zUUKE4yU|4~S3bJL!0t&2!u?(f;r^pCk?7`4fA|%A;oDdGzw0(W@8@oAha)XfKA`&= za>uQ#^x`|<@UX;pwe#(98EgPALjW>rX}qz8q^Lb74?emWwh*ll;6$#Qlct73giux> z4Rh?)LZw#BrN-KW;`#&0)iBCvWj{M-aeIYlIe{zYok)6Wo>OKYPa7kn z6#sE9LFX2n2YXmb5H-6976?L3j>|@F9(tkp;?=Y^e^nWcR2c=zszfyRnhX|zw?F_i zgL<#ufh}7%I8c5DkUEGuv=Ty>2qC%Uf~N$4CpmyN4)0mf=Dig?&thTWPcDb9mnvb{ zhREHS)7#+a<7`4Q8G*Kj_m@~k=f#2S;$HCpFJpFtH{_Y!R z!UpoSQV081S|B9ck~@x;nFKe)$nJTC1L8$BwP=z@tCMS?1>GBTkE+6o}^mIyi0ffT+tP zln*L2&;D@MhQ2K;-Dvdf|DBete&){Ir6s#8=tX+a(W0|Xn{FHhp_>=|_MD1<+i(2c zysVh31A*V)0ee{X6~IdnfQ^YZ12yTW z9p$k<{$)#sfu*h6_Ue3r-4YDyoN>x@yLoGuyFO!|U9-FfmGge~nXngRpaEWjK?rV^ zH=fmE-*Vn8GybS1pIgoTTUoo^eR|Rvj?PN~PxcH{?35tToi?}UPHHye#x~m5zP7=# zmbOYu<9WBe+J!DhbI*Nk_7Bc8vtbY{u(}5Yd+21?ODnJqG{9RR_*MtC96P?rXBwim zJocC-9CAIdF?dpKRkA%H;~Ygg_UI-D{06VtH*Z<*yfQBtoYOhjHT7-?2ar180FgF` zag#%@gZtG915l@MphNW)I3x#?poXq5d~T=LLKuK0O$M5x;Kg>rKK7g(k^zwl1p0v> zZlcc>I0ys{3XD|l55wr8*RH>&!$B;K5H$Jz@~{rQ`u{@ZRfa(#`zp}GQ=yO|mEaI% zIZ&kCy)K0V$SblZqy)iz*a`cWraJVB8XKe1WjG{#3i39*S^LuiJ{Vt}5aJmvFvv&W zeU_Zlhu}@)d6Z>1L}B)YR3O$4-w3geVQ*902AvF6fET3<0q~Px{~Zb|HMW;v&kC=Y zR_$|z{%)jg8`OJVMzHw%{aW@HfET3<5$xz=Ey3Us)Nsq_FKfHXPW-;+MCmG0fkV=( z?5_YX3NKGrh5-B$49s$MJcJ{l+hIQ&z7GaDPyk*_SiAG=`+E9Zf}NKN1OTsssGSeF z!v?t==m76c80_`xGr#XDyee==4#^=oB!f- Date: Tue, 1 Nov 2022 10:56:19 -0700 Subject: [PATCH 467/498] query to include data plane attributes (#18531) * query to include data plane attributes * rename functions * fix java build * more renaming fix --- .../io/airbyte/metrics/lib/MetricTags.java | 2 + .../io/airbyte/metrics/reporter/Emitter.java | 25 ++-- .../metrics/reporter/MetricRepository.java | 69 ++++++---- .../airbyte/metrics/reporter/EmitterTest.java | 52 +++++--- .../reporter/MetricRepositoryTest.java | 118 ++++++++++++------ 5 files changed, 181 insertions(+), 85 deletions(-) diff --git a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricTags.java b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricTags.java index 998d290717b8..6299963478cc 100644 --- a/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricTags.java +++ b/airbyte-metrics/metrics-lib/src/main/java/io/airbyte/metrics/lib/MetricTags.java @@ -20,6 +20,8 @@ public class MetricTags { public static final String RELEASE_STAGE = "release_stage"; public static final String RESET_WORKFLOW_FAILURE_CAUSE = "failure_cause"; public static final String WORKFLOW_TYPE = "workflow_type"; + public static final String ATTEMPT_QUEUE = "attempt_queue"; + public static final String GEOGRAPHY = "geography"; public static String getReleaseStage(final ReleaseStage stage) { return stage.getLiteral(); diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java index 56ab764c0a7b..26fae9f07e44 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/Emitter.java @@ -20,8 +20,11 @@ final class NumPendingJobs extends Emitter { public NumPendingJobs(final MetricClient client, final MetricRepository db) { super(client, () -> { - final var pending = db.numberOfPendingJobs(); - client.gauge(OssMetricsRegistry.NUM_PENDING_JOBS, pending); + db.numberOfPendingJobsByGeography().forEach((geography, count) -> client.gauge( + OssMetricsRegistry.NUM_PENDING_JOBS, + count, + new MetricAttribute(MetricTags.GEOGRAPHY, geography))); + return null; }); } @@ -33,8 +36,10 @@ final class NumRunningJobs extends Emitter { public NumRunningJobs(final MetricClient client, final MetricRepository db) { super(client, () -> { - final var running = db.numberOfRunningJobs(); - client.gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, running); + db.numberOfRunningJobsByTaskQueue().forEach((attemptQueue, count) -> client.gauge( + OssMetricsRegistry.NUM_RUNNING_JOBS, + count, + new MetricAttribute(MetricTags.ATTEMPT_QUEUE, attemptQueue))); return null; }); } @@ -59,8 +64,10 @@ final class OldestRunningJob extends Emitter { OldestRunningJob(final MetricClient client, final MetricRepository db) { super(client, () -> { - final var age = db.oldestRunningJobAgeSecs(); - client.gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, age); + db.oldestRunningJobAgeSecsByTaskQueue().forEach((attemptQueue, count) -> client.gauge( + OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, + count, + new MetricAttribute(MetricTags.ATTEMPT_QUEUE, attemptQueue))); return null; }); } @@ -72,8 +79,10 @@ final class OldestPendingJob extends Emitter { OldestPendingJob(final MetricClient client, final MetricRepository db) { super(client, () -> { - final var age = db.oldestPendingJobAgeSecs(); - client.gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, age); + db.oldestPendingJobAgeSecsByGeography().forEach((geographyType, count) -> client.gauge( + OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, + count, + new MetricAttribute(MetricTags.GEOGRAPHY, geographyType.getLiteral()))); return null; }); } diff --git a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java index c30d0111e501..d365cf756a41 100644 --- a/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java +++ b/airbyte-metrics/reporter/src/main/java/io/airbyte/metrics/reporter/MetricRepository.java @@ -5,10 +5,15 @@ package io.airbyte.metrics.reporter; import static io.airbyte.db.instance.configs.jooq.generated.Tables.CONNECTION; +import static io.airbyte.db.instance.jobs.jooq.generated.Tables.ATTEMPTS; import static io.airbyte.db.instance.jobs.jooq.generated.Tables.JOBS; +import static org.jooq.impl.DSL.asterisk; +import static org.jooq.impl.DSL.count; import static org.jooq.impl.SQLDataType.VARCHAR; +import io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType; import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; +import io.airbyte.db.instance.jobs.jooq.generated.enums.AttemptStatus; import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; import jakarta.inject.Singleton; import java.util.HashMap; @@ -25,22 +30,32 @@ class MetricRepository { this.ctx = ctx; } - int numberOfPendingJobs() { - return ctx.selectCount() + Map numberOfPendingJobsByGeography() { + var result = ctx.select(CONNECTION.GEOGRAPHY.cast(String.class), count(asterisk()).as("count")) .from(JOBS) + .join(CONNECTION) + .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) .where(JOBS.STATUS.eq(JobStatus.pending)) - .fetchOne(0, int.class); + .groupBy(CONNECTION.GEOGRAPHY); + return (Map) result.fetchMap(0, 1); } - int numberOfRunningJobs() { - return ctx.selectCount() + Map numberOfRunningJobsByTaskQueue() { + var result = ctx.select(ATTEMPTS.PROCESSING_TASK_QUEUE, count(asterisk()).as("count")) .from(JOBS) .join(CONNECTION) .on(CONNECTION.ID.cast(VARCHAR(255)).eq(JOBS.SCOPE)) + .join(ATTEMPTS) + .on(ATTEMPTS.JOB_ID.eq(JOBS.ID)) .where(JOBS.STATUS.eq(JobStatus.running).and(CONNECTION.STATUS.eq(StatusType.active))) - .fetchOne(0, int.class); + .and(ATTEMPTS.STATUS.eq(AttemptStatus.running)) + .groupBy(ATTEMPTS.PROCESSING_TASK_QUEUE); + return (Map) result.fetchMap(0, 1); + } + // This is a rare case and not likely to be related to data planes; So we will monitor them as a + // whole. int numberOfOrphanRunningJobs() { return ctx.selectCount() .from(JOBS) @@ -50,12 +65,32 @@ int numberOfOrphanRunningJobs() { .fetchOne(0, int.class); } - long oldestPendingJobAgeSecs() { - return oldestJobAgeSecs(JobStatus.pending); + Map oldestPendingJobAgeSecsByGeography() { + final var query = + """ + SELECT cast(connection.geography as varchar) AS geography, MAX(EXTRACT(EPOCH FROM (current_timestamp - jobs.created_at))) AS run_duration_seconds + FROM jobs + JOIN connection + ON jobs.scope::uuid = connection.id + WHERE jobs.status = 'pending' + GROUP BY geography; + """; + final var result = ctx.fetch(query); + return (Map) result.intoMap(0, 1); } - long oldestRunningJobAgeSecs() { - return oldestJobAgeSecs(JobStatus.running); + Map oldestRunningJobAgeSecsByTaskQueue() { + final var query = + """ + SELECT attempts.processing_task_queue AS task_queue, MAX(EXTRACT(EPOCH FROM (current_timestamp - jobs.created_at))) AS run_duration_seconds + FROM jobs + JOIN attempts + ON jobs.id = attempts.job_id + WHERE jobs.status = 'running' AND attempts.status = 'running' + GROUP BY task_queue; + """; + final var result = ctx.fetch(query); + return (Map) result.intoMap(0, 1); } List numberOfActiveConnPerWorkspace() { @@ -218,18 +253,4 @@ SELECT status, extract(epoch from age(updated_at, created_at)) AS sec FROM jobs return results; } - private long oldestJobAgeSecs(final JobStatus status) { - final var query = """ - SELECT id, EXTRACT(EPOCH FROM (current_timestamp - created_at)) AS run_duration_seconds - FROM jobs WHERE status = ?::job_status - ORDER BY created_at ASC limit 1; - """; - final var result = ctx.fetchOne(query, status.getLiteral()); - if (result == null) { - return 0L; - } - // as double can have rounding errors, round down to remove noise. - return result.getValue("run_duration_seconds", Double.class).longValue(); - } - } diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java index 55a2433246ae..431884923790 100644 --- a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/EmitterTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType; import io.airbyte.db.instance.jobs.jooq.generated.enums.JobStatus; import io.airbyte.metrics.lib.MetricAttribute; import io.airbyte.metrics.lib.MetricClient; @@ -25,6 +26,12 @@ class EmitterTest { private MetricClient client; private MetricRepository repo; + private static final String SYNC_QUEUE = "SYNC"; + private static final String AWS_QUEUE = "AWS"; + + private static final String EU_REGION = "EU"; + private static final String AUTO_REGION = "AUTO"; + @BeforeEach void setUp() { client = mock(MetricClient.class); @@ -33,29 +40,35 @@ void setUp() { @Test void TestNumPendingJobs() { - final var value = 101; - when(repo.numberOfPendingJobs()).thenReturn(value); + final var value = Map.of("AUTO", 101, "EU", 20); + when(repo.numberOfPendingJobsByGeography()).thenReturn(value); final var emitter = new NumPendingJobs(client, repo); emitter.Emit(); assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).numberOfPendingJobs(); - verify(client).gauge(OssMetricsRegistry.NUM_PENDING_JOBS, value); + verify(repo).numberOfPendingJobsByGeography(); + verify(client).gauge(OssMetricsRegistry.NUM_PENDING_JOBS, 101, + new MetricAttribute(MetricTags.GEOGRAPHY, "AUTO")); + verify(client).gauge(OssMetricsRegistry.NUM_PENDING_JOBS, 20, + new MetricAttribute(MetricTags.GEOGRAPHY, "EU")); verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); } @Test void TestNumRunningJobs() { - final var value = 101; - when(repo.numberOfRunningJobs()).thenReturn(value); + final var value = Map.of(SYNC_QUEUE, 101, AWS_QUEUE, 20); + when(repo.numberOfRunningJobsByTaskQueue()).thenReturn(value); final var emitter = new NumRunningJobs(client, repo); emitter.Emit(); assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).numberOfRunningJobs(); - verify(client).gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, value); + verify(repo).numberOfRunningJobsByTaskQueue(); + verify(client).gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, 101, + new MetricAttribute(MetricTags.ATTEMPT_QUEUE, SYNC_QUEUE)); + verify(client).gauge(OssMetricsRegistry.NUM_RUNNING_JOBS, 20, + new MetricAttribute(MetricTags.ATTEMPT_QUEUE, AWS_QUEUE)); verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); } @@ -75,29 +88,36 @@ void TestNumOrphanRunningJobs() { @Test void TestOldestRunningJob() { - final var value = 101; - when(repo.oldestRunningJobAgeSecs()).thenReturn((long) value); + final var value = Map.of(SYNC_QUEUE, 101.0, AWS_QUEUE, 20.0); + when(repo.oldestRunningJobAgeSecsByTaskQueue()).thenReturn(value); final var emitter = new OldestRunningJob(client, repo); emitter.Emit(); assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).oldestRunningJobAgeSecs(); - verify(client).gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, value); + verify(repo).oldestRunningJobAgeSecsByTaskQueue(); + verify(client).gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, 101, + new MetricAttribute(MetricTags.ATTEMPT_QUEUE, SYNC_QUEUE)); + verify(client).gauge(OssMetricsRegistry.OLDEST_RUNNING_JOB_AGE_SECS, 20, + new MetricAttribute(MetricTags.ATTEMPT_QUEUE, AWS_QUEUE)); verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); } @Test void TestOldestPendingJob() { - final var value = 101; - when(repo.oldestPendingJobAgeSecs()).thenReturn((long) value); + final var value = Map.of(GeographyType.AUTO, 101.0, GeographyType.EU, 20.0); + when(repo.oldestPendingJobAgeSecsByGeography()).thenReturn(value); final var emitter = new OldestPendingJob(client, repo); emitter.Emit(); assertEquals(Duration.ofSeconds(15), emitter.getDuration()); - verify(repo).oldestPendingJobAgeSecs(); - verify(client).gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, value); + verify(repo).oldestPendingJobAgeSecsByGeography(); + verify(client).gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, 101, + new MetricAttribute(MetricTags.GEOGRAPHY, AUTO_REGION)); + verify(client).gauge(OssMetricsRegistry.OLDEST_PENDING_JOB_AGE_SECS, 20, + new MetricAttribute(MetricTags.GEOGRAPHY, EU_REGION)); + verify(client).count(OssMetricsRegistry.EST_NUM_METRICS_EMITTED_BY_REPORTER, 1); } diff --git a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java index 4510cd01098e..0052dd10a8f5 100644 --- a/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java +++ b/airbyte-metrics/reporter/src/test/java/io/airbyte/metrics/reporter/MetricRepositoryTest.java @@ -18,9 +18,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.collect.Iterators; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.init.DatabaseInitializationException; import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; +import io.airbyte.db.instance.configs.jooq.generated.enums.GeographyType; import io.airbyte.db.instance.configs.jooq.generated.enums.NamespaceDefinitionType; import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; import io.airbyte.db.instance.configs.jooq.generated.enums.StatusType; @@ -33,7 +35,6 @@ import java.sql.SQLException; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; -import java.util.List; import java.util.Map; import java.util.UUID; import org.jooq.DSLContext; @@ -52,6 +53,9 @@ class MetricRepositoryTest { private static final String SRC = "src"; private static final String DEST = "dst"; private static final String CONN = "conn"; + private static final String SYNC_QUEUE = "SYNC"; + private static final String EU_REGION = "EU"; + private static final UUID SRC_DEF_ID = UUID.randomUUID(); private static final UUID DST_DEF_ID = UUID.randomUUID(); private static MetricRepository db; @@ -108,14 +112,13 @@ class NumJobs { @Test void shouldReturnReleaseStages() { + ctx.insertInto(ATTEMPTS, ATTEMPTS.ID, ATTEMPTS.JOB_ID, ATTEMPTS.STATUS, ATTEMPTS.PROCESSING_TASK_QUEUE) + .values(10L, 1L, AttemptStatus.running, SYNC_QUEUE).values(20L, 2L, AttemptStatus.running, SYNC_QUEUE) + .values(30L, 3L, AttemptStatus.running, SYNC_QUEUE).values(40L, 4L, AttemptStatus.running, SYNC_QUEUE) + .values(50L, 5L, AttemptStatus.running, SYNC_QUEUE) + .execute(); final var srcId = UUID.randomUUID(); final var dstId = UUID.randomUUID(); - - ctx.insertInto(ACTOR, ACTOR.ID, ACTOR.WORKSPACE_ID, ACTOR.ACTOR_DEFINITION_ID, ACTOR.NAME, ACTOR.CONFIGURATION, ACTOR.ACTOR_TYPE) - .values(srcId, UUID.randomUUID(), SRC_DEF_ID, SRC, JSONB.valueOf("{}"), ActorType.source) - .values(dstId, UUID.randomUUID(), DST_DEF_ID, DEST, JSONB.valueOf("{}"), ActorType.destination) - .execute(); - final var activeConnectionId = UUID.randomUUID(); final var inactiveConnectionId = UUID.randomUUID(); ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.STATUS, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, @@ -135,7 +138,7 @@ void shouldReturnReleaseStages() { .values(5L, inactiveConnectionId.toString(), JobStatus.running) .execute(); - assertEquals(2, db.numberOfRunningJobs()); + assertEquals(2, db.numberOfRunningJobsByTaskQueue().get(SYNC_QUEUE)); assertEquals(1, db.numberOfOrphanRunningJobs()); } @@ -145,34 +148,52 @@ void runningJobsShouldReturnZero() throws SQLException { ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(1L, "", JobStatus.pending).execute(); ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS).values(2L, "", JobStatus.failed).execute(); - final var res = db.numberOfRunningJobs(); - assertEquals(0, res); + final var res = db.numberOfRunningJobsByTaskQueue(); + assertTrue(res.isEmpty()); } @Test void pendingJobsShouldReturnCorrectCount() throws SQLException { // non-pending jobs + final var connectionUuid = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.GEOGRAPHY) + .values(connectionUuid, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active, + GeographyType.valueOf(EU_REGION)) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.pending) - .values(2L, "", JobStatus.failed) - .values(3L, "", JobStatus.pending) - .values(4L, "", JobStatus.running) + .values(1L, connectionUuid.toString(), JobStatus.pending) + .values(2L, connectionUuid.toString(), JobStatus.failed) + .values(3L, connectionUuid.toString(), JobStatus.pending) + .values(4L, connectionUuid.toString(), JobStatus.running) .execute(); - final var res = db.numberOfPendingJobs(); - assertEquals(2, res); + final var res = db.numberOfPendingJobsByGeography(); + assertEquals(2, res.get(EU_REGION)); } @Test void pendingJobsShouldReturnZero() throws SQLException { + final var connectionUuid = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.GEOGRAPHY) + .values(connectionUuid, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active, + GeographyType.valueOf(EU_REGION)) + .execute(); + // non-pending jobs ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.running) - .values(2L, "", JobStatus.failed) + .values(1L, connectionUuid.toString(), JobStatus.running) + .values(2L, connectionUuid.toString(), JobStatus.failed) .execute(); - final var res = db.numberOfPendingJobs(); - assertEquals(0, res); + final var res = db.numberOfPendingJobsByGeography(); + assertTrue(res.isEmpty()); } } @@ -184,33 +205,51 @@ class OldestPendingJob { void shouldReturnOnlyPendingSeconds() throws SQLException { final var expAgeSecs = 1000; final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); + final var connectionUuid = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.GEOGRAPHY) + .values(connectionUuid, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active, + GeographyType.valueOf(EU_REGION)) + .execute(); ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) // oldest pending job - .values(1L, "", JobStatus.pending, oldestCreateAt) + .values(1L, connectionUuid.toString(), JobStatus.pending, oldestCreateAt) // second-oldest pending job - .values(2L, "", JobStatus.pending, OffsetDateTime.now()) + .values(2L, connectionUuid.toString(), JobStatus.pending, OffsetDateTime.now()) .execute(); // non-pending jobs ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(3L, "", JobStatus.running) - .values(4L, "", JobStatus.failed) + .values(3L, connectionUuid.toString(), JobStatus.running) + .values(4L, connectionUuid.toString(), JobStatus.failed) .execute(); - final var res = db.oldestPendingJobAgeSecs(); + Double result = db.oldestPendingJobAgeSecsByGeography().get(EU_REGION); // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(999L, 1000L, 1001L).contains(res)); + assertTrue(999 < result && result < 1001); } @Test void shouldReturnNothingIfNotApplicable() { + final var connectionUuid = UUID.randomUUID(); + final var srcId = UUID.randomUUID(); + final var dstId = UUID.randomUUID(); + + ctx.insertInto(CONNECTION, CONNECTION.ID, CONNECTION.NAMESPACE_DEFINITION, CONNECTION.SOURCE_ID, CONNECTION.DESTINATION_ID, + CONNECTION.NAME, CONNECTION.CATALOG, CONNECTION.MANUAL, CONNECTION.STATUS, CONNECTION.GEOGRAPHY) + .values(connectionUuid, NamespaceDefinitionType.source, srcId, dstId, CONN, JSONB.valueOf("{}"), true, StatusType.active, GeographyType.EU) + .execute(); + ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) - .values(1L, "", JobStatus.succeeded) - .values(2L, "", JobStatus.running) - .values(3L, "", JobStatus.failed).execute(); + .values(1L, connectionUuid.toString(), JobStatus.succeeded) + .values(2L, connectionUuid.toString(), JobStatus.running) + .values(3L, connectionUuid.toString(), JobStatus.failed).execute(); - final var res = db.oldestPendingJobAgeSecs(); - assertEquals(0L, res); + final var res = db.oldestPendingJobAgeSecsByGeography(); + assertTrue(res.isEmpty()); } } @@ -222,7 +261,9 @@ class OldestRunningJob { void shouldReturnOnlyRunningSeconds() { final var expAgeSecs = 10000; final var oldestCreateAt = OffsetDateTime.now().minus(expAgeSecs, ChronoUnit.SECONDS); - + ctx.insertInto(ATTEMPTS, ATTEMPTS.ID, ATTEMPTS.JOB_ID, ATTEMPTS.STATUS, ATTEMPTS.PROCESSING_TASK_QUEUE) + .values(10L, 1L, AttemptStatus.running, SYNC_QUEUE).values(20L, 2L, AttemptStatus.running, SYNC_QUEUE) + .execute(); ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS, JOBS.CREATED_AT) // oldest pending job .values(1L, "", JobStatus.running, oldestCreateAt) @@ -236,21 +277,24 @@ void shouldReturnOnlyRunningSeconds() { .values(4L, "", JobStatus.failed) .execute(); - final var res = db.oldestRunningJobAgeSecs(); - // expected age is 10000 seconds, but allow for +/- 1 second to account for timing/rounding errors - assertTrue(List.of(9999L, 10000L, 10001L).contains(res)); + final var result = Iterators.getOnlyElement(db.oldestRunningJobAgeSecsByTaskQueue().entrySet().iterator()); + assertEquals(SYNC_QUEUE, result.getKey()); + // expected age is 1000 seconds, but allow for +/- 1 second to account for timing/rounding errors + assertTrue(9999 < result.getValue() && result.getValue() < 10001L); } @Test void shouldReturnNothingIfNotApplicable() { + ctx.insertInto(ATTEMPTS, ATTEMPTS.ID, ATTEMPTS.JOB_ID, ATTEMPTS.PROCESSING_TASK_QUEUE).values(10L, 1L, SYNC_QUEUE).values(20L, 2L, SYNC_QUEUE) + .values(30L, 3L, SYNC_QUEUE).execute(); ctx.insertInto(JOBS, JOBS.ID, JOBS.SCOPE, JOBS.STATUS) .values(1L, "", JobStatus.succeeded) .values(2L, "", JobStatus.pending) .values(3L, "", JobStatus.failed) .execute(); - final var res = db.oldestRunningJobAgeSecs(); - assertEquals(0L, res); + final var res = db.oldestRunningJobAgeSecsByTaskQueue(); + assertTrue(res.isEmpty()); } } From 5d34b4ebd2550ad71faaf9073e13d868130a8ed3 Mon Sep 17 00:00:00 2001 From: Liren Tu Date: Tue, 1 Nov 2022 11:12:58 -0700 Subject: [PATCH 468/498] Fix unit tests in source relational db (#18789) * Fix unit tests * Add extra test case for record count > 1 * Store record count in variable --- .../state/GlobalStateManagerTest.java | 12 ++++++--- .../state/StreamStateManagerTest.java | 26 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java index 7df8b7c4ee5e..d342347fbc0e 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/GlobalStateManagerTest.java @@ -90,6 +90,7 @@ void testToStateFromLegacyState() { .stream().sorted(Comparator.comparing(DbStreamState::getStreamName)).collect(Collectors.toList())); final StateManager stateManager = new GlobalStateManager(new AirbyteStateMessage().withData(Jsons.jsonNode(dbState)), catalog); + final long expectedRecordCount = 19L; final DbState expectedDbState = new DbState() .withCdc(true) .withCdcState(cdcState) @@ -98,7 +99,8 @@ void testToStateFromLegacyState() { .withStreamName(STREAM_NAME1) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD1)) - .withCursor("a"), + .withCursor("a") + .withCursorRecordCount(expectedRecordCount), new DbStreamState() .withStreamName(STREAM_NAME2) .withStreamNamespace(NAMESPACE) @@ -117,7 +119,8 @@ void testToStateFromLegacyState() { .withStreamName(STREAM_NAME1) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD1)) - .withCursor("a"))), + .withCursor("a") + .withCursorRecordCount(expectedRecordCount))), new AirbyteStreamState() .withStreamDescriptor(new StreamDescriptor().withName(STREAM_NAME2).withNamespace(NAMESPACE)) .withStreamState(Jsons.jsonNode(new DbStreamState() @@ -135,7 +138,7 @@ void testToStateFromLegacyState() { .withGlobal(expectedGlobalState) .withType(AirbyteStateType.GLOBAL); - final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a", 1L); + final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a", expectedRecordCount); assertEquals(expected, actualFirstEmission); } @@ -187,7 +190,8 @@ void testToState() { .withStreamName(STREAM_NAME1) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD1)) - .withCursor("a"))), + .withCursor("a") + .withCursorRecordCount(1L))), new AirbyteStreamState() .withStreamDescriptor(new StreamDescriptor().withName(STREAM_NAME2).withNamespace(NAMESPACE)) .withStreamState(Jsons.jsonNode(new DbStreamState() diff --git a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java index 55df035c283d..e2733bfbbb92 100644 --- a/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java +++ b/airbyte-integrations/connectors/source-relational-db/src/test/java/io/airbyte/integrations/source/relationaldb/state/StreamStateManagerTest.java @@ -62,8 +62,8 @@ void testCreationFromInvalidState() { @Test void testGetters() { final List state = new ArrayList<>(); - state.add(createStreamState(STREAM_NAME1, NAMESPACE, List.of(CURSOR_FIELD1), CURSOR)); - state.add(createStreamState(STREAM_NAME2, NAMESPACE, List.of(), null)); + state.add(createStreamState(STREAM_NAME1, NAMESPACE, List.of(CURSOR_FIELD1), CURSOR, 0L)); + state.add(createStreamState(STREAM_NAME2, NAMESPACE, List.of(), null, 0L)); final ConfiguredAirbyteCatalog catalog = new ConfiguredAirbyteCatalog() .withStreams(List.of( @@ -113,8 +113,7 @@ void testToState() { .withStreamName(STREAM_NAME1) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD1)) - .withCursor("a") - .withCursorRecordCount(1L), + .withCursor("a"), new DbStreamState() .withStreamName(STREAM_NAME2) .withStreamNamespace(NAMESPACE) @@ -124,11 +123,12 @@ void testToState() { .withStreamNamespace(NAMESPACE)) .stream().sorted(Comparator.comparing(DbStreamState::getStreamName)).collect(Collectors.toList())); final AirbyteStateMessage expectedFirstEmission = - createStreamState(STREAM_NAME1, NAMESPACE, List.of(CURSOR_FIELD1), "a").withData(Jsons.jsonNode(expectedFirstDbState)); + createStreamState(STREAM_NAME1, NAMESPACE, List.of(CURSOR_FIELD1), "a", 0L).withData(Jsons.jsonNode(expectedFirstDbState)); final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a"); assertEquals(expectedFirstEmission, actualFirstEmission); + final long expectedRecordCount = 17L; final DbState expectedSecondDbState = new DbState() .withCdc(false) .withStreams(List.of( @@ -141,15 +141,16 @@ void testToState() { .withStreamName(STREAM_NAME2) .withStreamNamespace(NAMESPACE) .withCursorField(List.of(CURSOR_FIELD2)) - .withCursor("b"), + .withCursor("b") + .withCursorRecordCount(expectedRecordCount), new DbStreamState() .withStreamName(STREAM_NAME3) .withStreamNamespace(NAMESPACE)) .stream().sorted(Comparator.comparing(DbStreamState::getStreamName)).collect(Collectors.toList())); final AirbyteStateMessage expectedSecondEmission = - createStreamState(STREAM_NAME2, NAMESPACE, List.of(CURSOR_FIELD2), "b").withData(Jsons.jsonNode(expectedSecondDbState)); + createStreamState(STREAM_NAME2, NAMESPACE, List.of(CURSOR_FIELD2), "b", expectedRecordCount).withData(Jsons.jsonNode(expectedSecondDbState)); - final AirbyteStateMessage actualSecondEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR2, "b"); + final AirbyteStateMessage actualSecondEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR2, "b", expectedRecordCount); assertEquals(expectedSecondEmission, actualSecondEmission); } @@ -228,7 +229,7 @@ void testToStateNullCursorField() { .stream().sorted(Comparator.comparing(DbStreamState::getStreamName)).collect(Collectors.toList())); final AirbyteStateMessage expectedFirstEmission = - createStreamState(STREAM_NAME1, NAMESPACE, List.of(CURSOR_FIELD1), "a").withData(Jsons.jsonNode(expectedFirstDbState)); + createStreamState(STREAM_NAME1, NAMESPACE, List.of(CURSOR_FIELD1), "a", 0L).withData(Jsons.jsonNode(expectedFirstDbState)); final AirbyteStateMessage actualFirstEmission = stateManager.updateAndEmit(NAME_NAMESPACE_PAIR1, "a"); assertEquals(expectedFirstEmission, actualFirstEmission); } @@ -248,7 +249,8 @@ private List createDefaultState() { private AirbyteStateMessage createStreamState(final String name, final String namespace, final List cursorFields, - final String cursorValue) { + final String cursorValue, + final long cursorRecordCount) { final DbStreamState dbStreamState = new DbStreamState() .withStreamName(name) .withStreamNamespace(namespace); @@ -261,6 +263,10 @@ private AirbyteStateMessage createStreamState(final String name, dbStreamState.withCursor(cursorValue); } + if (cursorRecordCount > 0L) { + dbStreamState.withCursorRecordCount(cursorRecordCount); + } + return new AirbyteStateMessage() .withType(AirbyteStateType.STREAM) .withStream(new AirbyteStreamState() From dc15f56e319cf6b5caa5ef48c493d05cb42b34c4 Mon Sep 17 00:00:00 2001 From: Conor Date: Tue, 1 Nov 2022 13:59:19 -0500 Subject: [PATCH 469/498] ci: use custom test-reporter action to upload job results (#18004) * ci: use custom action to upload job results --- .github/workflows/gradle.yml | 131 ++++++++++++++++++++----- tools/bin/prep_test_results_for_gcs.py | 54 ++++++++++ 2 files changed, 160 insertions(+), 25 deletions(-) create mode 100644 tools/bin/prep_test_results_for_gcs.py diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 012d7541d24d..fdf484ba6a9f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -86,6 +86,7 @@ jobs: ${{ secrets.SUPERTOPHER_PAT }} \ ${{ secrets.DAVINCHIA_PAT }} + # Uncomment to debug. # changes-output: # name: "Debug Change Detection Logic" @@ -237,6 +238,37 @@ jobs: - name: Ensure no file change run: git --no-pager diff && test -z "$(git --no-pager diff)" + - name: Publish Connectors Base Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + id: connectors-test-results + if: always() + with: + junit_files: "/actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml\n/actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml" + comment_mode: off + json_file: connectors_base_results.json + json_test_case_results: true + check_name: "Connectors Base Test Results" + + - name: Setup Google Cloud SDK + if: always() + uses: google-github-actions/setup-gcloud@v0 + with: + service_account_key: ${{ secrets.GKE_TEST_SA_KEY }} + export_default_credentials: true + + - name: Prep Test Results For GCS + if: always() + run: | + python tools/bin/prep_test_results_for_gcs.py --json connectors_base_results.json + + - name: Upload Test Results to GCS + if: always() + run: | + gcs_bucket_name="dev-ab-ci-run-results" + filename=$(echo "${{ fromJSON( steps.connectors-test-results.outputs.json ).check_url }}" | sed 's@.*/@@') + echo "$filename" + gsutil -h "Cache-Control:public" cp connectors_base_results.jsonl "gs://$gcs_bucket_name/oss/$filename.jsonl" + - name: Generate Test Report uses: dorny/test-reporter@v1 if: always() @@ -524,6 +556,42 @@ jobs: - name: Automatic Migration Acceptance Test run: SUB_BUILD=PLATFORM ./gradlew :airbyte-tests:automaticMigrationAcceptanceTest --scan -i + - uses: actions/setup-python@v2 + if: always() + with: + python-version: "3.9" + + - name: Publish Platform Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + id: platform-results + if: always() + with: + junit_files: "/actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml\n/actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml" + comment_mode: off + json_file: platform_results.json + json_test_case_results: true + check_name: "Platform Test Results" + + - name: Setup Google Cloud SDK + if: always() + uses: google-github-actions/setup-gcloud@v0 + with: + service_account_key: ${{ secrets.GKE_TEST_SA_KEY }} + export_default_credentials: true + + - name: Prep Test Results For GCS + if: always() + run: | + python tools/bin/prep_test_results_for_gcs.py --json platform_results.json + + - name: Upload Test Results to GCS + if: always() + run: | + gcs_bucket_name="dev-ab-ci-run-results" + filename=$(echo "${{ fromJSON( steps.platform-results.outputs.json ).check_url }}" | sed 's@.*/@@') + echo "$filename" + gsutil -h "Cache-Control:public" cp platform_results.jsonl "gs://$gcs_bucket_name/oss/$filename.jsonl" + - name: Generate Test Report uses: dorny/test-reporter@v1 if: always() # run this step even if previous step failed @@ -543,15 +611,6 @@ jobs: key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} - - name: Upload test results to Github for analysis - if: '!cancelled()' # Run this step even when the tests fail. Skip if the workflow is cancelled. - uses: actions/upload-artifact@v3 - with: - path: | - /actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml - /actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml - name: test-results-build - # In case of self-hosted EC2 errors, remove this block. stop-platform-build-runner: name: "Platform: Stop Build EC2 Runner" @@ -683,6 +742,42 @@ jobs: run: | CI=true IS_MINIKUBE=true ./tools/bin/acceptance_test_kube.sh + - uses: actions/setup-python@v2 + if: always() + with: + python-version: "3.9" + + - name: Publish Kube Test Results + id: kube-results + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + junit_files: "/actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml\n/actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml" + comment_mode: off + json_file: kube_results.json + json_test_case_results: true + check_name: "Kube Test Results" + + - name: Setup Google Cloud SDK + if: always() + uses: google-github-actions/setup-gcloud@v0 + with: + service_account_key: ${{ secrets.GKE_TEST_SA_KEY }} + export_default_credentials: true + + - name: Prep Test Results For GCS + if: always() + run: | + python tools/bin/prep_test_results_for_gcs.py --json kube_results.json + + - name: Upload Test Results to GCS + if: always() + run: | + gcs_bucket_name="dev-ab-ci-run-results" + filename=$(echo "${{ fromJSON( steps.kube-results.outputs.json ).check_url }}" | sed 's@.*/@@') + echo "$filename" + gsutil -h "Cache-Control:public" cp kube_results.jsonl "gs://$gcs_bucket_name/oss/$filename.jsonl" + - name: Generate Test Report uses: dorny/test-reporter@v1 if: always() # run this step even if previous step failed @@ -701,20 +796,13 @@ jobs: key: ${{ secrets.BUILDPULSE_ACCESS_KEY_ID }} secret: ${{ secrets.BUILDPULSE_SECRET_ACCESS_KEY }} - - name: Upload test results to Github for analysis - if: '!cancelled()' # Run this step even when the tests fail. Skip if the workflow is cancelled. - uses: actions/upload-artifact@v3 - with: - path: | - /actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml - /actions-runner/_work/airbyte/airbyte/*/*/build/test-results/*/*.xml - name: test-results-kube - - uses: actions/upload-artifact@v2 if: failure() with: name: Kubernetes Logs path: /tmp/kubernetes_logs/* + + # In case of self-hosted EC2 errors, remove this block. stop-kube-acceptance-test-runner: name: "Platform: Stop Kube Acceptance Test EC2 Runner" @@ -861,13 +949,6 @@ jobs: # SECRET_STORE_GCP_PROJECT_ID: ${{ secrets.SECRET_STORE_GCP_PROJECT_ID }} # run: | # CI=true IS_MINIKUBE=true ./tools/bin/acceptance_test_kube_helm.sh -# - name: Generate Test Report -# uses: dorny/test-reporter@v1 -# if: always() # run this step even if previous step failed -# with: -# name: Platform Helm E2E Test Report -# path: '/actions-runner/_work/airbyte/airbyte/*/build/test-results/*/*.xml' -# reporter: java-junit # # - uses: actions/upload-artifact@v2 # if: failure() diff --git a/tools/bin/prep_test_results_for_gcs.py b/tools/bin/prep_test_results_for_gcs.py new file mode 100644 index 000000000000..7f658eb381fc --- /dev/null +++ b/tools/bin/prep_test_results_for_gcs.py @@ -0,0 +1,54 @@ +import argparse +import json +import os + + +''' + +This script is intended to be run in conjuction with https://github.com/EnricoMi/publish-unit-test-result-action to upload trimmed +test results from the output to a GCS bucket for further analysis. + +The script takes as input the filename of the json output by the aforementioned action, trims it, and writes it out in jsonl format with ".jsonl" filename + +''' + +# Initiate the parser +parser = argparse.ArgumentParser() + +# Add long and short argument +parser.add_argument("--json", "-j", help="Path to the result json output by https://github.com/EnricoMi/publish-unit-test-result-action") + +def main(): + # Read arguments from the command line + args = parser.parse_args() + + f = open(args.json) + d = json.load(f) + out = [] + + check_run_id = int(d["check_url"].split("/")[-1]) + + for elem in d['cases']: + for conclusion in ('success', 'failure', 'skipped'): + if conclusion not in elem['states']: + continue + for i in range(len(elem['states'][conclusion])): + output = { + "test_name": elem['states'][conclusion][i]['test_name'], + "class_name": elem['states'][conclusion][i]['class_name'], + "result_file": elem['states'][conclusion][i]['result_file'], + "time": elem['states'][conclusion][i]['time'], + "state": conclusion, + "check_run_id": check_run_id, + "repo": "airbytehq/airbyte" + } + out.append(output) + + with open(args.json + "l", 'w') as f: + for o in out: + json.dump(o, f) + f.write('\n') + + +if __name__ == '__main__': + main() \ No newline at end of file From af7da95614b0f3eb9922714bdcdc12a8faf86046 Mon Sep 17 00:00:00 2001 From: Marcos Marx Date: Tue, 1 Nov 2022 15:11:05 -0400 Subject: [PATCH 470/498] Correct coinmarket spec (#18790) * correct coinmarket spec * remove duplicate support normalization from source spec * rollback coinmarketcap version in source def seed * update connector version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 61 +++++++++---------- .../source-coinmarketcap/Dockerfile | 2 +- .../source_coinmarketcap/spec.yaml | 5 +- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 303c592a0a80..c3f38bc42a40 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -228,7 +228,7 @@ - name: CoinMarketCap sourceDefinitionId: 239463f5-64bb-4d88-b4bd-18ce673fd572 dockerRepository: airbyte/source-coinmarketcap - dockerImageTag: 0.1.0 + dockerImageTag: 0.1.1 documentationUrl: https://docs.airbyte.com/integrations/sources/coinmarketcap sourceType: api releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 8c82c3ccdcc6..0ebdeab654f7 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2286,50 +2286,47 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-coinmarketcap:0.1.0" +- dockerImage: "airbyte/source-coinmarketcap:0.1.1" spec: - documentationUrl: https://docs.airbyte.com/integrations/sources/coinmarketcap + documentationUrl: "https://docs.airbyte.com/integrations/sources/coinmarketcap" connectionSpecification: - $schema: http://json-schema.org/draft-07/schema# - title: Coinmarketcap Spec - type: object + $schema: "http://json-schema.org/draft-07/schema#" + title: "Coinmarketcap Spec" + type: "object" required: - - api_key - - data_type + - "api_key" + - "data_type" additionalProperties: true properties: api_key: - title: API Key - type: string - description: >- - Your API Key. See here. The token is - case sensitive. + title: "API Key" + type: "string" + description: "Your API Key. See here. The token is case sensitive." airbyte_secret: true data_type: - title: Data type - type: string + title: "Data type" + type: "string" enum: - - latest - - historical - description: >- - /latest: Latest market ticker quotes and averages for cryptocurrencies and exchanges. - /historical: Intervals of historic market data like OHLCV data or data for use in charting libraries. See here. + - "latest" + - "historical" + description: "/latest: Latest market ticker quotes and averages for cryptocurrencies\ + \ and exchanges. /historical: Intervals of historic market data like OHLCV\ + \ data or data for use in charting libraries. See here." symbols: - title: Symbol - type: array - items: { - "type": "string" - } - description: Cryptocurrency symbols. (only used for quotes stream) + title: "Symbol" + type: "array" + items: + type: "string" + description: "Cryptocurrency symbols. (only used for quotes stream)" minItems: 1 examples: - - AVAX - - BTC - supportsNormalization: false - supportsDBT: false - supported_destination_sync_modes: [] + - "AVAX" + - "BTC" + supportsNormalization: false + supportsDBT: false + supported_destination_sync_modes: [] - dockerImage: "airbyte/source-commercetools:0.1.0" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/commercetools" diff --git a/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile b/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile index f0d9c84e18a4..d89d136206c8 100644 --- a/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile +++ b/airbyte-integrations/connectors/source-coinmarketcap/Dockerfile @@ -34,5 +34,5 @@ COPY source_coinmarketcap ./source_coinmarketcap ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.0 +LABEL io.airbyte.version=0.1.1 LABEL io.airbyte.name=airbyte/source-coinmarketcap diff --git a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml index b461cf69da6e..9d0e5c6b9135 100644 --- a/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml +++ b/airbyte-integrations/connectors/source-coinmarketcap/source_coinmarketcap/spec.yaml @@ -29,9 +29,8 @@ connectionSpecification: symbols: title: Symbol type: array - items: { - "type": "string" - } + items: + type: string description: Cryptocurrency symbols. (only used for quotes stream) minItems: 1 examples: From 6af98045f8bb101e979b55df172b8aea2e8483fd Mon Sep 17 00:00:00 2001 From: Greg Solovyev Date: Tue, 1 Nov 2022 09:55:32 -1000 Subject: [PATCH 471/498] Parameterize test_empty_streams and test_stream_with_1_airbyte_column by destination (#18197) * Remove lines that always add Postgres to list of destinations * Parameterize all tests in test_ephemeral by destination --- .../integration_tests/test_ephemeral.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py b/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py index 59ec20fc7a36..f459f5faecd6 100644 --- a/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py +++ b/airbyte-integrations/bases/base-normalization/integration_tests/test_ephemeral.py @@ -28,8 +28,7 @@ def before_all_tests(request): "test_type": "ephemeral", "tmp_folders": temporary_folders, } - if DestinationType.POSTGRES.value not in destinations_to_test: - destinations_to_test.append(DestinationType.POSTGRES.value) + dbt_test_utils.set_target_schema("test_ephemeral") dbt_test_utils.change_current_test_dir(request) dbt_test_utils.setup_db(destinations_to_test) @@ -54,10 +53,10 @@ def setup_test_path(request): @pytest.mark.parametrize("column_count", [1000]) @pytest.mark.parametrize("destination_type", list(DestinationType)) def test_destination_supported_limits(destination_type: DestinationType, column_count: int): - if destination_type.value not in dbt_test_utils.get_test_targets() or destination_type.value == DestinationType.MYSQL.value: + if destination_type.value == DestinationType.MYSQL.value: # In MySQL, the max number of columns is limited by row size (8KB), # not by absolute column count. It is way fewer than 1000. - pytest.skip(f"Destinations {destination_type} is not in NORMALIZATION_TEST_TARGET env variable (MYSQL is also skipped)") + pytest.skip("Skipping test for column limit, because in MySQL, the max number of columns is limited by row size (8KB)") if destination_type.value == DestinationType.ORACLE.value: # Airbyte uses a few columns for metadata and Oracle limits are right at 1000 column_count = 993 @@ -85,15 +84,20 @@ def test_destination_failure_over_limits(integration_type: str, column_count: in run_test(destination_type, column_count, expected_exception_message) -def test_empty_streams(setup_test_path): - run_test(DestinationType.POSTGRES, 0) +@pytest.mark.parametrize("destination_type", list(DestinationType)) +def test_empty_streams(destination_type: DestinationType, setup_test_path): + run_test(destination_type, 0) -def test_stream_with_1_airbyte_column(setup_test_path): - run_test(DestinationType.POSTGRES, 1) +@pytest.mark.parametrize("destination_type", list(DestinationType)) +def test_stream_with_1_airbyte_column(destination_type: DestinationType, setup_test_path): + run_test(destination_type, 1) def run_test(destination_type: DestinationType, column_count: int, expected_exception_message: str = ""): + if destination_type.value not in dbt_test_utils.get_test_targets(): + pytest.skip(f"Destinations {destination_type} is not in NORMALIZATION_TEST_TARGET env variable") + if destination_type.value == DestinationType.CLICKHOUSE.value: pytest.skip("ephemeral materialization isn't supported in ClickHouse yet") if destination_type.value == DestinationType.ORACLE.value: @@ -104,7 +108,7 @@ def run_test(destination_type: DestinationType, column_count: int, expected_exce dbt_test_utils.set_target_schema(dbt_test_utils.generate_random_string("test_ephemeral_")) else: dbt_test_utils.set_target_schema("test_ephemeral") - print("Testing ephemeral") + print(f"Testing ephemeral for destination {destination_type.value} with column count {column_count}") integration_type = destination_type.value # Create the test folder with dbt project and appropriate destination settings to run integration tests from test_root_dir = setup_test_dir(integration_type, temporary_folders) From 88bbb5dabc060c25155f167be25bd62037640bcf Mon Sep 17 00:00:00 2001 From: Artem Inzhyyants <36314070+artem1205@users.noreply.github.com> Date: Tue, 1 Nov 2022 21:42:39 +0100 Subject: [PATCH 472/498] =?UTF-8?q?=F0=9F=90=9B=20Source=20Facebook=20Mark?= =?UTF-8?q?eting:=20reduce=20request=20limit=20after=20specific=20error=20?= =?UTF-8?q?(#18734)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Source Facebook Marketing: reduce request limit after specific error * 🐛 Source Facebook Marketing: bump version; update docs * 🐛 Source Facebook Marketing: add test * 🐛 Source Facebook Marketing: increase timeout --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../source-facebook-marketing/Dockerfile | 2 +- .../acceptance-test-config.yml | 1 + .../source-facebook-marketing/setup.py | 2 +- .../streams/common.py | 11 +++++++- .../unit_tests/test_client.py | 25 +++++++++++++++++++ .../sources/facebook-marketing.md | 3 ++- 8 files changed, 42 insertions(+), 6 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index c3f38bc42a40..62f3c3000160 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -336,7 +336,7 @@ - name: Facebook Marketing sourceDefinitionId: e7778cfc-e97c-4458-9ecb-b4f2bba8946c dockerRepository: airbyte/source-facebook-marketing - dockerImageTag: 0.2.70 + dockerImageTag: 0.2.71 documentationUrl: https://docs.airbyte.com/integrations/sources/facebook-marketing icon: facebook.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 0ebdeab654f7..d8b56ccdd20d 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2873,7 +2873,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-facebook-marketing:0.2.70" +- dockerImage: "airbyte/source-facebook-marketing:0.2.71" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" changelogUrl: "https://docs.airbyte.com/integrations/sources/facebook-marketing" diff --git a/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile b/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile index 9e503180003e..960519b2afed 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile +++ b/airbyte-integrations/connectors/source-facebook-marketing/Dockerfile @@ -13,5 +13,5 @@ ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.2.70 +LABEL io.airbyte.version=0.2.71 LABEL io.airbyte.name=airbyte/source-facebook-marketing diff --git a/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml b/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml index 86d6aa901dd6..b7ffe7219255 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-facebook-marketing/acceptance-test-config.yml @@ -14,6 +14,7 @@ tests: basic_read: - config_path: "secrets/config.json" empty_streams: ["videos"] + timeout_seconds: 2400 incremental: - config_path: "secrets/config.json" timeout_seconds: 2400 diff --git a/airbyte-integrations/connectors/source-facebook-marketing/setup.py b/airbyte-integrations/connectors/source-facebook-marketing/setup.py index 8b9d7839bc03..ac757a2d80ae 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/setup.py +++ b/airbyte-integrations/connectors/source-facebook-marketing/setup.py @@ -6,7 +6,7 @@ from setuptools import find_packages, setup MAIN_REQUIREMENTS = [ - "airbyte-cdk~=0.1", + "airbyte-cdk~=0.2", "cached_property==1.5.2", "facebook_business==15.0.0", "pendulum>=2,<3", diff --git a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/common.py b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/common.py index 408a0b34e927..de359f3e0668 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/common.py +++ b/airbyte-integrations/connectors/source-facebook-marketing/source_facebook_marketing/streams/common.py @@ -33,6 +33,15 @@ def log_retry_attempt(details): logger.info(str(exc)) logger.info(f"Caught retryable error after {details['tries']} tries. Waiting {details['wait']} more seconds then retrying...") + def reduce_request_record_limit(details): + _, exc, _ = sys.exc_info() + if ( + details.get("kwargs", {}).get("params", {}).get("limit") + and exc.http_status() == http.client.INTERNAL_SERVER_ERROR + and exc.api_error_message() == "Please reduce the amount of data you're asking for, then retry your request" + ): + details["kwargs"]["params"]["limit"] = int(int(details["kwargs"]["params"]["limit"]) / 2) + def should_retry_api_error(exc): if isinstance(exc, FacebookRequestError): call_rate_limit_error = exc.api_error_code() in FACEBOOK_RATE_LIMIT_ERROR_CODES @@ -58,7 +67,7 @@ def should_retry_api_error(exc): backoff_type, exception, jitter=None, - on_backoff=log_retry_attempt, + on_backoff=[log_retry_attempt, reduce_request_record_limit], giveup=lambda exc: not should_retry_api_error(exc), **wait_gen_kwargs, ) diff --git a/airbyte-integrations/connectors/source-facebook-marketing/unit_tests/test_client.py b/airbyte-integrations/connectors/source-facebook-marketing/unit_tests/test_client.py index 600b27bca652..5ca337e11a34 100644 --- a/airbyte-integrations/connectors/source-facebook-marketing/unit_tests/test_client.py +++ b/airbyte-integrations/connectors/source-facebook-marketing/unit_tests/test_client.py @@ -38,6 +38,18 @@ def fb_call_rate_response_fixture(): } +@pytest.fixture(name="fb_call_amount_data_response") +def fb_call_amount_data_response_fixture(): + error = {"message": "Please reduce the amount of data you're asking for, then retry your request", "code": 1} + + return { + "json": { + "error": error, + }, + "status_code": 500, + } + + class TestBackoff: def test_limit_reached(self, mocker, requests_mock, api, fb_call_rate_response, account_id): """Error once, check that we retry and not fail""" @@ -132,3 +144,16 @@ def test_common_error_retry(self, error_response, requests_mock, api, account_id accounts = list(stream.read_records(sync_mode=SyncMode.full_refresh, stream_state={})) assert accounts == [account_data] + + def test_limit_error_retry(self, fb_call_amount_data_response, requests_mock, api, account_id): + """Error every time, check limit parameter decreases by 2 times every new call""" + + res = requests_mock.register_uri( + "GET", FacebookSession.GRAPH + f"/{FB_API_VERSION}/act_{account_id}/campaigns", [fb_call_amount_data_response] + ) + + stream = Campaigns(api=api, start_date=pendulum.now(), end_date=pendulum.now(), include_deleted=False, page_size=100) + try: + list(stream.read_records(sync_mode=SyncMode.full_refresh, stream_state={})) + except FacebookRequestError: + assert [x.qs.get("limit")[0] for x in res.request_history] == ["100", "50", "25", "12", "6"] diff --git a/docs/integrations/sources/facebook-marketing.md b/docs/integrations/sources/facebook-marketing.md index ea32ecfbc75d..70b95277d0fd 100644 --- a/docs/integrations/sources/facebook-marketing.md +++ b/docs/integrations/sources/facebook-marketing.md @@ -127,7 +127,8 @@ Please be informed that the connector uses the `lookback_window` parameter to pe | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 0.2.70 | 2022-10-26 | [18045](https://github.com/airbytehq/airbyte/pull/18045) | Upgrade FB SDK to v15.0 | +| 0.2.71 | 2022-10-31 | [18734](https://github.com/airbytehq/airbyte/pull/18734) | Reduce request record limit on retry | +| 0.2.70 | 2022-10-26 | [18045](https://github.com/airbytehq/airbyte/pull/18045) | Upgrade FB SDK to v15.0 | | 0.2.69 | 2022-10-17 | [18045](https://github.com/airbytehq/airbyte/pull/18045) | Remove "pixel" field from the Custom Conversions stream schema | | 0.2.68 | 2022-10-12 | [17869](https://github.com/airbytehq/airbyte/pull/17869) | Remove "format" from optional datetime `end_date` field | | 0.2.67 | 2022-10-04 | [17551](https://github.com/airbytehq/airbyte/pull/17551) | Add `cursor_field` for custom_insights stream schema | From b5adaef29c1f8e9f2213d4281096dc184148d106 Mon Sep 17 00:00:00 2001 From: perangel Date: Tue, 1 Nov 2022 17:00:12 -0400 Subject: [PATCH 473/498] [charts/airbyte-cron] Cleanup env vars (#18787) * [charts/airbyte-cron] Cleanup env vars * Remove unused env var --- charts/airbyte-cron/templates/deployment.yaml | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/charts/airbyte-cron/templates/deployment.yaml b/charts/airbyte-cron/templates/deployment.yaml index 5778de3d4dca..ddade74521b5 100644 --- a/charts/airbyte-cron/templates/deployment.yaml +++ b/charts/airbyte-cron/templates/deployment.yaml @@ -45,21 +45,11 @@ spec: imagePullPolicy: "{{ .Values.image.pullPolicy }}" env: {{- if eq .Values.global.deploymentMode "oss" }} - - name: AIRBYTE_ROLE - valueFrom: - configMapKeyRef: - name: {{ .Release.Name }}-airbyte-env - key: AIRBYTE_ROLE - name: AIRBYTE_VERSION valueFrom: configMapKeyRef: name: {{ .Release.Name }}-airbyte-env key: AIRBYTE_VERSION - - name: CONFIGS_DATABASE_INITIALIZATION_TIMEOUT_MS - valueFrom: - configMapKeyRef: - name: {{ .Release.Name }}-airbyte-env - key: CONFIGS_DATABASE_INITIALIZATION_TIMEOUT_MS - name: CONFIGS_DATABASE_MINIMUM_FLYWAY_MIGRATION_VERSION valueFrom: configMapKeyRef: @@ -85,21 +75,11 @@ spec: configMapKeyRef: name: {{ .Release.Name }}-airbyte-env key: CRON_MICRONAUT_ENVIRONMENTS - - name: REMOTE_CONNECTOR_CATALOG_URL - valueFrom: - configMapKeyRef: - name: {{ .Release.Name }}-airbyte-env - key: REMOTE_CONNECTOR_CATALOG_URL - name: TRACKING_STRATEGY valueFrom: configMapKeyRef: name: {{ .Release.Name }}-airbyte-env key: TRACKING_STRATEGY - - name: UPDATE_DEFINITIONS_CRON_ENABLED - valueFrom: - configMapKeyRef: - name: {{ .Release.Name }}-airbyte-env - key: UPDATE_DEFINITIONS_CRON_ENABLED - name: WORKFLOW_FAILURE_RESTART_DELAY_SECONDS valueFrom: configMapKeyRef: From b14bd05e91bcf7b43d8646af758d167339d887f3 Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Tue, 1 Nov 2022 17:04:54 -0400 Subject: [PATCH 474/498] Use equalsIgnoreCase (#18810) --- .../io/airbyte/workers/tracing/TemporalSdkInterceptor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/tracing/TemporalSdkInterceptor.java b/airbyte-workers/src/main/java/io/airbyte/workers/tracing/TemporalSdkInterceptor.java index 42f0e71e6a25..c736dde0feb8 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/tracing/TemporalSdkInterceptor.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/tracing/TemporalSdkInterceptor.java @@ -75,8 +75,8 @@ boolean isExitTrace(final MutableSpan trace) { return false; return trace.isError() && - EXIT_ERROR_MESSAGE.equals(trace.getTags().getOrDefault(ERROR_MESSAGE_TAG_KEY, "")) && - CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME.equals(trace.getResourceName()); + EXIT_ERROR_MESSAGE.equalsIgnoreCase(trace.getTags().getOrDefault(ERROR_MESSAGE_TAG_KEY, "").toString()) && + CONNECTION_MANAGER_WORKFLOW_IMPL_RESOURCE_NAME.equalsIgnoreCase(trace.getResourceName().toString()); } } From 66da2fcd58856e77b237d2a36e1407020e1c4619 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 23:14:24 +0200 Subject: [PATCH 475/498] Bump helm chart version reference to 0.40.40 (#18815) Co-authored-by: perangel Co-authored-by: Kyryl Skobylko --- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-metrics/Chart.yaml | 2 +- charts/airbyte-pod-sweeper/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.lock | 22 +++++++++++----------- charts/airbyte/Chart.yaml | 18 +++++++++--------- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 95207349d5a0..294f1ddb8ce1 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.33" +version: "0.40.40" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-metrics/Chart.yaml b/charts/airbyte-metrics/Chart.yaml index f0bfb04e7636..bc1f095709d7 100644 --- a/charts/airbyte-metrics/Chart.yaml +++ b/charts/airbyte-metrics/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.33" +version: "0.40.40" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-pod-sweeper/Chart.yaml b/charts/airbyte-pod-sweeper/Chart.yaml index 8320b8101fbf..962d7c3a57e3 100644 --- a/charts/airbyte-pod-sweeper/Chart.yaml +++ b/charts/airbyte-pod-sweeper/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.33" +version: "0.40.40" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index fd982a1a1407..545432196c29 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.33" +version: "0.40.40" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index 0ff920018f81..18e24fa0552a 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.33" +version: "0.40.40" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index e908126a1d1f..71ce617dd875 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: "0.40.33" +version: "0.40.40" # This is the version number of the application being deployed. This version number should be diff --git a/charts/airbyte/Chart.lock b/charts/airbyte/Chart.lock index 5bfe7f73a16d..42d80e57d1de 100644 --- a/charts/airbyte/Chart.lock +++ b/charts/airbyte/Chart.lock @@ -4,27 +4,27 @@ dependencies: version: 1.17.1 - name: airbyte-bootloader repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 + version: 0.40.40 - name: temporal repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 + version: 0.40.40 - name: webapp repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 + version: 0.40.40 - name: server repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 + version: 0.40.40 - name: worker repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 + version: 0.40.40 - name: pod-sweeper repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 + version: 0.40.40 - name: metrics repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.33 -- name: airbyte-cron + version: 0.40.40 +- name: cron repository: https://airbytehq.github.io/helm-charts/ - version: 0.40.36 -digest: sha256:4bf1839b99570f8c175bf147e3eae2deec2b1dc68a5191e47eb452d5e2c5859e -generated: "2022-10-31T18:20:32.211489+02:00" + version: 0.40.40 +digest: sha256:d6b432e0202e04571f1ed229d34c39ff5f0945538934c7f8086a0f68a4f7ab2f +generated: "2022-11-01T21:08:34.786286801Z" diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index 14f260918ce0..a928c05c19f3 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.40.33 +version: 0.40.40 # This is the version number of the application being deployed. This version number should be @@ -33,33 +33,33 @@ dependencies: - condition: airbyte-bootloader.enabled name: airbyte-bootloader repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: temporal.enabled name: temporal repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: webapp.enabled name: webapp repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: server.enabled name: server repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: worker.enabled name: worker repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: pod-sweeper.enabled name: pod-sweeper repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: metrics.enabled name: metrics repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 - condition: cron.enabled name: cron repository: "https://airbytehq.github.io/helm-charts/" - version: 0.40.36 + version: 0.40.40 From bc090834e0ecc796c23fff4e95913741254b8e96 Mon Sep 17 00:00:00 2001 From: Serhii Lazebnyi <53845333+lazebnyi@users.noreply.github.com> Date: Tue, 1 Nov 2022 23:19:46 +0100 Subject: [PATCH 476/498] =?UTF-8?q?=F0=9F=90=9BDestination=20Google=20Shee?= =?UTF-8?q?ts:=20Fix=20empty=20headers=20list=20(#18729)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix empty headers list * Updated PR number * Bumped version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../init/src/main/resources/seed/destination_definitions.yaml | 2 +- .../init/src/main/resources/seed/destination_specs.yaml | 2 +- .../connectors/destination-google-sheets/Dockerfile | 2 +- .../connectors/destination-google-sheets/README.md | 2 +- .../destination_google_sheets/spreadsheet.py | 3 ++- docs/integrations/destinations/google-sheets.md | 1 + 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml index 3ff0328318f0..0d7af84e48f8 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_definitions.yaml @@ -317,7 +317,7 @@ - name: Google Sheets destinationDefinitionId: a4cbd2d1-8dbe-4818-b8bc-b90ad782d12a dockerRepository: airbyte/destination-google-sheets - dockerImageTag: 0.1.1 + dockerImageTag: 0.1.2 documentationUrl: https://docs.airbyte.com/integrations/destinations/google-sheets icon: google-sheets.svg releaseStage: alpha diff --git a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml index b27586cd2f2f..ea31f59258e3 100644 --- a/airbyte-config/init/src/main/resources/seed/destination_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/destination_specs.yaml @@ -6108,7 +6108,7 @@ supported_destination_sync_modes: - "overwrite" - "append" -- dockerImage: "airbyte/destination-google-sheets:0.1.1" +- dockerImage: "airbyte/destination-google-sheets:0.1.2" spec: documentationUrl: "https://docs.airbyte.com/integrations/destinations/google-sheets" connectionSpecification: diff --git a/airbyte-integrations/connectors/destination-google-sheets/Dockerfile b/airbyte-integrations/connectors/destination-google-sheets/Dockerfile index 202dba1a37c0..dcbd96c665b5 100644 --- a/airbyte-integrations/connectors/destination-google-sheets/Dockerfile +++ b/airbyte-integrations/connectors/destination-google-sheets/Dockerfile @@ -13,5 +13,5 @@ RUN pip install . ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=0.1.1 +LABEL io.airbyte.version=0.1.2 LABEL io.airbyte.name=airbyte/destination-google-sheets diff --git a/airbyte-integrations/connectors/destination-google-sheets/README.md b/airbyte-integrations/connectors/destination-google-sheets/README.md index 4f5752367415..a39a52d52f30 100644 --- a/airbyte-integrations/connectors/destination-google-sheets/README.md +++ b/airbyte-integrations/connectors/destination-google-sheets/README.md @@ -79,7 +79,7 @@ cat integration_tests/test_data/messages.txt | docker run --rm -v $(pwd)/secrets Make sure to familiarize yourself with [pytest test discovery](https://docs.pytest.org/en/latest/goodpractices.html#test-discovery) to know how your test files and methods should be named. First install test dependencies into your virtual environment: ``` -pip install .[tests] +pip install .'[tests]' ``` ### Unit Tests To run unit tests locally, from the connector directory run: diff --git a/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spreadsheet.py b/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spreadsheet.py index cc104973ff16..d496fb77eb80 100644 --- a/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spreadsheet.py +++ b/airbyte-integrations/connectors/destination-google-sheets/destination_google_sheets/spreadsheet.py @@ -46,7 +46,8 @@ def set_headers(self, stream_name: str, headers_list: List[str]): Sets headers belonging to the input stream """ stream: Worksheet = self.open_worksheet(stream_name) - stream.update_row(1, headers_list) + if headers_list: + stream.update_row(1, headers_list) def index_cols(self, stream: Worksheet) -> Mapping[str, int]: """ diff --git a/docs/integrations/destinations/google-sheets.md b/docs/integrations/destinations/google-sheets.md index 1d135ba23d95..ef4c122966ae 100644 --- a/docs/integrations/destinations/google-sheets.md +++ b/docs/integrations/destinations/google-sheets.md @@ -130,5 +130,6 @@ You cannot create more than 200 worksheets within single spreadsheet. | Version | Date | Pull Request | Subject | | ------- | ---------- | -------------------------------------------------------- | ----------------------------------- | +| 0.1.2 | 2022-10-31 | [18729](https://github.com/airbytehq/airbyte/pull/18729) | Fix empty headers list | | 0.1.1 | 2022-06-15 | [14751](https://github.com/airbytehq/airbyte/pull/14751) | Yield state only when records saved | | 0.1.0 | 2022-04-26 | [12135](https://github.com/airbytehq/airbyte/pull/12135) | Initial Release | From e45aec3ef500b60c2195c352ffd86ec0c7fea17d Mon Sep 17 00:00:00 2001 From: Serhii Lazebnyi <53845333+lazebnyi@users.noreply.github.com> Date: Tue, 1 Nov 2022 23:20:20 +0100 Subject: [PATCH 477/498] =?UTF-8?q?=F0=9F=90=9BSource=20Exchange=20Rates:?= =?UTF-8?q?=20Fix=20handling=20error=20during=20check=20connection=20(#187?= =?UTF-8?q?26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix handling error during check connection * Updated PR number * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- .../source-exchange-rates/Dockerfile | 2 +- .../source_exchange_rates/source.py | 2 +- .../unit_tests/__init__.py | 0 .../unit_tests/conftest.py | 24 ++++++++++++ .../unit_tests/test_source.py | 37 +++++++++++++++++++ docs/integrations/sources/exchangeratesapi.md | 21 ++++++----- 8 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 airbyte-integrations/connectors/source-exchange-rates/unit_tests/__init__.py create mode 100644 airbyte-integrations/connectors/source-exchange-rates/unit_tests/conftest.py create mode 100644 airbyte-integrations/connectors/source-exchange-rates/unit_tests/test_source.py diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index 62f3c3000160..d417180ca357 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -328,7 +328,7 @@ - name: Exchange Rates Api sourceDefinitionId: e2b40e36-aa0e-4bed-b41b-bcea6fa348b1 dockerRepository: airbyte/source-exchange-rates - dockerImageTag: 1.2.6 + dockerImageTag: 1.2.7 documentationUrl: https://docs.airbyte.com/integrations/sources/exchangeratesapi icon: exchangeratesapi.svg sourceType: api diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index d8b56ccdd20d..1d45ee2f23eb 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -2835,7 +2835,7 @@ supportsDBT: false supported_destination_sync_modes: [] protocol_version: "0.2.1" -- dockerImage: "airbyte/source-exchange-rates:1.2.6" +- dockerImage: "airbyte/source-exchange-rates:1.2.7" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/exchangeratesapi" connectionSpecification: diff --git a/airbyte-integrations/connectors/source-exchange-rates/Dockerfile b/airbyte-integrations/connectors/source-exchange-rates/Dockerfile index 8b81bb2107c8..bdbd1d9f4da3 100644 --- a/airbyte-integrations/connectors/source-exchange-rates/Dockerfile +++ b/airbyte-integrations/connectors/source-exchange-rates/Dockerfile @@ -16,5 +16,5 @@ RUN pip install . ENV AIRBYTE_ENTRYPOINT "python /airbyte/integration_code/main.py" ENTRYPOINT ["python", "/airbyte/integration_code/main.py"] -LABEL io.airbyte.version=1.2.6 +LABEL io.airbyte.version=1.2.7 LABEL io.airbyte.name=airbyte/source-exchange-rates diff --git a/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/source.py b/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/source.py index 9b45a5d06a7d..95183449df7a 100644 --- a/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/source.py +++ b/airbyte-integrations/connectors/source-exchange-rates/source_exchange_rates/source.py @@ -101,7 +101,7 @@ def check_connection(self, logger: AirbyteLogger, config: Mapping[str, Any]) -> # When API requests is sent but the requested data is not available or the API call fails # for some reason, a JSON error is returned. # https://exchangeratesapi.io/documentation/#errors - error = resp.json().get("error") + error = resp.json().get("error", resp.json()) code = error.get("code") message = error.get("message") or error.get("info") # If code is base_currency_access_restricted, error is caused by switching base currency while using free diff --git a/airbyte-integrations/connectors/source-exchange-rates/unit_tests/__init__.py b/airbyte-integrations/connectors/source-exchange-rates/unit_tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/airbyte-integrations/connectors/source-exchange-rates/unit_tests/conftest.py b/airbyte-integrations/connectors/source-exchange-rates/unit_tests/conftest.py new file mode 100644 index 000000000000..24b680db9bed --- /dev/null +++ b/airbyte-integrations/connectors/source-exchange-rates/unit_tests/conftest.py @@ -0,0 +1,24 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +from pytest import fixture + + +@fixture(name="config") +def config_fixture(requests_mock): + config = {"start_date": "2022-09-08", "base": "USD", "access_key": "KEY"} + + return config + + +@fixture(name="mock_stream") +def mock_stream_fixture(requests_mock): + def _mock_stream(path, response=None, status_code=200): + if response is None: + response = {} + + url = f"https://api.apilayer.com/exchangerates_data/{path}" + requests_mock.get(url, json=response, status_code=status_code) + + return _mock_stream diff --git a/airbyte-integrations/connectors/source-exchange-rates/unit_tests/test_source.py b/airbyte-integrations/connectors/source-exchange-rates/unit_tests/test_source.py new file mode 100644 index 000000000000..750ed6806c2b --- /dev/null +++ b/airbyte-integrations/connectors/source-exchange-rates/unit_tests/test_source.py @@ -0,0 +1,37 @@ +# +# Copyright (c) 2022 Airbyte, Inc., all rights reserved. +# + +import logging + +from source_exchange_rates.source import SourceExchangeRates + +logger = logging.getLogger("airbyte") + + +def test_check_connection_ok(config, mock_stream): + response = {"success": True, "timestamp": 1662681599, "historical": True, "base": "USD", "date": "2022-09-08", "rates": {"AED": 1}} + mock_stream(config["start_date"], response=response) + ok, error_msg = SourceExchangeRates().check_connection(logger, config=config) + + assert ok + assert not error_msg + + +def test_check_connection_exception(config, mock_stream): + message = ( + "You have exceeded your daily/monthly API rate limit. Please review and upgrade your subscription plan at " + "https://promptapi.com/subscriptions to continue. " + ) + response = {"message": message} + mock_stream(config["start_date"], response=response, status_code=429) + ok, error_msg = SourceExchangeRates().check_connection(logger, config=config) + + assert not ok + assert error_msg == message + + +def test_streams(config): + streams = SourceExchangeRates().streams(config) + + assert len(streams) == 1 diff --git a/docs/integrations/sources/exchangeratesapi.md b/docs/integrations/sources/exchangeratesapi.md index e5f33ace327b..6b4f79a305a1 100644 --- a/docs/integrations/sources/exchangeratesapi.md +++ b/docs/integrations/sources/exchangeratesapi.md @@ -44,13 +44,14 @@ If you have `free` subscription plan \(you may check it [here](https://manage.ex ## Changelog -| Version | Date | Pull Request | Subject | -|:--------| :--- | :--- | :--- | -| 1.2.6 | 2022-08-23 | [15884](https://github.com/airbytehq/airbyte/pull/15884) | Migrated to new API Layer endpoint | -| 0.2.6 | 2022-04-20 | [12230](https://github.com/airbytehq/airbyte/pull/12230) | Update connector to use a `spec.yaml` | -| 0.2.5 | 2021-11-12 | [7936](https://github.com/airbytehq/airbyte/pull/7936) | Add ignore_weekends boolean option | -| 0.2.4 | 2021-11-08 | [7499](https://github.com/airbytehq/airbyte/pull/7499) | Remove base-python dependencies | -| 0.2.3 | 2021-06-06 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for kubernetes support | -| 0.2.2 | 2021-05-28 | [3677](https://github.com/airbytehq/airbyte/pull/3677) | Adding clearer error message when a currency isn't supported. access_key field in spec.json was marked as sensitive | -| 0.2.0 | 2021-05-26 | [3566](https://github.com/airbytehq/airbyte/pull/3566) | Move from `api.ratesapi.io/` to `api.exchangeratesapi.io/`. Add required field `access_key` to `config.json`. | -| 0.1.0 | 2021-04-19 | [2942](https://github.com/airbytehq/airbyte/pull/2942) | Implement Exchange API using the CDK | +| Version | Date | Pull Request | Subject | +|:--------| :--------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | +| 1.2.7 | 2022-10-31 | [18726](https://github.com/airbytehq/airbyte/pull/18726) | Fix handling error during check connection | +| 1.2.6 | 2022-08-23 | [15884](https://github.com/airbytehq/airbyte/pull/15884) | Migrated to new API Layer endpoint | +| 0.2.6 | 2022-04-20 | [12230](https://github.com/airbytehq/airbyte/pull/12230) | Update connector to use a `spec.yaml` | +| 0.2.5 | 2021-11-12 | [7936](https://github.com/airbytehq/airbyte/pull/7936) | Add ignore_weekends boolean option | +| 0.2.4 | 2021-11-08 | [7499](https://github.com/airbytehq/airbyte/pull/7499) | Remove base-python dependencies | +| 0.2.3 | 2021-06-06 | [3973](https://github.com/airbytehq/airbyte/pull/3973) | Add `AIRBYTE_ENTRYPOINT` for kubernetes support | +| 0.2.2 | 2021-05-28 | [3677](https://github.com/airbytehq/airbyte/pull/3677) | Adding clearer error message when a currency isn't supported. access_key field in spec.json was marked as sensitive | +| 0.2.0 | 2021-05-26 | [3566](https://github.com/airbytehq/airbyte/pull/3566) | Move from `api.ratesapi.io/` to `api.exchangeratesapi.io/`. Add required field `access_key` to `config.json`. | +| 0.1.0 | 2021-04-19 | [2942](https://github.com/airbytehq/airbyte/pull/2942) | Implement Exchange API using the CDK | From 7b9a0970817c9e9399a033531bdfada2ca89254f Mon Sep 17 00:00:00 2001 From: Greg Solovyev Date: Tue, 1 Nov 2022 12:36:31 -1000 Subject: [PATCH 478/498] Add normalization changelog and bump normalization version in platform (#18813) --- .../NormalizationRunnerFactory.java | 2 +- .../basic-normalization.md | 113 +++++++++--------- 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java index cc1530d3fd49..d3943e0f7811 100644 --- a/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java +++ b/airbyte-commons-worker/src/main/java/io/airbyte/workers/normalization/NormalizationRunnerFactory.java @@ -13,7 +13,7 @@ public class NormalizationRunnerFactory { public static final String BASE_NORMALIZATION_IMAGE_NAME = "airbyte/normalization"; - public static final String NORMALIZATION_VERSION = "0.2.23"; + public static final String NORMALIZATION_VERSION = "0.2.24"; static final Map> NORMALIZATION_MAPPING = ImmutableMap.>builder() diff --git a/docs/understanding-airbyte/basic-normalization.md b/docs/understanding-airbyte/basic-normalization.md index 90a684809f6e..46fa8763f94a 100644 --- a/docs/understanding-airbyte/basic-normalization.md +++ b/docs/understanding-airbyte/basic-normalization.md @@ -351,60 +351,61 @@ Note that Basic Normalization is packaged in a docker image `airbyte/normalizati Therefore, in order to "upgrade" to the desired normalization version, you need to use the corresponding Airbyte version that it's being released in: -| Airbyte Version | Normalization Version | Date | Pull Request | Subject | -|:----------------|:----------------------|:-----------|:------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------| +| Airbyte Version | Normalization Version | Date | Pull Request | Subject | +|:----------------|:----------------------|:-----------|:-------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------| +| | 0.2.24 | 2022-11-01 | [\#18015](https://github.com/airbytehq/airbyte/pull/18015) | Add a drop table hook that drops *_scd tables after overwrite/reset | | | 0.2.23 | 2022-10-12 | [\#17483](https://github.com/airbytehq/airbyte/pull/17483) (published in [\#17896](https://github.com/airbytehq/airbyte/pull/17896)) | Remove unnecessary `Native Port` config option | -| | 0.2.22 | 2022-09-05 | [\#16339](https://github.com/airbytehq/airbyte/pull/16339) | Update Clickhouse DBT to 1.1.8 | -| | 0.2.21 | 2022-09-09 | [\#15833](https://github.com/airbytehq/airbyte/pull/15833/) | SSH Tunnel: allow using OPENSSH key format (published in [\#16545](https://github.com/airbytehq/airbyte/pull/16545)) | -| | 0.2.20 | 2022-08-30 | [\#15592](https://github.com/airbytehq/airbyte/pull/15592) | Add TiDB support | -| | 0.2.19 | 2022-08-21 | [\#14897](https://github.com/airbytehq/airbyte/pull/14897) | Update Clickhouse DBT to 1.1.7 | -| | 0.2.16 | 2022-08-04 | [\#14295](https://github.com/airbytehq/airbyte/pull/14295) | Fixed SSH tunnel port usage | -| | 0.2.14 | 2022-08-01 | [\#14790](https://github.com/airbytehq/airbyte/pull/14790) | Add and persist job failures for Normalization | -| | 0.2.13 | 2022-07-27 | [\#14683](https://github.com/airbytehq/airbyte/pull/14683) | Quote schema name to allow reserved keywords | -| | 0.2.12 | 2022-07-26 | [\#14362](https://github.com/airbytehq/airbyte/pull/14362) | Handle timezone in date-time format. Parse date correct in clickhouse. | -| | 0.2.11 | 2022-07-26 | [\#13591](https://github.com/airbytehq/airbyte/pull/13591) | Updated support for integer columns. | -| | 0.2.10 | 2022-07-18 | [\#14792](https://github.com/airbytehq/airbyte/pull/14792) | Add support for key pair auth for snowflake | -| | 0.2.9 | 2022-07-06 | [\#14485](https://github.com/airbytehq/airbyte/pull/14485) | BigQuery partition pruning otimization | -| | 0.2.8 | 2022-07-13 | [\#14522](https://github.com/airbytehq/airbyte/pull/14522) | BigQuery replaces `NULL` array entries with the string value `"NULL"` | -| | 0.2.7 | 2022-07-05 | [\#11694](https://github.com/airbytehq/airbyte/pull/11694) | Do not return NULL for MySQL column values > 512 chars | -| | 0.2.6 | 2022-06-16 | [\#13894](https://github.com/airbytehq/airbyte/pull/13894) | Fix incorrect jinja2 macro `json_extract_array` call | -| | 0.2.5 | 2022-06-15 | [\#11470](https://github.com/airbytehq/airbyte/pull/11470) | Upgrade MySQL to dbt 1.0.0 | -| | 0.2.4 | 2022-06-14 | [\#12846](https://github.com/airbytehq/airbyte/pull/12846) | CDC correctly deletes propagates deletions to final tables | -| | 0.2.3 | 2022-06-10 | [\#11204](https://github.com/airbytehq/airbyte/pull/11204) | MySQL: add support for SSh tunneling | -| | 0.2.2 | 2022-06-02 | [\#13289](https://github.com/airbytehq/airbyte/pull/13289) | BigQuery use `json_extract_string_array` for array of simple type elements | -| | 0.2.1 | 2022-05-17 | [\#12924](https://github.com/airbytehq/airbyte/pull/12924) | Fixed checking --event-buffer-size on old dbt crashed entrypoint.sh | -| | 0.2.0 | 2022-05-15 | [\#12745](https://github.com/airbytehq/airbyte/pull/12745) | Snowflake: add datetime without timezone | -| | 0.1.78 | 2022-05-06 | [\#12305](https://github.com/airbytehq/airbyte/pull/12305) | Mssql: use NVARCHAR and datetime2 by default | -| 0.36.2-alpha | 0.1.77 | 2022-04-19 | [\#12064](https://github.com/airbytehq/airbyte/pull/12064) | Add support redshift SUPER type | -| 0.35.65-alpha | 0.1.75 | 2022-04-09 | [\#11511](https://github.com/airbytehq/airbyte/pull/11511) | Move DBT modules from `/tmp/dbt_modules` to `/dbt` | -| 0.35.61-alpha | 0.1.74 | 2022-03-24 | [\#10905](https://github.com/airbytehq/airbyte/pull/10905) | Update clickhouse dbt version | -| 0.35.60-alpha | 0.1.73 | 2022-03-25 | [\#11267](https://github.com/airbytehq/airbyte/pull/11267) | Set `--event-buffer-size` to reduce memory usage | -| 0.35.59-alpha | 0.1.72 | 2022-03-24 | [\#11093](https://github.com/airbytehq/airbyte/pull/11093) | Added Snowflake OAuth2.0 support | -| 0.35.53-alpha | 0.1.71 | 2022-03-14 | [\#11077](https://github.com/airbytehq/airbyte/pull/11077) | Enable BigQuery to handle project ID embedded inside dataset ID | -| 0.35.49-alpha | 0.1.70 | 2022-03-11 | [\#11051](https://github.com/airbytehq/airbyte/pull/11051) | Upgrade dbt to 1.0.0 (except for MySQL and Oracle) | -| 0.35.45-alpha | 0.1.69 | 2022-03-04 | [\#10754](https://github.com/airbytehq/airbyte/pull/10754) | Enable Clickhouse normalization over SSL | -| 0.35.32-alpha | 0.1.68 | 2022-02-20 | [\#10485](https://github.com/airbytehq/airbyte/pull/10485) | Fix row size too large for table with numerous `string` fields | -| | 0.1.66 | 2022-02-04 | [\#9341](https://github.com/airbytehq/airbyte/pull/9341) | Fix normalization for bigquery datasetId and tables | -| 0.35.13-alpha | 0.1.65 | 2021-01-28 | [\#9846](https://github.com/airbytehq/airbyte/pull/9846) | Tweak dbt multi-thread parameter down | -| 0.35.12-alpha | 0.1.64 | 2021-01-28 | [\#9793](https://github.com/airbytehq/airbyte/pull/9793) | Support PEM format for ssh-tunnel keys | -| 0.35.4-alpha | 0.1.63 | 2021-01-07 | [\#9301](https://github.com/airbytehq/airbyte/pull/9301) | Fix Snowflake prefix tables starting with numbers | -| | 0.1.62 | 2021-01-07 | [\#9340](https://github.com/airbytehq/airbyte/pull/9340) | Use TCP-port support for clickhouse | -| | 0.1.62 | 2021-01-07 | [\#9063](https://github.com/airbytehq/airbyte/pull/9063) | Change Snowflake-specific materialization settings | -| | 0.1.62 | 2021-01-07 | [\#9317](https://github.com/airbytehq/airbyte/pull/9317) | Fix issue with quoted & case sensitive columns | -| | 0.1.62 | 2021-01-07 | [\#9281](https://github.com/airbytehq/airbyte/pull/9281) | Fix SCD partition by float columns in BigQuery | -| 0.32.11-alpha | 0.1.61 | 2021-12-02 | [\#8394](https://github.com/airbytehq/airbyte/pull/8394) | Fix incremental queries not updating empty tables | -| | 0.1.61 | 2021-12-01 | [\#8378](https://github.com/airbytehq/airbyte/pull/8378) | Fix un-nesting queries and add proper ref hints | -| 0.32.5-alpha | 0.1.60 | 2021-11-22 | [\#8088](https://github.com/airbytehq/airbyte/pull/8088) | Speed-up incremental queries for SCD table on Snowflake | -| 0.30.32-alpha | 0.1.59 | 2021-11-08 | [\#7669](https://github.com/airbytehq/airbyte/pull/7169) | Fix nested incremental dbt | -| 0.30.24-alpha | 0.1.57 | 2021-10-26 | [\#7162](https://github.com/airbytehq/airbyte/pull/7162) | Implement incremental dbt updates | -| 0.30.16-alpha | 0.1.52 | 2021-10-07 | [\#6379](https://github.com/airbytehq/airbyte/pull/6379) | Handle empty string for date and date-time format | -| | 0.1.51 | 2021-10-08 | [\#6799](https://github.com/airbytehq/airbyte/pull/6799) | Added support for ad\_cdc\_log\_pos while normalization | -| | 0.1.50 | 2021-10-07 | [\#6079](https://github.com/airbytehq/airbyte/pull/6079) | Added support for MS SQL Server normalization | -| | 0.1.49 | 2021-10-06 | [\#6709](https://github.com/airbytehq/airbyte/pull/6709) | Forward destination dataset location to dbt profiles | -| 0.29.17-alpha | 0.1.47 | 2021-09-20 | [\#6317](https://github.com/airbytehq/airbyte/pull/6317) | MySQL: updated MySQL normalization with using SSH tunnel | -| | 0.1.45 | 2021-09-18 | [\#6052](https://github.com/airbytehq/airbyte/pull/6052) | Snowflake: accept any date-time format | -| 0.29.8-alpha | 0.1.40 | 2021-08-18 | [\#5433](https://github.com/airbytehq/airbyte/pull/5433) | Allow optional credentials\_json for BigQuery | -| 0.29.5-alpha | 0.1.39 | 2021-08-11 | [\#4557](https://github.com/airbytehq/airbyte/pull/4557) | Handle date times and solve conflict name btw stream/field | -| 0.28.2-alpha | 0.1.38 | 2021-07-28 | [\#5027](https://github.com/airbytehq/airbyte/pull/5027) | Handle quotes in column names when parsing JSON blob | -| 0.27.5-alpha | 0.1.37 | 2021-07-22 | [\#3947](https://github.com/airbytehq/airbyte/pull/4881/) | Handle `NULL` cursor field values when deduping | -| 0.27.2-alpha | 0.1.36 | 2021-07-09 | [\#3947](https://github.com/airbytehq/airbyte/pull/4163/) | Enable normalization for MySQL destination | +| | 0.2.22 | 2022-09-05 | [\#16339](https://github.com/airbytehq/airbyte/pull/16339) | Update Clickhouse DBT to 1.1.8 | +| | 0.2.21 | 2022-09-09 | [\#15833](https://github.com/airbytehq/airbyte/pull/15833/) | SSH Tunnel: allow using OPENSSH key format (published in [\#16545](https://github.com/airbytehq/airbyte/pull/16545)) | +| | 0.2.20 | 2022-08-30 | [\#15592](https://github.com/airbytehq/airbyte/pull/15592) | Add TiDB support | +| | 0.2.19 | 2022-08-21 | [\#14897](https://github.com/airbytehq/airbyte/pull/14897) | Update Clickhouse DBT to 1.1.7 | +| | 0.2.16 | 2022-08-04 | [\#14295](https://github.com/airbytehq/airbyte/pull/14295) | Fixed SSH tunnel port usage | +| | 0.2.14 | 2022-08-01 | [\#14790](https://github.com/airbytehq/airbyte/pull/14790) | Add and persist job failures for Normalization | +| | 0.2.13 | 2022-07-27 | [\#14683](https://github.com/airbytehq/airbyte/pull/14683) | Quote schema name to allow reserved keywords | +| | 0.2.12 | 2022-07-26 | [\#14362](https://github.com/airbytehq/airbyte/pull/14362) | Handle timezone in date-time format. Parse date correct in clickhouse. | +| | 0.2.11 | 2022-07-26 | [\#13591](https://github.com/airbytehq/airbyte/pull/13591) | Updated support for integer columns. | +| | 0.2.10 | 2022-07-18 | [\#14792](https://github.com/airbytehq/airbyte/pull/14792) | Add support for key pair auth for snowflake | +| | 0.2.9 | 2022-07-06 | [\#14485](https://github.com/airbytehq/airbyte/pull/14485) | BigQuery partition pruning otimization | +| | 0.2.8 | 2022-07-13 | [\#14522](https://github.com/airbytehq/airbyte/pull/14522) | BigQuery replaces `NULL` array entries with the string value `"NULL"` | +| | 0.2.7 | 2022-07-05 | [\#11694](https://github.com/airbytehq/airbyte/pull/11694) | Do not return NULL for MySQL column values > 512 chars | +| | 0.2.6 | 2022-06-16 | [\#13894](https://github.com/airbytehq/airbyte/pull/13894) | Fix incorrect jinja2 macro `json_extract_array` call | +| | 0.2.5 | 2022-06-15 | [\#11470](https://github.com/airbytehq/airbyte/pull/11470) | Upgrade MySQL to dbt 1.0.0 | +| | 0.2.4 | 2022-06-14 | [\#12846](https://github.com/airbytehq/airbyte/pull/12846) | CDC correctly deletes propagates deletions to final tables | +| | 0.2.3 | 2022-06-10 | [\#11204](https://github.com/airbytehq/airbyte/pull/11204) | MySQL: add support for SSh tunneling | +| | 0.2.2 | 2022-06-02 | [\#13289](https://github.com/airbytehq/airbyte/pull/13289) | BigQuery use `json_extract_string_array` for array of simple type elements | +| | 0.2.1 | 2022-05-17 | [\#12924](https://github.com/airbytehq/airbyte/pull/12924) | Fixed checking --event-buffer-size on old dbt crashed entrypoint.sh | +| | 0.2.0 | 2022-05-15 | [\#12745](https://github.com/airbytehq/airbyte/pull/12745) | Snowflake: add datetime without timezone | +| | 0.1.78 | 2022-05-06 | [\#12305](https://github.com/airbytehq/airbyte/pull/12305) | Mssql: use NVARCHAR and datetime2 by default | +| 0.36.2-alpha | 0.1.77 | 2022-04-19 | [\#12064](https://github.com/airbytehq/airbyte/pull/12064) | Add support redshift SUPER type | +| 0.35.65-alpha | 0.1.75 | 2022-04-09 | [\#11511](https://github.com/airbytehq/airbyte/pull/11511) | Move DBT modules from `/tmp/dbt_modules` to `/dbt` | +| 0.35.61-alpha | 0.1.74 | 2022-03-24 | [\#10905](https://github.com/airbytehq/airbyte/pull/10905) | Update clickhouse dbt version | +| 0.35.60-alpha | 0.1.73 | 2022-03-25 | [\#11267](https://github.com/airbytehq/airbyte/pull/11267) | Set `--event-buffer-size` to reduce memory usage | +| 0.35.59-alpha | 0.1.72 | 2022-03-24 | [\#11093](https://github.com/airbytehq/airbyte/pull/11093) | Added Snowflake OAuth2.0 support | +| 0.35.53-alpha | 0.1.71 | 2022-03-14 | [\#11077](https://github.com/airbytehq/airbyte/pull/11077) | Enable BigQuery to handle project ID embedded inside dataset ID | +| 0.35.49-alpha | 0.1.70 | 2022-03-11 | [\#11051](https://github.com/airbytehq/airbyte/pull/11051) | Upgrade dbt to 1.0.0 (except for MySQL and Oracle) | +| 0.35.45-alpha | 0.1.69 | 2022-03-04 | [\#10754](https://github.com/airbytehq/airbyte/pull/10754) | Enable Clickhouse normalization over SSL | +| 0.35.32-alpha | 0.1.68 | 2022-02-20 | [\#10485](https://github.com/airbytehq/airbyte/pull/10485) | Fix row size too large for table with numerous `string` fields | +| | 0.1.66 | 2022-02-04 | [\#9341](https://github.com/airbytehq/airbyte/pull/9341) | Fix normalization for bigquery datasetId and tables | +| 0.35.13-alpha | 0.1.65 | 2021-01-28 | [\#9846](https://github.com/airbytehq/airbyte/pull/9846) | Tweak dbt multi-thread parameter down | +| 0.35.12-alpha | 0.1.64 | 2021-01-28 | [\#9793](https://github.com/airbytehq/airbyte/pull/9793) | Support PEM format for ssh-tunnel keys | +| 0.35.4-alpha | 0.1.63 | 2021-01-07 | [\#9301](https://github.com/airbytehq/airbyte/pull/9301) | Fix Snowflake prefix tables starting with numbers | +| | 0.1.62 | 2021-01-07 | [\#9340](https://github.com/airbytehq/airbyte/pull/9340) | Use TCP-port support for clickhouse | +| | 0.1.62 | 2021-01-07 | [\#9063](https://github.com/airbytehq/airbyte/pull/9063) | Change Snowflake-specific materialization settings | +| | 0.1.62 | 2021-01-07 | [\#9317](https://github.com/airbytehq/airbyte/pull/9317) | Fix issue with quoted & case sensitive columns | +| | 0.1.62 | 2021-01-07 | [\#9281](https://github.com/airbytehq/airbyte/pull/9281) | Fix SCD partition by float columns in BigQuery | +| 0.32.11-alpha | 0.1.61 | 2021-12-02 | [\#8394](https://github.com/airbytehq/airbyte/pull/8394) | Fix incremental queries not updating empty tables | +| | 0.1.61 | 2021-12-01 | [\#8378](https://github.com/airbytehq/airbyte/pull/8378) | Fix un-nesting queries and add proper ref hints | +| 0.32.5-alpha | 0.1.60 | 2021-11-22 | [\#8088](https://github.com/airbytehq/airbyte/pull/8088) | Speed-up incremental queries for SCD table on Snowflake | +| 0.30.32-alpha | 0.1.59 | 2021-11-08 | [\#7669](https://github.com/airbytehq/airbyte/pull/7169) | Fix nested incremental dbt | +| 0.30.24-alpha | 0.1.57 | 2021-10-26 | [\#7162](https://github.com/airbytehq/airbyte/pull/7162) | Implement incremental dbt updates | +| 0.30.16-alpha | 0.1.52 | 2021-10-07 | [\#6379](https://github.com/airbytehq/airbyte/pull/6379) | Handle empty string for date and date-time format | +| | 0.1.51 | 2021-10-08 | [\#6799](https://github.com/airbytehq/airbyte/pull/6799) | Added support for ad\_cdc\_log\_pos while normalization | +| | 0.1.50 | 2021-10-07 | [\#6079](https://github.com/airbytehq/airbyte/pull/6079) | Added support for MS SQL Server normalization | +| | 0.1.49 | 2021-10-06 | [\#6709](https://github.com/airbytehq/airbyte/pull/6709) | Forward destination dataset location to dbt profiles | +| 0.29.17-alpha | 0.1.47 | 2021-09-20 | [\#6317](https://github.com/airbytehq/airbyte/pull/6317) | MySQL: updated MySQL normalization with using SSH tunnel | +| | 0.1.45 | 2021-09-18 | [\#6052](https://github.com/airbytehq/airbyte/pull/6052) | Snowflake: accept any date-time format | +| 0.29.8-alpha | 0.1.40 | 2021-08-18 | [\#5433](https://github.com/airbytehq/airbyte/pull/5433) | Allow optional credentials\_json for BigQuery | +| 0.29.5-alpha | 0.1.39 | 2021-08-11 | [\#4557](https://github.com/airbytehq/airbyte/pull/4557) | Handle date times and solve conflict name btw stream/field | +| 0.28.2-alpha | 0.1.38 | 2021-07-28 | [\#5027](https://github.com/airbytehq/airbyte/pull/5027) | Handle quotes in column names when parsing JSON blob | +| 0.27.5-alpha | 0.1.37 | 2021-07-22 | [\#3947](https://github.com/airbytehq/airbyte/pull/4881/) | Handle `NULL` cursor field values when deduping | +| 0.27.2-alpha | 0.1.36 | 2021-07-09 | [\#3947](https://github.com/airbytehq/airbyte/pull/4163/) | Enable normalization for MySQL destination | From ebb912666ee77f7d6a60dac84506f6ddd3d89dba Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 1 Nov 2022 15:59:29 -0700 Subject: [PATCH 479/498] Remove ConfigPersistence usage from SecretsMigrator (#18747) --- .../io/airbyte/bootloader/BootloaderApp.java | 25 ++- .../io/airbyte/bootloader/SecretMigrator.java | 182 ++++++------------ .../airbyte/bootloader/BootloaderAppTest.java | 14 +- .../bootloader/SecretMigratorTest.java | 139 ++++--------- .../config/persistence/ConfigPersistence.java | 4 + 5 files changed, 126 insertions(+), 238 deletions(-) diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java index 8c508fe5b151..5ae47928ed6c 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java @@ -17,11 +17,15 @@ import io.airbyte.config.init.ApplyDefinitionsHelper; import io.airbyte.config.init.DefinitionsProvider; import io.airbyte.config.init.LocalDefinitionsProvider; +import io.airbyte.config.persistence.ConfigNotFoundException; import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; +import io.airbyte.config.persistence.SecretsRepositoryReader; +import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.config.persistence.split_secrets.SecretPersistence; +import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.DataSourceFactory; @@ -68,7 +72,7 @@ public class BootloaderApp { private final Runnable postLoadExecution; private final FeatureFlags featureFlags; private final SecretMigrator secretMigrator; - private ConfigPersistence configPersistence; + private ConfigRepository configRepository; private DefinitionsProvider localDefinitionsProvider; private Database configDatabase; private Database jobDatabase; @@ -128,9 +132,6 @@ public BootloaderApp(final Configs configs, postLoadExecution = () -> { try { - final ConfigRepository configRepository = - new ConfigRepository(configPersistence, configDatabase); - final ApplyDefinitionsHelper applyDefinitionsHelper = new ApplyDefinitionsHelper(configRepository, localDefinitionsProvider); applyDefinitionsHelper.apply(); @@ -141,7 +142,7 @@ public BootloaderApp(final Configs configs, } } LOGGER.info("Loaded seed data.."); - } catch (final IOException | JsonValidationException e) { + } catch (final IOException | JsonValidationException | ConfigNotFoundException e) { throw new RuntimeException(e); } }; @@ -173,9 +174,6 @@ public void load() throws Exception { runFlywayMigration(configs, configDbMigrator, jobDbMigrator); LOGGER.info("Ran Flyway migrations."); - final ConfigRepository configRepository = - new ConfigRepository(configPersistence, configDatabase); - createWorkspaceIfNoneExists(configRepository); LOGGER.info("Default workspace created."); @@ -219,7 +217,7 @@ private static JobPersistence getJobPersistence(final Database jobDatabase) thro private void initPersistences(final DSLContext configsDslContext, final DSLContext jobsDslContext) { try { configDatabase = getConfigDatabase(configsDslContext); - configPersistence = getConfigPersistence(configDatabase); + configRepository = new ConfigRepository(getConfigPersistence(configDatabase), configDatabase); localDefinitionsProvider = getLocalDefinitionsProvider(); jobDatabase = getJobDatabase(jobsDslContext); jobPersistence = getJobPersistence(jobDatabase); @@ -244,10 +242,17 @@ public static void main(final String[] args) throws Exception { // TODO Will be converted to an injected singleton during DI migration final Database configDatabase = getConfigDatabase(configsDslContext); final ConfigPersistence configPersistence = getConfigPersistence(configDatabase); + final ConfigRepository configRepository = new ConfigRepository(configPersistence, configDatabase); final Database jobDatabase = getJobDatabase(jobsDslContext); final JobPersistence jobPersistence = getJobPersistence(jobDatabase); + + final SecretsHydrator secretsHydrator = SecretPersistence.getSecretsHydrator(configsDslContext, configs); + final Optional secretPersistence = SecretPersistence.getLongLived(configsDslContext, configs); + final SecretsRepositoryReader secretsRepositoryReader = new SecretsRepositoryReader(configRepository, secretsHydrator); + final SecretsRepositoryWriter secretsRepositoryWriter = new SecretsRepositoryWriter(configRepository, secretPersistence, Optional.empty()); + final SecretMigrator secretMigrator = - new SecretMigrator(configPersistence, jobPersistence, SecretPersistence.getLongLived(configsDslContext, configs)); + new SecretMigrator(secretsRepositoryReader, secretsRepositoryWriter, configRepository, jobPersistence, secretPersistence); final Flyway configsFlyway = FlywayFactory.create(configsDataSource, BootloaderApp.class.getSimpleName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); final Flyway jobsFlyway = FlywayFactory.create(jobsDataSource, BootloaderApp.class.getSimpleName(), JobsDatabaseMigrator.DB_IDENTIFIER, diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/SecretMigrator.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/SecretMigrator.java index 69e966cd5c22..456e85d38944 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/SecretMigrator.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/SecretMigrator.java @@ -4,30 +4,27 @@ package io.airbyte.bootloader; -import static io.airbyte.config.persistence.split_secrets.SecretsHelpers.COORDINATE_FIELD; - import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; -import io.airbyte.commons.json.JsonPaths; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.persistence.ConfigPersistence; -import io.airbyte.config.persistence.split_secrets.SecretCoordinate; +import io.airbyte.config.persistence.ConfigNotFoundException; +import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.config.persistence.SecretsRepositoryReader; +import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.split_secrets.SecretPersistence; -import io.airbyte.config.persistence.split_secrets.SecretsHelpers; import io.airbyte.persistence.job.JobPersistence; +import io.airbyte.protocol.models.ConnectorSpecification; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.Value; @@ -37,7 +34,9 @@ @Slf4j public class SecretMigrator { - private final ConfigPersistence configPersistence; + private final SecretsRepositoryReader secretsReader; + private final SecretsRepositoryWriter secretsWriter; + private final ConfigRepository configRepository; private final JobPersistence jobPersistence; private final Optional secretPersistence; @@ -55,34 +54,39 @@ static class ConnectorConfiguration { * Then for all the secret that are stored in a plain text format, it will save the plain text in * the secret manager and store the coordinate in the config DB. */ - public void migrateSecrets() throws JsonValidationException, IOException { + public void migrateSecrets() throws JsonValidationException, IOException, ConfigNotFoundException { if (secretPersistence.isEmpty()) { log.info("No secret persistence is provided, the migration won't be run "); return; } - final List standardSourceDefinitions = - configPersistence.listConfigs(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class); + final List standardSourceDefinitions = configRepository.listStandardSourceDefinitions(true); - final Map definitionIdToSourceSpecs = standardSourceDefinitions - .stream().collect(Collectors.toMap(StandardSourceDefinition::getSourceDefinitionId, - def -> def.getSpec().getConnectionSpecification())); + final Map definitionIdToSourceSpecs = standardSourceDefinitions + .stream().collect(Collectors.toMap(StandardSourceDefinition::getSourceDefinitionId, StandardSourceDefinition::getSpec)); - final List sources = configPersistence.listConfigs(ConfigSchema.SOURCE_CONNECTION, SourceConnection.class); + final List sourcesWithoutSecrets = configRepository.listSourceConnection(); + final List sourcesWithSecrets = new ArrayList<>(); + for (final SourceConnection source : sourcesWithoutSecrets) { + final SourceConnection sourceWithSecrets = secretsReader.getSourceConnectionWithSecrets(source.getSourceId()); + sourcesWithSecrets.add(sourceWithSecrets); + } - migrateSources(sources, definitionIdToSourceSpecs); + migrateSources(sourcesWithSecrets, definitionIdToSourceSpecs); - final List standardDestinationDefinitions = - configPersistence.listConfigs(ConfigSchema.STANDARD_DESTINATION_DEFINITION, - StandardDestinationDefinition.class); + final List standardDestinationDefinitions = configRepository.listStandardDestinationDefinitions(true); - final Map definitionIdToDestinationSpecs = standardDestinationDefinitions.stream() - .collect(Collectors.toMap(StandardDestinationDefinition::getDestinationDefinitionId, - def -> def.getSpec().getConnectionSpecification())); + final Map definitionIdToDestinationSpecs = standardDestinationDefinitions.stream() + .collect(Collectors.toMap(StandardDestinationDefinition::getDestinationDefinitionId, StandardDestinationDefinition::getSpec)); - final List destinations = configPersistence.listConfigs(ConfigSchema.DESTINATION_CONNECTION, DestinationConnection.class); + final List destinationsWithoutSecrets = configRepository.listDestinationConnection(); + final List destinationsWithSecrets = new ArrayList<>(); + for (final DestinationConnection destination : destinationsWithoutSecrets) { + final DestinationConnection destinationWithoutSecrets = secretsReader.getDestinationConnectionWithSecrets(destination.getDestinationId()); + destinationsWithSecrets.add(destinationWithoutSecrets); + } - migrateDestinations(destinations, definitionIdToDestinationSpecs); + migrateDestinations(destinationsWithSecrets, definitionIdToDestinationSpecs); jobPersistence.setSecretMigrationDone(); } @@ -91,23 +95,21 @@ public void migrateSecrets() throws JsonValidationException, IOException { * This is migrating the secrets for the source actors */ @VisibleForTesting - void migrateSources(final List sources, final Map definitionIdToSourceSpecs) + void migrateSources(final List sources, final Map definitionIdToSourceSpecs) throws JsonValidationException, IOException { log.info("Migrating Sources"); - final List sourceConnections = sources.stream() - .map(source -> { - final JsonNode migratedConfig = migrateConfiguration(new ConnectorConfiguration( - source.getWorkspaceId(), - source.getConfiguration(), - definitionIdToSourceSpecs.get(source.getSourceDefinitionId())), - () -> UUID.randomUUID()); - source.setConfiguration(migratedConfig); - return source; - }) - .toList(); - - for (final SourceConnection source : sourceConnections) { - configPersistence.writeConfig(ConfigSchema.SOURCE_CONNECTION, source.getSourceId().toString(), source); + for (final SourceConnection source : sources) { + final Optional specOptional = Optional.ofNullable(definitionIdToSourceSpecs.get(source.getSourceDefinitionId())); + + if (specOptional.isPresent()) { + secretsWriter.writeSourceConnection(source, specOptional.get()); + } else { + // if the spec can't be found, don't risk writing secrets to db. wipe out the configuration for the + // connector. + final SourceConnection sourceWithConfigRemoved = Jsons.clone(source); + sourceWithConfigRemoved.setConfiguration(Jsons.emptyObject()); + secretsWriter.writeSourceConnection(sourceWithConfigRemoved, new ConnectorSpecification().withConnectionSpecification(Jsons.emptyObject())); + } } } @@ -115,96 +117,24 @@ void migrateSources(final List sources, final Map destinations, final Map definitionIdToDestinationSpecs) + void migrateDestinations(final List destinations, final Map definitionIdToDestinationSpecs) throws JsonValidationException, IOException { log.info("Migration Destinations"); + for (final DestinationConnection destination : destinations) { + final Optional specOptional = + Optional.ofNullable(definitionIdToDestinationSpecs.get(destination.getDestinationDefinitionId())); - final List destinationConnections = destinations.stream().map(destination -> { - final JsonNode migratedConfig = migrateConfiguration(new ConnectorConfiguration( - destination.getWorkspaceId(), - destination.getConfiguration(), - definitionIdToDestinationSpecs.get(destination.getDestinationDefinitionId())), - () -> UUID.randomUUID()); - destination.setConfiguration(migratedConfig); - return destination; - }) - .toList(); - for (final DestinationConnection destination : destinationConnections) { - configPersistence.writeConfig(ConfigSchema.DESTINATION_CONNECTION, destination.getDestinationId().toString(), destination); - } - } - - /** - * This is a generic method to migrate an actor configuration It will extract the secret path form - * the provided spec and then replace them by coordinates in the actor configuration - */ - @VisibleForTesting - JsonNode migrateConfiguration(final ConnectorConfiguration connectorConfiguration, final Supplier uuidProvider) { - if (connectorConfiguration.getSpec() == null) { - throw new IllegalStateException("No connector definition to match the connector"); - } - - final AtomicReference connectorConfigurationJson = new AtomicReference<>(connectorConfiguration.getConfiguration()); - final List uniqSecretPaths = getSecretPath(connectorConfiguration.getSpec()) - .stream() - .flatMap(secretPath -> getAllExplodedPath(connectorConfigurationJson.get(), secretPath).stream()) - .toList(); - - final UUID workspaceId = connectorConfiguration.getWorkspace(); - uniqSecretPaths.forEach(secretPath -> { - final Optional secretValue = getValueForPath(connectorConfigurationJson.get(), secretPath); - if (secretValue.isEmpty()) { - throw new IllegalStateException("Missing secret for the path: " + secretPath); - } - - // Only migrate plain text. - if (secretValue.get().isTextual()) { - final JsonNode stringSecretValue = secretValue.get(); - - final SecretCoordinate coordinate = - new SecretCoordinate(SecretsHelpers.getCoordinatorBase("airbyte_workspace_", workspaceId, uuidProvider), 1); - secretPersistence.get().write(coordinate, stringSecretValue.textValue()); - connectorConfigurationJson.set(replaceAtJsonNode(connectorConfigurationJson.get(), secretPath, - Jsons.jsonNode(Map.of(COORDINATE_FIELD, coordinate.getFullCoordinate())))); + if (specOptional.isPresent()) { + secretsWriter.writeDestinationConnection(destination, specOptional.get()); } else { - log.error("Not migrating already migrated secrets"); + // if the spec can't be found, don't risk writing secrets to db. wipe out the configuration for the + // connector. + final DestinationConnection destinationWithConfigRemoved = Jsons.clone(destination); + destinationWithConfigRemoved.setConfiguration(Jsons.emptyObject()); + secretsWriter.writeDestinationConnection(destinationWithConfigRemoved, + new ConnectorSpecification().withConnectionSpecification(Jsons.emptyObject())); } - - }); - - return connectorConfigurationJson.get(); - } - - /** - * Wrapper to help to mock static methods - */ - @VisibleForTesting - JsonNode replaceAtJsonNode(final JsonNode connectorConfigurationJson, final String secretPath, final JsonNode replacement) { - return JsonPaths.replaceAtJsonNode(connectorConfigurationJson, secretPath, replacement); - } - - /** - * Wrapper to help to mock static methods - */ - @VisibleForTesting - List getSecretPath(final JsonNode specs) { - return SecretsHelpers.getSortedSecretPaths(specs); - } - - /** - * Wrapper to help to mock static methods - */ - @VisibleForTesting - List getAllExplodedPath(final JsonNode node, final String path) { - return JsonPaths.getPaths(node, path); - } - - /** - * Wrapper to help to mock static methods - */ - @VisibleForTesting - Optional getValueForPath(final JsonNode node, final String path) { - return JsonPaths.getSingleValue(node, path); + } } } diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index cf2630467ca4..95b3286c143a 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -30,7 +30,11 @@ import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; +import io.airbyte.config.persistence.SecretsRepositoryReader; +import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; +import io.airbyte.config.persistence.split_secrets.LocalTestingSecretPersistence; +import io.airbyte.config.persistence.split_secrets.RealSecretsHydrator; import io.airbyte.config.persistence.split_secrets.SecretPersistence; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.DataSourceFactory; @@ -185,10 +189,17 @@ void testBootloaderAppRunSecretMigration() throws Exception { val jobDatabase = new JobsDatabaseTestProvider(jobsDslContext, jobsFlyway).create(false); val configPersistence = new DatabaseConfigPersistence(configDatabase, jsonSecretsProcessor); + val configRepository = new ConfigRepository(configPersistence, configDatabase); val jobsPersistence = new DefaultJobPersistence(jobDatabase); + val secretsPersistence = SecretPersistence.getLongLived(configsDslContext, mockedConfigs); + final LocalTestingSecretPersistence localTestingSecretPersistence = new LocalTestingSecretPersistence(configDatabase); + + val secretsReader = new SecretsRepositoryReader(configRepository, new RealSecretsHydrator(localTestingSecretPersistence)); + val secretsWriter = new SecretsRepositoryWriter(configRepository, secretsPersistence, Optional.empty()); + val spiedSecretMigrator = - spy(new SecretMigrator(configPersistence, jobsPersistence, SecretPersistence.getLongLived(configsDslContext, mockedConfigs))); + spy(new SecretMigrator(secretsReader, secretsWriter, configRepository, jobsPersistence, secretsPersistence)); // Although we are able to inject mocked configs into the Bootloader, a particular migration in the // configs database requires the env var to be set. Flyway prevents injection, so we dynamically set @@ -202,7 +213,6 @@ void testBootloaderAppRunSecretMigration() throws Exception { initBootloader.load(); final DefinitionsProvider localDefinitions = new LocalDefinitionsProvider(LocalDefinitionsProvider.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); - final ConfigRepository configRepository = new ConfigRepository(configPersistence, configDatabase); final ConfigPersistence localConfigPersistence = new DefinitionProviderToConfigPersistenceAdapter(localDefinitions); configRepository.loadDataNoSecrets(localConfigPersistence); diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/SecretMigratorTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/SecretMigratorTest.java index 23accd8fca9f..b75668feaa8b 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/SecretMigratorTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/SecretMigratorTest.java @@ -4,17 +4,20 @@ package io.airbyte.bootloader; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; -import io.airbyte.bootloader.SecretMigrator.ConnectorConfiguration; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.persistence.ConfigPersistence; -import io.airbyte.config.persistence.split_secrets.SecretCoordinate; +import io.airbyte.config.persistence.ConfigNotFoundException; +import io.airbyte.config.persistence.ConfigRepository; +import io.airbyte.config.persistence.SecretsRepositoryReader; +import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.split_secrets.SecretPersistence; import io.airbyte.persistence.job.JobPersistence; import io.airbyte.protocol.models.ConnectorSpecification; @@ -25,7 +28,6 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -39,7 +41,13 @@ class SecretMigratorTest { private final UUID workspaceId = UUID.randomUUID(); @Mock - private ConfigPersistence configPersistence; + private ConfigRepository configRepository; + + @Mock + private SecretsRepositoryReader secretsReader; + + @Mock + private SecretsRepositoryWriter secretsWriter; @Mock private SecretPersistence secretPersistence; @@ -51,11 +59,11 @@ class SecretMigratorTest { @BeforeEach void setup() { - secretMigrator = Mockito.spy(new SecretMigrator(configPersistence, jobPersistence, Optional.of(secretPersistence))); + secretMigrator = Mockito.spy(new SecretMigrator(secretsReader, secretsWriter, configRepository, jobPersistence, Optional.of(secretPersistence))); } @Test - void testMigrateSecret() throws JsonValidationException, IOException { + void testMigrateSecret() throws JsonValidationException, IOException, ConfigNotFoundException { final JsonNode sourceSpec = Jsons.jsonNode("sourceSpec"); final UUID sourceDefinitionId = UUID.randomUUID(); final StandardSourceDefinition standardSourceDefinition = new StandardSourceDefinition() @@ -63,9 +71,9 @@ void testMigrateSecret() throws JsonValidationException, IOException { .withSpec( new ConnectorSpecification() .withConnectionSpecification(sourceSpec)); - final Map standardSourceDefinitions = new HashMap<>(); - standardSourceDefinitions.put(sourceDefinitionId, standardSourceDefinition.getSpec().getConnectionSpecification()); - Mockito.when(configPersistence.listConfigs(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class)) + final Map standardSourceDefinitions = new HashMap<>(); + standardSourceDefinitions.put(sourceDefinitionId, standardSourceDefinition.getSpec()); + when(configRepository.listStandardSourceDefinitions(true)) .thenReturn(Lists.newArrayList(standardSourceDefinition)); final JsonNode sourceConfiguration = Jsons.jsonNode("sourceConfiguration"); @@ -75,7 +83,7 @@ void testMigrateSecret() throws JsonValidationException, IOException { .withConfiguration(sourceConfiguration) .withWorkspaceId(workspaceId); final List sourceConnections = Lists.newArrayList(sourceConnection); - Mockito.when(configPersistence.listConfigs(ConfigSchema.SOURCE_CONNECTION, SourceConnection.class)) + when(configRepository.listSourceConnection()) .thenReturn(sourceConnections); final JsonNode destinationSpec = Jsons.jsonNode("destinationSpec"); @@ -85,9 +93,9 @@ void testMigrateSecret() throws JsonValidationException, IOException { .withSpec( new ConnectorSpecification() .withConnectionSpecification(destinationSpec)); - final Map standardDestinationDefinitions = new HashMap<>(); - standardDestinationDefinitions.put(destinationDefinitionId, standardDestinationDefinition.getSpec().getConnectionSpecification()); - Mockito.when(configPersistence.listConfigs(ConfigSchema.STANDARD_DESTINATION_DEFINITION, StandardDestinationDefinition.class)) + final Map standardDestinationDefinitions = new HashMap<>(); + standardDestinationDefinitions.put(destinationDefinitionId, standardDestinationDefinition.getSpec()); + when(configRepository.listStandardDestinationDefinitions(true)) .thenReturn(Lists.newArrayList(standardDestinationDefinition)); final JsonNode destinationConfiguration = Jsons.jsonNode("destinationConfiguration"); @@ -97,29 +105,19 @@ void testMigrateSecret() throws JsonValidationException, IOException { .withConfiguration(destinationConfiguration) .withWorkspaceId(workspaceId); final List destinationConnections = Lists.newArrayList(destinationConnection); - Mockito.when(configPersistence.listConfigs(ConfigSchema.DESTINATION_CONNECTION, DestinationConnection.class)) + when(configRepository.listDestinationConnection()) .thenReturn(destinationConnections); - // Mockito.doNothing().when(secretMigrator).migrateDestinations(Mockito.any(), Mockito.any()); - - final String path = "Mocked static call source"; - Mockito.doReturn(Lists.newArrayList(path)).when(secretMigrator).getSecretPath(sourceSpec); - Mockito.doReturn(Lists.newArrayList(path)).when(secretMigrator).getAllExplodedPath(sourceConfiguration, path); - final String sourceSecret = "sourceSecret"; - Mockito.doReturn(Optional.of(Jsons.jsonNode(sourceSecret))).when(secretMigrator).getValueForPath(sourceConfiguration, path); - Mockito.doReturn(Lists.newArrayList(path)).when(secretMigrator).getSecretPath(destinationSpec); - Mockito.doReturn(Lists.newArrayList(path)).when(secretMigrator).getAllExplodedPath(destinationConfiguration, path); - final String destinationSecret = "destinationSecret"; - Mockito.doReturn(Optional.of(Jsons.jsonNode(destinationSecret))).when(secretMigrator).getValueForPath(destinationConfiguration, path); + when(secretsReader.getSourceConnectionWithSecrets(sourceConnection.getSourceId())).thenReturn(sourceConnection); + when(secretsReader.getDestinationConnectionWithSecrets(destinationConnection.getDestinationId())).thenReturn(destinationConnection); - Mockito.doReturn(Jsons.jsonNode("sanitized")).when(secretMigrator).replaceAtJsonNode(Mockito.any(), Mockito.any(), Mockito.any()); secretMigrator.migrateSecrets(); Mockito.verify(secretMigrator).migrateSources(sourceConnections, standardSourceDefinitions); - Mockito.verify(secretPersistence).write(Mockito.any(), Mockito.eq(sourceSecret)); - secretPersistence.write(Mockito.any(), Mockito.any()); + Mockito.verify(secretsWriter).writeSourceConnection(sourceConnection, standardSourceDefinition.getSpec()); + secretPersistence.write(any(), any()); Mockito.verify(secretMigrator).migrateDestinations(destinationConnections, standardDestinationDefinitions); - Mockito.verify(secretPersistence).write(Mockito.any(), Mockito.eq(destinationSecret)); + Mockito.verify(secretsWriter).writeDestinationConnection(destinationConnection, standardDestinationDefinition.getSpec()); Mockito.verify(jobPersistence).setSecretMigrationDone(); } @@ -132,8 +130,8 @@ void testSourceMigration() throws JsonValidationException, IOException { final UUID sourceId2 = UUID.randomUUID(); final JsonNode sourceConfiguration1 = Jsons.jsonNode("conf1"); final JsonNode sourceConfiguration2 = Jsons.jsonNode("conf2"); - final JsonNode sourceDefinition1 = Jsons.jsonNode("def1"); - final JsonNode sourceDefinition2 = Jsons.jsonNode("def2"); + final ConnectorSpecification sourceDefinition1 = new ConnectorSpecification().withConnectionSpecification(Jsons.jsonNode("def1")); + final ConnectorSpecification sourceDefinition2 = new ConnectorSpecification().withConnectionSpecification(Jsons.jsonNode("def2")); final SourceConnection sourceConnection1 = new SourceConnection() .withSourceId(sourceId1) .withSourceDefinitionId(definitionId1) @@ -144,18 +142,14 @@ void testSourceMigration() throws JsonValidationException, IOException { .withConfiguration(sourceConfiguration2); final List sources = Lists.newArrayList(sourceConnection1, sourceConnection2); - final Map definitionIdToDestinationSpecs = new HashMap<>(); + final Map definitionIdToDestinationSpecs = new HashMap<>(); definitionIdToDestinationSpecs.put(definitionId1, sourceDefinition1); definitionIdToDestinationSpecs.put(definitionId2, sourceDefinition2); - Mockito.doReturn(Jsons.emptyObject()).when(secretMigrator).migrateConfiguration( - Mockito.any(), - Mockito.any()); - secretMigrator.migrateSources(sources, definitionIdToDestinationSpecs); - Mockito.verify(configPersistence).writeConfig(ConfigSchema.SOURCE_CONNECTION, sourceId1.toString(), sourceConnection1); - Mockito.verify(configPersistence).writeConfig(ConfigSchema.SOURCE_CONNECTION, sourceId2.toString(), sourceConnection2); + Mockito.verify(secretsWriter).writeSourceConnection(sourceConnection1, sourceDefinition1); + Mockito.verify(secretsWriter).writeSourceConnection(sourceConnection2, sourceDefinition2); } @Test @@ -166,8 +160,8 @@ void testDestinationMigration() throws JsonValidationException, IOException { final UUID destinationId2 = UUID.randomUUID(); final JsonNode destinationConfiguration1 = Jsons.jsonNode("conf1"); final JsonNode destinationConfiguration2 = Jsons.jsonNode("conf2"); - final JsonNode destinationDefinition1 = Jsons.jsonNode("def1"); - final JsonNode destinationDefinition2 = Jsons.jsonNode("def2"); + final ConnectorSpecification destinationDefinition1 = new ConnectorSpecification().withConnectionSpecification(Jsons.jsonNode("def1")); + final ConnectorSpecification destinationDefinition2 = new ConnectorSpecification().withConnectionSpecification(Jsons.jsonNode("def2")); final DestinationConnection destinationConnection1 = new DestinationConnection() .withDestinationId(destinationId1) .withDestinationDefinitionId(definitionId1) @@ -178,69 +172,14 @@ void testDestinationMigration() throws JsonValidationException, IOException { .withConfiguration(destinationConfiguration2); final List destinations = Lists.newArrayList(destinationConnection1, destinationConnection2); - final Map definitionIdToDestinationSpecs = new HashMap<>(); + final Map definitionIdToDestinationSpecs = new HashMap<>(); definitionIdToDestinationSpecs.put(definitionId1, destinationDefinition1); definitionIdToDestinationSpecs.put(definitionId2, destinationDefinition2); - Mockito.doReturn(Jsons.emptyObject()).when(secretMigrator).migrateConfiguration( - Mockito.any(), - Mockito.any()); - secretMigrator.migrateDestinations(destinations, definitionIdToDestinationSpecs); - Mockito.verify(configPersistence).writeConfig(ConfigSchema.DESTINATION_CONNECTION, destinationId1.toString(), destinationConnection1); - Mockito.verify(configPersistence).writeConfig(ConfigSchema.DESTINATION_CONNECTION, destinationId2.toString(), destinationConnection2); - } - - @Test - void testMigrateConfigurationWithoutSpecs() { - final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(null, null, null); - - Assertions.assertThrows(IllegalStateException.class, () -> secretMigrator.migrateConfiguration(connectorConfiguration, null)); - } - - @Test - void testMissingSecret() { - final List secretPathList = Lists.newArrayList("secretPath"); - - Mockito.doReturn(secretPathList).when(secretMigrator).getSecretPath(Mockito.any()); - Mockito.doReturn(secretPathList).when(secretMigrator).getAllExplodedPath(Mockito.any(), Mockito.eq(secretPathList.get(0))); - Mockito.doReturn(Optional.empty()).when(secretMigrator).getValueForPath(Mockito.any(), Mockito.eq(secretPathList.get(0))); - - final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(UUID.randomUUID(), Jsons.emptyObject(), Jsons.emptyObject()); - Assertions.assertThrows(IllegalStateException.class, () -> secretMigrator.migrateConfiguration(connectorConfiguration, () -> UUID.randomUUID())); - } - - @Test - void testMigrateConfiguration() { - final List secretPathList = Lists.newArrayList("$.secretPath"); - - Mockito.doReturn(secretPathList).when(secretMigrator).getSecretPath(Mockito.any()); - Mockito.doReturn(secretPathList).when(secretMigrator).getAllExplodedPath(Mockito.any(), Mockito.eq(secretPathList.get(0))); - Mockito.doReturn(Optional.of(Jsons.jsonNode(secretPathList.get(0)))).when(secretMigrator).getValueForPath(Mockito.any(), - Mockito.eq(secretPathList.get(0))); - - final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(UUID.randomUUID(), Jsons.emptyObject(), Jsons.emptyObject()); - - secretMigrator.migrateConfiguration(connectorConfiguration, () -> UUID.randomUUID()); - Mockito.verify(secretPersistence).write(Mockito.any(), Mockito.any()); - } - - @Test - void testMigrateConfigurationAlreadyInSecretManager() { - final List secretPathList = Lists.newArrayList("$.secretPath"); - - Mockito.doReturn(secretPathList).when(secretMigrator).getSecretPath(Mockito.any()); - Mockito.doReturn(secretPathList).when(secretMigrator).getAllExplodedPath(Mockito.any(), Mockito.eq(secretPathList.get(0))); - - final SecretCoordinate fakeCoordinate = new SecretCoordinate("fake", 1); - Mockito.doReturn(Optional.of(Jsons.jsonNode(fakeCoordinate))).when(secretMigrator).getValueForPath(Mockito.any(), - Mockito.eq(secretPathList.get(0))); - - final ConnectorConfiguration connectorConfiguration = new ConnectorConfiguration(UUID.randomUUID(), Jsons.emptyObject(), Jsons.emptyObject()); - - secretMigrator.migrateConfiguration(connectorConfiguration, () -> UUID.randomUUID()); - Mockito.verify(secretPersistence, Mockito.times(0)).write(Mockito.any(), Mockito.any()); + Mockito.verify(secretsWriter).writeDestinationConnection(destinationConnection1, destinationDefinition1); + Mockito.verify(secretsWriter).writeDestinationConnection(destinationConnection2, destinationDefinition2); } } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java index 6752e88be48a..d87c21fcebfc 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java @@ -13,6 +13,10 @@ import java.util.Map; import java.util.stream.Stream; +/** + * We are moving migrating away from this interface entirely. Use ConfigRepository instead. + */ +@Deprecated public interface ConfigPersistence { T getConfig(AirbyteConfig configType, String configId, Class clazz) throws ConfigNotFoundException, JsonValidationException, IOException; From c9988c4db87f581ef8ce331f07b47c1a0e83e1d2 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 1 Nov 2022 16:48:01 -0700 Subject: [PATCH 480/498] remove config persistence from seeding logic (#18749) --- .../airbyte/bootloader/BootloaderAppTest.java | 5 +- .../persistence/ActorDefinitionMigrator.java | 383 ++++++++++++++++++ .../ClassEnforcingConfigPersistence.java | 5 - .../config/persistence/ConfigPersistence.java | 2 - .../config/persistence/ConfigRepository.java | 43 +- .../DatabaseConfigPersistence.java | 12 +- .../ValidatingConfigPersistence.java | 5 - ...DatabaseConfigPersistenceLoadDataTest.java | 155 ------- .../config/init/ApplyDefinitionsHelper.java | 7 +- ...ionProviderToConfigPersistenceAdapter.java | 107 ----- .../init/ApplyDefinitionsHelperTest.java | 10 +- ...roviderToConfigPersistenceAdapterTest.java | 126 ------ 12 files changed, 415 insertions(+), 445 deletions(-) create mode 100644 airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ActorDefinitionMigrator.java delete mode 100644 airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceLoadDataTest.java delete mode 100644 airbyte-config/init/src/main/java/io/airbyte/config/init/DefinitionProviderToConfigPersistenceAdapter.java delete mode 100644 airbyte-config/init/src/test/java/io/airbyte/config/init/DefinitionsProviderToConfigPersistenceAdapterTest.java diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index 95b3286c143a..8d21d41fc128 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -24,10 +24,8 @@ import io.airbyte.config.Geography; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardWorkspace; -import io.airbyte.config.init.DefinitionProviderToConfigPersistenceAdapter; import io.airbyte.config.init.DefinitionsProvider; import io.airbyte.config.init.LocalDefinitionsProvider; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.SecretsRepositoryReader; @@ -213,8 +211,7 @@ void testBootloaderAppRunSecretMigration() throws Exception { initBootloader.load(); final DefinitionsProvider localDefinitions = new LocalDefinitionsProvider(LocalDefinitionsProvider.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); - final ConfigPersistence localConfigPersistence = new DefinitionProviderToConfigPersistenceAdapter(localDefinitions); - configRepository.loadDataNoSecrets(localConfigPersistence); + configRepository.seedActorDefinitions(localDefinitions.getSourceDefinitions(), localDefinitions.getDestinationDefinitions()); final String sourceSpecs = """ { diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ActorDefinitionMigrator.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ActorDefinitionMigrator.java new file mode 100644 index 000000000000..7e53abb01162 --- /dev/null +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ActorDefinitionMigrator.java @@ -0,0 +1,383 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.config.persistence; + +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR; +import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; +import static org.jooq.impl.DSL.asterisk; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Sets; +import io.airbyte.commons.enums.Enums; +import io.airbyte.commons.json.Jsons; +import io.airbyte.commons.util.MoreIterators; +import io.airbyte.commons.version.AirbyteProtocolVersion; +import io.airbyte.commons.version.AirbyteVersion; +import io.airbyte.config.AirbyteConfig; +import io.airbyte.config.ConfigSchema; +import io.airbyte.config.StandardDestinationDefinition; +import io.airbyte.config.StandardSourceDefinition; +import io.airbyte.config.StandardSourceDefinition.SourceType; +import io.airbyte.db.ExceptionWrappingDatabase; +import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; +import io.airbyte.db.instance.configs.jooq.generated.enums.ReleaseStage; +import io.airbyte.protocol.models.ConnectorSpecification; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jooq.DSLContext; +import org.jooq.Record1; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Takes in most up-to-date source and destination definitions from the Airbyte connector catalog + * and merges them with those already present in the database. See javadocs on methods for rules. + */ +public class ActorDefinitionMigrator { + + private static final Logger LOGGER = LoggerFactory.getLogger(ActorDefinitionMigrator.class); + + private static final String UNKNOWN_CONFIG_TYPE = "Unknown Config Type "; + + private final ExceptionWrappingDatabase database; + + public ActorDefinitionMigrator(final ExceptionWrappingDatabase database) { + this.database = database; + } + + public void migrate(final List latestSources, final List latestDestinations) + throws IOException { + database.transaction(ctx -> { + try { + updateConfigsFromSeed(ctx, latestSources, latestDestinations); + } catch (final IOException e) { + throw new SQLException(e); + } + return null; + }); + } + + @VisibleForTesting + void updateConfigsFromSeed(final DSLContext ctx, + final List latestSources, + final List latestDestinations) + throws IOException { + LOGGER.info("Updating connector definitions from the seed if necessary..."); + + final Set connectorRepositoriesInUse = getConnectorRepositoriesInUse(ctx); + LOGGER.info("Connectors in use: {}", connectorRepositoriesInUse); + + final Map connectorRepositoryToInfoMap = getConnectorRepositoryToInfoMap(ctx); + LOGGER.info("Current connector versions: {}", connectorRepositoryToInfoMap.values()); + + int newConnectorCount = 0; + int updatedConnectorCount = 0; + + final ConnectorCounter sourceConnectorCounter = updateConnectorDefinitions(ctx, ConfigSchema.STANDARD_SOURCE_DEFINITION, + latestSources, connectorRepositoriesInUse, connectorRepositoryToInfoMap); + newConnectorCount += sourceConnectorCounter.newCount; + updatedConnectorCount += sourceConnectorCounter.updateCount; + + final ConnectorCounter destinationConnectorCounter = updateConnectorDefinitions(ctx, ConfigSchema.STANDARD_DESTINATION_DEFINITION, + latestDestinations, connectorRepositoriesInUse, connectorRepositoryToInfoMap); + newConnectorCount += destinationConnectorCounter.newCount; + updatedConnectorCount += destinationConnectorCounter.updateCount; + + LOGGER.info("Connector definitions have been updated ({} new connectors, and {} updates)", newConnectorCount, updatedConnectorCount); + } + + /** + * @return A set of connectors (both source and destination) that are already used in standard + * syncs. We identify connectors by its repository name instead of definition id because + * connectors can be added manually by users, and their config ids are not always the same + * as those in the seed. + */ + private Set getConnectorRepositoriesInUse(final DSLContext ctx) { + final Set usedConnectorDefinitionIds = ctx + .select(ACTOR.ACTOR_DEFINITION_ID) + .from(ACTOR) + .fetch() + .stream() + .flatMap(row -> Stream.of(row.value1())) + .collect(Collectors.toSet()); + + return ctx.select(ACTOR_DEFINITION.DOCKER_REPOSITORY) + .from(ACTOR_DEFINITION) + .where(ACTOR_DEFINITION.ID.in(usedConnectorDefinitionIds)) + .fetch().stream() + .map(Record1::value1) + .collect(Collectors.toSet()); + } + + /** + * @return A map about current connectors (both source and destination). It maps from connector + * repository to its definition id and docker image tag. We identify a connector by its + * repository name instead of definition id because connectors can be added manually by + * users, and are not always the same as those in the seed. + */ + @VisibleForTesting + Map getConnectorRepositoryToInfoMap(final DSLContext ctx) { + return ctx.select(asterisk()) + .from(ACTOR_DEFINITION) + .where(ACTOR_DEFINITION.RELEASE_STAGE.isNull() + .or(ACTOR_DEFINITION.RELEASE_STAGE.ne(ReleaseStage.custom).or(ACTOR_DEFINITION.CUSTOM))) + .fetch() + .stream() + .collect(Collectors.toMap( + row -> row.getValue(ACTOR_DEFINITION.DOCKER_REPOSITORY), + row -> { + final JsonNode jsonNode; + if (row.get(ACTOR_DEFINITION.ACTOR_TYPE) == ActorType.source) { + jsonNode = Jsons.jsonNode(new StandardSourceDefinition() + .withSourceDefinitionId(row.get(ACTOR_DEFINITION.ID)) + .withDockerImageTag(row.get(ACTOR_DEFINITION.DOCKER_IMAGE_TAG)) + .withIcon(row.get(ACTOR_DEFINITION.ICON)) + .withDockerRepository(row.get(ACTOR_DEFINITION.DOCKER_REPOSITORY)) + .withDocumentationUrl(row.get(ACTOR_DEFINITION.DOCUMENTATION_URL)) + .withName(row.get(ACTOR_DEFINITION.NAME)) + .withPublic(row.get(ACTOR_DEFINITION.PUBLIC)) + .withCustom(row.get(ACTOR_DEFINITION.CUSTOM)) + .withSourceType(row.get(ACTOR_DEFINITION.SOURCE_TYPE) == null ? null + : Enums.toEnum(row.get(ACTOR_DEFINITION.SOURCE_TYPE, String.class), SourceType.class).orElseThrow()) + .withSpec(Jsons.deserialize(row.get(ACTOR_DEFINITION.SPEC).data(), ConnectorSpecification.class))); + } else if (row.get(ACTOR_DEFINITION.ACTOR_TYPE) == ActorType.destination) { + jsonNode = Jsons.jsonNode(new StandardDestinationDefinition() + .withDestinationDefinitionId(row.get(ACTOR_DEFINITION.ID)) + .withDockerImageTag(row.get(ACTOR_DEFINITION.DOCKER_IMAGE_TAG)) + .withIcon(row.get(ACTOR_DEFINITION.ICON)) + .withDockerRepository(row.get(ACTOR_DEFINITION.DOCKER_REPOSITORY)) + .withDocumentationUrl(row.get(ACTOR_DEFINITION.DOCUMENTATION_URL)) + .withName(row.get(ACTOR_DEFINITION.NAME)) + .withPublic(row.get(ACTOR_DEFINITION.PUBLIC)) + .withCustom(row.get(ACTOR_DEFINITION.CUSTOM)) + .withSpec(Jsons.deserialize(row.get(ACTOR_DEFINITION.SPEC).data(), ConnectorSpecification.class))); + } else { + throw new RuntimeException("Unknown Actor Type " + row.get(ACTOR_DEFINITION.ACTOR_TYPE)); + } + return new ConnectorInfo(row.getValue(ACTOR_DEFINITION.ID).toString(), jsonNode); + }, + (c1, c2) -> { + final AirbyteVersion v1 = new AirbyteVersion(c1.dockerImageTag); + final AirbyteVersion v2 = new AirbyteVersion(c2.dockerImageTag); + LOGGER.warn("Duplicated connector version found for {}: {} ({}) vs {} ({})", + c1.dockerRepository, c1.dockerImageTag, c1.definitionId, c2.dockerImageTag, c2.definitionId); + final int comparison = v1.patchVersionCompareTo(v2); + if (comparison >= 0) { + return c1; + } else { + return c2; + } + })); + } + + /** + * The custom connector are not present in the seed and thus it is not relevant to validate their + * latest version. This method allows to filter them out. + * + * @param connectorRepositoryToIdVersionMap + * @param configType + * @return + */ + @VisibleForTesting + Map filterCustomConnector(final Map connectorRepositoryToIdVersionMap, + final AirbyteConfig configType) { + return connectorRepositoryToIdVersionMap.entrySet().stream() + // The validation is based on the of the connector name is based on the seed which doesn't contain + // any custom connectors. They can thus be + // filtered out. + .filter(entry -> { + if (configType == ConfigSchema.STANDARD_SOURCE_DEFINITION) { + return !Jsons.object(entry.getValue().definition, StandardSourceDefinition.class).getCustom(); + } else if (configType == ConfigSchema.STANDARD_DESTINATION_DEFINITION) { + return !Jsons.object(entry.getValue().definition, StandardDestinationDefinition.class).getCustom(); + } else { + return true; + } + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + /** + * @param connectorRepositoriesInUse when a connector is used in any standard sync, its definition + * will not be updated. This is necessary because the new connector version may not be + * backward compatible. + */ + @VisibleForTesting + ConnectorCounter updateConnectorDefinitions(final DSLContext ctx, + final AirbyteConfig configType, + final List latestDefinitions, + final Set connectorRepositoriesInUse, + final Map connectorRepositoryToIdVersionMap) + throws IOException { + int newCount = 0; + int updatedCount = 0; + + for (final T definition : latestDefinitions) { + final JsonNode latestDefinition = Jsons.jsonNode(definition); + final String repository = latestDefinition.get("dockerRepository").asText(); + + final Map connectorRepositoryToIdVersionMapWithoutCustom = filterCustomConnector(connectorRepositoryToIdVersionMap, + configType); + + // Add new connector + if (!connectorRepositoryToIdVersionMapWithoutCustom.containsKey(repository)) { + LOGGER.info("Adding new connector {}: {}", repository, latestDefinition); + writeOrUpdateStandardDefinition(ctx, configType, latestDefinition); + newCount++; + continue; + } + + final ConnectorInfo connectorInfo = connectorRepositoryToIdVersionMapWithoutCustom.get(repository); + final JsonNode currentDefinition = connectorInfo.definition; + + // todo (lmossman) - this logic to remove the "spec" field is temporary; it is necessary to avoid + // breaking users who are actively using an old connector version, otherwise specs from the most + // recent connector versions may be inserted into the db which could be incompatible with the + // version they are actually using. + // Once the faux major version bump has been merged, this "new field" logic will be removed + // entirely. + final Set newFields = Sets.difference(getNewFields(currentDefinition, latestDefinition), Set.of("spec")); + + // Process connector in use + if (connectorRepositoriesInUse.contains(repository)) { + final String latestImageTag = latestDefinition.get("dockerImageTag").asText(); + if (hasNewPatchVersion(connectorInfo.dockerImageTag, latestImageTag)) { + // Update connector to the latest patch version + LOGGER.info("Connector {} needs update: {} vs {}", repository, connectorInfo.dockerImageTag, latestImageTag); + writeOrUpdateStandardDefinition(ctx, configType, latestDefinition); + updatedCount++; + } else if (newFields.isEmpty()) { + LOGGER.info("Connector {} is in use and has all fields; skip updating", repository); + } else { + // Add new fields to the connector definition + final JsonNode definitionToUpdate = getDefinitionWithNewFields(currentDefinition, latestDefinition, newFields); + LOGGER.info("Connector {} has new fields: {}", repository, String.join(", ", newFields)); + writeOrUpdateStandardDefinition(ctx, configType, definitionToUpdate); + updatedCount++; + } + continue; + } + + // Process unused connector + final String latestImageTag = latestDefinition.get("dockerImageTag").asText(); + if (hasNewVersion(connectorInfo.dockerImageTag, latestImageTag)) { + // Update connector to the latest version + LOGGER.info("Connector {} needs update: {} vs {}", repository, connectorInfo.dockerImageTag, latestImageTag); + writeOrUpdateStandardDefinition(ctx, configType, latestDefinition); + updatedCount++; + } else if (!newFields.isEmpty()) { + // Add new fields to the connector definition + final JsonNode definitionToUpdate = getDefinitionWithNewFields(currentDefinition, latestDefinition, newFields); + LOGGER.info("Connector {} has new fields: {}", repository, String.join(", ", newFields)); + writeOrUpdateStandardDefinition(ctx, configType, definitionToUpdate); + updatedCount++; + } else { + LOGGER.info("Connector {} does not need update: {}", repository, connectorInfo.dockerImageTag); + } + } + + return new ConnectorCounter(newCount, updatedCount); + } + + private void writeOrUpdateStandardDefinition(final DSLContext ctx, + final AirbyteConfig configType, + final JsonNode definition) { + if (configType == ConfigSchema.STANDARD_SOURCE_DEFINITION) { + final StandardSourceDefinition sourceDef = Jsons.object(definition, StandardSourceDefinition.class); + sourceDef.withProtocolVersion(getProtocolVersion(sourceDef.getSpec())); + ConfigWriter.writeStandardSourceDefinition(Collections.singletonList(sourceDef), ctx); + } else if (configType == ConfigSchema.STANDARD_DESTINATION_DEFINITION) { + final StandardDestinationDefinition destDef = Jsons.object(definition, StandardDestinationDefinition.class); + destDef.withProtocolVersion(getProtocolVersion(destDef.getSpec())); + ConfigWriter.writeStandardDestinationDefinition(Collections.singletonList(destDef), ctx); + } else { + throw new IllegalArgumentException(UNKNOWN_CONFIG_TYPE + configType); + } + } + + private static String getProtocolVersion(final ConnectorSpecification spec) { + return AirbyteProtocolVersion.getWithDefault(spec != null ? spec.getProtocolVersion() : null).serialize(); + } + + @VisibleForTesting + static Set getNewFields(final JsonNode currentDefinition, final JsonNode latestDefinition) { + final Set currentFields = MoreIterators.toSet(currentDefinition.fieldNames()); + final Set latestFields = MoreIterators.toSet(latestDefinition.fieldNames()); + return Sets.difference(latestFields, currentFields); + } + + /** + * @return a clone of the current definition with the new fields from the latest definition. + */ + @VisibleForTesting + static JsonNode getDefinitionWithNewFields(final JsonNode currentDefinition, final JsonNode latestDefinition, final Set newFields) { + final ObjectNode currentClone = (ObjectNode) Jsons.clone(currentDefinition); + newFields.forEach(field -> currentClone.set(field, latestDefinition.get(field))); + return currentClone; + } + + @VisibleForTesting + static boolean hasNewVersion(final String currentVersion, final String latestVersion) { + try { + return new AirbyteVersion(latestVersion).patchVersionCompareTo(new AirbyteVersion(currentVersion)) > 0; + } catch (final Exception e) { + LOGGER.error("Failed to check version: {} vs {}", currentVersion, latestVersion); + return false; + } + } + + @VisibleForTesting + static boolean hasNewPatchVersion(final String currentVersion, final String latestVersion) { + try { + return new AirbyteVersion(latestVersion).checkOnlyPatchVersionIsUpdatedComparedTo(new AirbyteVersion(currentVersion)); + } catch (final Exception e) { + LOGGER.error("Failed to check version: {} vs {}", currentVersion, latestVersion); + return false; + } + } + + static class ConnectorInfo { + + final String definitionId; + final JsonNode definition; + final String dockerRepository; + final String dockerImageTag; + + ConnectorInfo(final String definitionId, final JsonNode definition) { + this.definitionId = definitionId; + this.definition = definition; + dockerRepository = definition.get("dockerRepository").asText(); + dockerImageTag = definition.get("dockerImageTag").asText(); + } + + @Override + public String toString() { + return String.format("%s: %s (%s)", dockerRepository, dockerImageTag, definitionId); + } + + } + + private static class ConnectorCounter { + + private final int newCount; + private final int updateCount; + + private ConnectorCounter(final int newCount, final int updateCount) { + this.newCount = newCount; + this.updateCount = updateCount; + } + + } + +} diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java index 4141af34c6ba..526399453354 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java @@ -91,9 +91,4 @@ public Map> dumpConfigs() throws IOException { return decoratedPersistence.dumpConfigs(); } - @Override - public void loadData(final ConfigPersistence seedPersistence) throws IOException { - decoratedPersistence.loadData(seedPersistence); - } - } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java index d87c21fcebfc..07e825781b28 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java @@ -38,6 +38,4 @@ ConfigWithMetadata getConfigWithMetadata(AirbyteConfig configType, String Map> dumpConfigs() throws IOException; - void loadData(ConfigPersistence seedPersistence) throws IOException; - } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 2e0f1c33861a..32900b20b19b 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -94,14 +94,17 @@ public class ConfigRepository { private final ConfigPersistence persistence; private final ExceptionWrappingDatabase database; + private final ActorDefinitionMigrator actorDefinitionMigrator; public ConfigRepository(final ConfigPersistence persistence, final Database database) { - this.persistence = persistence; - this.database = new ExceptionWrappingDatabase(database); + this(persistence, database, new ActorDefinitionMigrator(new ExceptionWrappingDatabase(database))); } - public ConfigPersistence getConfigPersistence() { - return persistence; + @VisibleForTesting + ConfigRepository(final ConfigPersistence persistence, final Database database, final ActorDefinitionMigrator actorDefinitionMigrator) { + this.persistence = persistence; + this.database = new ExceptionWrappingDatabase(database); + this.actorDefinitionMigrator = actorDefinitionMigrator; } /** @@ -657,7 +660,7 @@ public List listDestinationConnection() throws JsonValida * @return destinations * @throws IOException - you never know when you IO */ - public List listWorkspaceDestinationConnection(UUID workspaceId) throws IOException { + public List listWorkspaceDestinationConnection(final UUID workspaceId) throws IOException { final Result result = database.query(ctx -> ctx.select(asterisk()) .from(ACTOR) .where(ACTOR.ACTOR_TYPE.eq(ActorType.destination)) @@ -673,7 +676,7 @@ public List listWorkspaceDestinationConnection(UUID works * @return sources * @throws IOException */ - public List listSourcesForDefinition(UUID definitionId) throws IOException { + public List listSourcesForDefinition(final UUID definitionId) throws IOException { final Result result = database.query(ctx -> ctx.select(asterisk()) .from(ACTOR) .where(ACTOR.ACTOR_TYPE.eq(ActorType.source)) @@ -689,7 +692,7 @@ public List listSourcesForDefinition(UUID definitionId) throws * @return destinations * @throws IOException */ - public List listDestinationsForDefinition(UUID definitionId) throws IOException { + public List listDestinationsForDefinition(final UUID definitionId) throws IOException { final Result result = database.query(ctx -> ctx.select(asterisk()) .from(ACTOR) .where(ACTOR.ACTOR_TYPE.eq(ActorType.destination)) @@ -915,6 +918,19 @@ private Map findCatalogByHash(final String catalogHash, fi return result; } + /** + * Updates the database with the most up-to-date source and destination definitions in the connector + * catalog. + * + * @param seedSourceDefs - most up-to-date source definitions + * @param seedDestDefs - most up-to-date destination definitions + * @throws IOException - throws if exception when interacting with db + */ + public void seedActorDefinitions(final List seedSourceDefs, final List seedDestDefs) + throws IOException { + actorDefinitionMigrator.migrate(seedSourceDefs, seedDestDefs); + } + // Data-carrier records to hold combined result of query for a Source or Destination and its // corresponding Definition. This enables the API layer to // process combined information about a Source/Destination/Definition pair without requiring two @@ -1146,19 +1162,6 @@ public Map> dumpConfigsNoSecrets() throws IOException { return persistence.dumpConfigs(); } - /** - * MUST NOT ACCEPT SECRETS - Package private so that secrets are not accidentally passed in. Should - * only be called from { @link SecretsRepositoryWriter } - * - * Loads all Data from a ConfigPersistence into the database. - * - * @param seedPersistenceWithoutSecrets - seed persistence WITHOUT secrets - * @throws IOException - you never know when you IO - */ - public void loadDataNoSecrets(final ConfigPersistence seedPersistenceWithoutSecrets) throws IOException { - persistence.loadData(seedPersistenceWithoutSecrets); - } - /** * The following methods are present to allow the JobCreationAndStatusUpdateActivity class to emit * metrics without exposing the underlying database connection. diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index b0c941bac8cc..357cc9a4c1e1 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -1638,14 +1638,6 @@ public Map> dumpConfigs() throws IOException { return result; } - @Override - public void loadData(final ConfigPersistence seedConfigPersistence) throws IOException { - database.transaction(ctx -> { - updateConfigsFromSeed(ctx, seedConfigPersistence); - return null; - }); - } - @VisibleForTesting void updateConfigsFromSeed(final DSLContext ctx, final ConfigPersistence seedConfigPersistence) throws SQLException { LOGGER.info("Updating connector definitions from the seed if necessary..."); @@ -1940,8 +1932,8 @@ static class ConnectorInfo { ConnectorInfo(final String definitionId, final JsonNode definition) { this.definitionId = definitionId; this.definition = definition; - this.dockerRepository = definition.get("dockerRepository").asText(); - this.dockerImageTag = definition.get("dockerImageTag").asText(); + dockerRepository = definition.get("dockerRepository").asText(); + dockerImageTag = definition.get("dockerImageTag").asText(); } @Override diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java index 6648ef2e6747..f7d74d8632ad 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java @@ -115,11 +115,6 @@ public Map> dumpConfigs() throws IOException { return decoratedPersistence.dumpConfigs(); } - @Override - public void loadData(final ConfigPersistence seedPersistence) throws IOException { - decoratedPersistence.loadData(seedPersistence); - } - private void validateJson(final T config, final AirbyteConfig configType) throws JsonValidationException { final JsonNode schema = JsonSchemaValidator.getSchema(configType.getConfigSchemaFile()); schemaValidator.ensure(schema, Jsons.jsonNode(config)); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceLoadDataTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceLoadDataTest.java deleted file mode 100644 index 8702e9201a15..000000000000 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceLoadDataTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.persistence; - -import static io.airbyte.db.instance.configs.jooq.generated.Tables.ACTOR_DEFINITION; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.google.common.collect.Lists; -import io.airbyte.commons.json.Jsons; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.DestinationConnection; -import io.airbyte.config.Geography; -import io.airbyte.config.SourceConnection; -import io.airbyte.config.StandardDestinationDefinition; -import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.StandardWorkspace; -import io.airbyte.db.factory.DSLContextFactory; -import io.airbyte.db.factory.DataSourceFactory; -import io.airbyte.db.factory.FlywayFactory; -import io.airbyte.db.instance.configs.ConfigsDatabaseMigrator; -import io.airbyte.db.instance.configs.ConfigsDatabaseTestProvider; -import io.airbyte.db.instance.development.DevDatabaseMigrator; -import io.airbyte.db.instance.development.MigrationDevHelper; -import io.airbyte.test.utils.DatabaseConnectionHelper; -import java.util.Collections; -import java.util.UUID; -import org.jooq.DSLContext; -import org.jooq.SQLDialect; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; - -/** - * Unit test for the {@link DatabaseConfigPersistence#loadData} method. - */ -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -@SuppressWarnings("PMD.SignatureDeclareThrowsException") -class DatabaseConfigPersistenceLoadDataTest extends BaseDatabaseConfigPersistenceTest { - - private final String DEFAULT_PROTOCOL_VERSION = "0.2.0"; - private final ConfigPersistence seedPersistence = mock(ConfigPersistence.class); - - @BeforeAll - public static void setup() throws Exception { - dataSource = DatabaseConnectionHelper.createDataSource(container); - dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, - ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); - database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); - configPersistence = spy(new DatabaseConfigPersistence(database, jsonSecretsProcessor)); - final ConfigsDatabaseMigrator configsDatabaseMigrator = - new ConfigsDatabaseMigrator(database, flyway); - final DevDatabaseMigrator devDatabaseMigrator = new DevDatabaseMigrator(configsDatabaseMigrator); - MigrationDevHelper.runLastMigration(devDatabaseMigrator); - truncateAllTables(); - } - - @AfterAll - public static void tearDown() throws Exception { - dslContext.close(); - DataSourceFactory.close(dataSource); - } - - @BeforeEach - public void resetPersistence() { - reset(seedPersistence); - reset(configPersistence); - } - - @Test - @Order(1) - @DisplayName("When database is empty, configs should be inserted") - void testUpdateConfigsInNonEmptyDatabase() throws Exception { - when(seedPersistence.listConfigs(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class)) - .thenReturn(Lists.newArrayList(SOURCE_GITHUB)); - when(seedPersistence.listConfigs(ConfigSchema.STANDARD_DESTINATION_DEFINITION, StandardDestinationDefinition.class)) - .thenReturn(Lists.newArrayList(DESTINATION_S3, DESTINATION_SNOWFLAKE)); - - configPersistence.loadData(seedPersistence); - - // the new destination is added - assertRecordCount(3, ACTOR_DEFINITION); - assertHasDestination(DESTINATION_SNOWFLAKE); - - verify(configPersistence, times(1)).updateConfigsFromSeed(any(DSLContext.class), any(ConfigPersistence.class)); - } - - @Test - @Order(2) - @DisplayName("When a connector is in use, its definition should not be updated") - void testNoUpdateForUsedConnector() throws Exception { - // the seed has a newer version of s3 destination and github source - final StandardDestinationDefinition destinationS3V2 = Jsons.clone(DESTINATION_S3).withDockerImageTag("10000.1.0"); - when(seedPersistence.listConfigs(ConfigSchema.STANDARD_DESTINATION_DEFINITION, StandardDestinationDefinition.class)) - .thenReturn(Collections.singletonList(destinationS3V2)); - final StandardSourceDefinition sourceGithubV2 = Jsons.clone(SOURCE_GITHUB).withDockerImageTag("10000.15.3"); - when(seedPersistence.listConfigs(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class)) - .thenReturn(Collections.singletonList(sourceGithubV2)); - - // create connections to mark the source and destination as in use - final DestinationConnection s3Connection = new DestinationConnection() - .withDestinationId(UUID.randomUUID()) - .withWorkspaceId(UUID.randomUUID()) - .withName("s3Connection") - .withDestinationDefinitionId(destinationS3V2.getDestinationDefinitionId()); - final StandardWorkspace standardWorkspace = new StandardWorkspace() - .withWorkspaceId(s3Connection.getWorkspaceId()) - .withName("workspace") - .withSlug("slug") - .withInitialSetupComplete(true) - .withDefaultGeography(Geography.AUTO); - configPersistence.writeConfig(ConfigSchema.STANDARD_WORKSPACE, standardWorkspace.getWorkspaceId().toString(), standardWorkspace); - configPersistence.writeConfig(ConfigSchema.DESTINATION_CONNECTION, s3Connection.getDestinationId().toString(), s3Connection); - final SourceConnection githubConnection = new SourceConnection() - .withSourceId(UUID.randomUUID()) - .withWorkspaceId(standardWorkspace.getWorkspaceId()) - .withName("githubConnection") - .withSourceDefinitionId(sourceGithubV2.getSourceDefinitionId()); - configPersistence.writeConfig(ConfigSchema.SOURCE_CONNECTION, githubConnection.getSourceId().toString(), githubConnection); - - configPersistence.loadData(seedPersistence); - // s3 destination is not updated - assertHasDestination(DESTINATION_S3); - assertHasSource(SOURCE_GITHUB); - } - - @Test - @Order(3) - @DisplayName("When a connector is not in use, its definition should be updated") - void testUpdateForUnusedConnector() throws Exception { - // the seed has a newer version of snowflake destination - final StandardDestinationDefinition snowflakeV2 = Jsons.clone(DESTINATION_SNOWFLAKE) - .withDockerImageTag("10000.2.0") - .withProtocolVersion(DEFAULT_PROTOCOL_VERSION); - when(seedPersistence.listConfigs(ConfigSchema.STANDARD_DESTINATION_DEFINITION, StandardDestinationDefinition.class)) - .thenReturn(Collections.singletonList(snowflakeV2)); - - configPersistence.loadData(seedPersistence); - assertHasDestination(snowflakeV2); - } - -} diff --git a/airbyte-config/init/src/main/java/io/airbyte/config/init/ApplyDefinitionsHelper.java b/airbyte-config/init/src/main/java/io/airbyte/config/init/ApplyDefinitionsHelper.java index 4c464995e2fc..8bd251ebd0dd 100644 --- a/airbyte-config/init/src/main/java/io/airbyte/config/init/ApplyDefinitionsHelper.java +++ b/airbyte-config/init/src/main/java/io/airbyte/config/init/ApplyDefinitionsHelper.java @@ -6,7 +6,6 @@ import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; @@ -49,9 +48,9 @@ public void apply(final boolean updateAll) throws JsonValidationException, IOExc } else { // todo (pedroslopez): Logic to apply definitions should be moved outside of the // DatabaseConfigPersistence class and behavior standardized - final ConfigPersistence dbConfigPersistence = configRepository.getConfigPersistence(); - final ConfigPersistence providerConfigPersistence = new DefinitionProviderToConfigPersistenceAdapter(definitionsProvider); - dbConfigPersistence.loadData(providerConfigPersistence); + configRepository.seedActorDefinitions( + definitionsProvider.getSourceDefinitions(), + definitionsProvider.getDestinationDefinitions()); } } diff --git a/airbyte-config/init/src/main/java/io/airbyte/config/init/DefinitionProviderToConfigPersistenceAdapter.java b/airbyte-config/init/src/main/java/io/airbyte/config/init/DefinitionProviderToConfigPersistenceAdapter.java deleted file mode 100644 index 0763c4730d0b..000000000000 --- a/airbyte-config/init/src/main/java/io/airbyte/config/init/DefinitionProviderToConfigPersistenceAdapter.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.init; - -import com.fasterxml.jackson.databind.JsonNode; -import io.airbyte.commons.json.Jsons; -import io.airbyte.config.AirbyteConfig; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.ConfigWithMetadata; -import io.airbyte.config.StandardDestinationDefinition; -import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.persistence.ConfigNotFoundException; -import io.airbyte.config.persistence.ConfigPersistence; -import io.airbyte.validation.json.JsonValidationException; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Stream; - -public class DefinitionProviderToConfigPersistenceAdapter implements ConfigPersistence { - - private final DefinitionsProvider definitionsProvider; - private static final String PERSISTENCE_READ_ONLY_ERROR_MSG = "The remote definitions are read only."; - - public DefinitionProviderToConfigPersistenceAdapter(DefinitionsProvider definitionsProvider) { - this.definitionsProvider = definitionsProvider; - } - - @Override - public T getConfig(AirbyteConfig configType, String configId, Class clazz) - throws ConfigNotFoundException, JsonValidationException, IOException { - if (configType == ConfigSchema.STANDARD_SOURCE_DEFINITION && clazz == StandardSourceDefinition.class) { - return (T) definitionsProvider.getSourceDefinition(UUID.fromString(configId)); - } else if (configType == ConfigSchema.STANDARD_DESTINATION_DEFINITION && clazz == StandardDestinationDefinition.class) { - return (T) definitionsProvider.getDestinationDefinition(UUID.fromString(configId)); - } else { - throw new UnsupportedOperationException("The config type you passed does not match any existing model class."); - } - } - - @Override - public List listConfigs(AirbyteConfig configType, Class clazz) throws JsonValidationException, IOException { - if (configType == ConfigSchema.STANDARD_SOURCE_DEFINITION && clazz == StandardSourceDefinition.class) { - return (List) definitionsProvider.getSourceDefinitions(); - } else if (configType == ConfigSchema.STANDARD_DESTINATION_DEFINITION && clazz == StandardDestinationDefinition.class) { - return (List) definitionsProvider.getDestinationDefinitions(); - } else { - throw new UnsupportedOperationException("The config type you passed does not match any existing model class."); - } - } - - @Override - public ConfigWithMetadata getConfigWithMetadata(AirbyteConfig configType, String configId, Class clazz) - throws ConfigNotFoundException, JsonValidationException, IOException { - return null; - } - - @Override - public List> listConfigsWithMetadata(AirbyteConfig configType, Class clazz) - throws JsonValidationException, IOException { - throw new UnsupportedOperationException("Definition provider doesn't support metadata"); - } - - @Override - public void writeConfig(AirbyteConfig configType, String configId, T config) throws JsonValidationException, IOException { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - - } - - @Override - public void writeConfigs(AirbyteConfig configType, Map configs) throws IOException, JsonValidationException { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - - } - - @Override - public void deleteConfig(AirbyteConfig configType, String configId) throws ConfigNotFoundException, IOException { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - - } - - @Override - public void replaceAllConfigs(Map> configs, boolean dryRun) throws IOException { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - - @Override - public Map> dumpConfigs() throws IOException { - Stream jsonSourceDefinitions = definitionsProvider.getSourceDefinitions().stream().map(Jsons::jsonNode); - Stream jsonDestinationDefinitions = definitionsProvider.getDestinationDefinitions().stream().map(Jsons::jsonNode); - return Map.of(SeedType.STANDARD_SOURCE_DEFINITION.name(), jsonSourceDefinitions, SeedType.STANDARD_DESTINATION_DEFINITION.name(), - jsonDestinationDefinitions); - } - - @Override - public void loadData(ConfigPersistence seedPersistence) throws IOException { - throw new UnsupportedOperationException(PERSISTENCE_READ_ONLY_ERROR_MSG); - } - - public DefinitionsProvider getDefinitionsProvider() { - return definitionsProvider; - } - -} diff --git a/airbyte-config/init/src/test/java/io/airbyte/config/init/ApplyDefinitionsHelperTest.java b/airbyte-config/init/src/test/java/io/airbyte/config/init/ApplyDefinitionsHelperTest.java index 413d329930bf..aeae4f5a15b7 100644 --- a/airbyte-config/init/src/test/java/io/airbyte/config/init/ApplyDefinitionsHelperTest.java +++ b/airbyte-config/init/src/test/java/io/airbyte/config/init/ApplyDefinitionsHelperTest.java @@ -4,7 +4,6 @@ package io.airbyte.config.init; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -12,7 +11,6 @@ import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; @@ -125,13 +123,11 @@ void testApplyOSS() throws JsonValidationException, IOException { when(definitionsProvider.getSourceDefinitions()).thenReturn(List.of(SOURCE_DEF1)); when(definitionsProvider.getDestinationDefinitions()).thenReturn(List.of(DEST_DEF1)); - final ConfigPersistence mConfigPersistence = mock(ConfigPersistence.class); - when(configRepository.getConfigPersistence()).thenReturn(mConfigPersistence); - applyDefinitionsHelper.apply(); - verify(mConfigPersistence).loadData(any()); - verify(configRepository).getConfigPersistence(); + verify(configRepository).seedActorDefinitions(List.of(SOURCE_DEF1), List.of(DEST_DEF1)); + verify(definitionsProvider).getDestinationDefinitions(); + verify(definitionsProvider).getSourceDefinitions(); verifyNoMoreInteractions(configRepository); verifyNoMoreInteractions(definitionsProvider); } diff --git a/airbyte-config/init/src/test/java/io/airbyte/config/init/DefinitionsProviderToConfigPersistenceAdapterTest.java b/airbyte-config/init/src/test/java/io/airbyte/config/init/DefinitionsProviderToConfigPersistenceAdapterTest.java deleted file mode 100644 index 55c6293569ea..000000000000 --- a/airbyte-config/init/src/test/java/io/airbyte/config/init/DefinitionsProviderToConfigPersistenceAdapterTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.init; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.io.Resources; -import io.airbyte.commons.json.Jsons; -import io.airbyte.commons.util.MoreIterators; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.StandardDestinationDefinition; -import io.airbyte.config.StandardSourceDefinition; -import java.io.IOException; -import java.net.URI; -import java.net.URL; -import java.nio.charset.Charset; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class DefinitionsProviderToConfigPersistenceAdapterTest { - - private MockWebServer webServer; - private static MockResponse validCatalogResponse; - private static URI catalogUrl; - private static JsonNode jsonCatalog; - - @BeforeEach - void setup() throws IOException { - webServer = new MockWebServer(); - catalogUrl = URI.create(webServer.url("/connector_catalog.json").toString()); - - final URL testCatalog = Resources.getResource("connector_catalog.json"); - final String jsonBody = Resources.toString(testCatalog, Charset.defaultCharset()); - jsonCatalog = Jsons.deserialize(jsonBody); - validCatalogResponse = new MockResponse().setResponseCode(200) - .addHeader("Content-Type", "application/json; charset=utf-8") - .addHeader("Cache-Control", "no-cache") - .setBody(jsonBody); - } - - @Test - void testGetConfig() throws Exception { - webServer.enqueue(validCatalogResponse); - final RemoteDefinitionsProvider remoteDefinitionsProvider = new RemoteDefinitionsProvider(catalogUrl); - final DefinitionProviderToConfigPersistenceAdapter adapter = new DefinitionProviderToConfigPersistenceAdapter(remoteDefinitionsProvider); - // source - final String stripeSourceId = "e094cb9a-26de-4645-8761-65c0c425d1de"; - final StandardSourceDefinition stripeSource = adapter - .getConfig(ConfigSchema.STANDARD_SOURCE_DEFINITION, stripeSourceId, StandardSourceDefinition.class); - assertEquals(stripeSourceId, stripeSource.getSourceDefinitionId().toString()); - assertEquals("Stripe", stripeSource.getName()); - assertEquals("airbyte/source-stripe", stripeSource.getDockerRepository()); - assertEquals("https://docs.airbyte.io/integrations/sources/stripe", stripeSource.getDocumentationUrl()); - assertEquals("stripe.svg", stripeSource.getIcon()); - assertEquals(URI.create("https://docs.airbyte.io/integrations/sources/stripe"), stripeSource.getSpec().getDocumentationUrl()); - - // destination - final String s3DestinationId = "4816b78f-1489-44c1-9060-4b19d5fa9362"; - final StandardDestinationDefinition s3Destination = adapter - .getConfig(ConfigSchema.STANDARD_DESTINATION_DEFINITION, s3DestinationId, StandardDestinationDefinition.class); - assertEquals(s3DestinationId, s3Destination.getDestinationDefinitionId().toString()); - assertEquals("S3", s3Destination.getName()); - assertEquals("airbyte/destination-s3", s3Destination.getDockerRepository()); - assertEquals("https://docs.airbyte.io/integrations/destinations/s3", s3Destination.getDocumentationUrl()); - assertEquals(URI.create("https://docs.airbyte.io/integrations/destinations/s3"), s3Destination.getSpec().getDocumentationUrl()); - assertThrows(UnsupportedOperationException.class, () -> { - adapter.getConfig(ConfigSchema.STANDARD_SOURCE_DEFINITION, s3DestinationId, StandardDestinationDefinition.class); - }); - } - - // TODO This test can be removed once ConfigRepository gets refactored to use DefinitionsProvider - // interface - @Test - void testListConfig() throws Exception { - webServer.enqueue(validCatalogResponse); - final RemoteDefinitionsProvider remoteDefinitionsProvider = new RemoteDefinitionsProvider(catalogUrl); - final DefinitionProviderToConfigPersistenceAdapter adapter = new DefinitionProviderToConfigPersistenceAdapter(remoteDefinitionsProvider); - - final List sourceDefinitions = - adapter.listConfigs(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class); - final int expectedNumberOfSources = MoreIterators.toList(jsonCatalog.get("sources").elements()).size(); - assertEquals(expectedNumberOfSources, sourceDefinitions.size()); - - final List destinationDefinitions = - adapter.listConfigs(ConfigSchema.STANDARD_DESTINATION_DEFINITION, StandardDestinationDefinition.class); - final int expectedNumberOfDestinations = MoreIterators.toList(jsonCatalog.get("destinations").elements()).size(); - assertEquals(expectedNumberOfDestinations, destinationDefinitions.size()); - - assertThrows(UnsupportedOperationException.class, () -> { - adapter.listConfigs(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardDestinationDefinition.class); - }); - } - - // TODO This test can be removed once ConfigRepository gets refactored to use DefinitionsProvider - // interface - @Test - void testDumpConfigs() throws Exception { - webServer.enqueue(validCatalogResponse); - final RemoteDefinitionsProvider remoteDefinitionsProvider = new RemoteDefinitionsProvider(catalogUrl, Duration.ofSeconds(1)); - final DefinitionProviderToConfigPersistenceAdapter adapter = new DefinitionProviderToConfigPersistenceAdapter(remoteDefinitionsProvider); - - final Map> allRemoteConfigs = adapter.dumpConfigs(); - assertEquals(2, allRemoteConfigs.size()); - List sourceDefinitions = allRemoteConfigs.get(ConfigSchema.STANDARD_SOURCE_DEFINITION.name()).collect(Collectors.toList()); - final int expectedNumberOfSources = MoreIterators.toList(jsonCatalog.get("sources").elements()).size(); - assertEquals(expectedNumberOfSources, sourceDefinitions.size()); - assertEquals(Jsons.object(sourceDefinitions.get(0), StandardSourceDefinition.class), remoteDefinitionsProvider.getSourceDefinitions().get(0)); - List destinationDefinitions = allRemoteConfigs.get(ConfigSchema.STANDARD_DESTINATION_DEFINITION.name()).collect(Collectors.toList()); - final int expectedNumberOfDestinations = MoreIterators.toList(jsonCatalog.get("destinations").elements()).size(); - assertEquals(expectedNumberOfDestinations, destinationDefinitions.size()); - assertEquals(Jsons.object(destinationDefinitions.get(0), StandardDestinationDefinition.class), - remoteDefinitionsProvider.getDestinationDefinitions().get(0)); - } - -} From 20ab047ae0f6be41028e815e7d2a5c753e25e7b3 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 1 Nov 2022 16:50:03 -0700 Subject: [PATCH 481/498] Remove the bulk actions from ConfigPersistence (#18800) --- .../io/airbyte/bootloader/BootloaderApp.java | 7 +- .../airbyte/bootloader/BootloaderAppTest.java | 7 +- .../ClassEnforcingConfigPersistence.java | 94 ------ .../config/persistence/ConfigPersistence.java | 6 - .../config/persistence/ConfigRepository.java | 30 -- .../DatabaseConfigPersistence.java | 278 +----------------- .../persistence/SecretsRepositoryReader.java | 14 +- .../persistence/SecretsRepositoryWriter.java | 79 +---- .../ValidatingConfigPersistence.java | 24 -- .../ClassEnforcingConfigPersistenceTest.java | 176 ----------- .../ConfigRepositoryE2EReadWriteTest.java | 16 +- ...baseConfigPersistenceE2EReadWriteTest.java | 4 +- .../DatabaseConfigPersistenceTest.java | 69 +---- ...istenceUpdateConnectorDefinitionsTest.java | 5 +- .../SecretsRepositoryReaderTest.java | 30 -- .../SecretsRepositoryWriterTest.java | 44 --- .../persistence/StatePersistenceTest.java | 9 +- .../StreamResetPersistenceTest.java | 2 +- .../ValidatingConfigPersistenceTest.java | 41 --- .../cron/config/DatabaseBeanFactory.java | 2 +- .../java/io/airbyte/server/ServerApp.java | 6 +- .../workers/config/DatabaseBeanFactory.java | 2 +- 22 files changed, 29 insertions(+), 916 deletions(-) delete mode 100644 airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java delete mode 100644 airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistenceTest.java diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java index 5ae47928ed6c..5d6d5067f983 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java @@ -23,7 +23,6 @@ import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.SecretsRepositoryReader; import io.airbyte.config.persistence.SecretsRepositoryWriter; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.config.persistence.split_secrets.SecretPersistence; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import io.airbyte.db.Database; @@ -195,11 +194,7 @@ private static Database getConfigDatabase(final DSLContext dslContext) throws IO } private static ConfigPersistence getConfigPersistence(final Database configDatabase) throws IOException { - final JsonSecretsProcessor jsonSecretsProcessor = JsonSecretsProcessor.builder() - .copySecrets(true) - .build(); - - return DatabaseConfigPersistence.createWithValidation(configDatabase, jsonSecretsProcessor); + return DatabaseConfigPersistence.createWithValidation(configDatabase); } private static DefinitionsProvider getLocalDefinitionsProvider() throws IOException { diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index 8d21d41fc128..ecd6cf516ade 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -30,7 +30,6 @@ import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.SecretsRepositoryReader; import io.airbyte.config.persistence.SecretsRepositoryWriter; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.config.persistence.split_secrets.LocalTestingSecretPersistence; import io.airbyte.config.persistence.split_secrets.RealSecretsHydrator; import io.airbyte.config.persistence.split_secrets.SecretPersistence; @@ -173,10 +172,6 @@ void testBootloaderAppRunSecretMigration() throws Exception { val mockedFeatureFlags = mock(FeatureFlags.class); - final JsonSecretsProcessor jsonSecretsProcessor = JsonSecretsProcessor.builder() - .copySecrets(true) - .build(); - try (val configsDslContext = DSLContextFactory.create(configsDataSource, SQLDialect.POSTGRES); val jobsDslContext = DSLContextFactory.create(configsDataSource, SQLDialect.POSTGRES)) { @@ -186,7 +181,7 @@ void testBootloaderAppRunSecretMigration() throws Exception { val configDatabase = new ConfigsDatabaseTestProvider(configsDslContext, configsFlyway).create(false); val jobDatabase = new JobsDatabaseTestProvider(jobsDslContext, jobsFlyway).create(false); - val configPersistence = new DatabaseConfigPersistence(configDatabase, jsonSecretsProcessor); + val configPersistence = new DatabaseConfigPersistence(configDatabase); val configRepository = new ConfigRepository(configPersistence, configDatabase); val jobsPersistence = new DefaultJobPersistence(jobDatabase); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java deleted file mode 100644 index 526399453354..000000000000 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistence.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.persistence; - -import com.fasterxml.jackson.databind.JsonNode; -import com.google.api.client.util.Preconditions; -import io.airbyte.config.AirbyteConfig; -import io.airbyte.config.ConfigWithMetadata; -import io.airbyte.validation.json.JsonValidationException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Validates that the class of inputs and outputs matches the class specified in the AirbyteConfig - * enum. Helps avoid type mistakes, which can happen because this iface can't type check at compile - * time. - */ -public class ClassEnforcingConfigPersistence implements ConfigPersistence { - - private final ConfigPersistence decoratedPersistence; - - public ClassEnforcingConfigPersistence(final ConfigPersistence decoratedPersistence) { - this.decoratedPersistence = decoratedPersistence; - } - - @Override - public T getConfig(final AirbyteConfig configType, final String configId, final Class clazz) - throws ConfigNotFoundException, JsonValidationException, IOException { - Preconditions.checkArgument(configType.getClassName().equals(clazz)); - return decoratedPersistence.getConfig(configType, configId, clazz); - } - - @Override - public List listConfigs(final AirbyteConfig configType, final Class clazz) throws JsonValidationException, IOException { - Preconditions.checkArgument(configType.getClassName().equals(clazz)); - return decoratedPersistence.listConfigs(configType, clazz); - } - - @Override - public ConfigWithMetadata getConfigWithMetadata(final AirbyteConfig configType, final String configId, final Class clazz) - throws ConfigNotFoundException, JsonValidationException, IOException { - Preconditions.checkArgument(configType.getClassName().equals(clazz)); - return decoratedPersistence.getConfigWithMetadata(configType, configId, clazz); - } - - @Override - public List> listConfigsWithMetadata(final AirbyteConfig configType, final Class clazz) - throws JsonValidationException, IOException { - Preconditions.checkArgument(configType.getClassName().equals(clazz)); - return decoratedPersistence.listConfigsWithMetadata(configType, clazz); - } - - @Override - public void writeConfig(final AirbyteConfig configType, final String configId, final T config) throws JsonValidationException, IOException { - Preconditions.checkArgument(configType.getClassName().equals(config.getClass())); - decoratedPersistence.writeConfig(configType, configId, config); - } - - @Override - public void writeConfigs(final AirbyteConfig configType, final Map configs) throws IOException, JsonValidationException { - // attempt to check the input type. if it is empty, then there is nothing to check. - Preconditions.checkArgument(configs.isEmpty() || configType.getClassName().equals(new ArrayList<>(configs.values()).get(0).getClass())); - decoratedPersistence.writeConfigs(configType, configs); - } - - @Override - public void deleteConfig(final AirbyteConfig configType, final String configId) throws ConfigNotFoundException, IOException { - decoratedPersistence.deleteConfig(configType, configId); - } - - @Override - public void replaceAllConfigs(final Map> configs, final boolean dryRun) throws IOException { - final Map> augmentedMap = new HashMap<>(configs).entrySet() - .stream() - .collect(Collectors.toMap( - Entry::getKey, - entry -> entry.getValue().peek(config -> Preconditions.checkArgument(entry.getKey().getClassName().equals(config.getClass()))))); - decoratedPersistence.replaceAllConfigs(augmentedMap, dryRun); - } - - @Override - public Map> dumpConfigs() throws IOException { - return decoratedPersistence.dumpConfigs(); - } - -} diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java index 07e825781b28..9849c4ed7b65 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java @@ -4,14 +4,12 @@ package io.airbyte.config.persistence; -import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigWithMetadata; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.stream.Stream; /** * We are moving migrating away from this interface entirely. Use ConfigRepository instead. @@ -34,8 +32,4 @@ ConfigWithMetadata getConfigWithMetadata(AirbyteConfig configType, String void deleteConfig(AirbyteConfig configType, String configId) throws ConfigNotFoundException, IOException; - void replaceAllConfigs(Map> configs, boolean dryRun) throws IOException; - - Map> dumpConfigs() throws IOException; - } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index 32900b20b19b..eca206641691 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -19,7 +19,6 @@ import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.select; -import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.collect.Sets; @@ -30,7 +29,6 @@ import io.airbyte.commons.version.AirbyteProtocolVersion; import io.airbyte.config.ActorCatalog; import io.airbyte.config.ActorCatalogFetchEvent; -import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.DestinationOAuthParameter; @@ -70,7 +68,6 @@ import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.apache.commons.lang3.ArrayUtils; import org.jooq.Condition; import org.jooq.DSLContext; @@ -1135,33 +1132,6 @@ public int countDestinationsForWorkspace(final UUID workspaceId) throws IOExcept .andNot(ACTOR.TOMBSTONE)).fetchOne().into(int.class); } - /** - * MUST NOT ACCEPT SECRETS - Package private so that secrets are not accidentally passed in. Should - * only be called from { @link SecretsRepositoryWriter } - * - * Takes as inputs configurations that it then uses to overwrite the contents of the existing Config - * Database. - * - * @param configs - configurations to load. - * @param dryRun - whether to test run of the load - * @throws IOException - you never know when you IO. - */ - void replaceAllConfigsNoSecrets(final Map> configs, final boolean dryRun) throws IOException { - persistence.replaceAllConfigs(configs, dryRun); - } - - /** - * Dumps all configurations in the Config Database. Note: It will not contain secrets as the Config - * Database does not contain connector configurations that include secrets. In order to hydrate with - * secrets see { @link SecretsRepositoryReader#dumpConfigs() }. - * - * @return all configurations in the Config Database - * @throws IOException - you never know when you IO - */ - public Map> dumpConfigsNoSecrets() throws IOException { - return persistence.dumpConfigs(); - } - /** * The following methods are present to allow the JobCreationAndStatusUpdateActivity class to emit * metrics without exposing the underlying database connection. diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index 357cc9a4c1e1..68b2469306d8 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -50,7 +50,6 @@ import io.airbyte.config.State; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.config.helpers.ScheduleHelpers; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.Database; import io.airbyte.db.ExceptionWrappingDatabase; import io.airbyte.db.instance.configs.jooq.generated.enums.ActorType; @@ -62,8 +61,6 @@ import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -87,27 +84,22 @@ public class DatabaseConfigPersistence implements ConfigPersistence { private final ExceptionWrappingDatabase database; - private final JsonSecretsProcessor jsonSecretsProcessor; private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseConfigPersistence.class); private static final String UNKNOWN_CONFIG_TYPE = "Unknown Config Type "; - private static final String NOT_FOUND = " not found"; /** * Entrypoint into DatabaseConfigPersistence. Except in testing, we should never be using it without * it being decorated with validation classes. * * @param database - database where configs are stored - * @param jsonSecretsProcessor - for filtering secrets in export * @return database config persistence wrapped in validation decorators */ - public static ConfigPersistence createWithValidation(final Database database, - final JsonSecretsProcessor jsonSecretsProcessor) { - return new ValidatingConfigPersistence(new DatabaseConfigPersistence(database, jsonSecretsProcessor)); + public static ConfigPersistence createWithValidation(final Database database) { + return new ValidatingConfigPersistence(new DatabaseConfigPersistence(database)); } - public DatabaseConfigPersistence(final Database database, final JsonSecretsProcessor jsonSecretsProcessor) { + public DatabaseConfigPersistence(final Database database) { this.database = new ExceptionWrappingDatabase(database); - this.jsonSecretsProcessor = jsonSecretsProcessor; } @Override @@ -1374,270 +1366,6 @@ private void deleteStandardSync(final String configId) throws IOException { }); } - @Override - public void replaceAllConfigs(final Map> configs, final boolean dryRun) throws IOException { - if (dryRun) { - return; - } - - LOGGER.info("Replacing all configs"); - final Set originalConfigs = new HashSet<>(configs.keySet()); - database.transaction(ctx -> { - ctx.truncate(WORKSPACE).restartIdentity().cascade().execute(); - ctx.truncate(ACTOR_DEFINITION).restartIdentity().cascade().execute(); - ctx.truncate(ACTOR).restartIdentity().cascade().execute(); - ctx.truncate(ACTOR_OAUTH_PARAMETER).restartIdentity().cascade().execute(); - ctx.truncate(OPERATION).restartIdentity().cascade().execute(); - ctx.truncate(CONNECTION).restartIdentity().cascade().execute(); - ctx.truncate(CONNECTION_OPERATION).restartIdentity().cascade().execute(); - ctx.truncate(STATE).restartIdentity().cascade().execute(); - ctx.truncate(ACTOR_CATALOG).restartIdentity().cascade().execute(); - ctx.truncate(ACTOR_CATALOG_FETCH_EVENT).restartIdentity().cascade().execute(); - ctx.truncate(WORKSPACE_SERVICE_ACCOUNT).restartIdentity().cascade().execute(); - - if (configs.containsKey(ConfigSchema.STANDARD_WORKSPACE)) { - configs.get(ConfigSchema.STANDARD_WORKSPACE).map(c -> (StandardWorkspace) c) - .forEach(c -> writeStandardWorkspace(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.STANDARD_WORKSPACE); - } else { - LOGGER.warn(ConfigSchema.STANDARD_WORKSPACE + NOT_FOUND); - } - if (configs.containsKey(ConfigSchema.STANDARD_SOURCE_DEFINITION)) { - configs.get(ConfigSchema.STANDARD_SOURCE_DEFINITION).map(c -> (StandardSourceDefinition) c) - .forEach(c -> ConfigWriter.writeStandardSourceDefinition(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.STANDARD_SOURCE_DEFINITION); - } else { - LOGGER.warn(ConfigSchema.STANDARD_SOURCE_DEFINITION + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.STANDARD_DESTINATION_DEFINITION)) { - configs.get(ConfigSchema.STANDARD_DESTINATION_DEFINITION).map(c -> (StandardDestinationDefinition) c) - .forEach(c -> ConfigWriter.writeStandardDestinationDefinition(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.STANDARD_DESTINATION_DEFINITION); - } else { - LOGGER.warn(ConfigSchema.STANDARD_DESTINATION_DEFINITION + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.SOURCE_CONNECTION)) { - configs.get(ConfigSchema.SOURCE_CONNECTION).map(c -> (SourceConnection) c) - .forEach(c -> writeSourceConnection(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.SOURCE_CONNECTION); - } else { - LOGGER.warn(ConfigSchema.SOURCE_CONNECTION + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.DESTINATION_CONNECTION)) { - configs.get(ConfigSchema.DESTINATION_CONNECTION).map(c -> (DestinationConnection) c) - .forEach(c -> writeDestinationConnection(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.DESTINATION_CONNECTION); - } else { - LOGGER.warn(ConfigSchema.DESTINATION_CONNECTION + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.SOURCE_OAUTH_PARAM)) { - configs.get(ConfigSchema.SOURCE_OAUTH_PARAM).map(c -> (SourceOAuthParameter) c) - .forEach(c -> writeSourceOauthParameter(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.SOURCE_OAUTH_PARAM); - } else { - LOGGER.warn(ConfigSchema.SOURCE_OAUTH_PARAM + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.DESTINATION_OAUTH_PARAM)) { - configs.get(ConfigSchema.DESTINATION_OAUTH_PARAM).map(c -> (DestinationOAuthParameter) c) - .forEach(c -> writeDestinationOauthParameter(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.DESTINATION_OAUTH_PARAM); - } else { - LOGGER.warn(ConfigSchema.DESTINATION_OAUTH_PARAM + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.STANDARD_SYNC_OPERATION)) { - configs.get(ConfigSchema.STANDARD_SYNC_OPERATION).map(c -> (StandardSyncOperation) c) - .forEach(c -> writeStandardSyncOperation(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.STANDARD_SYNC_OPERATION); - } else { - LOGGER.warn(ConfigSchema.STANDARD_SYNC_OPERATION + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.ACTOR_CATALOG)) { - configs.get(ConfigSchema.ACTOR_CATALOG).map(c -> (ActorCatalog) c) - .forEach(c -> writeActorCatalog(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.ACTOR_CATALOG); - } else { - LOGGER.warn(ConfigSchema.ACTOR_CATALOG + NOT_FOUND); - } - if (configs.containsKey(ConfigSchema.ACTOR_CATALOG_FETCH_EVENT)) { - configs.get(ConfigSchema.ACTOR_CATALOG_FETCH_EVENT).map(c -> (ActorCatalogFetchEvent) c) - .forEach(c -> writeActorCatalogFetchEvent(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.ACTOR_CATALOG_FETCH_EVENT); - } else { - LOGGER.warn(ConfigSchema.ACTOR_CATALOG_FETCH_EVENT + NOT_FOUND); - } - - // Syncs need to be imported after Actor Catalogs as they have a foreign key association with Actor - // Catalogs. - // e.g. They reference catalogs and thus catalogs need to exist before or the insert will fail. - if (configs.containsKey(ConfigSchema.STANDARD_SYNC)) { - configs.get(ConfigSchema.STANDARD_SYNC).map(c -> (StandardSync) c).forEach(c -> writeStandardSync(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.STANDARD_SYNC); - } else { - LOGGER.warn(ConfigSchema.STANDARD_SYNC + NOT_FOUND); - } - - if (configs.containsKey(ConfigSchema.STANDARD_SYNC_STATE)) { - configs.get(ConfigSchema.STANDARD_SYNC_STATE).map(c -> (StandardSyncState) c) - .forEach(c -> writeStandardSyncState(Collections.singletonList(c), ctx)); - originalConfigs.remove(ConfigSchema.STANDARD_SYNC_STATE); - } else { - LOGGER.warn(ConfigSchema.STANDARD_SYNC_STATE + NOT_FOUND); - } - - if (!originalConfigs.isEmpty()) { - originalConfigs.forEach(c -> LOGGER.warn("Unknown Config " + c + " ignored")); - } - - return null; - }); - - LOGGER.info("Config database is reset"); - } - - @Override - public Map> dumpConfigs() throws IOException { - LOGGER.info("Exporting all configs..."); - - final Map> result = new HashMap<>(); - final List> standardWorkspaceWithMetadata = listStandardWorkspaceWithMetadata(); - if (!standardWorkspaceWithMetadata.isEmpty()) { - result.put(ConfigSchema.STANDARD_WORKSPACE.name(), - standardWorkspaceWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> standardSourceDefinitionWithMetadata = listStandardSourceDefinitionWithMetadata(); - if (!standardSourceDefinitionWithMetadata.isEmpty()) { - result.put(ConfigSchema.STANDARD_SOURCE_DEFINITION.name(), - standardSourceDefinitionWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> standardDestinationDefinitionWithMetadata = - listStandardDestinationDefinitionWithMetadata(); - if (!standardDestinationDefinitionWithMetadata.isEmpty()) { - result.put(ConfigSchema.STANDARD_DESTINATION_DEFINITION.name(), - standardDestinationDefinitionWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> sourceConnectionWithMetadata = listSourceConnectionWithMetadata(); - if (!sourceConnectionWithMetadata.isEmpty()) { - result.put(ConfigSchema.SOURCE_CONNECTION.name(), - sourceConnectionWithMetadata - .stream() - .map(configWithMetadata -> { - try { - final UUID sourceDefinitionId = configWithMetadata.getConfig().getSourceDefinitionId(); - final StandardSourceDefinition standardSourceDefinition = getConfig( - ConfigSchema.STANDARD_SOURCE_DEFINITION, - sourceDefinitionId.toString(), - StandardSourceDefinition.class); - - final JsonNode connectionSpecs = standardSourceDefinition.getSpec().getConnectionSpecification(); - final JsonNode sanitizedConfig = - jsonSecretsProcessor.prepareSecretsForOutput(configWithMetadata.getConfig().getConfiguration(), connectionSpecs); - - configWithMetadata.getConfig().setConfiguration(sanitizedConfig); - return Jsons.jsonNode(configWithMetadata.getConfig()); - } catch (final ConfigNotFoundException | JsonValidationException | IOException e) { - throw new RuntimeException(e); - } - })); - } - final List> destinationConnectionWithMetadata = listDestinationConnectionWithMetadata(); - if (!destinationConnectionWithMetadata.isEmpty()) { - final Stream jsonNodeStream = destinationConnectionWithMetadata - .stream() - .map(configWithMetadata -> { - try { - final UUID destinationDefinition = configWithMetadata.getConfig().getDestinationDefinitionId(); - final StandardDestinationDefinition standardDestinationDefinition = getConfig( - ConfigSchema.STANDARD_DESTINATION_DEFINITION, - destinationDefinition.toString(), - StandardDestinationDefinition.class); - final JsonNode connectionSpecs = standardDestinationDefinition.getSpec().getConnectionSpecification(); - final JsonNode sanitizedConfig = - jsonSecretsProcessor.prepareSecretsForOutput(configWithMetadata.getConfig().getConfiguration(), connectionSpecs); - - configWithMetadata.getConfig().setConfiguration(sanitizedConfig); - return Jsons.jsonNode(configWithMetadata.getConfig()); - } catch (final ConfigNotFoundException | JsonValidationException | IOException e) { - throw new RuntimeException(e); - } - }); - result.put(ConfigSchema.DESTINATION_CONNECTION.name(), jsonNodeStream); - - } - final List> sourceOauthParamWithMetadata = listSourceOauthParamWithMetadata(); - if (!sourceOauthParamWithMetadata.isEmpty()) { - result.put(ConfigSchema.SOURCE_OAUTH_PARAM.name(), - sourceOauthParamWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> destinationOauthParamWithMetadata = listDestinationOauthParamWithMetadata(); - if (!destinationOauthParamWithMetadata.isEmpty()) { - result.put(ConfigSchema.DESTINATION_OAUTH_PARAM.name(), - destinationOauthParamWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> standardSyncOperationWithMetadata = listStandardSyncOperationWithMetadata(); - if (!standardSyncOperationWithMetadata.isEmpty()) { - result.put(ConfigSchema.STANDARD_SYNC_OPERATION.name(), - standardSyncOperationWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> standardSyncWithMetadata = listStandardSyncWithMetadata(); - if (!standardSyncWithMetadata.isEmpty()) { - result.put(ConfigSchema.STANDARD_SYNC.name(), - standardSyncWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> standardSyncStateWithMetadata = listStandardSyncStateWithMetadata(); - if (!standardSyncStateWithMetadata.isEmpty()) { - result.put(ConfigSchema.STANDARD_SYNC_STATE.name(), - standardSyncStateWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> actorCatalogWithMetadata = listActorCatalogWithMetadata(); - if (!actorCatalogWithMetadata.isEmpty()) { - result.put(ConfigSchema.ACTOR_CATALOG.name(), - actorCatalogWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - final List> actorCatalogFetchEventWithMetadata = listActorCatalogFetchEventWithMetadata(); - if (!actorCatalogFetchEventWithMetadata.isEmpty()) { - result.put(ConfigSchema.ACTOR_CATALOG_FETCH_EVENT.name(), - actorCatalogFetchEventWithMetadata - .stream() - .map(ConfigWithMetadata::getConfig) - .map(Jsons::jsonNode)); - } - return result; - } - @VisibleForTesting void updateConfigsFromSeed(final DSLContext ctx, final ConfigPersistence seedConfigPersistence) throws SQLException { LOGGER.info("Updating connector definitions from the seed if necessary..."); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java index 5d22a12c6029..7ba4e173e522 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryReader.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.json.Jsons; import io.airbyte.commons.lang.Exceptions; -import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; import io.airbyte.config.StandardWorkspace; @@ -15,7 +14,6 @@ import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -66,17 +64,6 @@ public List listDestinationConnectionWithSecrets() throws .collect(Collectors.toList()); } - public Map> dumpConfigsWithSecrets() throws IOException { - final Map> dump = new HashMap<>(configRepository.dumpConfigsNoSecrets()); - final String sourceKey = ConfigSchema.SOURCE_CONNECTION.name(); - final String destinationKey = ConfigSchema.DESTINATION_CONNECTION.name(); - - hydrateValuesIfKeyPresent(sourceKey, dump); - hydrateValuesIfKeyPresent(destinationKey, dump); - - return dump; - } - private SourceConnection hydrateSourcePartialConfig(final SourceConnection sourceWithPartialConfig) { final JsonNode hydratedConfig = secretsHydrator.hydrate(sourceWithPartialConfig.getConfiguration()); return Jsons.clone(sourceWithPartialConfig).withConfiguration(hydratedConfig); @@ -87,6 +74,7 @@ private DestinationConnection hydrateDestinationPartialConfig(final DestinationC return Jsons.clone(sourceWithPartialConfig).withConfiguration(hydratedConfig); } + @SuppressWarnings("unused") private void hydrateValuesIfKeyPresent(final String key, final Map> dump) { if (dump.containsKey(key)) { final Stream augmentedValue = dump.get(key).map(secretsHydrator::hydrate); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java index 176ec83aebd6..6e8aa6db99fc 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/SecretsRepositoryWriter.java @@ -7,12 +7,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.annotations.VisibleForTesting; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; -import io.airbyte.config.StandardDestinationDefinition; -import io.airbyte.config.StandardSourceDefinition; import io.airbyte.config.StandardWorkspace; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.config.persistence.split_secrets.SecretCoordinateToPayload; @@ -23,13 +20,8 @@ import io.airbyte.validation.json.JsonSchemaValidator; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * This class takes secrets as arguments but never returns a secrets as return values (even the ones @@ -123,6 +115,7 @@ public void writeDestinationConnection(final DestinationConnection destination, * @param spec connector specification * @return partial config */ + @SuppressWarnings("unused") private JsonNode statefulSplitSecrets(final UUID workspaceId, final JsonNode fullConfig, final ConnectorSpecification spec) { return splitSecretConfig(workspaceId, fullConfig, spec, longLivedSecretPersistence); } @@ -197,74 +190,6 @@ private JsonNode splitSecretConfig(final UUID workspaceId, } } - public void replaceAllConfigs(final Map> configs, final boolean dryRun) throws IOException { - if (longLivedSecretPersistence.isPresent()) { - final var augmentedMap = new HashMap<>(configs); - - // get all source defs so that we can use their specs when storing secrets. - @SuppressWarnings("unchecked") - final List sourceDefs = - (List) augmentedMap.get(ConfigSchema.STANDARD_SOURCE_DEFINITION).collect(Collectors.toList()); - // restore data in the map that gets consumed downstream. - augmentedMap.put(ConfigSchema.STANDARD_SOURCE_DEFINITION, sourceDefs.stream()); - final Map sourceDefIdToSpec = sourceDefs - .stream() - .collect(Collectors.toMap(StandardSourceDefinition::getSourceDefinitionId, StandardSourceDefinition::getSpec)); - - // get all destination defs so that we can use their specs when storing secrets. - @SuppressWarnings("unchecked") - final List destinationDefs = - (List) augmentedMap.get(ConfigSchema.STANDARD_DESTINATION_DEFINITION).collect(Collectors.toList()); - augmentedMap.put(ConfigSchema.STANDARD_DESTINATION_DEFINITION, destinationDefs.stream()); - final Map destinationDefIdToSpec = destinationDefs - .stream() - .collect(Collectors.toMap(StandardDestinationDefinition::getDestinationDefinitionId, StandardDestinationDefinition::getSpec)); - - if (augmentedMap.containsKey(ConfigSchema.SOURCE_CONNECTION)) { - final Stream augmentedValue = augmentedMap.get(ConfigSchema.SOURCE_CONNECTION) - .map(config -> { - final SourceConnection source = (SourceConnection) config; - - if (!sourceDefIdToSpec.containsKey(source.getSourceDefinitionId())) { - throw new RuntimeException(new ConfigNotFoundException(ConfigSchema.STANDARD_SOURCE_DEFINITION, source.getSourceDefinitionId())); - } - - final var partialConfig = statefulSplitSecrets( - source.getWorkspaceId(), - source.getConfiguration(), - sourceDefIdToSpec.get(source.getSourceDefinitionId())); - - return source.withConfiguration(partialConfig); - }); - augmentedMap.put(ConfigSchema.SOURCE_CONNECTION, augmentedValue); - } - - if (augmentedMap.containsKey(ConfigSchema.DESTINATION_CONNECTION)) { - final Stream augmentedValue = augmentedMap.get(ConfigSchema.DESTINATION_CONNECTION) - .map(config -> { - final DestinationConnection destination = (DestinationConnection) config; - - if (!destinationDefIdToSpec.containsKey(destination.getDestinationDefinitionId())) { - throw new RuntimeException( - new ConfigNotFoundException(ConfigSchema.STANDARD_DESTINATION_DEFINITION, destination.getDestinationDefinitionId())); - } - - final var partialConfig = statefulSplitSecrets( - destination.getWorkspaceId(), - destination.getConfiguration(), - destinationDefIdToSpec.get(destination.getDestinationDefinitionId())); - - return destination.withConfiguration(partialConfig); - }); - augmentedMap.put(ConfigSchema.DESTINATION_CONNECTION, augmentedValue); - } - - configRepository.replaceAllConfigsNoSecrets(augmentedMap, dryRun); - } else { - configRepository.replaceAllConfigsNoSecrets(configs, dryRun); - } - } - public void writeServiceAccountJsonCredentials(final WorkspaceServiceAccount workspaceServiceAccount) throws JsonValidationException, IOException { final WorkspaceServiceAccount workspaceServiceAccountForDB = getWorkspaceServiceAccountWithSecretCoordinate(workspaceServiceAccount); @@ -350,7 +275,7 @@ private Optional getWorkspaceIfExists(final UUID workspaceId, try { final StandardWorkspace existingWorkspace = configRepository.getStandardWorkspaceNoSecrets(workspaceId, includeTombstone); return existingWorkspace == null ? Optional.empty() : Optional.of(existingWorkspace); - } catch (JsonValidationException | IOException | ConfigNotFoundException e) { + } catch (final JsonValidationException | IOException | ConfigNotFoundException e) { return Optional.empty(); } } diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java index f7d74d8632ad..0e2dcbac7f9b 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java @@ -14,9 +14,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Validates that json input and outputs for the ConfigPersistence against their schemas. @@ -94,27 +91,6 @@ public void deleteConfig(final AirbyteConfig configType, final String configId) decoratedPersistence.deleteConfig(configType, configId); } - @Override - public void replaceAllConfigs(final Map> configs, final boolean dryRun) throws IOException { - final Map> augmentedMap = new HashMap<>(configs).entrySet() - .stream() - .collect(Collectors.toMap( - Entry::getKey, - entry -> entry.getValue().peek(config -> { - try { - validateJson(config, entry.getKey()); - } catch (final JsonValidationException e) { - throw new RuntimeException(e); - } - }))); - decoratedPersistence.replaceAllConfigs(augmentedMap, dryRun); - } - - @Override - public Map> dumpConfigs() throws IOException { - return decoratedPersistence.dumpConfigs(); - } - private void validateJson(final T config, final AirbyteConfig configType) throws JsonValidationException { final JsonNode schema = JsonSchemaValidator.getSchema(configType.getConfigSchemaFile()); schemaValidator.ensure(schema, Jsons.jsonNode(config)); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistenceTest.java deleted file mode 100644 index bed2281812cd..000000000000 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ClassEnforcingConfigPersistenceTest.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2022 Airbyte, Inc., all rights reserved. - */ - -package io.airbyte.config.persistence; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -import com.google.common.collect.ImmutableMap; -import io.airbyte.config.AirbyteConfig; -import io.airbyte.config.ConfigSchema; -import io.airbyte.config.ConfigWithMetadata; -import io.airbyte.config.StandardSync; -import io.airbyte.config.StandardWorkspace; -import io.airbyte.validation.json.JsonValidationException; -import java.io.IOException; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.stubbing.Answer; - -class ClassEnforcingConfigPersistenceTest { - - public static final UUID UUID1 = UUID.randomUUID(); - public static final Instant INSTANT = Instant.now(); - public static final StandardWorkspace WORKSPACE = new StandardWorkspace(); - public static final StandardSync STANDARD_SYNC = new StandardSync().withConnectionId(UUID1); - - private ClassEnforcingConfigPersistence configPersistence; - private ConfigPersistence decoratedConfigPersistence; - - @BeforeEach - void setUp() { - decoratedConfigPersistence = mock(ConfigPersistence.class); - configPersistence = new ClassEnforcingConfigPersistence(decoratedConfigPersistence); - } - - @Test - void testWriteConfigSuccess() throws IOException, JsonValidationException { - configPersistence.writeConfig(ConfigSchema.STANDARD_SYNC, UUID1.toString(), STANDARD_SYNC); - verify(decoratedConfigPersistence).writeConfig(ConfigSchema.STANDARD_SYNC, UUID1.toString(), STANDARD_SYNC); - } - - @Test - void testWriteConfigFailure() { - assertThrows(IllegalArgumentException.class, - () -> configPersistence.writeConfig(ConfigSchema.STANDARD_SYNC, UUID1.toString(), WORKSPACE)); - verifyNoInteractions(decoratedConfigPersistence); - } - - @Test - void testWriteConfigsSuccess() throws IOException, JsonValidationException { - final Map configs = ImmutableMap.of(UUID1.toString(), STANDARD_SYNC); - configPersistence.writeConfigs(ConfigSchema.STANDARD_SYNC, configs); - verify(decoratedConfigPersistence).writeConfigs(ConfigSchema.STANDARD_SYNC, configs); - } - - @Test - void testWriteConfigsFailure() { - final Map configs = ImmutableMap.of(UUID1.toString(), WORKSPACE); - assertThrows(IllegalArgumentException.class, () -> configPersistence.writeConfigs(ConfigSchema.STANDARD_SYNC, configs)); - verifyNoInteractions(decoratedConfigPersistence); - } - - @Test - void testGetConfigSuccess() throws IOException, JsonValidationException, ConfigNotFoundException { - when(decoratedConfigPersistence.getConfig(ConfigSchema.STANDARD_SYNC, UUID1.toString(), StandardSync.class)) - .thenReturn(STANDARD_SYNC); - assertEquals(STANDARD_SYNC, configPersistence.getConfig(ConfigSchema.STANDARD_SYNC, UUID1.toString(), StandardSync.class)); - } - - @Test - void testGetConfigFailure() { - assertThrows(IllegalArgumentException.class, - () -> configPersistence.getConfig(ConfigSchema.STANDARD_SYNC, UUID1.toString(), StandardWorkspace.class)); - verifyNoInteractions(decoratedConfigPersistence); - } - - @Test - void testListConfigsSuccess() throws IOException, JsonValidationException { - when(decoratedConfigPersistence.listConfigs(ConfigSchema.STANDARD_SYNC, StandardSync.class)).thenReturn(List.of(STANDARD_SYNC)); - assertEquals(List.of(STANDARD_SYNC), configPersistence.listConfigs(ConfigSchema.STANDARD_SYNC, StandardSync.class)); - } - - @Test - void testListConfigsFailure() { - assertThrows(IllegalArgumentException.class, - () -> configPersistence.listConfigs(ConfigSchema.STANDARD_SYNC, StandardWorkspace.class)); - verifyNoInteractions(decoratedConfigPersistence); - } - - @Test - void testGetConfigWithMetadataSuccess() throws IOException, JsonValidationException, ConfigNotFoundException { - when(decoratedConfigPersistence.getConfigWithMetadata(ConfigSchema.STANDARD_SYNC, UUID1.toString(), StandardSync.class)) - .thenReturn(withMetadata(STANDARD_SYNC)); - assertEquals(withMetadata(STANDARD_SYNC), - configPersistence.getConfigWithMetadata(ConfigSchema.STANDARD_SYNC, UUID1.toString(), StandardSync.class)); - } - - @Test - void testGetConfigWithMetadataFailure() { - assertThrows(IllegalArgumentException.class, - () -> configPersistence.getConfigWithMetadata(ConfigSchema.STANDARD_SYNC, UUID1.toString(), StandardWorkspace.class)); - verifyNoInteractions(decoratedConfigPersistence); - } - - @Test - void testListConfigsWithMetadataSuccess() throws IOException, JsonValidationException { - when(decoratedConfigPersistence.listConfigsWithMetadata(ConfigSchema.STANDARD_SYNC, StandardSync.class)) - .thenReturn(List.of(withMetadata(STANDARD_SYNC))); - assertEquals( - List.of(withMetadata(STANDARD_SYNC)), - configPersistence.listConfigsWithMetadata(ConfigSchema.STANDARD_SYNC, StandardSync.class)); - } - - @Test - void testListConfigsWithMetadataFailure() { - assertThrows(IllegalArgumentException.class, - () -> configPersistence.listConfigsWithMetadata(ConfigSchema.STANDARD_SYNC, StandardWorkspace.class)); - verifyNoInteractions(decoratedConfigPersistence); - } - - @Test - void testReplaceAllConfigsSuccess() throws IOException, JsonValidationException { - consumeConfigInputStreams(decoratedConfigPersistence); - final Map> configs = ImmutableMap.of(ConfigSchema.STANDARD_SYNC, Stream.of(STANDARD_SYNC)); - configPersistence.replaceAllConfigs(configs, false); - verify(decoratedConfigPersistence).replaceAllConfigs(any(), eq(false)); - } - - @Test - void testReplaceAllConfigsFailure() throws IOException { - consumeConfigInputStreams(decoratedConfigPersistence); - final Map> configs = ImmutableMap.of(ConfigSchema.STANDARD_SYNC, Stream.of(WORKSPACE)); - assertThrows(IllegalArgumentException.class, () -> configPersistence.replaceAllConfigs(configs, false)); - verify(decoratedConfigPersistence).replaceAllConfigs(any(), eq(false)); - } - - /** - * Consumes all streams input via replaceAllConfigs. This will trigger any exceptions that are - * thrown during processing. - * - * @param configPersistence - config persistence mock where this runs. - */ - private static void consumeConfigInputStreams(final ConfigPersistence configPersistence) throws IOException { - doAnswer((Answer) invocation -> { - final Map> argument = invocation.getArgument(0); - // force the streams to be consumed so that we can verify the exception was thrown. - argument.values().forEach(entry -> entry.collect(Collectors.toList())); - return null; - }).when(configPersistence).replaceAllConfigs(any(), eq(false)); - } - - @SuppressWarnings("SameParameterValue") - private static ConfigWithMetadata withMetadata(final StandardSync actorDefinition) { - return new ConfigWithMetadata<>(actorDefinition.getConnectionId().toString(), - ConfigSchema.STANDARD_SYNC.name(), - INSTANT, - INSTANT, - actorDefinition); - } - -} diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index 77c8b231a738..dac71a7f4a2d 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -12,7 +12,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import io.airbyte.commons.json.Jsons; @@ -32,7 +31,6 @@ import io.airbyte.config.StandardWorkspace; import io.airbyte.config.persistence.ConfigRepository.DestinationAndDefinition; import io.airbyte.config.persistence.ConfigRepository.SourceAndDefinition; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.Database; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.FlywayFactory; @@ -78,7 +76,6 @@ class ConfigRepositoryE2EReadWriteTest { private Database database; private ConfigRepository configRepository; private DatabaseConfigPersistence configPersistence; - private JsonSecretsProcessor jsonSecretsProcessor; private Flyway flyway; private final static String DOCKER_IMAGE_TAG = "1.2.0"; private final static String CONFIG_HASH = "ConfigHash"; @@ -96,11 +93,10 @@ public static void dbSetup() { void setup() throws IOException, JsonValidationException, SQLException, DatabaseInitializationException, InterruptedException { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, + flyway = FlywayFactory.create(dataSource, ConfigRepositoryE2EReadWriteTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); - jsonSecretsProcessor = mock(JsonSecretsProcessor.class); - configPersistence = spy(new DatabaseConfigPersistence(database, jsonSecretsProcessor)); + configPersistence = spy(new DatabaseConfigPersistence(database)); configRepository = spy(new ConfigRepository(configPersistence, database)); final ConfigsDatabaseMigrator configsDatabaseMigrator = new ConfigsDatabaseMigrator(database, flyway); @@ -160,8 +156,8 @@ void testWorkspaceCountConnectionsDeprecated() throws IOException { @Test void testFetchActorsUsingDefinition() throws IOException { - UUID destinationDefinitionId = MockData.publicDestinationDefinition().getDestinationDefinitionId(); - UUID sourceDefinitionId = MockData.publicSourceDefinition().getSourceDefinitionId(); + final UUID destinationDefinitionId = MockData.publicDestinationDefinition().getDestinationDefinitionId(); + final UUID sourceDefinitionId = MockData.publicSourceDefinition().getSourceDefinitionId(); final List destinationConnections = configRepository.listDestinationsForDefinition( destinationDefinitionId); final List sourceConnections = configRepository.listSourcesForDefinition( @@ -315,7 +311,7 @@ void testListPublicSourceDefinitions() throws IOException { @Test void testListWorkspaceSources() throws IOException { - UUID workspaceId = MockData.standardWorkspaces().get(1).getWorkspaceId(); + final UUID workspaceId = MockData.standardWorkspaces().get(1).getWorkspaceId(); final List expectedSources = MockData.sourceConnections().stream() .filter(source -> source.getWorkspaceId().equals(workspaceId)).collect(Collectors.toList()); final List sources = configRepository.listWorkspaceSourceConnection(workspaceId); @@ -324,7 +320,7 @@ void testListWorkspaceSources() throws IOException { @Test void testListWorkspaceDestinations() throws IOException { - UUID workspaceId = MockData.standardWorkspaces().get(0).getWorkspaceId(); + final UUID workspaceId = MockData.standardWorkspaces().get(0).getWorkspaceId(); final List expectedDestinations = MockData.destinationConnections().stream() .filter(destination -> destination.getWorkspaceId().equals(workspaceId)).collect(Collectors.toList()); final List destinations = configRepository.listWorkspaceDestinationConnection(workspaceId); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceE2EReadWriteTest.java index c9001b062421..604859718181 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceE2EReadWriteTest.java @@ -47,10 +47,10 @@ class DatabaseConfigPersistenceE2EReadWriteTest extends BaseDatabaseConfigPersis void setup() throws Exception { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, + flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceE2EReadWriteTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); - configPersistence = spy(new DatabaseConfigPersistence(database, jsonSecretsProcessor)); + configPersistence = spy(new DatabaseConfigPersistence(database)); final ConfigsDatabaseMigrator configsDatabaseMigrator = new ConfigsDatabaseMigrator(database, flyway); final DevDatabaseMigrator devDatabaseMigrator = new DevDatabaseMigrator(configsDatabaseMigrator); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceTest.java index ef27d3a6f4d1..177b79441871 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceTest.java @@ -13,17 +13,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Lists; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigWithMetadata; import io.airbyte.config.StandardDestinationDefinition; import io.airbyte.config.StandardSourceDefinition; @@ -36,7 +30,6 @@ import io.airbyte.db.instance.configs.ConfigsDatabaseTestProvider; import io.airbyte.db.instance.development.DevDatabaseMigrator; import io.airbyte.db.instance.development.MigrationDevHelper; -import io.airbyte.protocol.models.ConnectorSpecification; import io.airbyte.test.utils.DatabaseConnectionHelper; import java.time.Duration; import java.time.Instant; @@ -47,16 +40,14 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.jooq.SQLDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** - * See {@link DatabaseConfigPersistenceLoadDataTest} and - * {@link DatabaseConfigPersistenceUpdateConnectorDefinitionsTest} for testing of specific methods. + * See {@link DatabaseConfigPersistenceUpdateConnectorDefinitionsTest} for testing of specific + * methods. */ @SuppressWarnings({"PMD.SignatureDeclareThrowsException", "PMD.ShortVariable", "PMD.JUnitTestsShouldIncludeAssert"}) class DatabaseConfigPersistenceTest extends BaseDatabaseConfigPersistenceTest { @@ -65,10 +56,10 @@ class DatabaseConfigPersistenceTest extends BaseDatabaseConfigPersistenceTest { public void setup() throws Exception { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, + flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); - configPersistence = spy(new DatabaseConfigPersistence(database, jsonSecretsProcessor)); + configPersistence = spy(new DatabaseConfigPersistence(database)); final ConfigsDatabaseMigrator configsDatabaseMigrator = new ConfigsDatabaseMigrator(database, flyway); final DevDatabaseMigrator devDatabaseMigrator = new DevDatabaseMigrator(configsDatabaseMigrator); @@ -156,58 +147,6 @@ void testDeleteConfig() throws Exception { .hasSameElementsAs(List.of(DESTINATION_SNOWFLAKE)); } - @Test - void testReplaceAllConfigs() throws Exception { - writeDestination(configPersistence, DESTINATION_S3); - writeDestination(configPersistence, DESTINATION_SNOWFLAKE); - - final Map> newConfigs = Map.of(STANDARD_SOURCE_DEFINITION, Stream.of(SOURCE_GITHUB, SOURCE_POSTGRES)); - - configPersistence.replaceAllConfigs(newConfigs, true); - - // dry run does not change anything - assertRecordCount(2, ACTOR_DEFINITION); - assertHasDestination(DESTINATION_S3); - assertHasDestination(DESTINATION_SNOWFLAKE); - - configPersistence.replaceAllConfigs(newConfigs, false); - assertRecordCount(2, ACTOR_DEFINITION); - assertHasSource(SOURCE_GITHUB); - assertHasSource(SOURCE_POSTGRES); - } - - @Test - void testDumpConfigs() throws Exception { - writeSource(configPersistence, SOURCE_GITHUB); - writeSource(configPersistence, SOURCE_POSTGRES); - writeDestination(configPersistence, DESTINATION_S3); - final Map> actual = configPersistence.dumpConfigs(); - final Map> expected = Map.of( - STANDARD_SOURCE_DEFINITION.name(), Stream.of(Jsons.jsonNode(SOURCE_GITHUB), Jsons.jsonNode(SOURCE_POSTGRES)), - STANDARD_DESTINATION_DEFINITION.name(), Stream.of(Jsons.jsonNode(DESTINATION_S3))); - assertSameConfigDump(expected, actual); - } - - @Test - void testDumpConfigsWithoutSecret() throws Exception { - final ConnectorSpecification mockedConnectorSpec = new ConnectorSpecification() - .withConnectionSpecification( - Jsons.emptyObject()); - doReturn(new StandardDestinationDefinition() - .withSpec(mockedConnectorSpec)).when(configPersistence).getConfig(eq(STANDARD_DESTINATION_DEFINITION), any(), any()); - doReturn(new StandardSourceDefinition() - .withSpec(mockedConnectorSpec)).when(configPersistence).getConfig(eq(STANDARD_SOURCE_DEFINITION), any(), any()); - - writeSourceWithSourceConnection(configPersistence, SOURCE_GITHUB); - writeSourceWithSourceConnection(configPersistence, SOURCE_POSTGRES); - writeDestinationWithDestinationConnection(configPersistence, DESTINATION_S3); - final Map> result = configPersistence.dumpConfigs(); - result.values().forEach(stream -> { - stream.collect(Collectors.toList()); - }); - verify(jsonSecretsProcessor, times(3)).prepareSecretsForOutput(any(), any()); - } - @Test void testGetConnectorRepositoryToInfoMap() throws Exception { final String connectorRepository = "airbyte/duplicated-connector"; diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceUpdateConnectorDefinitionsTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceUpdateConnectorDefinitionsTest.java index abd6af47e6bb..4bea431aa5a0 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceUpdateConnectorDefinitionsTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/DatabaseConfigPersistenceUpdateConnectorDefinitionsTest.java @@ -50,10 +50,11 @@ class DatabaseConfigPersistenceUpdateConnectorDefinitionsTest extends BaseDataba public static void setup() throws Exception { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, + flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceUpdateConnectorDefinitionsTest.class.getName(), + ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); - configPersistence = new DatabaseConfigPersistence(database, jsonSecretsProcessor); + configPersistence = new DatabaseConfigPersistence(database); final ConfigsDatabaseMigrator configsDatabaseMigrator = new ConfigsDatabaseMigrator(database, flyway); final DevDatabaseMigrator devDatabaseMigrator = new DevDatabaseMigrator(configsDatabaseMigrator); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryReaderTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryReaderTest.java index 4de5cc6e30b3..07f1eb915180 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryReaderTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryReaderTest.java @@ -13,10 +13,8 @@ import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.SourceConnection; -import io.airbyte.config.StandardWorkspace; import io.airbyte.config.WorkspaceServiceAccount; import io.airbyte.config.persistence.split_secrets.MemorySecretPersistence; import io.airbyte.config.persistence.split_secrets.RealSecretsHydrator; @@ -25,14 +23,10 @@ import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -101,30 +95,6 @@ void testListDestinationsWithSecrets() throws JsonValidationException, IOExcepti assertEquals(List.of(DESTINATION_WITH_FULL_CONFIG), secretsRepositoryReader.listDestinationConnectionWithSecrets()); } - @Test - void testDumpConfigsWithSecrets() throws IOException { - secretPersistence.write(COORDINATE, SECRET); - final StandardWorkspace workspace = new StandardWorkspace().withWorkspaceId(UUID.randomUUID()); - - final Map> dumpFromConfigRepository = new HashMap<>(); - dumpFromConfigRepository.put(ConfigSchema.STANDARD_WORKSPACE.name(), Stream.of(Jsons.jsonNode(workspace))); - dumpFromConfigRepository.put(ConfigSchema.SOURCE_CONNECTION.name(), Stream.of(Jsons.jsonNode(SOURCE_WITH_PARTIAL_CONFIG))); - dumpFromConfigRepository.put(ConfigSchema.DESTINATION_CONNECTION.name(), Stream.of(Jsons.jsonNode(DESTINATION_WITH_PARTIAL_CONFIG))); - when(configRepository.dumpConfigsNoSecrets()).thenReturn(dumpFromConfigRepository); - - final Map> expected = new HashMap<>(); - expected.put(ConfigSchema.STANDARD_WORKSPACE.name(), List.of(Jsons.jsonNode(workspace))); - expected.put(ConfigSchema.SOURCE_CONNECTION.name(), List.of(Jsons.jsonNode(SOURCE_WITH_FULL_CONFIG))); - expected.put(ConfigSchema.DESTINATION_CONNECTION.name(), List.of(Jsons.jsonNode(DESTINATION_WITH_FULL_CONFIG))); - - final Map> actual = secretsRepositoryReader.dumpConfigsWithSecrets() - .entrySet() - .stream() - .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().collect(Collectors.toList()))); - - assertEquals(expected, actual); - } - @Test void testReadingServiceAccount() throws JsonValidationException, ConfigNotFoundException, IOException { final ConfigRepository configRepository = mock(ConfigRepository.class); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java index 1ba17f029c73..92e55cb8c134 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/SecretsRepositoryWriterTest.java @@ -15,7 +15,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -26,9 +25,7 @@ import static org.mockito.Mockito.when; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.airbyte.commons.json.Jsons; -import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigSchema; import io.airbyte.config.DestinationConnection; import io.airbyte.config.Geography; @@ -48,14 +45,10 @@ import io.airbyte.validation.json.JsonValidationException; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -239,43 +232,6 @@ void testStatefulSplitEphemeralSecrets() throws JsonValidationException, IOExcep assertEquals(SOURCE_WITH_FULL_CONFIG.getConfiguration(), ephemeralSecretsHydrator.hydrate(split)); } - @SuppressWarnings("unchecked") - @Test - void testReplaceAllConfigs() throws IOException { - final Map> configs = new HashMap<>(); - configs.put(ConfigSchema.STANDARD_SOURCE_DEFINITION, Stream.of(Jsons.clone(SOURCE_DEF))); - configs.put(ConfigSchema.STANDARD_DESTINATION_DEFINITION, Stream.of(Jsons.clone(DEST_DEF))); - configs.put(ConfigSchema.SOURCE_CONNECTION, Stream.of(Jsons.clone(SOURCE_WITH_FULL_CONFIG))); - configs.put(ConfigSchema.DESTINATION_CONNECTION, Stream.of(Jsons.clone(DESTINATION_WITH_FULL_CONFIG))); - - secretsRepositoryWriter.replaceAllConfigs(configs, false); - - final ArgumentCaptor>> argument = ArgumentCaptor.forClass(Map.class); - verify(configRepository).replaceAllConfigsNoSecrets(argument.capture(), eq(false)); - final Map> actual = argument.getValue().entrySet() - .stream() - .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().collect(Collectors.toList()))); - - assertEquals(SOURCE_DEF, actual.get(ConfigSchema.STANDARD_SOURCE_DEFINITION).get(0)); - assertEquals(DEST_DEF, actual.get(ConfigSchema.STANDARD_DESTINATION_DEFINITION).get(0)); - - // we can't easily get the pointer, so verify the secret has been stripped out and then make sure - // the rest of the object meets expectations. - final SourceConnection actualSource = (SourceConnection) actual.get(ConfigSchema.SOURCE_CONNECTION).get(0); - assertTrue(actualSource.getConfiguration().get(PASSWORD_PROPERTY_NAME).has(PASSWORD_FIELD_NAME)); - ((ObjectNode) actualSource.getConfiguration()).remove(PASSWORD_PROPERTY_NAME); - final SourceConnection expectedSource = Jsons.clone(SOURCE_WITH_FULL_CONFIG); - ((ObjectNode) expectedSource.getConfiguration()).remove(PASSWORD_PROPERTY_NAME); - assertEquals(expectedSource, actualSource); - - final DestinationConnection actualDest = (DestinationConnection) actual.get(ConfigSchema.DESTINATION_CONNECTION).get(0); - assertTrue(actualDest.getConfiguration().get(PASSWORD_PROPERTY_NAME).has(PASSWORD_FIELD_NAME)); - ((ObjectNode) actualDest.getConfiguration()).remove(PASSWORD_PROPERTY_NAME); - final DestinationConnection expectedDest = Jsons.clone(DESTINATION_WITH_FULL_CONFIG); - ((ObjectNode) expectedDest.getConfiguration()).remove(PASSWORD_PROPERTY_NAME); - assertEquals(expectedDest, actualDest); - } - // this only works if the secrets store has one secret. private SecretCoordinate getCoordinateFromSecretsStore(final MemorySecretPersistence secretPersistence) { return secretPersistence.getMap() diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java index 3901488e1454..88357923de47 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java @@ -4,8 +4,6 @@ package io.airbyte.config.persistence; -import static org.mockito.Mockito.mock; - import com.fasterxml.jackson.databind.JsonNode; import io.airbyte.commons.enums.Enums; import io.airbyte.commons.json.Jsons; @@ -18,7 +16,6 @@ import io.airbyte.config.State; import io.airbyte.config.StateType; import io.airbyte.config.StateWrapper; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.FlywayFactory; import io.airbyte.db.init.DatabaseInitializationException; @@ -530,7 +527,7 @@ void testStatePersistenceLegacyWriteConsistency() throws IOException { void beforeEach() throws DatabaseInitializationException, IOException, JsonValidationException { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), + flyway = FlywayFactory.create(dataSource, StatePersistenceTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(true); setupTestData(); @@ -547,9 +544,7 @@ void afterEach() { } private void setupTestData() throws JsonValidationException, IOException { - configRepository = new ConfigRepository( - new DatabaseConfigPersistence(database, mock(JsonSecretsProcessor.class)), - database); + configRepository = new ConfigRepository(new DatabaseConfigPersistence(database), database); final StandardWorkspace workspace = MockData.standardWorkspaces().get(0); final StandardSourceDefinition sourceDefinition = MockData.publicSourceDefinition(); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StreamResetPersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StreamResetPersistenceTest.java index 95f703b37c05..f121051430a9 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StreamResetPersistenceTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StreamResetPersistenceTest.java @@ -36,7 +36,7 @@ class StreamResetPersistenceTest extends BaseDatabaseConfigPersistenceTest { public void setup() throws Exception { dataSource = DatabaseConnectionHelper.createDataSource(container); dslContext = DSLContextFactory.create(dataSource, SQLDialect.POSTGRES); - flyway = FlywayFactory.create(dataSource, DatabaseConfigPersistenceLoadDataTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, + flyway = FlywayFactory.create(dataSource, StreamResetPersistenceTest.class.getName(), ConfigsDatabaseMigrator.DB_IDENTIFIER, ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); streamResetPersistence = spy(new StreamResetPersistence(database)); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ValidatingConfigPersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ValidatingConfigPersistenceTest.java index ec573c1c23ea..a07c5a808213 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ValidatingConfigPersistenceTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ValidatingConfigPersistenceTest.java @@ -7,17 +7,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; -import io.airbyte.config.AirbyteConfig; import io.airbyte.config.ConfigSchema; import io.airbyte.config.ConfigWithMetadata; import io.airbyte.config.StandardSourceDefinition; @@ -29,11 +25,8 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.stubbing.Answer; class ValidatingConfigPersistenceTest { @@ -196,40 +189,6 @@ void testListConfigsWithMetadataFailure() throws JsonValidationException, IOExce .listConfigsWithMetadata(ConfigSchema.STANDARD_SOURCE_DEFINITION, StandardSourceDefinition.class)); } - @Test - void testReplaceAllConfigsSuccess() throws IOException, JsonValidationException { - consumeConfigInputStreams(decoratedConfigPersistence); - final Map> configs = ImmutableMap.of(ConfigSchema.STANDARD_SOURCE_DEFINITION, Stream.of(SOURCE_1)); - configPersistence.replaceAllConfigs(configs, false); - verify(decoratedConfigPersistence).replaceAllConfigs(any(), eq(false)); - } - - @Test - void testReplaceAllConfigsFailure() throws IOException, JsonValidationException { - doThrow(new JsonValidationException(ERROR_MESSAGE)).when(schemaValidator).ensure(any(), any()); - consumeConfigInputStreams(decoratedConfigPersistence); - final Map> configs = ImmutableMap.of(ConfigSchema.STANDARD_SOURCE_DEFINITION, Stream.of(SOURCE_1)); - // because this takes place in a lambda the JsonValidationException gets rethrown as a - // RuntimeException. - assertThrows(RuntimeException.class, () -> configPersistence.replaceAllConfigs(configs, false)); - verify(decoratedConfigPersistence).replaceAllConfigs(any(), eq(false)); - } - - /** - * Consumes all streams input via replaceAllConfigs. This will trigger any exceptions that are - * thrown during processing. - * - * @param configPersistence - config persistence mock where this runs. - */ - private static void consumeConfigInputStreams(final ConfigPersistence configPersistence) throws IOException { - doAnswer((Answer) invocation -> { - final Map> argument = invocation.getArgument(0); - // force the streams to be consumed so that we can verify the exception was thrown. - argument.values().forEach(entry -> entry.collect(Collectors.toList())); - return null; - }).when(configPersistence).replaceAllConfigs(any(), eq(false)); - } - private static ConfigWithMetadata withMetadata(final StandardSourceDefinition sourceDef) { return new ConfigWithMetadata<>(sourceDef.getSourceDefinitionId().toString(), ConfigSchema.STANDARD_SOURCE_DEFINITION.name(), diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java b/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java index d3ecc3a2de9d..4def28600908 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java @@ -70,7 +70,7 @@ public Flyway configFlyway(@Named("config") final FlywayConfigurationProperties @Singleton public ConfigPersistence configPersistence(@Named("configDatabase") final Database configDatabase, final JsonSecretsProcessor jsonSecretsProcessor) { - return DatabaseConfigPersistence.createWithValidation(configDatabase, jsonSecretsProcessor); + return DatabaseConfigPersistence.createWithValidation(configDatabase); } @Singleton diff --git a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java index 8ec8ac60b824..902519c1d77f 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java +++ b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java @@ -28,7 +28,6 @@ import io.airbyte.config.persistence.SecretsRepositoryReader; import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.StreamResetPersistence; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.config.persistence.split_secrets.SecretPersistence; import io.airbyte.config.persistence.split_secrets.SecretsHydrator; import io.airbyte.db.Database; @@ -190,10 +189,7 @@ public static ServerRunnable getServer(final ServerFactory apiFactory, LOGGER.info("Creating config repository..."); final Database configsDatabase = new Database(configsDslContext); - final JsonSecretsProcessor jsonSecretsProcessor = JsonSecretsProcessor.builder() - .copySecrets(false) - .build(); - final ConfigPersistence configPersistence = DatabaseConfigPersistence.createWithValidation(configsDatabase, jsonSecretsProcessor); + final ConfigPersistence configPersistence = DatabaseConfigPersistence.createWithValidation(configsDatabase); final SecretsHydrator secretsHydrator = SecretPersistence.getSecretsHydrator(configsDslContext, configs); final Optional secretPersistence = SecretPersistence.getLongLived(configsDslContext, configs); final Optional ephemeralSecretPersistence = SecretPersistence.getEphemeral(configsDslContext, configs); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java index 6f91a8de5931..e3e240b23dea 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java @@ -92,7 +92,7 @@ public Flyway jobsFlyway(@Named("jobs") final FlywayConfigurationProperties jobs @Requires(env = WorkerMode.CONTROL_PLANE) public ConfigPersistence configPersistence(@Named("configDatabase") final Database configDatabase, final JsonSecretsProcessor jsonSecretsProcessor) { - return DatabaseConfigPersistence.createWithValidation(configDatabase, jsonSecretsProcessor); + return DatabaseConfigPersistence.createWithValidation(configDatabase); } @Singleton From 87a0a2a27c6bedc9314d3a6fb762083e66a116b9 Mon Sep 17 00:00:00 2001 From: Charles Date: Tue, 1 Nov 2022 16:51:09 -0700 Subject: [PATCH 482/498] hide ConfigPersistence inside ConfigRepository to discourage use (#18803) --- .../io/airbyte/bootloader/BootloaderApp.java | 11 ++------- .../airbyte/bootloader/BootloaderAppTest.java | 5 ++-- .../config/persistence/ConfigPersistence.java | 2 +- .../config/persistence/ConfigRepository.java | 24 +++++++++++++++++-- .../DatabaseConfigPersistence.java | 1 + .../ValidatingConfigPersistence.java | 1 + .../ConfigRepositoryE2EReadWriteTest.java | 3 ++- .../persistence/ConfigRepositoryTest.java | 3 ++- .../persistence/StatePersistenceTest.java | 6 ++++- .../cron/config/DatabaseBeanFactory.java | 14 ++--------- .../java/io/airbyte/server/ServerApp.java | 5 +--- .../workers/config/DatabaseBeanFactory.java | 15 ++---------- 12 files changed, 43 insertions(+), 47 deletions(-) diff --git a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java index 5d6d5067f983..9d3d8cfac7b2 100644 --- a/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java +++ b/airbyte-bootloader/src/main/java/io/airbyte/bootloader/BootloaderApp.java @@ -18,9 +18,7 @@ import io.airbyte.config.init.DefinitionsProvider; import io.airbyte.config.init.LocalDefinitionsProvider; import io.airbyte.config.persistence.ConfigNotFoundException; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; -import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.SecretsRepositoryReader; import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.split_secrets.SecretPersistence; @@ -193,10 +191,6 @@ private static Database getConfigDatabase(final DSLContext dslContext) throws IO return new Database(dslContext); } - private static ConfigPersistence getConfigPersistence(final Database configDatabase) throws IOException { - return DatabaseConfigPersistence.createWithValidation(configDatabase); - } - private static DefinitionsProvider getLocalDefinitionsProvider() throws IOException { return new LocalDefinitionsProvider(LocalDefinitionsProvider.DEFAULT_SEED_DEFINITION_RESOURCE_CLASS); } @@ -212,7 +206,7 @@ private static JobPersistence getJobPersistence(final Database jobDatabase) thro private void initPersistences(final DSLContext configsDslContext, final DSLContext jobsDslContext) { try { configDatabase = getConfigDatabase(configsDslContext); - configRepository = new ConfigRepository(getConfigPersistence(configDatabase), configDatabase); + configRepository = new ConfigRepository(configDatabase); localDefinitionsProvider = getLocalDefinitionsProvider(); jobDatabase = getJobDatabase(jobsDslContext); jobPersistence = getJobPersistence(jobDatabase); @@ -236,8 +230,7 @@ public static void main(final String[] args) throws Exception { // TODO Will be converted to an injected singleton during DI migration final Database configDatabase = getConfigDatabase(configsDslContext); - final ConfigPersistence configPersistence = getConfigPersistence(configDatabase); - final ConfigRepository configRepository = new ConfigRepository(configPersistence, configDatabase); + final ConfigRepository configRepository = new ConfigRepository(configDatabase); final Database jobDatabase = getJobDatabase(jobsDslContext); final JobPersistence jobPersistence = getJobPersistence(jobDatabase); diff --git a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java index ecd6cf516ade..c9697aa427ae 100644 --- a/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java +++ b/airbyte-bootloader/src/test/java/io/airbyte/bootloader/BootloaderAppTest.java @@ -27,7 +27,6 @@ import io.airbyte.config.init.DefinitionsProvider; import io.airbyte.config.init.LocalDefinitionsProvider; import io.airbyte.config.persistence.ConfigRepository; -import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.SecretsRepositoryReader; import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.split_secrets.LocalTestingSecretPersistence; @@ -181,8 +180,7 @@ void testBootloaderAppRunSecretMigration() throws Exception { val configDatabase = new ConfigsDatabaseTestProvider(configsDslContext, configsFlyway).create(false); val jobDatabase = new JobsDatabaseTestProvider(jobsDslContext, jobsFlyway).create(false); - val configPersistence = new DatabaseConfigPersistence(configDatabase); - val configRepository = new ConfigRepository(configPersistence, configDatabase); + val configRepository = new ConfigRepository(configDatabase); val jobsPersistence = new DefaultJobPersistence(jobDatabase); val secretsPersistence = SecretPersistence.getLongLived(configsDslContext, mockedConfigs); @@ -236,6 +234,7 @@ void testBootloaderAppRunSecretMigration() throws Exception { .withSourceId(sourceId) .withName("test source") .withWorkspaceId(workspaceId) + .withTombstone(false) .withConfiguration(mapper.readTree(sourceSpecs))); when(mockedFeatureFlags.forceSecretMigration()).thenReturn(false); diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java index 9849c4ed7b65..abfdbe9b7136 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigPersistence.java @@ -14,7 +14,7 @@ /** * We are moving migrating away from this interface entirely. Use ConfigRepository instead. */ -@Deprecated +@Deprecated(forRemoval = true) public interface ConfigPersistence { T getConfig(AirbyteConfig configType, String configId, Class clazz) throws ConfigNotFoundException, JsonValidationException, IOException; diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java index eca206641691..24f2090cf866 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ConfigRepository.java @@ -93,8 +93,8 @@ public class ConfigRepository { private final ExceptionWrappingDatabase database; private final ActorDefinitionMigrator actorDefinitionMigrator; - public ConfigRepository(final ConfigPersistence persistence, final Database database) { - this(persistence, database, new ActorDefinitionMigrator(new ExceptionWrappingDatabase(database))); + public ConfigRepository(final Database database) { + this(DatabaseConfigPersistence.createWithValidation(database), database, new ActorDefinitionMigrator(new ExceptionWrappingDatabase(database))); } @VisibleForTesting @@ -579,6 +579,16 @@ public void writeSourceConnectionNoSecrets(final SourceConnection partialSource) persistence.writeConfig(ConfigSchema.SOURCE_CONNECTION, partialSource.getSourceId().toString(), partialSource); } + public boolean deleteSource(final UUID sourceId) throws JsonValidationException, ConfigNotFoundException, IOException { + try { + getSourceConnection(sourceId); + persistence.deleteConfig(ConfigSchema.SOURCE_CONNECTION, sourceId.toString()); + return true; + } catch (final ConfigNotFoundException e) { + return false; + } + } + /** * Returns all sources in the database. Does not contain secrets. To hydrate with secrets see * { @link SecretsRepositoryReader#listSourceConnectionWithSecrets() }. @@ -638,6 +648,16 @@ public void writeDestinationConnectionNoSecrets(final DestinationConnection part persistence.writeConfig(ConfigSchema.DESTINATION_CONNECTION, partialDestination.getDestinationId().toString(), partialDestination); } + public boolean deleteDestination(final UUID destId) throws JsonValidationException, ConfigNotFoundException, IOException { + try { + getDestinationConnection(destId); + persistence.deleteConfig(ConfigSchema.DESTINATION_CONNECTION, destId.toString()); + return true; + } catch (final ConfigNotFoundException e) { + return false; + } + } + /** * Returns all destinations in the database. Does not contain secrets. To hydrate with secrets see * { @link SecretsRepositoryReader#listDestinationConnectionWithSecrets() }. diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java index 68b2469306d8..8313465964c7 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/DatabaseConfigPersistence.java @@ -79,6 +79,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@Deprecated(forRemoval = true) @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.CognitiveComplexity", "PMD.NPathComplexity", "PMD.ExcessiveMethodLength", "PMD.AvoidThrowingRawExceptionTypes", "PMD.ShortVariable", "PMD.LongVariable", "PMD.ExcessiveClassLength", "PMD.AvoidLiteralsInIfCondition"}) public class DatabaseConfigPersistence implements ConfigPersistence { diff --git a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java index 0e2dcbac7f9b..c29e15dbb453 100644 --- a/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java +++ b/airbyte-config/config-persistence/src/main/java/io/airbyte/config/persistence/ValidatingConfigPersistence.java @@ -19,6 +19,7 @@ * Validates that json input and outputs for the ConfigPersistence against their schemas. */ @SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes") +@Deprecated(forRemoval = true) public class ValidatingConfigPersistence implements ConfigPersistence { private final JsonSchemaValidator schemaValidator; diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java index dac71a7f4a2d..c33dbb21ba3b 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryE2EReadWriteTest.java @@ -32,6 +32,7 @@ import io.airbyte.config.persistence.ConfigRepository.DestinationAndDefinition; import io.airbyte.config.persistence.ConfigRepository.SourceAndDefinition; import io.airbyte.db.Database; +import io.airbyte.db.ExceptionWrappingDatabase; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.FlywayFactory; import io.airbyte.db.init.DatabaseInitializationException; @@ -97,7 +98,7 @@ void setup() throws IOException, JsonValidationException, SQLException, Database ConfigsDatabaseMigrator.MIGRATION_FILE_LOCATION); database = new ConfigsDatabaseTestProvider(dslContext, flyway).create(false); configPersistence = spy(new DatabaseConfigPersistence(database)); - configRepository = spy(new ConfigRepository(configPersistence, database)); + configRepository = spy(new ConfigRepository(configPersistence, database, new ActorDefinitionMigrator(new ExceptionWrappingDatabase(database)))); final ConfigsDatabaseMigrator configsDatabaseMigrator = new ConfigsDatabaseMigrator(database, flyway); final DevDatabaseMigrator devDatabaseMigrator = new DevDatabaseMigrator(configsDatabaseMigrator); diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java index d4507005049c..1893b7a193e3 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/ConfigRepositoryTest.java @@ -29,6 +29,7 @@ import io.airbyte.config.StandardWorkspace; import io.airbyte.config.State; import io.airbyte.db.Database; +import io.airbyte.db.ExceptionWrappingDatabase; import io.airbyte.protocol.models.AirbyteStream; import io.airbyte.protocol.models.ConfiguredAirbyteCatalog; import io.airbyte.protocol.models.ConfiguredAirbyteStream; @@ -62,7 +63,7 @@ class ConfigRepositoryTest { void setup() { configPersistence = mock(ConfigPersistence.class); database = mock(Database.class); - configRepository = spy(new ConfigRepository(configPersistence, database)); + configRepository = spy(new ConfigRepository(configPersistence, database, new ActorDefinitionMigrator(new ExceptionWrappingDatabase(database)))); } @AfterEach diff --git a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java index 88357923de47..a3a888f9530d 100644 --- a/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java +++ b/airbyte-config/config-persistence/src/test/java/io/airbyte/config/persistence/StatePersistenceTest.java @@ -16,6 +16,7 @@ import io.airbyte.config.State; import io.airbyte.config.StateType; import io.airbyte.config.StateWrapper; +import io.airbyte.db.ExceptionWrappingDatabase; import io.airbyte.db.factory.DSLContextFactory; import io.airbyte.db.factory.FlywayFactory; import io.airbyte.db.init.DatabaseInitializationException; @@ -544,7 +545,10 @@ void afterEach() { } private void setupTestData() throws JsonValidationException, IOException { - configRepository = new ConfigRepository(new DatabaseConfigPersistence(database), database); + configRepository = new ConfigRepository( + new DatabaseConfigPersistence(database), + database, + new ActorDefinitionMigrator(new ExceptionWrappingDatabase(database))); final StandardWorkspace workspace = MockData.standardWorkspaces().get(0); final StandardSourceDefinition sourceDefinition = MockData.publicSourceDefinition(); diff --git a/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java b/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java index 4def28600908..fc791b1c3e62 100644 --- a/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java +++ b/airbyte-cron/src/main/java/io/airbyte/cron/config/DatabaseBeanFactory.java @@ -5,11 +5,8 @@ package io.airbyte.cron.config; import io.airbyte.commons.temporal.config.WorkerMode; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; -import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.StreamResetPersistence; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.Database; import io.airbyte.db.check.DatabaseMigrationCheck; import io.airbyte.db.factory.DatabaseCheckFactory; @@ -68,15 +65,8 @@ public Flyway configFlyway(@Named("config") final FlywayConfigurationProperties } @Singleton - public ConfigPersistence configPersistence(@Named("configDatabase") final Database configDatabase, - final JsonSecretsProcessor jsonSecretsProcessor) { - return DatabaseConfigPersistence.createWithValidation(configDatabase); - } - - @Singleton - public ConfigRepository configRepository(@Named("configPersistence") final ConfigPersistence configPersistence, - @Named("configDatabase") final Database configDatabase) { - return new ConfigRepository(configPersistence, configDatabase); + public ConfigRepository configRepository(@Named("configDatabase") final Database configDatabase) { + return new ConfigRepository(configDatabase); } @Singleton diff --git a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java index 902519c1d77f..06109721f2f7 100644 --- a/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java +++ b/airbyte-server/src/main/java/io/airbyte/server/ServerApp.java @@ -22,9 +22,7 @@ import io.airbyte.config.StandardSync.Status; import io.airbyte.config.helpers.LogClientSingleton; import io.airbyte.config.persistence.ConfigNotFoundException; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; -import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.SecretsRepositoryReader; import io.airbyte.config.persistence.SecretsRepositoryWriter; import io.airbyte.config.persistence.StreamResetPersistence; @@ -189,11 +187,10 @@ public static ServerRunnable getServer(final ServerFactory apiFactory, LOGGER.info("Creating config repository..."); final Database configsDatabase = new Database(configsDslContext); - final ConfigPersistence configPersistence = DatabaseConfigPersistence.createWithValidation(configsDatabase); final SecretsHydrator secretsHydrator = SecretPersistence.getSecretsHydrator(configsDslContext, configs); final Optional secretPersistence = SecretPersistence.getLongLived(configsDslContext, configs); final Optional ephemeralSecretPersistence = SecretPersistence.getEphemeral(configsDslContext, configs); - final ConfigRepository configRepository = new ConfigRepository(configPersistence, configsDatabase); + final ConfigRepository configRepository = new ConfigRepository(configsDatabase); final SecretsRepositoryReader secretsRepositoryReader = new SecretsRepositoryReader(configRepository, secretsHydrator); final SecretsRepositoryWriter secretsRepositoryWriter = new SecretsRepositoryWriter(configRepository, secretPersistence, ephemeralSecretPersistence); diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java b/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java index e3e240b23dea..d203f2b24ef0 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/config/DatabaseBeanFactory.java @@ -5,12 +5,9 @@ package io.airbyte.workers.config; import io.airbyte.commons.temporal.config.WorkerMode; -import io.airbyte.config.persistence.ConfigPersistence; import io.airbyte.config.persistence.ConfigRepository; -import io.airbyte.config.persistence.DatabaseConfigPersistence; import io.airbyte.config.persistence.StatePersistence; import io.airbyte.config.persistence.StreamResetPersistence; -import io.airbyte.config.persistence.split_secrets.JsonSecretsProcessor; import io.airbyte.db.Database; import io.airbyte.db.check.DatabaseMigrationCheck; import io.airbyte.db.check.impl.JobsDatabaseAvailabilityCheck; @@ -90,16 +87,8 @@ public Flyway jobsFlyway(@Named("jobs") final FlywayConfigurationProperties jobs @Singleton @Requires(env = WorkerMode.CONTROL_PLANE) - public ConfigPersistence configPersistence(@Named("configDatabase") final Database configDatabase, - final JsonSecretsProcessor jsonSecretsProcessor) { - return DatabaseConfigPersistence.createWithValidation(configDatabase); - } - - @Singleton - @Requires(env = WorkerMode.CONTROL_PLANE) - public ConfigRepository configRepository(@Named("configPersistence") final ConfigPersistence configPersistence, - @Named("configDatabase") final Database configDatabase) { - return new ConfigRepository(configPersistence, configDatabase); + public ConfigRepository configRepository(@Named("configDatabase") final Database configDatabase) { + return new ConfigRepository(configDatabase); } @Singleton From 1167d35ce7452976f76af996d8e368107163f9df Mon Sep 17 00:00:00 2001 From: Conor Date: Tue, 1 Nov 2022 20:49:16 -0500 Subject: [PATCH 483/498] ci: add job and run id to test reports (#18832) --- .github/workflows/gradle.yml | 6 +++--- tools/bin/prep_test_results_for_gcs.py | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index fdf484ba6a9f..587d760ae113 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -259,7 +259,7 @@ jobs: - name: Prep Test Results For GCS if: always() run: | - python tools/bin/prep_test_results_for_gcs.py --json connectors_base_results.json + python tools/bin/prep_test_results_for_gcs.py --json connectors_base_results.json --jobid $GITHUB_JOB --runid $GITHUB_RUN_ID - name: Upload Test Results to GCS if: always() @@ -582,7 +582,7 @@ jobs: - name: Prep Test Results For GCS if: always() run: | - python tools/bin/prep_test_results_for_gcs.py --json platform_results.json + python tools/bin/prep_test_results_for_gcs.py --json platform_results.json --jobid $GITHUB_JOB --runid $GITHUB_RUN_ID - name: Upload Test Results to GCS if: always() @@ -768,7 +768,7 @@ jobs: - name: Prep Test Results For GCS if: always() run: | - python tools/bin/prep_test_results_for_gcs.py --json kube_results.json + python tools/bin/prep_test_results_for_gcs.py --json kube_results.json --jobid $GITHUB_JOB --runid $GITHUB_RUN_ID - name: Upload Test Results to GCS if: always() diff --git a/tools/bin/prep_test_results_for_gcs.py b/tools/bin/prep_test_results_for_gcs.py index 7f658eb381fc..d1f015ea9423 100644 --- a/tools/bin/prep_test_results_for_gcs.py +++ b/tools/bin/prep_test_results_for_gcs.py @@ -17,6 +17,8 @@ # Add long and short argument parser.add_argument("--json", "-j", help="Path to the result json output by https://github.com/EnricoMi/publish-unit-test-result-action") +parser.add_argument("--runid", "-r", help="Run id of the action") # this can be derived from checks api, but it's easier to derive it here +parser.add_argument("--jobid", "-c", help="Job id of the action") # this can be derived from checks api, but it's easier to derive it here def main(): # Read arguments from the command line @@ -40,6 +42,8 @@ def main(): "time": elem['states'][conclusion][i]['time'], "state": conclusion, "check_run_id": check_run_id, + "workflow_run_id": args.runid, + "job_id": args.jobid, "repo": "airbytehq/airbyte" } out.append(output) From b299688f3bf3ff6afeccbed9367749a688ea53ac Mon Sep 17 00:00:00 2001 From: Octavia Squidington III <90398440+octavia-squidington-iii@users.noreply.github.com> Date: Tue, 1 Nov 2022 21:35:23 -0500 Subject: [PATCH 484/498] Bump Airbyte version from 0.40.17 to 0.40.18 (#18827) Co-authored-by: grishick --- .bumpversion.cfg | 2 +- .env | 2 +- airbyte-bootloader/Dockerfile | 2 +- airbyte-container-orchestrator/Dockerfile | 2 +- airbyte-cron/Dockerfile | 2 +- airbyte-metrics/reporter/Dockerfile | 2 +- airbyte-proxy/Dockerfile | 2 +- airbyte-server/Dockerfile | 2 +- airbyte-webapp/package-lock.json | 4 ++-- airbyte-webapp/package.json | 2 +- airbyte-workers/Dockerfile | 2 +- charts/airbyte-bootloader/Chart.yaml | 2 +- charts/airbyte-cron/Chart.yaml | 2 +- charts/airbyte-server/Chart.yaml | 2 +- charts/airbyte-temporal/Chart.yaml | 2 +- charts/airbyte-webapp/Chart.yaml | 2 +- charts/airbyte-worker/Chart.yaml | 2 +- charts/airbyte/Chart.yaml | 2 +- charts/airbyte/README.md | 4 ++-- docs/operator-guides/upgrading-airbyte.md | 2 +- kube/overlays/stable-with-resource-limits/.env | 2 +- .../stable-with-resource-limits/kustomization.yaml | 12 ++++++------ kube/overlays/stable/.env | 2 +- kube/overlays/stable/kustomization.yaml | 12 ++++++------ octavia-cli/Dockerfile | 2 +- octavia-cli/README.md | 4 ++-- octavia-cli/install.sh | 2 +- octavia-cli/setup.py | 2 +- 28 files changed, 41 insertions(+), 41 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d493dbfd04e7..66a305f3ba46 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.40.17 +current_version = 0.40.18 commit = False tag = False parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-[a-z]+)? diff --git a/.env b/.env index fdf824b82731..61f8e2c720e9 100644 --- a/.env +++ b/.env @@ -10,7 +10,7 @@ ### SHARED ### -VERSION=0.40.17 +VERSION=0.40.18 # When using the airbyte-db via default docker image CONFIG_ROOT=/data diff --git a/airbyte-bootloader/Dockerfile b/airbyte-bootloader/Dockerfile index 2652c0af1710..b04199481af7 100644 --- a/airbyte-bootloader/Dockerfile +++ b/airbyte-bootloader/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-bootloader ENV VERSION ${VERSION} diff --git a/airbyte-container-orchestrator/Dockerfile b/airbyte-container-orchestrator/Dockerfile index b42d0050da95..8aed70dfb3fc 100644 --- a/airbyte-container-orchestrator/Dockerfile +++ b/airbyte-container-orchestrator/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-container-orchestrator ENV VERSION=${VERSION} diff --git a/airbyte-cron/Dockerfile b/airbyte-cron/Dockerfile index 3f1c7b9ce3ca..edfd9e552682 100644 --- a/airbyte-cron/Dockerfile +++ b/airbyte-cron/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS cron -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-cron ENV VERSION ${VERSION} diff --git a/airbyte-metrics/reporter/Dockerfile b/airbyte-metrics/reporter/Dockerfile index 3bde3162e52b..2448f3f348bc 100644 --- a/airbyte-metrics/reporter/Dockerfile +++ b/airbyte-metrics/reporter/Dockerfile @@ -1,7 +1,7 @@ ARG JDK_IMAGE=airbyte/airbyte-base-java-image:1.0 FROM ${JDK_IMAGE} AS metrics-reporter -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-metrics-reporter ENV VERSION ${VERSION} diff --git a/airbyte-proxy/Dockerfile b/airbyte-proxy/Dockerfile index ae61793f24c1..aef41fd0dae3 100644 --- a/airbyte-proxy/Dockerfile +++ b/airbyte-proxy/Dockerfile @@ -2,7 +2,7 @@ FROM nginx:latest -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-proxy ENV VERSION ${VERSION} diff --git a/airbyte-server/Dockerfile b/airbyte-server/Dockerfile index 6b3417eec972..8a1e61b8c3ad 100644 --- a/airbyte-server/Dockerfile +++ b/airbyte-server/Dockerfile @@ -3,7 +3,7 @@ FROM ${JDK_IMAGE} AS server EXPOSE 8000 -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-server ENV VERSION ${VERSION} diff --git a/airbyte-webapp/package-lock.json b/airbyte-webapp/package-lock.json index d6a677da4c03..a5efc582dc71 100644 --- a/airbyte-webapp/package-lock.json +++ b/airbyte-webapp/package-lock.json @@ -1,12 +1,12 @@ { "name": "airbyte-webapp", - "version": "0.40.17", + "version": "0.40.18", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "airbyte-webapp", - "version": "0.40.17", + "version": "0.40.18", "dependencies": { "@datadog/browser-rum": "^4.21.2", "@floating-ui/react-dom": "^1.0.0", diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index 53e0150b8a8e..58c2f077f34d 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -1,6 +1,6 @@ { "name": "airbyte-webapp", - "version": "0.40.17", + "version": "0.40.18", "private": true, "engines": { "node": ">=16.0.0" diff --git a/airbyte-workers/Dockerfile b/airbyte-workers/Dockerfile index 63e97461ad83..926fad98f31c 100644 --- a/airbyte-workers/Dockerfile +++ b/airbyte-workers/Dockerfile @@ -10,7 +10,7 @@ RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/s && chmod +x kubectl && mv kubectl /usr/local/bin/ # Don't change this manually. Bump version expects to make moves based on this string -ARG VERSION=0.40.17 +ARG VERSION=0.40.18 ENV APPLICATION airbyte-workers ENV VERSION ${VERSION} diff --git a/charts/airbyte-bootloader/Chart.yaml b/charts/airbyte-bootloader/Chart.yaml index 294f1ddb8ce1..c104e0be3c3e 100644 --- a/charts/airbyte-bootloader/Chart.yaml +++ b/charts/airbyte-bootloader/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.40" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte-cron/Chart.yaml b/charts/airbyte-cron/Chart.yaml index 89490ec4236b..22a638c37155 100644 --- a/charts/airbyte-cron/Chart.yaml +++ b/charts/airbyte-cron/Chart.yaml @@ -21,7 +21,7 @@ version: 0.1.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte-server/Chart.yaml b/charts/airbyte-server/Chart.yaml index 2074d108b9a8..62e164ffc987 100644 --- a/charts/airbyte-server/Chart.yaml +++ b/charts/airbyte-server/Chart.yaml @@ -21,7 +21,7 @@ version: 0.45.3 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte-temporal/Chart.yaml b/charts/airbyte-temporal/Chart.yaml index 545432196c29..77f36d0fcfe1 100644 --- a/charts/airbyte-temporal/Chart.yaml +++ b/charts/airbyte-temporal/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.40" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte-webapp/Chart.yaml b/charts/airbyte-webapp/Chart.yaml index 18e24fa0552a..1880344ff015 100644 --- a/charts/airbyte-webapp/Chart.yaml +++ b/charts/airbyte-webapp/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.40" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte-worker/Chart.yaml b/charts/airbyte-worker/Chart.yaml index 71ce617dd875..934add5a2509 100644 --- a/charts/airbyte-worker/Chart.yaml +++ b/charts/airbyte-worker/Chart.yaml @@ -22,7 +22,7 @@ version: "0.40.40" # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte/Chart.yaml b/charts/airbyte/Chart.yaml index a928c05c19f3..a8b4538eaa78 100644 --- a/charts/airbyte/Chart.yaml +++ b/charts/airbyte/Chart.yaml @@ -22,7 +22,7 @@ version: 0.40.40 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "0.40.17" +appVersion: "0.40.18" dependencies: - name: common diff --git a/charts/airbyte/README.md b/charts/airbyte/README.md index 7efbd55200ee..bb659ae5884b 100644 --- a/charts/airbyte/README.md +++ b/charts/airbyte/README.md @@ -1,6 +1,6 @@ # airbyte -![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.17](https://img.shields.io/badge/AppVersion-0.40.17-informational?style=flat-square) +![Version: 0.39.36](https://img.shields.io/badge/Version-0.39.36-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.40.18](https://img.shields.io/badge/AppVersion-0.40.18-informational?style=flat-square) Helm chart to deploy airbyte @@ -248,7 +248,7 @@ Helm chart to deploy airbyte | worker.hpa.enabled | bool | `false` | | | worker.image.pullPolicy | string | `"IfNotPresent"` | | | worker.image.repository | string | `"airbyte/worker"` | | -| worker.image.tag | string | `"0.40.17"` | | +| worker.image.tag | string | `"0.40.18"` | | | worker.livenessProbe.enabled | bool | `true` | | | worker.livenessProbe.failureThreshold | int | `3` | | | worker.livenessProbe.initialDelaySeconds | int | `30` | | diff --git a/docs/operator-guides/upgrading-airbyte.md b/docs/operator-guides/upgrading-airbyte.md index 02738a5d9ba5..cf357aa67694 100644 --- a/docs/operator-guides/upgrading-airbyte.md +++ b/docs/operator-guides/upgrading-airbyte.md @@ -103,7 +103,7 @@ If you are upgrading from (i.e. your current version of Airbyte is) Airbyte vers Here's an example of what it might look like with the values filled in. It assumes that the downloaded `airbyte_archive.tar.gz` is in `/tmp`. ```bash - docker run --rm -v /tmp:/config airbyte/migration:0.40.17 --\ + docker run --rm -v /tmp:/config airbyte/migration:0.40.18 --\ --input /config/airbyte_archive.tar.gz\ --output /config/airbyte_archive_migrated.tar.gz ``` diff --git a/kube/overlays/stable-with-resource-limits/.env b/kube/overlays/stable-with-resource-limits/.env index 286bdc188a50..396ee8be6016 100644 --- a/kube/overlays/stable-with-resource-limits/.env +++ b/kube/overlays/stable-with-resource-limits/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.17 +AIRBYTE_VERSION=0.40.18 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable-with-resource-limits/kustomization.yaml b/kube/overlays/stable-with-resource-limits/kustomization.yaml index 624c5994f469..dad84e6e0b22 100644 --- a/kube/overlays/stable-with-resource-limits/kustomization.yaml +++ b/kube/overlays/stable-with-resource-limits/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/bootloader - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/server - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/webapp - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/worker - newTag: 0.40.17 + newTag: 0.40.18 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.17 + newTag: 0.40.18 configMapGenerator: - name: airbyte-env diff --git a/kube/overlays/stable/.env b/kube/overlays/stable/.env index 05f61be870ab..4a87575b8976 100644 --- a/kube/overlays/stable/.env +++ b/kube/overlays/stable/.env @@ -1,4 +1,4 @@ -AIRBYTE_VERSION=0.40.17 +AIRBYTE_VERSION=0.40.18 # Airbyte Internal Database, see https://docs.airbyte.io/operator-guides/configuring-airbyte-db DATABASE_HOST=airbyte-db-svc diff --git a/kube/overlays/stable/kustomization.yaml b/kube/overlays/stable/kustomization.yaml index ec9936bd0c5d..6257fd19e7b2 100644 --- a/kube/overlays/stable/kustomization.yaml +++ b/kube/overlays/stable/kustomization.yaml @@ -8,19 +8,19 @@ bases: images: - name: airbyte/db - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/bootloader - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/server - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/webapp - newTag: 0.40.17 + newTag: 0.40.18 - name: airbyte/worker - newTag: 0.40.17 + newTag: 0.40.18 - name: temporalio/auto-setup newTag: 1.7.0 - name: airbyte/cron - newTag: 0.40.17 + newTag: 0.40.18 configMapGenerator: - name: airbyte-env diff --git a/octavia-cli/Dockerfile b/octavia-cli/Dockerfile index 19712f3a250c..a4338a06359c 100644 --- a/octavia-cli/Dockerfile +++ b/octavia-cli/Dockerfile @@ -14,5 +14,5 @@ USER octavia-cli WORKDIR /home/octavia-project ENTRYPOINT ["octavia"] -LABEL io.airbyte.version=0.40.17 +LABEL io.airbyte.version=0.40.18 LABEL io.airbyte.name=airbyte/octavia-cli diff --git a/octavia-cli/README.md b/octavia-cli/README.md index dede649cbc38..c2252b6bef05 100644 --- a/octavia-cli/README.md +++ b/octavia-cli/README.md @@ -104,7 +104,7 @@ This script: ```bash touch ~/.octavia # Create a file to store env variables that will be mapped the octavia-cli container mkdir my_octavia_project_directory # Create your octavia project directory where YAML configurations will be stored. -docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.17 +docker run --name octavia-cli -i --rm -v my_octavia_project_directory:/home/octavia-project --network host --user $(id -u):$(id -g) --env-file ~/.octavia airbyte/octavia-cli:0.40.18 ``` ### Using `docker-compose` @@ -712,7 +712,7 @@ You can disable telemetry by setting the `OCTAVIA_ENABLE_TELEMETRY` environment | Version | Date | Description | PR | | ------- | ---------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | 0.41.0 | 2022-10-13 | Use Basic Authentication for making API requests | [#17982](https://github.com/airbytehq/airbyte/pull/17982) | -| 0.40.17 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | +| 0.40.18 | 2022-08-10 | Enable cron and basic scheduling | [#15253](https://github.com/airbytehq/airbyte/pull/15253) | | 0.39.33 | 2022-07-05 | Add `octavia import all` command | [#14374](https://github.com/airbytehq/airbyte/pull/14374) | | 0.39.32 | 2022-06-30 | Create import command to import and manage existing Airbyte resource from octavia-cli | [#14137](https://github.com/airbytehq/airbyte/pull/14137) | | 0.39.27 | 2022-06-24 | Create get command to retrieve resources JSON representation | [#13254](https://github.com/airbytehq/airbyte/pull/13254) | diff --git a/octavia-cli/install.sh b/octavia-cli/install.sh index 30daa1d8e956..6ab6db554f05 100755 --- a/octavia-cli/install.sh +++ b/octavia-cli/install.sh @@ -3,7 +3,7 @@ # This install scripts currently only works for ZSH and Bash profiles. # It creates an octavia alias in your profile bound to a docker run command and your current user. -VERSION=0.40.17 +VERSION=0.40.18 OCTAVIA_ENV_FILE=${HOME}/.octavia detect_profile() { diff --git a/octavia-cli/setup.py b/octavia-cli/setup.py index 4262184b420e..4cd38c4362dd 100644 --- a/octavia-cli/setup.py +++ b/octavia-cli/setup.py @@ -15,7 +15,7 @@ setup( name="octavia-cli", - version="0.40.17", + version="0.40.18", description="A command line interface to manage Airbyte configurations", long_description=README, author="Airbyte", From 578f40affe184e3179296a4c8d62a0ca8a783bf5 Mon Sep 17 00:00:00 2001 From: Joey Marshment-Howell Date: Wed, 2 Nov 2022 09:40:44 +0100 Subject: [PATCH 485/498] =?UTF-8?q?=F0=9F=AA=9F=F0=9F=94=A7=20Remove=20sty?= =?UTF-8?q?led=20components=20(round=201)=20(#18766)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor EditorHeader (untested) * refactor BaseClearView * delete unused Subtitle * refactor ConfirmationModal * refactor Arrow * refactor BulkHeader * refactor CatalogTreeSearch * refactor StreamFieldTable * refactor StreamHeader * refactor ConnectorIcon * refactor TreeRowWrapper * refactor DeleteBlock * refactor EmptyResourceBlock * revert unintended element change --- .../components/EditorHeader.module.scss | 13 +++++++ .../components/EditorHeader.tsx | 17 ++------ .../ConfirmationModal.module.scss | 13 +++++++ .../ConfirmationModal/ConfirmationModal.tsx | 22 ++--------- .../ConnectorIcon/ConnectorIcon.module.scss | 5 +++ .../common/ConnectorIcon/ConnectorIcon.tsx | 15 +++---- .../DeleteBlock/DeleteBlock.module.scss | 16 ++++++++ .../common/DeleteBlock/DeleteBlock.tsx | 27 +++---------- .../EmptyResourceBlock.module.scss | 27 +++++++++++++ .../EmptyResourceBlock/EmptyResourceBlock.tsx | 39 ++++--------------- .../BaseClearView.module.scss | 22 +++++++++++ .../PageViewContainer/BaseClearView.tsx | 35 +++-------------- .../connection/CatalogTree/Arrow.module.scss | 19 +++++++++ .../connection/CatalogTree/Arrow.tsx | 31 ++++++--------- .../CatalogTree/BulkHeader.module.scss | 13 +++++++ .../connection/CatalogTree/BulkHeader.tsx | 20 ++-------- .../CatalogTree/CatalogTreeSearch.module.scss | 12 ++++++ .../CatalogTree/CatalogTreeSearch.tsx | 23 +++-------- .../CatalogTree/StreamFieldTable.module.scss | 7 ++++ .../CatalogTree/StreamFieldTable.tsx | 12 ++---- .../CatalogTree/StreamHeader.module.scss | 4 ++ .../connection/CatalogTree/StreamHeader.tsx | 9 +---- .../CatalogTree/TreeRowWrapper.module.scss | 20 ++++++++++ .../connection/CatalogTree/TreeRowWrapper.tsx | 28 +++++-------- 24 files changed, 239 insertions(+), 210 deletions(-) create mode 100644 airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.module.scss create mode 100644 airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.module.scss create mode 100644 airbyte-webapp/src/components/common/DeleteBlock/DeleteBlock.module.scss create mode 100644 airbyte-webapp/src/components/common/EmptyResourceBlock/EmptyResourceBlock.module.scss create mode 100644 airbyte-webapp/src/components/common/PageViewContainer/BaseClearView.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/Arrow.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeSearch.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.module.scss create mode 100644 airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.module.scss diff --git a/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.module.scss b/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.module.scss new file mode 100644 index 000000000000..834c58a237ea --- /dev/null +++ b/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.module.scss @@ -0,0 +1,13 @@ +@use "scss/colors"; + +.editorHeader { + display: flex; + justify-content: space-between; + align-items: center; + flex-direction: row; + color: colors.$dark-blue-900; + font-weight: 500; + font-size: 14px; + line-height: 17px; + margin: 5px 0 10px; +} diff --git a/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.tsx b/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.tsx index 55a49eb0c42c..f6990cd45702 100644 --- a/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.tsx +++ b/airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.tsx @@ -1,22 +1,11 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; import { Button } from "components/ui/Button"; import { ConnectionFormMode } from "hooks/services/ConnectionForm/ConnectionFormService"; -const Content = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - flex-direction: row; - color: ${({ theme }) => theme.textColor}; - font-weight: 500; - font-size: 14px; - line-height: 17px; - margin: 5px 0 10px; -`; +import styles from "./EditorHeader.module.scss"; interface EditorHeaderProps { mainTitle?: React.ReactNode; @@ -36,14 +25,14 @@ const EditorHeader: React.FC = ({ disabled, }) => { return ( - +
    {mainTitle || } {mode !== "readonly" && ( )} - +
    ); }; diff --git a/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.module.scss b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.module.scss index 86081ff47b39..4f7c81dcc15d 100644 --- a/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.module.scss +++ b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.module.scss @@ -1,3 +1,16 @@ .buttonWithMargin { margin-right: 12px; } + +.content { + width: 585px; + font-size: 14px; + padding: 25px; + white-space: pre-line; +} + +.buttonContent { + margin-top: 26px; + display: flex; + justify-content: flex-end; +} diff --git a/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx index 4f09c55badda..2b45d4333b88 100644 --- a/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx +++ b/airbyte-webapp/src/components/common/ConfirmationModal/ConfirmationModal.tsx @@ -1,6 +1,5 @@ import React from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; import { Button } from "components/ui/Button"; import { Modal } from "components/ui/Modal"; @@ -8,19 +7,6 @@ import { Modal } from "components/ui/Modal"; import useLoadingState from "../../../hooks/useLoadingState"; import styles from "./ConfirmationModal.module.scss"; -const Content = styled.div` - width: 585px; - font-size: 14px; - padding: 25px; - white-space: pre-line; -`; - -const ButtonContent = styled.div` - margin-top: 26px; - display: flex; - justify-content: flex-end; -`; - export interface ConfirmationModalProps { onClose: () => void; title: string; @@ -45,9 +31,9 @@ export const ConfirmationModal: React.FC = ({ return ( }> - +
    - +
    - - +
    +
    ); }; diff --git a/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.module.scss b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.module.scss new file mode 100644 index 000000000000..7e0592687e48 --- /dev/null +++ b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.module.scss @@ -0,0 +1,5 @@ +.content { + height: 25px; + width: 25px; + overflow: hidden; +} diff --git a/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx index a62a90c7cdbf..9f4012e48522 100644 --- a/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx +++ b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx @@ -1,22 +1,17 @@ import React from "react"; -import styled from "styled-components"; import { getIcon } from "utils/imageUtils"; +import styles from "./ConnectorIcon.module.scss"; + interface Props { icon?: string; className?: string; small?: boolean; } -export const Content = styled.div` - height: 25px; - width: 25px; - overflow: hidden; -`; - -export const ConnectorIcon: React.FC = ({ icon, className }) => ( -
    ); }; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.module.scss new file mode 100644 index 000000000000..c47665eda94a --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.module.scss @@ -0,0 +1,7 @@ +@use "scss/colors"; + +.rowsContainer { + background: colors.$white; + border-radius: 8px; + margin: 0 10px 5px; +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx b/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx index 538e1b5b8210..023bc03b2ade 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamFieldTable.tsx @@ -1,5 +1,4 @@ import React from "react"; -import styled from "styled-components"; import { SyncSchemaField, SyncSchemaFieldObject } from "core/domain/catalog"; import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; @@ -7,14 +6,9 @@ import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; import { FieldHeader } from "./FieldHeader"; import { FieldRow } from "./FieldRow"; import { pathDisplayName } from "./PathPopout"; +import styles from "./StreamFieldTable.module.scss"; import { TreeRowWrapper } from "./TreeRowWrapper"; -const RowsContainer = styled.div` - background: ${({ theme }) => theme.whiteColor}; - border-radius: 8px; - margin: 0 10px 5px 10px; -`; - interface StreamFieldTableProps { syncSchemaFields: SyncSchemaField[]; config: AirbyteStreamConfiguration | undefined; @@ -30,7 +24,7 @@ export const StreamFieldTable: React.FC = (props) => { - +
    {props.syncSchemaFields.map((field) => ( = (props) => { /> ))} - +
    ); }; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss index f7a7b2fed34a..967241b4ad12 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.module.scss @@ -60,3 +60,7 @@ .purpleBackground { background-color: colors.$blue-transparent; } + +.noHeader { + color: colors.$grey-300; +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx index eacb3024d886..a3b0d542bf36 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx @@ -3,7 +3,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classnames from "classnames"; import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; -import styled from "styled-components"; import { Cell, Row } from "components"; import { CheckBox } from "components/ui/CheckBox"; @@ -20,10 +19,6 @@ import styles from "./StreamHeader.module.scss"; import { ArrowCell, HeaderCell } from "./styles"; import { SyncSettingsDropdown } from "./SyncSettingsDropdown"; -const EmptyField = styled.span` - color: ${({ theme }) => theme.greyColor40}; -`; - interface SyncSchema { syncMode: SyncMode; destinationSyncMode: DestinationSyncMode; @@ -126,9 +121,9 @@ export const StreamHeader: React.FC = ({ {stream.stream?.namespace || ( - + - + )} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.module.scss new file mode 100644 index 000000000000..3f8529f2bd45 --- /dev/null +++ b/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.module.scss @@ -0,0 +1,20 @@ +@use "scss/colors"; + +.rowWrapper { + height: 40px; + border-bottom: 1px solid colors.$grey-50; + + &--noBorder { + border-bottom: none; + } + + &--hasDepth:last-child { + border: none; + } +} + +.rowContent { + height: 100%; + white-space: nowrap; + font-size: 12px; +} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.tsx b/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.tsx index c74647f7a5d8..a728047bd3da 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/TreeRowWrapper.tsx @@ -1,31 +1,23 @@ +import classNames from "classnames"; import React from "react"; -import styled from "styled-components"; import { Row } from "components/SimpleTableComponents"; -const RowWrapper = styled.div<{ depth?: number; noBorder?: boolean }>` - height: 40px; - border-bottom: ${({ theme, noBorder }) => (noBorder ? "none" : `1px solid ${theme.greyColor0}`)}; - - &:last-child { - border: ${({ depth = 0 }) => depth > 0 && "none"}; - } -`; - -const RowContent = styled(Row)` - height: 100%; - white-space: nowrap; - font-size: 12px; -`; +import styles from "./TreeRowWrapper.module.scss"; const TreeRowWrapper: React.FC> = ({ depth, children, noBorder, }) => ( - - {children} - +
    0, + [styles["rowWrapper--noBorder"]]: noBorder, + })} + > + {children} +
    ); export { TreeRowWrapper }; From 02095d20da504424d7d8677a226c3a95ce9579ea Mon Sep 17 00:00:00 2001 From: darynaishchenko <80129833+darynaishchenko@users.noreply.github.com> Date: Wed, 2 Nov 2022 14:03:36 +0200 Subject: [PATCH 486/498] fixed acceptance tests (#18699) --- .../source-amazon-ads/acceptance-test-config.yml | 1 + .../integration_tests/expected_records.txt | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml b/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml index a1e7adb95e7a..b13b28249017 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml +++ b/airbyte-integrations/connectors/source-amazon-ads/acceptance-test-config.yml @@ -21,6 +21,7 @@ tests: extra_records: no empty_streams: [ + "profiles", "sponsored_brands_ad_groups", "sponsored_brands_campaigns", "sponsored_brands_keywords", diff --git a/airbyte-integrations/connectors/source-amazon-ads/integration_tests/expected_records.txt b/airbyte-integrations/connectors/source-amazon-ads/integration_tests/expected_records.txt index d74c774f8342..baecd065086b 100644 --- a/airbyte-integrations/connectors/source-amazon-ads/integration_tests/expected_records.txt +++ b/airbyte-integrations/connectors/source-amazon-ads/integration_tests/expected_records.txt @@ -1,12 +1,5 @@ -{"stream":"profiles","data":{"profileId":3991703629696934,"countryCode":"CA","currencyCode":"CAD","dailyBudget":999999999,"timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"A2EUQ1WTGCTBG2","id":"A3LUQZ2NBMFGO4","type":"seller","name":"The Airbyte Store","validPaymentMethod":true}},"emitted_at":1659020216524} -{"stream":"profiles","data":{"profileId":2935840597082037,"countryCode":"CA","currencyCode":"CAD","timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"A2EUQ1WTGCTBG2","id":"ENTITY1T4PQ8E0Y1LVJ","type":"vendor","name":"test","validPaymentMethod":false}},"emitted_at":1659020216526} -{"stream":"profiles","data":{"profileId":3664951271230581,"countryCode":"MX","currencyCode":"MXN","dailyBudget":999999999,"timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"A1AM78C64UM0Y8","id":"A3LUQZ2NBMFGO4","type":"seller","name":"The Airbyte Store","validPaymentMethod":true}},"emitted_at":1659020216527} -{"stream":"profiles","data":{"profileId":1861552880916640,"countryCode":"US","currencyCode":"USD","timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"ATVPDKIKX0DER","id":"ENTITYVFIQ1E6W9INI","type":"vendor","name":"Sponsored ads - KDP","subType":"KDP_AUTHOR","validPaymentMethod":true}},"emitted_at":1659020216527} -{"stream":"profiles","data":{"profileId":3312910465837761,"countryCode":"US","currencyCode":"USD","dailyBudget":999999999,"timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"ATVPDKIKX0DER","id":"A3LUQZ2NBMFGO4","type":"seller","name":"The Airbyte Store","validPaymentMethod":true}},"emitted_at":1659020216527} -{"stream":"profiles","data":{"profileId":2445745172318948,"countryCode":"US","currencyCode":"USD","timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"ATVPDKIKX0DER","id":"ENTITY3QRPN1GHC1Q0U","type":"vendor","name":"3PTestBrand-A3LUQZ2NBMFGO46750119612846","validPaymentMethod":true}},"emitted_at":1659020216527} -{"stream":"profiles","data":{"profileId":3039403378822505,"countryCode":"US","currencyCode":"USD","timezone":"America/Los_Angeles","accountInfo":{"marketplaceStringId":"ATVPDKIKX0DER","id":"ENTITY2ZP3PPFBG2043","type":"vendor","name":"3PTestBrand-A3LUQZ2NBMFGO4215634471126","validPaymentMethod":true}},"emitted_at":1659020216528} +{"stream":"sponsored_display_ad_groups","data":{"adGroupId": 239470166910761, "campaignId": 25934734632378, "defaultBid": 0.02, "name": "Ad group - 7/20/2022 15:45:46", "state": "enabled", "bidOptimization": "clicks", "tactic": "T00020", "creativeType": "IMAGE"},"emitted_at":1659020218593} {"stream":"sponsored_display_campaigns","data":{"campaignId":25934734632378,"name":"Campaign - 7/20/2022 15:45:46","tactic":"T00020","startDate":"20240510","state":"enabled","costType":"cpc","budget":1,"budgetType":"daily","deliveryProfile":"as_soon_as_possible"},"emitted_at":1659020217679} -{"stream":"sponsored_display_ad_groups","data":{"adGroupId":239470166910761,"campaignId":25934734632378,"defaultBid":0.02,"name":"Ad group - 7/20/2022 15:45:46","state":"enabled","bidOptimization":"clicks","tactic":"T00020"},"emitted_at":1659020218593} {"stream":"sponsored_display_product_ads","data":{"adId":125773733335504,"adGroupId":239470166910761,"campaignId":25934734632378,"asin":"B000BNT390","state":"enabled"},"emitted_at":1659020219604} {"stream":"sponsored_display_product_ads","data":{"adId":22923447445879,"adGroupId":239470166910761,"campaignId":25934734632378,"asin":"B000BNQBJK","state":"enabled"},"emitted_at":1659020219605} {"stream":"sponsored_display_product_ads","data":{"adId":174434781640143,"adGroupId":239470166910761,"campaignId":25934734632378,"asin":"B006K1JR0W","state":"enabled"},"emitted_at":1659020219605} From 23a7a07bec12589d58030dc43fc4289335339b62 Mon Sep 17 00:00:00 2001 From: Joey Marshment-Howell Date: Wed, 2 Nov 2022 14:51:09 +0100 Subject: [PATCH 487/498] =?UTF-8?q?=F0=9F=AA=9F=F0=9F=94=A7=20Reactor=20Br?= =?UTF-8?q?eadcrumbs=20component=20to=20use=20anchors=20(#18764)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor breadcrumbs to use actual links * PR comments on styles --- .../ui/Breadcrumbs/Breadcrumbs.module.scss | 18 +++++++ .../components/ui/Breadcrumbs/Breadcrumbs.tsx | 54 +++++++------------ .../ui/Breadcrumbs/index.stories.tsx | 15 ++---- .../DestinationItemPage.tsx | 11 ++-- .../pages/SourceItemPage/SourceItemPage.tsx | 9 ++-- 5 files changed, 53 insertions(+), 54 deletions(-) create mode 100644 airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.module.scss diff --git a/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.module.scss b/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.module.scss new file mode 100644 index 000000000000..37bcac8f8e48 --- /dev/null +++ b/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.module.scss @@ -0,0 +1,18 @@ +@use "scss/colors"; + +.container { + font-weight: normal; + cursor: default; +} + +.item { + display: inline-block; + + a { + text-decoration: none; + } +} + +.unlinked { + font-weight: bold; +} diff --git a/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.tsx b/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.tsx index 87df194a700a..40e3a7a034a6 100644 --- a/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.tsx +++ b/airbyte-webapp/src/components/ui/Breadcrumbs/Breadcrumbs.tsx @@ -1,28 +1,12 @@ import React from "react"; -import styled from "styled-components"; -const BreadcrumbsContainer = styled.div` - font-weight: normal; - cursor: default; -`; +import { Link } from "components"; -const LastBreadcrumbsItem = styled.span` - font-weight: bold; -`; - -const BreadcrumbsItem = styled.div` - display: inline-block; - cursor: pointer; - color: ${({ theme }) => theme.primaryColor}; - - &:hover { - opacity: 0.8; - } -`; +import styles from "./Breadcrumbs.module.scss"; export interface BreadcrumbsDataItem { - name: string | React.ReactNode; - onClick?: () => void; + label: string; + to?: string; } interface BreadcrumbsProps { @@ -30,20 +14,22 @@ interface BreadcrumbsProps { } export const Breadcrumbs: React.FC = ({ data }) => { - const lastIndex = data.length - 1; - return ( - - {data.map((item, key) => - key === lastIndex ? ( - {item.name} - ) : ( - - {item.name} - / - - ) - )} - +
    + {data.map((item, index) => ( + + {item.to ? ( + + {item.label} + + ) : ( + + {item.label} + + )} + {index !== data.length - 1 && / } + + ))} +
    ); }; diff --git a/airbyte-webapp/src/components/ui/Breadcrumbs/index.stories.tsx b/airbyte-webapp/src/components/ui/Breadcrumbs/index.stories.tsx index 96f93c2f62ef..645309ea5756 100644 --- a/airbyte-webapp/src/components/ui/Breadcrumbs/index.stories.tsx +++ b/airbyte-webapp/src/components/ui/Breadcrumbs/index.stories.tsx @@ -10,22 +10,17 @@ export default { const Template: ComponentStory = (args) => ; -const onClick = () => { - console.log("onClick"); -}; - const data: BreadcrumbsDataItem[] = [ { - name: "Workspace", - onClick, + label: "Workspace", + to: "/workspace", }, { - name: "Source", - onClick, + label: "Source", + to: "/workspace/source", }, { - name: "Settings", - onClick, + label: "Settings", }, ]; diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx index 849453db15aa..12b2421d2c8f 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/DestinationItemPage/DestinationItemPage.tsx @@ -1,5 +1,5 @@ import React, { Suspense, useMemo } from "react"; -import { FormattedMessage } from "react-intl"; +import { useIntl } from "react-intl"; import { Route, Routes, useNavigate, useParams } from "react-router-dom"; import { LoadingPage } from "components"; @@ -29,6 +29,7 @@ const DestinationItemPage: React.FC = () => { useTrackPage(PageTrackingCodes.DESTINATION_ITEM); const params = useParams() as { "*": StepsTypes | ""; id: string }; const navigate = useNavigate(); + const { formatMessage } = useIntl(); const currentStep = useMemo(() => (params["*"] === "" ? StepsTypes.OVERVIEW : params["*"]), [params]); const { sources } = useSourceList(); @@ -41,8 +42,6 @@ const DestinationItemPage: React.FC = () => { const { connections } = useConnectionList(); - const onClickBack = () => navigate(".."); - const onSelectStep = (id: string) => { const path = id === StepsTypes.OVERVIEW ? "." : id.toLowerCase(); navigate(path); @@ -50,10 +49,10 @@ const DestinationItemPage: React.FC = () => { const breadcrumbsData = [ { - name: , - onClick: onClickBack, + label: formatMessage({ id: "admin.destinations" }), + to: "..", }, - { name: destination.name }, + { label: destination.name }, ]; const connectionsWithDestination = connections.filter( diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx index a5ea2816e196..9d9e70da9eab 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/SourceItemPage/SourceItemPage.tsx @@ -1,5 +1,5 @@ import React, { Suspense, useMemo } from "react"; -import { FormattedMessage } from "react-intl"; +import { useIntl } from "react-intl"; import { Route, Routes, useNavigate, useParams } from "react-router-dom"; import { ApiErrorBoundary } from "components/common/ApiErrorBoundary"; @@ -29,6 +29,7 @@ const SourceItemPage: React.FC = () => { useTrackPage(PageTrackingCodes.SOURCE_ITEM); const params = useParams<{ "*": StepsTypes | "" | undefined; id: string }>(); const navigate = useNavigate(); + const { formatMessage } = useIntl(); const currentStep = useMemo( () => (params["*"] === "" ? StepsTypes.OVERVIEW : params["*"]), [params] @@ -45,10 +46,10 @@ const SourceItemPage: React.FC = () => { const breadcrumbsData = [ { - name: , - onClick: () => navigate(".."), + label: formatMessage({ id: "sidebar.sources" }), + to: "..", }, - { name: source.name }, + { label: source.name }, ]; const connectionsWithSource = connections.filter((connectionItem) => connectionItem.sourceId === source.sourceId); From 968fc6950eb3f1a0ef66e1d4a4291c5c337e1aac Mon Sep 17 00:00:00 2001 From: Michael Siega <109092231+mfsiega-airbyte@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:49:40 +0100 Subject: [PATCH 488/498] increase test timeout for some webapp tests to prevent flakes (#18807) --- .../pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx index 48b1457135a8..a63ad3d78c18 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.test.tsx @@ -19,6 +19,7 @@ import { ConnectionReplicationTab } from "./ConnectionReplicationTab"; jest.mock("services/connector/DestinationDefinitionSpecificationService", () => ({ useGetDestinationDefinitionSpecification: () => mockDest, })); +jest.setTimeout(10000); describe("ConnectionReplicationTab", () => { const Wrapper: React.FC> = ({ children }) => ( From 4b2d65f712a1a0c175a45be2937656ce5f2cb53f Mon Sep 17 00:00:00 2001 From: Tyler B <104733644+TBernstein4@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:12:47 -0400 Subject: [PATCH 489/498] Remove "Filters and Segments" from Google Analytics v4 (#18508) Filters and Segments info was incorrectly added to the Google Analytics v4 connector instead of the Google Analytics (Universal Analytics) Connector. --- docs/integrations/sources/google-analytics-v4.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/integrations/sources/google-analytics-v4.md b/docs/integrations/sources/google-analytics-v4.md index 6ab4ac083f03..e4d56c1009b3 100644 --- a/docs/integrations/sources/google-analytics-v4.md +++ b/docs/integrations/sources/google-analytics-v4.md @@ -19,8 +19,6 @@ This connector supports GA4 properties through the [Analytics Data API v1](https * Custom report format when using segments and / or filters `[{"name": "", "dimensions": ["", ...], "metrics": ["", ...], "segments": [""}]` * When using segments, make sure you add the `ga:segment` dimension. * Custom reports: [Dimensions and metrics explorer](https://ga-dev-tools.web.app/dimensions-metrics-explorer/) -* Custom reports: [Segment](https://developers.google.com/analytics/devguides/reporting/core/v3/reference#segment) and [filter](https://developers.google.com/analytics/devguides/reporting/core/v3/reference#filters) definition guide (v3 format) -* Example: `[{"name": "sessions_example", "dimensions": ["ga:date", "ga:segment", "ga:deviceCategory"], "metrics": ["ga:pageviews","ga:sessions","ga:users","ga:transactions"], "segments": ["sessions::condition::ga:browser==Chrome"], "filter": "ga:deviceCategory==desktop"}]` ## Step 1: Set up Source @@ -72,7 +70,6 @@ added by default to any report. There are 8 default reports. To add more reports | Version | Date | Pull Request | Subject | |:--------|:-----------|:---------------------------------------------------------|:---------------------------------------------------| -| 0.0.4 | 2022-09-24 | [16920](https://github.com/airbytehq/airbyte/pull/16920) | Added segments and filters to custom reports | | 0.0.3 | 2022-08-15 | [15229](https://github.com/airbytehq/airbyte/pull/15229) | Source Google Analytics Data Api: code refactoring | | 0.0.2 | 2022-07-27 | [15087](https://github.com/airbytehq/airbyte/pull/15087) | fix documentationUrl | | 0.0.1 | 2022-05-09 | [12701](https://github.com/airbytehq/airbyte/pull/12701) | Introduce Google Analytics Data API source | From 016907ed986d3bb79a589c0bcef8ff76b840bb06 Mon Sep 17 00:00:00 2001 From: Yowan Ramchoreeter <26179814+YowanR@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:13:30 -0400 Subject: [PATCH 490/498] Add notes about EU OAUth (#18835) EU OAuth is not fully tested so adding a note to account for that. --- docs/integrations/sources/surveymonkey.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/integrations/sources/surveymonkey.md b/docs/integrations/sources/surveymonkey.md index 29df833ea184..78f8a31ddf2d 100644 --- a/docs/integrations/sources/surveymonkey.md +++ b/docs/integrations/sources/surveymonkey.md @@ -2,6 +2,12 @@ This page guides you through the process of setting up the SurveyMonkey source connector. +:::note + +OAuth for Survey Monkey is officially supported only for the US. We are testing how to enable it in the EU at the moment. If you run into any issues, please [reach out to us](mailto:product@airbyte.io) so we can promptly assist you. + +::: + ## Prerequisites ### For Airbyte Open Source: From 237987693769c5b9c611dfdb2126e094ff3acca7 Mon Sep 17 00:00:00 2001 From: Joey Marshment-Howell Date: Wed, 2 Nov 2022 16:14:54 +0100 Subject: [PATCH 491/498] =?UTF-8?q?=F0=9F=AA=9F=F0=9F=90=9B=20Fix:=20visua?= =?UTF-8?q?l=20regression=20in=20ConnectorIcon=20(#18849)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix visual regression * remove unused prop --- .../src/components/common/ConnectorIcon/ConnectorIcon.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx index 9f4012e48522..b6a3dd9bfff7 100644 --- a/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx +++ b/airbyte-webapp/src/components/common/ConnectorIcon/ConnectorIcon.tsx @@ -1,17 +1,17 @@ +import classNames from "classnames"; import React from "react"; import { getIcon } from "utils/imageUtils"; import styles from "./ConnectorIcon.module.scss"; -interface Props { +interface ConnectorIconProps { icon?: string; className?: string; - small?: boolean; } -export const ConnectorIcon: React.FC = ({ icon }) => ( - ); }; diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index e6f728bcd7c1..d3b783c79e52 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -17,11 +17,12 @@ "sidebar.resources": "Resources", "sidebar.documentation": "Documentation", "sidebar.joinSlack": "Join our Slack", - "sidebar.status": "Airbyte status", + "sidebar.status": "Airbyte Status", "sidebar.chat": "Chat with us", "sidebar.support": "Support", "sidebar.supportTicket": "Submit a Ticket", "sidebar.recipes": "Tutorials - Use cases", + "sidebar.demo": "Explore our demo", "form.continue": "Continue", "form.error": "Error: {message}", @@ -334,6 +335,7 @@ "syncMode.full_refresh": "Full refresh", "syncMode.incremental": "Incremental", + "connection.emptyStateFooter": "or play around in our demo instance.", "connection.resetModalTitle": "Stream configuration changed", "connection.streamResetHint": "Due to changes in the stream configuration, we recommend a data reset. A reset will delete data in the destination of the affected streams and then re-sync that data. Skipping the reset is discouraged and might lead to unexpected behavior.", "connection.streamFullResetHint": "Due to changes in the stream configuration, we recommend a data reset. A reset will delete data in the destination of the affected streams and then re-sync that data. Skipping the reset is discouraged and might lead to unexpected behavior.", diff --git a/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx b/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx index 6696a666a3ec..46476daf7e0f 100644 --- a/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx +++ b/airbyte-webapp/src/packages/cloud/views/layout/SideBar/SideBar.tsx @@ -1,6 +1,6 @@ import { faSlack } from "@fortawesome/free-brands-svg-icons"; import { faEnvelope } from "@fortawesome/free-regular-svg-icons"; -import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { faDesktop, faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React from "react"; import { FormattedMessage, FormattedNumber } from "react-intl"; @@ -23,7 +23,6 @@ import ChatIcon from "views/layout/SideBar/components/ChatIcon"; import ConnectionsIcon from "views/layout/SideBar/components/ConnectionsIcon"; import DestinationIcon from "views/layout/SideBar/components/DestinationIcon"; import OnboardingIcon from "views/layout/SideBar/components/OnboardingIcon"; -import RecipesIcon from "views/layout/SideBar/components/RecipesIcon"; import SettingsIcon from "views/layout/SideBar/components/SettingsIcon"; import { SidebarDropdownMenu, SidebarDropdownMenuItemType } from "views/layout/SideBar/components/SidebarDropdownMenu"; import SourceIcon from "views/layout/SideBar/components/SourceIcon"; @@ -129,9 +128,9 @@ const SideBar: React.FC = () => { }, { type: SidebarDropdownMenuItemType.LINK, - href: links.tutorialLink, - icon: , - displayName: , + href: links.demoLink, + icon: , + displayName: , }, ]} /> diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.module.scss new file mode 100644 index 000000000000..8bced990e787 --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.module.scss @@ -0,0 +1,10 @@ +@use "scss/colors"; + +.link { + color: colors.$dark-blue; + + &:hover, + &:focus { + color: colors.$blue-400; + } +} diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx index 081605bb7ab1..790b863e2846 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/AllConnectionsPage/AllConnectionsPage.tsx @@ -12,8 +12,10 @@ import { PageHeader } from "components/ui/PageHeader"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; import { useConnectionList } from "hooks/services/useConnectionHook"; +import { links } from "utils/links"; import { RoutePaths } from "../../../routePaths"; +import styles from "./AllConnectionsPage.module.scss"; import ConnectionsTable from "./components/ConnectionsTable"; const AllConnectionsPage: React.FC = () => { @@ -48,6 +50,18 @@ const AllConnectionsPage: React.FC = () => { resourceType="connections" onCreateClick={onCreateClick} buttonLabel={formatMessage({ id: "connection.createFirst" })} + footer={ + ( + + {children} + + ), + }} + /> + } /> )} From 0a37a8d35211f2308c3d3274492c65ce458eaafe Mon Sep 17 00:00:00 2001 From: Subodh Kant Chaturvedi Date: Wed, 2 Nov 2022 20:54:05 +0530 Subject: [PATCH 493/498] mysql-source:fix tinyint unsigned handling (#18619) * mysql-source:fix tinyint unsigned handling * update doc * format * upgrade version * auto-bump connector version Co-authored-by: Octavia Squidington III --- .../resources/seed/source_definitions.yaml | 2 +- .../src/main/resources/seed/source_specs.yaml | 2 +- ...stomMySQLTinyIntOneToBooleanConverter.java | 21 +++ .../source-mysql-strict-encrypt/Dockerfile | 2 +- .../connectors/source-mysql/Dockerfile | 2 +- .../source/mysql/MySqlCdcProperties.java | 2 +- .../AbstractMySqlSourceDatatypeTest.java | 9 ++ docs/integrations/sources/mysql.md | 150 +++++++++--------- 8 files changed, 111 insertions(+), 79 deletions(-) create mode 100644 airbyte-integrations/bases/debezium-v1-9-6/src/main/java/io/airbyte/integrations/debezium/internals/CustomMySQLTinyIntOneToBooleanConverter.java diff --git a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml index d417180ca357..5ffa74976882 100644 --- a/airbyte-config/init/src/main/resources/seed/source_definitions.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_definitions.yaml @@ -792,7 +792,7 @@ - name: MySQL sourceDefinitionId: 435bb9a5-7887-4809-aa58-28c27df0d7ad dockerRepository: airbyte/source-mysql - dockerImageTag: 1.0.9 + dockerImageTag: 1.0.10 documentationUrl: https://docs.airbyte.com/integrations/sources/mysql icon: mysql.svg sourceType: database diff --git a/airbyte-config/init/src/main/resources/seed/source_specs.yaml b/airbyte-config/init/src/main/resources/seed/source_specs.yaml index 1d45ee2f23eb..bf161d581b1e 100644 --- a/airbyte-config/init/src/main/resources/seed/source_specs.yaml +++ b/airbyte-config/init/src/main/resources/seed/source_specs.yaml @@ -7587,7 +7587,7 @@ supportsNormalization: false supportsDBT: false supported_destination_sync_modes: [] -- dockerImage: "airbyte/source-mysql:1.0.9" +- dockerImage: "airbyte/source-mysql:1.0.10" spec: documentationUrl: "https://docs.airbyte.com/integrations/sources/mysql" connectionSpecification: diff --git a/airbyte-integrations/bases/debezium-v1-9-6/src/main/java/io/airbyte/integrations/debezium/internals/CustomMySQLTinyIntOneToBooleanConverter.java b/airbyte-integrations/bases/debezium-v1-9-6/src/main/java/io/airbyte/integrations/debezium/internals/CustomMySQLTinyIntOneToBooleanConverter.java new file mode 100644 index 000000000000..a6d76d5772e6 --- /dev/null +++ b/airbyte-integrations/bases/debezium-v1-9-6/src/main/java/io/airbyte/integrations/debezium/internals/CustomMySQLTinyIntOneToBooleanConverter.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Airbyte, Inc., all rights reserved. + */ + +package io.airbyte.integrations.debezium.internals; + +import io.debezium.connector.mysql.converters.TinyIntOneToBooleanConverter; +import io.debezium.spi.converter.RelationalColumn; +import org.apache.kafka.connect.data.SchemaBuilder; + +public class CustomMySQLTinyIntOneToBooleanConverter extends TinyIntOneToBooleanConverter { + + @Override + public void converterFor(final RelationalColumn field, final ConverterRegistration registration) { + if (!"TINYINT".equalsIgnoreCase(field.typeName())) { + return; + } + super.converterFor(field, registration); + } + +} diff --git a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile index d218c622e9c1..9baa9badbdb4 100644 --- a/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql-strict-encrypt/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql-strict-encrypt COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.9 +LABEL io.airbyte.version=1.0.10 LABEL io.airbyte.name=airbyte/source-mysql-strict-encrypt diff --git a/airbyte-integrations/connectors/source-mysql/Dockerfile b/airbyte-integrations/connectors/source-mysql/Dockerfile index 9d649ad52399..8be6326053cb 100644 --- a/airbyte-integrations/connectors/source-mysql/Dockerfile +++ b/airbyte-integrations/connectors/source-mysql/Dockerfile @@ -16,6 +16,6 @@ ENV APPLICATION source-mysql COPY --from=build /airbyte /airbyte -LABEL io.airbyte.version=1.0.9 +LABEL io.airbyte.version=1.0.10 LABEL io.airbyte.name=airbyte/source-mysql diff --git a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java index b50caaef548c..3c57cf3f2d55 100644 --- a/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java +++ b/airbyte-integrations/connectors/source-mysql/src/main/java/io/airbyte/integrations/source/mysql/MySqlCdcProperties.java @@ -50,7 +50,7 @@ private static Properties commonProperties(final JdbcDatabase database) { * {@link MySQLConverter} */ props.setProperty("converters", "boolean, datetime"); - props.setProperty("boolean.type", "io.debezium.connector.mysql.converters.TinyIntOneToBooleanConverter"); + props.setProperty("boolean.type", "io.airbyte.integrations.debezium.internals.CustomMySQLTinyIntOneToBooleanConverter"); props.setProperty("datetime.type", "io.airbyte.integrations.debezium.internals.MySQLDateTimeConverter"); // For CDC mode, the user cannot provide timezone arguments as JDBC parameters - they are diff --git a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java index 55ed3a3654d4..352514893e68 100644 --- a/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java +++ b/airbyte-integrations/connectors/source-mysql/src/test-integration/java/io/airbyte/integrations/io/airbyte/integration_tests/sources/AbstractMySqlSourceDatatypeTest.java @@ -100,6 +100,15 @@ protected void initTests() { .addExpectedValues(null, "true", "false") .build()); + addDataTypeTestData( + TestDataHolder.builder() + .sourceType("tinyint") + .fullSourceDataType("tinyint(1) unsigned") + .airbyteType(JsonSchemaType.INTEGER) + .addInsertValues("null", "0", "1", "2", "3") + .addExpectedValues(null, "0", "1", "2", "3") + .build()); + addDataTypeTestData( TestDataHolder.builder() .sourceType("tinyint") diff --git a/docs/integrations/sources/mysql.md b/docs/integrations/sources/mysql.md index 12987af8604f..5dcc873df3a0 100644 --- a/docs/integrations/sources/mysql.md +++ b/docs/integrations/sources/mysql.md @@ -183,6 +183,7 @@ MySQL data types are mapped to the following data types when synchronizing data. | `boolean` | boolean | | | `tinyint(1)` | boolean | | | `tinyint(>1)` | number | | +| `tinyint(>=1) unsigned` | number | | | `smallint` | number | | | `mediumint` | number | | | `int` | number | | @@ -249,78 +250,79 @@ WHERE actor_definition_id ='435bb9a5-7887-4809-aa58-28c27df0d7ad' AND (configura ``` ## Changelog -| Version | Date | Pull Request | Subject | -|:--------|:-----------|:--------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| -| 1.0.9 | 2022-10-31 | [18538](https://github.com/airbytehq/airbyte/pull/18538) | Encode database name | -| 1.0.8 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages | -| 1.0.7 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | -| 1.0.6 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | -| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | -| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | -| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | -| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | -| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | -| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | -| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | -| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | -| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | -| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | -| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | -| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | -| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | -| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | -| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | -| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | -| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | -| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | -| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | -| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | -| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | -| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | -| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | -| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | -| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | -| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | -| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | -| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | -| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | -| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | -| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | -| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | -| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | -| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | -| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | -| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | -| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | -| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | -| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | -| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | -| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | -| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | -| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | -| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | -| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | -| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | -| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | -| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | -| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | -| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | -| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | -| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | -| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | -| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | -| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | -| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | -| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | -| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | -| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | -| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | -| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | -| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | -| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | -| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | -| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | -| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | -| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | -| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | +| Version | Date | Pull Request | Subject | +|:--------|:-----------|:-----------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| +| 1.0.10 | 2022-11-02 | [18619](https://github.com/airbytehq/airbyte/pull/18619) | Fix bug with handling Tinyint(1) Unsigned values as boolean | +| 1.0.9 | 2022-10-31 | [18538](https://github.com/airbytehq/airbyte/pull/18538) | Encode database name | +| 1.0.8 | 2022-10-25 | [18383](https://github.com/airbytehq/airbyte/pull/18383) | Better SSH error handling + messages | +| 1.0.7 | 2022-10-21 | [18263](https://github.com/airbytehq/airbyte/pull/18263) | Fixes bug introduced in [15833](https://github.com/airbytehq/airbyte/pull/15833) and adds better error messaging for SSH tunnel in Desstinations | +| 1.0.6 | 2022-10-19 | [18087](https://github.com/airbytehq/airbyte/pull/18087) | Better error messaging for configuration errors (SSH configs, choosing an invalid cursor) | +| 1.0.5 | 2022-10-17 | [18041](https://github.com/airbytehq/airbyte/pull/18041) | Fixes bug introduced 2022-09-12 with SshTunnel, handles iterator exception properly | +| | 2022-10-13 | [15535](https://github.com/airbytehq/airbyte/pull/16238) | Update incremental query to avoid data missing when new data is inserted at the same time as a sync starts under non-CDC incremental mode | +| 1.0.4 | 2022-10-11 | [17815](https://github.com/airbytehq/airbyte/pull/17815) | Expose setting server timezone for CDC syncs | +| 1.0.3 | 2022-10-07 | [17236](https://github.com/airbytehq/airbyte/pull/17236) | Fix large table issue by fetch size | +| 1.0.2 | 2022-10-03 | [17170](https://github.com/airbytehq/airbyte/pull/17170) | Make initial CDC waiting time configurable | +| 1.0.1 | 2022-10-01 | [17459](https://github.com/airbytehq/airbyte/pull/17459) | Upgrade debezium version to 1.9.6 from 1.9.2 | +| 1.0.0 | 2022-09-27 | [17164](https://github.com/airbytehq/airbyte/pull/17164) | Certify MySQL Source as Beta | +| 0.6.15 | 2022-09-27 | [17299](https://github.com/airbytehq/airbyte/pull/17299) | Improve error handling for strict-encrypt mysql source | +| 0.6.14 | 2022-09-26 | [16954](https://github.com/airbytehq/airbyte/pull/16954) | Implement support for snapshot of new tables in CDC mode | +| 0.6.13 | 2022-09-14 | [15668](https://github.com/airbytehq/airbyte/pull/15668) | Wrap logs in AirbyteLogMessage | +| 0.6.12 | 2022-09-13 | [16657](https://github.com/airbytehq/airbyte/pull/16657) | Improve CDC record queueing performance | +| 0.6.11 | 2022-09-08 | [16202](https://github.com/airbytehq/airbyte/pull/16202) | Adds error messaging factory to UI | +| 0.6.10 | 2022-09-08 | [16007](https://github.com/airbytehq/airbyte/pull/16007) | Implement per stream state support. | +| 0.6.9 | 2022-09-03 | [16216](https://github.com/airbytehq/airbyte/pull/16216) | Standardize spec for CDC replication. See upgrade instructions [above](#upgrading-from-0.6.8-and-older-versions-to-0.6.9-and-later-versions). | +| 0.6.8 | 2022-09-01 | [16259](https://github.com/airbytehq/airbyte/pull/16259) | Emit state messages more frequently | +| 0.6.7 | 2022-08-30 | [16114](https://github.com/airbytehq/airbyte/pull/16114) | Prevent traffic going on an unsecured channel in strict-encryption version of source mysql | +| 0.6.6 | 2022-08-25 | [15993](https://github.com/airbytehq/airbyte/pull/15993) | Improved support for connecting over SSL | +| 0.6.5 | 2022-08-25 | [15917](https://github.com/airbytehq/airbyte/pull/15917) | Fix temporal data type default value bug | +| 0.6.4 | 2022-08-18 | [14356](https://github.com/airbytehq/airbyte/pull/14356) | DB Sources: only show a table can sync incrementally if at least one column can be used as a cursor field | +| 0.6.3 | 2022-08-12 | [15044](https://github.com/airbytehq/airbyte/pull/15044) | Added the ability to connect using different SSL modes and SSL certificates | +| 0.6.2 | 2022-08-11 | [15538](https://github.com/airbytehq/airbyte/pull/15538) | Allow additional properties in db stream state | +| 0.6.1 | 2022-08-02 | [14801](https://github.com/airbytehq/airbyte/pull/14801) | Fix multiple log bindings | +| 0.6.0 | 2022-07-26 | [14362](https://github.com/airbytehq/airbyte/pull/14362) | Integral columns are now discovered as int64 fields. | +| 0.5.17 | 2022-07-22 | [14714](https://github.com/airbytehq/airbyte/pull/14714) | Clarified error message when invalid cursor column selected | +| 0.5.16 | 2022-07-14 | [14574](https://github.com/airbytehq/airbyte/pull/14574) | Removed additionalProperties:false from JDBC source connectors | +| 0.5.15 | 2022-06-23 | [14077](https://github.com/airbytehq/airbyte/pull/14077) | Use the new state management | +| 0.5.13 | 2022-06-21 | [13945](https://github.com/airbytehq/airbyte/pull/13945) | Aligned datatype test | +| 0.5.12 | 2022-06-17 | [13864](https://github.com/airbytehq/airbyte/pull/13864) | Updated stacktrace format for any trace message errors | +| 0.5.11 | 2022-05-03 | [12544](https://github.com/airbytehq/airbyte/pull/12544) | Prevent source from hanging under certain circumstances by adding a watcher for orphaned threads. | +| 0.5.10 | 2022-04-29 | [12480](https://github.com/airbytehq/airbyte/pull/12480) | Query tables with adaptive fetch size to optimize JDBC memory consumption | +| 0.5.9 | 2022-04-06 | [11729](https://github.com/airbytehq/airbyte/pull/11729) | Bump mina-sshd from 2.7.0 to 2.8.0 | +| 0.5.6 | 2022-02-21 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Fixed cursor for old connectors that use non-microsecond format. Now connectors work with both formats | +| 0.5.5 | 2022-02-18 | [10242](https://github.com/airbytehq/airbyte/pull/10242) | Updated timestamp transformation with microseconds | +| 0.5.4 | 2022-02-11 | [10251](https://github.com/airbytehq/airbyte/issues/10251) | bug Source MySQL CDC: sync failed when has Zero-date value in mandatory column | +| 0.5.2 | 2021-12-14 | [6425](https://github.com/airbytehq/airbyte/issues/6425) | MySQL CDC sync fails because starting binlog position not found in DB | +| 0.5.1 | 2021-12-13 | [8582](https://github.com/airbytehq/airbyte/pull/8582) | Update connector fields title/description | +| 0.5.0 | 2021-12-11 | [7970](https://github.com/airbytehq/airbyte/pull/7970) | Support all MySQL types | +| 0.4.13 | 2021-12-03 | [8335](https://github.com/airbytehq/airbyte/pull/8335) | Source-MySql: do not check cdc required param binlog_row_image for standard replication | +| 0.4.12 | 2021-12-01 | [8371](https://github.com/airbytehq/airbyte/pull/8371) | Fixed incorrect handling "\n" in ssh key | +| 0.4.11 | 2021-11-19 | [8047](https://github.com/airbytehq/airbyte/pull/8047) | Source MySQL: transform binary data base64 format | +| 0.4.10 | 2021-11-15 | [7820](https://github.com/airbytehq/airbyte/pull/7820) | Added basic performance test | +| 0.4.9 | 2021-11-02 | [7559](https://github.com/airbytehq/airbyte/pull/7559) | Correctly process large unsigned short integer values which may fall outside java's `Short` data type capability | +| 0.4.8 | 2021-09-16 | [6093](https://github.com/airbytehq/airbyte/pull/6093) | Improve reliability of processing various data types like decimals, dates, datetime, binary, and text | +| 0.4.7 | 2021-09-30 | [6585](https://github.com/airbytehq/airbyte/pull/6585) | Improved SSH Tunnel key generation steps | +| 0.4.6 | 2021-09-29 | [6510](https://github.com/airbytehq/airbyte/pull/6510) | Support SSL connection | +| 0.4.5 | 2021-09-17 | [6146](https://github.com/airbytehq/airbyte/pull/6146) | Added option to connect to DB via SSH | +| 0.4.1 | 2021-07-23 | [4956](https://github.com/airbytehq/airbyte/pull/4956) | Fix log link | +| 0.3.7 | 2021-06-09 | [3179](https://github.com/airbytehq/airbyte/pull/3973) | Add AIRBYTE\_ENTRYPOINT for Kubernetes support | +| 0.3.6 | 2021-06-09 | [3966](https://github.com/airbytehq/airbyte/pull/3966) | Fix excessive logging for CDC method | +| 0.3.5 | 2021-06-07 | [3890](https://github.com/airbytehq/airbyte/pull/3890) | Fix CDC handle tinyint\(1\) and boolean types | +| 0.3.4 | 2021-06-04 | [3846](https://github.com/airbytehq/airbyte/pull/3846) | Fix max integer value failure | +| 0.3.3 | 2021-06-02 | [3789](https://github.com/airbytehq/airbyte/pull/3789) | MySQL CDC poll wait 5 minutes when not received a single record | +| 0.3.2 | 2021-06-01 | [3757](https://github.com/airbytehq/airbyte/pull/3757) | MySQL CDC poll 5s to 5 min | +| 0.3.1 | 2021-06-01 | [3505](https://github.com/airbytehq/airbyte/pull/3505) | Implemented MySQL CDC | +| 0.3.0 | 2021-04-21 | [2990](https://github.com/airbytehq/airbyte/pull/2990) | Support namespaces | +| 0.2.5 | 2021-04-15 | [2899](https://github.com/airbytehq/airbyte/pull/2899) | Fix bug in tests | +| 0.2.4 | 2021-03-28 | [2600](https://github.com/airbytehq/airbyte/pull/2600) | Add NCHAR and NVCHAR support to DB and cursor type casting | +| 0.2.3 | 2021-03-26 | [2611](https://github.com/airbytehq/airbyte/pull/2611) | Add an optional `jdbc_url_params` in parameters | +| 0.2.2 | 2021-03-26 | [2460](https://github.com/airbytehq/airbyte/pull/2460) | Destination supports destination sync mode | +| 0.2.1 | 2021-03-18 | [2488](https://github.com/airbytehq/airbyte/pull/2488) | Sources support primary keys | +| 0.2.0 | 2021-03-09 | [2238](https://github.com/airbytehq/airbyte/pull/2238) | Protocol allows future/unknown properties | +| 0.1.10 | 2021-02-02 | [1887](https://github.com/airbytehq/airbyte/pull/1887) | Migrate AbstractJdbcSource to use iterators | +| 0.1.9 | 2021-01-25 | [1746](https://github.com/airbytehq/airbyte/pull/1746) | Fix NPE in State Decorator | +| 0.1.8 | 2021-01-19 | [1724](https://github.com/airbytehq/airbyte/pull/1724) | Fix JdbcSource handling of tables with same names in different schemas | +| 0.1.7 | 2021-01-14 | [1655](https://github.com/airbytehq/airbyte/pull/1655) | Fix JdbcSource OOM | +| 0.1.6 | 2021-01-08 | [1307](https://github.com/airbytehq/airbyte/pull/1307) | Migrate Postgres and MySQL to use new JdbcSource | +| 0.1.5 | 2020-12-11 | [1267](https://github.com/airbytehq/airbyte/pull/1267) | Support incremental sync | +| 0.1.4 | 2020-11-30 | [1046](https://github.com/airbytehq/airbyte/pull/1046) | Add connectors using an index YAML file | From 4ddd414359a278ecab78d74c8a3bfcb3bc21691b Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 2 Nov 2022 08:49:14 -0700 Subject: [PATCH 494/498] :window: :tada: Allow environment specific sections in docs (#18829) * Allow environment specific sections in docs * Change syntax to lower case --- .../DocumentationPanel.test.ts | 40 +++++++++++++++++++ .../DocumentationPanel/DocumentationPanel.tsx | 14 ++++++- .../sources/facebook-marketing.md | 6 +++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.test.ts diff --git a/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.test.ts b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.test.ts new file mode 100644 index 000000000000..8e285b1bbf97 --- /dev/null +++ b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.test.ts @@ -0,0 +1,40 @@ +import { prepareMarkdown } from "./DocumentationPanel"; + +const testMarkdown = `## Some documentation + + +### Only relevant for Cloud + +some specific documentation that only applies for cloud. + + + +### Only relevant for OSS users + +some specific documentation that only applies for OSS users +`; + +describe("prepareMarkdown", () => { + it("should prepare markdown for cloud", () => { + expect(prepareMarkdown(testMarkdown, "cloud")).toBe(`## Some documentation + + +### Only relevant for Cloud + +some specific documentation that only applies for cloud. + + +`); + }); + it("should prepare markdown for oss", () => { + expect(prepareMarkdown(testMarkdown, "oss")).toBe(`## Some documentation + + + + +### Only relevant for OSS users + +some specific documentation that only applies for OSS users +`); + }); +}); diff --git a/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.tsx b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.tsx index 4835bbcdc35c..0f7bd385faeb 100644 --- a/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.tsx +++ b/airbyte-webapp/src/components/common/DocumentationPanel/DocumentationPanel.tsx @@ -14,11 +14,19 @@ import { PageHeader } from "components/ui/PageHeader"; import { useConfig } from "config"; import { useDocumentation } from "hooks/services/useDocumentation"; +import { isCloudApp } from "utils/app"; import { links } from "utils/links"; import { useDocumentationPanelContext } from "views/Connector/ConnectorDocumentationLayout/DocumentationPanelContext"; import styles from "./DocumentationPanel.module.scss"; +const OSS_ENV_MARKERS = /([\s\S]*?)/gm; +const CLOUD_ENV_MARKERS = /([\s\S]*?)/gm; + +export const prepareMarkdown = (markdown: string, env: "oss" | "cloud"): string => { + return env === "oss" ? markdown.replaceAll(CLOUD_ENV_MARKERS, "") : markdown.replaceAll(OSS_ENV_MARKERS, ""); +}; + export const DocumentationPanel: React.FC = () => { const { formatMessage } = useIntl(); const config = useConfig(); @@ -57,7 +65,11 @@ export const DocumentationPanel: React.FC = () => { } />
    diff --git a/docs/integrations/sources/facebook-marketing.md b/docs/integrations/sources/facebook-marketing.md index 70b95277d0fd..25caeb26f633 100644 --- a/docs/integrations/sources/facebook-marketing.md +++ b/docs/integrations/sources/facebook-marketing.md @@ -11,10 +11,13 @@ This page guides you through the process of setting up the Facebook Marketing so ## Prerequisites * A [Facebook Ad Account ID](https://www.facebook.com/business/help/1492627900875762) + * (For Open Source) A [Facebook App](https://developers.facebook.com/apps/) with the Marketing API enabled + ## Set up Facebook Marketing as a source in Airbyte + ### For Airbyte Cloud To set up Facebook Marketing as a source in Airbyte Cloud: @@ -60,7 +63,9 @@ To set up Facebook Marketing as a source in Airbyte Cloud: 12. For **Page Size of Requests**, fill in the size of the page in case pagintion kicks in. Feel free to ignore it, the default value should work in most cases. 13. For **Insights Lookback Window**, fill in the appropriate value. See [more](#facebook-marketing-attribution-reporting) on this parameter. 14. Click **Set up source**. + + ### For Airbyte Open Source To set up Facebook Marketing as a source in Airbyte Open Source: @@ -76,6 +81,7 @@ To set up Facebook Marketing as a source in Airbyte Open Source: See the Facebook [documentation on Authorization](https://developers.facebook.com/docs/marketing-api/overview/authorization/#access-levels) to request Advanced Access to the relevant permissions. 5. Navigate to the Airbyte Open Source Dashboard. Add the access token when prompted to do so and follow the same instructions as for [setting up the Facebook Connector on Airbyte Cloud](#for-airbyte-cloud). + ## Supported sync modes From b77d039e6b76868970dec72a64974ffd9cfcd7e1 Mon Sep 17 00:00:00 2001 From: Volodymyr Pochtar Date: Wed, 2 Nov 2022 17:57:48 +0200 Subject: [PATCH 495/498] ci: replace GITHUB_OUTPUT with GITHUB_ENV on multiline variables (#18809) * ci: replace GITHUB_OUTPUT with GITHUB_ENV on multiline variables * ci: replace github set-ouput with new syntax in ./tools/bin/ --- .github/workflows/publish-helm-charts.yml | 7 +++++-- .github/workflows/release-airbyte-os.yml | 7 ++++--- tools/bin/bump_version.sh | 6 +++--- tools/bin/find_non_rate_limited_PAT | 4 ++-- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish-helm-charts.yml b/.github/workflows/publish-helm-charts.yml index d53feac9adf6..53208e22a350 100644 --- a/.github/workflows/publish-helm-charts.yml +++ b/.github/workflows/publish-helm-charts.yml @@ -109,7 +109,10 @@ jobs: id: changelog run: | cd ./airbyte/ - echo "::set-output name=changelog::$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no)" + changelog=$(PAGER=cat git log $(git describe --tags --match "*-helm" $(git rev-list --tags --max-count=1))..HEAD --oneline --decorate=no) + echo "changelog<> $GITHUB_ENV + echo "$changelog" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV - name: Create Pull Request uses: peter-evans/create-pull-request@v4 @@ -122,7 +125,7 @@ jobs: ## What Bump version reference in all Chart.yaml files to ${{ needs.generate-semantic-version.outputs.next-version }} CHANGELOG: - ${{ steps.changelog.outputs.changelog }} + ${{ env.changelog }} commit-message: Bump helm chart version reference to ${{ needs.generate-semantic-version.outputs.next-version }} delete-branch: true diff --git a/.github/workflows/release-airbyte-os.yml b/.github/workflows/release-airbyte-os.yml index e1f9f046f38f..23dd09cb6e6f 100644 --- a/.github/workflows/release-airbyte-os.yml +++ b/.github/workflows/release-airbyte-os.yml @@ -135,8 +135,9 @@ jobs: run: | chmod +x tools/bin/pr_body.sh body=$(./tools/bin/pr_body.sh) - body="${body//$'\n'/'%0A'}" - echo PR_BODY=$body >> $GITHUB_OUTPUT + echo "PR_BODY<> $GITHUB_ENV + echo "$body" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV - name: Create Pull Request id: cpr uses: peter-evans/create-pull-request@v3 @@ -146,7 +147,7 @@ jobs: branch-suffix: random delete-branch: true title: Bump Airbyte version from ${{ steps.bump_version.outputs.PREV_VERSION }} to ${{ steps.bump_version.outputs.NEW_VERSION }} - body: ${{ steps.pr_body.outputs.PR_BODY }} + body: ${{ env.PR_BODY }} commit-message: Bump Airbyte version from ${{ steps.bump_version.outputs.PREV_VERSION }} to ${{ steps.bump_version.outputs.NEW_VERSION }} - name: PR Details run: | diff --git a/tools/bin/bump_version.sh b/tools/bin/bump_version.sh index 8ba4612549fd..e545406f5182 100755 --- a/tools/bin/bump_version.sh +++ b/tools/bin/bump_version.sh @@ -20,6 +20,6 @@ export VERSION=$NEW_VERSION # for safety, since lib.sh exports a VERSION that is set +o xtrace echo "Bumped version from ${PREV_VERSION} to ${NEW_VERSION}" -echo ::set-output name=PREV_VERSION::${PREV_VERSION} -echo ::set-output name=NEW_VERSION::${NEW_VERSION} -echo ::set-output name=GIT_REVISION::${GIT_REVISION} +echo "PREV_VERSION=${PREV_VERSION}" >> $GITHUB_OUTPUT +echo "NEW_VERSION=${NEW_VERSION}" >> $GITHUB_OUTPUT +echo "GIT_REVISION=${GIT_REVISION}" >> $GITHUB_OUTPUT diff --git a/tools/bin/find_non_rate_limited_PAT b/tools/bin/find_non_rate_limited_PAT index 99e0e2e92907..23b6d0b78a47 100755 --- a/tools/bin/find_non_rate_limited_PAT +++ b/tools/bin/find_non_rate_limited_PAT @@ -40,8 +40,8 @@ for personal_access_token in $@ # github actions will NOT pass a string that looks like a secret base64_valid_pat=$(echo "$personal_access_token" | base64) echo -e "$blue_text""Found a good PAT!!""$default_text" - # ::set-output name is a github action magic string for output - echo "::set-output name=pat::$base64_valid_pat" + # $GITHUB_OUTPUT is a github action magic env variable for output + echo "pat=$base64_valid_pat" >> $GITHUB_OUTPUT echo "PAT=$personal_access_token" >> $GITHUB_ENV exit 0 else From 7c2c701098d7374004d62d8b7ce9c88a5d03855d Mon Sep 17 00:00:00 2001 From: Jonathan Pearlin Date: Wed, 2 Nov 2022 11:58:11 -0400 Subject: [PATCH 496/498] Add connection ID to span (#18852) --- .../activities/JobCreationAndStatusUpdateActivityImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java index 019442df1911..2582eee235a5 100644 --- a/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java +++ b/airbyte-workers/src/main/java/io/airbyte/workers/temporal/scheduling/activities/JobCreationAndStatusUpdateActivityImpl.java @@ -259,8 +259,6 @@ public void jobSuccessWithAttemptNumber(final JobSuccessInputWithAttemptNumber i @Override public void jobFailure(final JobFailureInput input) { try { - ApmTraceUtils.addTagsToTrace(Map.of(JOB_ID_KEY, input.getJobId())); - final long jobId = input.getJobId(); jobPersistence.failJob(jobId); final Job job = jobPersistence.getJob(jobId); @@ -269,6 +267,7 @@ public void jobFailure(final JobFailureInput input) { emitJobIdToReleaseStagesMetric(OssMetricsRegistry.JOB_FAILED_BY_RELEASE_STAGE, jobId); final UUID connectionId = UUID.fromString(job.getScope()); + ApmTraceUtils.addTagsToTrace(Map.of(CONNECTION_ID_KEY, connectionId, JOB_ID_KEY, jobId)); final JobSyncConfig jobSyncConfig = job.getConfig().getSync(); final SyncJobReportingContext jobContext = new SyncJobReportingContext(jobId, jobSyncConfig.getSourceDockerImage(), jobSyncConfig.getDestinationDockerImage()); From fbe16d1bef8dbad4bfea76aee839f683c53ebe15 Mon Sep 17 00:00:00 2001 From: Amruta Ranade <11484018+Amruta-Ranade@users.noreply.github.com> Date: Wed, 2 Nov 2022 12:14:37 -0400 Subject: [PATCH 497/498] edited connector docs (#18855) --- docs/integrations/sources/chargebee.md | 84 ++++++++--------------- docs/integrations/sources/freshdesk.md | 66 +++++------------- docs/integrations/sources/greenhouse.md | 43 ++++-------- docs/integrations/sources/harvest.md | 48 ++++++------- docs/integrations/sources/iterable.md | 42 ++++-------- docs/integrations/sources/klaviyo.md | 58 +++++++--------- docs/integrations/sources/mixpanel.md | 59 +++++++--------- docs/integrations/sources/notion.md | 2 +- docs/integrations/sources/pinterest.md | 60 ++++++++-------- docs/integrations/sources/sentry.md | 44 ++++-------- docs/integrations/sources/zendesk-chat.md | 52 +++++++------- docs/integrations/sources/zendesk-talk.md | 2 +- 12 files changed, 208 insertions(+), 352 deletions(-) diff --git a/docs/integrations/sources/chargebee.md b/docs/integrations/sources/chargebee.md index c3fa7618e7ae..6c1f089e9ddb 100644 --- a/docs/integrations/sources/chargebee.md +++ b/docs/integrations/sources/chargebee.md @@ -4,47 +4,27 @@ This page contains the setup guide and reference information for the Chargebee s ## Prerequisites -This Chargebee source uses the [Chargebee Python Client Library](https://github.com/chargebee/chargebee-python/). - -## Setup guide -### Step 1: Set up Chargebee - -Log into Chargebee and then generate an [API Key](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2#api_authentication). -Then follow [these](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2) instructions, under `API Version` section, on how to find your Product Catalog version. - -### Step 2: Set up the Chargebee connector in Airbyte - -#### For Airbyte Cloud: -1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. Click **Sources** and then click **+New source**. -3. On the Set up the source page, enter the name for the Harvest connector and select **Chargebee** from the Source type dropdown. -4. Enter a name for your source -5. Enter your `site_api_key` -6. Enter your `site` -7. Enter your `product_catalog` -8. Enter the `start_date` you want your sync to start from -9. Click **Set up source** - -#### For Airbyte OSS: -1. Navigate to the Airbyte Open Source dashboard -2. Enter a name for your source -3. Enter your `site_api_key` -4. Enter your `site` -5. Enter your `product_catalog` -6. Enter the `start_date` you want your sync to start from -7. Click **Set up source** +To set up the Chargebee source connector, you'll need the [Chargebee API key](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2#api_authentication) and the [Product Catalog version](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2). + +## Set up the Chargebee connector in Airbyte + +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Chargebee** from the Source type dropdown. +4. Enter the name for the Chargebee connector. +5. For **Site**, enter the site prefix for your Chargebee instance. +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. For **API Key**, enter the [Chargebee API key](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2#api_authentication). +8. For **Product Catalog**, enter the Chargebee [Product Catalog version](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2). +9. Click **Set up source**. ## Supported sync modes The Chargebee source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -| :--- | :--- | -| Full Refresh Sync | Yes | -| Incremental - Append Sync | Yes | -| Replicate Incremental Deletes | No | -| SSL connection | Yes | -| Namespaces | No | +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) ## Supported Streams @@ -58,16 +38,9 @@ The Chargebee source connector supports the following [sync modes](https://docs. * [Item Prices](https://apidocs.chargebee.com/docs/api/item_prices?prod_cat_ver=2#list_item_prices) * [Attached Items](https://apidocs.chargebee.com/docs/api/attached_items?prod_cat_ver=2#list_attached_items) -## Performance considerations - -The Chargebee connector should not run into [Chargebee API](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2#api_rate_limits) limitations under normal usage. -Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +Some streams are available only for specific on Product Catalog versions: -## Tutorials - -Some streams may depend on Product Catalog version and be accessible only on sites with specific Product Catalog version. This means that we have following streams: - -1. presented in both `Product Catalog 1.0` and `Product Catalog 2.0`: +1. Available in `Product Catalog 1.0` and `Product Catalog 2.0`: * Customers * Events * Invoices @@ -76,27 +49,26 @@ Some streams may depend on Product Catalog version and be accessible only on sit * Coupons * Subscriptions * Transactions -2. presented only in `Product Catalog 1.0`: +2. Available only in `Product Catalog 1.0`: * Plans * Addons -3. presented only in `Product Catalog 2.0`: +3. Available only in `Product Catalog 2.0`: * Items * Item Prices * Attached Items -Also, 12 streams from the above 13 incremental streams are pure incremental meaning that they: +Note that except the `Attached Items` stream, all the streams listed above are incremental streams, which means they: -* read only new records; -* output only new records. +* Read only new records +* Output only new records -`Attached Items` incremental stream is also incremental but with one difference, it: +The `Attached Items` stream is also incremental but it reads _all_ records and outputs only new records, which is why syncing the `Attached Items` stream, even in incremental mode, is expensive in terms of your Chargebee API quota. -* read all records; -* output only new records. +Generally speaking, it incurs a number of API calls equal to the total number of attached items in your chargebee instance divided by 100, regardless of how many `AttachedItems` were actually changed or synced in a particular sync job. + +## Performance considerations -This means that syncing the `Attached Items` stream, even in incremental mode, is expensive in terms of your Chargebee API quota. -Generally speaking, it incurs a number of API calls equal to the total number of attached items in your chargebee instance divided by 100, -regardless of how many AttachedItems were actually changed or synced in a particular sync job. +The Chargebee connector should not run into [Chargebee API](https://apidocs.chargebee.com/docs/api?prod_cat_ver=2#api_rate_limits) limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you encounter any rate limit issues that are not automatically retried successfully. ## Changelog diff --git a/docs/integrations/sources/freshdesk.md b/docs/integrations/sources/freshdesk.md index c1e7176c1e4e..458790c1f48b 100644 --- a/docs/integrations/sources/freshdesk.md +++ b/docs/integrations/sources/freshdesk.md @@ -4,56 +4,26 @@ This page guides you through the process of setting up the Freshdesk source conn ## Prerequisites -* Freshdesk Account -* Domain URL -* Freshdesk API Key +To set up the Freshdesk source connector, you'll need the Freshdesk [domain URL](https://support.freshdesk.com/en/support/solutions/articles/50000004704-customizing-your-helpdesk-url) and the [API key](https://support.freshdesk.com/support/solutions/articles/215517). -## Step 1: Set up Freshdesk +## Set up the Freshdesk connector in Airbyte -### Get Domain URL +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Freshdesk** from the Source type dropdown. +4. Enter the name for the Freshdesk connector. +5. For **Domain**, enter your [Freshdesk domain URL](https://support.freshdesk.com/en/support/solutions/articles/50000004704-customizing-your-helpdesk-url). +6. For **API Key**, enter your [Freshdesk API key](https://support.freshdesk.com/support/solutions/articles/215517). +7. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +8. For **Requests per minute**, enter the number of requests per minute that this source allowed to use. The Freshdesk rate limit is 50 requests per minute per app per account. +9. Click **Set up source**. -You can find your domain URL by logging into your account and check the URL in your browser, the domain url should look like: `https://myaccount.freshdesk.com/...`, where `myaccount.freshdesk.com` - is your domain URL. +## Supported sync modes -### Get Freshdesk API Key - -Follow the link to read more about [how to find your API key](https://support.freshdesk.com/support/solutions/articles/215517). You need the admin permissions to access the account settings. - - -## Step 2: Set up the Freshdesk connector in Airbyte - -**For Airbyte Cloud** - -1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. Click **Sources** and then click **+ New source**. -3. On the source setup page, select **Freshdesk** from the Source type dropdown and enter a name for this connector. -4. Enter your `Domain URL`. -5. Enter your `Freshdesk API Key`. -6. Choose the `Start Date` as the starting point for your data replication. -5. Click `Set up source`. - -**For Airbyte Open Source:** - -1. Go to local Airbyte page. -2. Click **Sources** and then click **+ New source**. -3. On the source setup page, select **Freshdesk** from the Source type dropdown and enter a name for this connector. -4. Enter your `Domain URL`. -5. Enter your `Freshdesk API Key`. -6. Choose the `Start Date` as the starting point for your data replication. -5. Click `Set up source`. - -## Supported sync modes & Features - -| Feature | Supported? | -| :---------------- | :--------- | -| Full Refresh Sync | Yes | -| Incremental Sync | Yes | -| SSL connection | Yes | -| Namespaces | No | - -The Freshdesk supports full refresh and incremental sync. You can choose if this connector will copy only the new or updated data, or all rows in the tables and columns you set up for replication, every time a sync is run. There are two types of incremental sync: - -* server level \(native\) - when API supports filter on specific columns that Airbyte use to track changes \(`updated_at`, `created_at`, etc\) -* client level - when API doesn't support filter and Airbyte performs filtration on its side. +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams @@ -71,11 +41,9 @@ Several output streams are available from this source: * [Tickets](https://developers.freshdesk.com/api/#tickets) \(Native Incremental Sync\) * [Time Entries](https://developers.freshdesk.com/api/#time-entries) -If there are more endpoints you'd like Airbyte to support, please [create an issue.](https://github.com/airbytehq/airbyte/issues/new/choose) - ## Performance considerations -The Freshdesk connector should not run into Freshdesk API limitations under normal usage. Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +The Freshdesk connector should not run into Freshdesk API limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you encounter any rate limit issues that are not automatically retried successfully. ## Changelog diff --git a/docs/integrations/sources/greenhouse.md b/docs/integrations/sources/greenhouse.md index 0d96146776ab..0d6bdbeb414a 100644 --- a/docs/integrations/sources/greenhouse.md +++ b/docs/integrations/sources/greenhouse.md @@ -4,36 +4,25 @@ This page contains the setup guide and reference information for the Greenhouse ## Prerequisites -Please follow the [Greenhouse documentation for generating an API key](https://developers.greenhouse.io/harvest.html#authentication). +To set up the Greenhouse source connector, you'll need the [Harvest API key](https://developers.greenhouse.io/harvest.html#authentication) with permissions to the resources Airbyte should be able to access. ## Set up the Greenhouse connector in Airbyte -### For Airbyte Cloud: - -1. Log into your [Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Source**. In the top-right corner, click **+New source**. -3. On the Set up the source page, enter the name for the Greenhouse connector and select **Greenhouse** from the Source type dropdown. -4. Enter your `api_key` -5. Click **Set up source** - -### For Airbyte OSS: - -1. Navigate to the Airbyte Open Source dashboard -2. Set the name for your source -3. Enter your `api_key` -4. Click **Set up source** +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Greenhouse** from the Source type dropdown. +4. Enter the name for the Greenhouse connector. +4. Enter your [**Harvest API Key**](https://developers.greenhouse.io/harvest.html#authentication) that you obtained from Greenhouse. +5. Click **Set up source**. ## Supported sync modes The Greenhouse source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -|:------------------------------|:------------| -| Full Refresh Sync | Yes | -| Incremental Sync | Yes | -| Replicate Incremental Deletes | Coming soon | -| SSL connection | Yes | -| Namespaces | No | +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams @@ -56,17 +45,9 @@ The Greenhouse source connector supports the following [sync modes](https://docs * [Sources](https://developers.greenhouse.io/harvest.html#get-list-sources) * [Users](https://developers.greenhouse.io/harvest.html#get-list-users) -## Setting permissions for API Keys -You can specify which API endpoints your API keys have access to from the Greenhouse Dev Center. This will allow you to permit or deny access to each endpoint individually. Any API keys created before January 18th, 2017 will have full permissions to all API endpoints that existed at that time, but any new API keys created after that point will need the required endpoint permissions to be explicitly granted. -To add or remove endpoint permissions on an API key, go to the Dev Center in Greenhouse, click “API Credential Management”, then click “Manage Permissions” next to your Harvest API Key. From there, check or uncheck permissions for any endpoints. - -**Important Note**: Users with Harvest API keys may access all the data in the endpoint. Access to data in Harvest is binary: everything or nothing. Harvest API keys should be given to internal developers with this understanding and to third parties with caution. Each key should only be allowed to access the endpoints it absolutely needs. -See more on this [here](https://developers.greenhouse.io/harvest.html#authentication). - ## Performance considerations -The Greenhouse connector should not run into Greenhouse API limitations under normal usage. -Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +The Greenhouse connector should not run into Greenhouse API limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you encounter any rate limit issues that are not automatically retried successfully. ## Changelog diff --git a/docs/integrations/sources/harvest.md b/docs/integrations/sources/harvest.md index d6c33f9e01ee..ab7e8425375e 100644 --- a/docs/integrations/sources/harvest.md +++ b/docs/integrations/sources/harvest.md @@ -4,38 +4,40 @@ This page contains the setup guide and reference information for the Harvest sou ## Prerequisites -See [Harvest documentation](https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/) for more details. +To set up the Harvest source connector, you'll need the [Harvest Account ID and API key](https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/). -## Setup guide -### Step 1: Set up Harvest +## Set up the Harvest connector in Airbyte -This connector supports only authentication with API Key. To obtain API key follow the instructions below: +### For Airbyte Cloud -1. Go to Account Settings page; -2. Under Integrations section press Authorized OAuth2 API Clients button; -3. New page will be opened on which you need to click on Create New Personal Access Token button and follow instructions. +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces). +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Harvest** from the Source type dropdown. +4. Enter the name for the Harvest connector. +5. Enter your [Harvest Account ID](https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/). +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. For Authentication mechanism, select **Authenticate via Harvest (OAuth)** from the dropdown and click **Authenticate your Harvest account**. Log in and authorize your Harvest account. +8. Click **Set up source**. -## Step 2: Set up the Harvest connector in Airbyte +### For Airbyte Open Source -1. Navigate to the Airbyte Open Source dashboard -2. Set the name for your source -3. Enter your `api_token` -4. Enter your `account_id` -5. Enter the `replication_start_date` you want your sync to start from -6. Click **Set up source** +1. Navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Harvest** from the Source type dropdown. +4. Enter the name for the Harvest connector. +5. Enter your [Harvest Account ID](https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/). +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. For **Authentication mechanism**, select **Authenticate with Personal Access Token** from the dropdown. Enter your [Personal Access Token](https://help.getharvest.com/api-v2/authentication-api/authentication/authentication/#personal-access-tokens). +8. Click **Set up source**. ## Supported sync modes The Harvest source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -| :---------------------------- | :--------- | -| Full Refresh Sync | Yes | -| Incremental Sync | Yes | -| Replicate Incremental Deletes | No | -| SSL connection | Yes | -| Namespaces | No | - +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams @@ -67,7 +69,7 @@ The Harvest source connector supports the following [sync modes](https://docs.ai ## Performance considerations -The Harvest connector will gracefully handle rate limits. For more information, see [the Harvest docs for rate limitations](https://help.getharvest.com/api-v2/introduction/overview/general/#rate-limiting). +The connector is restricted by the [Harvest rate limits](https://help.getharvest.com/api-v2/introduction/overview/general/#rate-limiting). ## Changelog diff --git a/docs/integrations/sources/iterable.md b/docs/integrations/sources/iterable.md index fd5f15acb015..b71d93c1ee75 100644 --- a/docs/integrations/sources/iterable.md +++ b/docs/integrations/sources/iterable.md @@ -4,42 +4,26 @@ This page contains the setup guide and reference information for the Iterable so ## Prerequisites -* Iterable Account -* Iterable `Server-side` API Key with `standard` permissions. See [API Keys docs](https://support.iterable.com/hc/en-us/articles/360043464871-API-Keys-) for more details. +To set up the Iterable source connector, you'll need the Iterable [`Server-side` API Key with `standard` permissions](https://support.iterable.com/hc/en-us/articles/360043464871-API-Keys-). -## Setup guide -### Step 1: Set up Iterable +## Set up the Iterable connector in Airbyte -* Please read [How to find your API key](https://support.iterable.com/hc/en-us/articles/360043464871-API-Keys-#creating-api-keys). -* Make sure that selected API Key has sufficient **permissions** to read all selected [streams](https://api.iterable.com/api/docs#). - -## Step 2: Set up the Iterable connector in Airbyte -### For Airbyte Cloud: - -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. -3. On the Set up the source page, enter the name for the Iterable connector and select **Iterable** from the Source type dropdown. -4. Enter your `api_key` -5. Enter the `start_date` you want your sync to start from -6. Click **Set up source** - -### For Airbyte OSS: - -1. Navigate to the Airbyte Open Source dashboard -2. Set the name for your source -3. Enter your `api_key` -4. Enter the `start_date` you want your sync to start from -5. Click **Set up source** +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Iterable** from the Source type dropdown. +4. Enter the name for the Iterable connector. +5. For **API Key**, enter the [Iterable API key](https://support.iterable.com/hc/en-us/articles/360043464871-API-Keys-). +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. Click **Set up source**. ## Supported sync modes The Iterable source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -| :---------------- | :--------- | -| Full Refresh Sync | Yes | -| Incremental Sync | Yes | -| SSL connection | Yes | +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams diff --git a/docs/integrations/sources/klaviyo.md b/docs/integrations/sources/klaviyo.md index 4626af0ee9a1..dc8ed649f072 100644 --- a/docs/integrations/sources/klaviyo.md +++ b/docs/integrations/sources/klaviyo.md @@ -4,54 +4,42 @@ This page contains the setup guide and reference information for the Klaviyo sou ## Prerequisites -* Klaviyo Private API Key +To set up the Klaviyo source connector, you'll need the [Klaviyo Private API key](https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys#your-private-api-keys3). -## Setup guide -### Step 1: Set up Klaviyo -Please follow these [steps](https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys#your-private-api-keys3) to obtain Private API Key for your account. +## Set up the Klaviyo connector in Airbyte -### Step 2: Set up the Klaviyo connector in Airbyte - -### For Airbyte Cloud: - -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. -3. On the Set up the source page, enter the name for the Klaviyo connector and select **Klaviyo** from the Source type dropdown. -4. Enter you private API key from Prerequisites -5. Enter the date you want your sync to start from -6. Submit the form - -### For Airbyte Open Source: -1. Navigate to the Airbyte Open Source dashboard -2. Set the name for your source -4. Enter you private API key from Prerequisites -5. Enter the date you want your sync to start from -6. Click **Set up source** +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Klaviyo** from the Source type dropdown. +4. Enter the name for the Klaviyo connector. +5. For **Api Key**, enter the Klaviyo [Private API key](https://help.klaviyo.com/hc/en-us/articles/115005062267-How-to-Manage-Your-Account-s-API-Keys#your-private-api-keys3). +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. Click **Set up source**. ## Supported sync modes -The Klaviyo source connector supports the following[ sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): - - Full Refresh | Overwrite - - Full Refresh | Append - - Incremental Sync | Append - - Incremental Sync | Deduped History +The Klaviyo source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -## Supported Streams +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) -This Source is capable of syncing the following core Streams: +## Supported Streams -* [Campaigns](https://apidocs.klaviyo.com/reference/campaigns#get-campaigns) -* [Events](https://apidocs.klaviyo.com/reference/metrics#metrics-timeline) -* [GlobalExclusions](https://apidocs.klaviyo.com/reference/lists-segments#get-global-exclusions) -* [Lists](https://apidocs.klaviyo.com/reference/lists#get-lists-deprecated) -* [Metrics](https://apidocs.klaviyo.com/en/reference/get-metrics) +* [Campaigns](https://developers.klaviyo.com/en/v1-2/reference/get-campaigns#get-campaigns) +* [Events](https://developers.klaviyo.com/en/v1-2/reference/metrics-timeline) +* [GlobalExclusions](https://developers.klaviyo.com/en/v1-2/reference/get-global-exclusions) +* [Lists](https://developers.klaviyo.com/en/v1-2/reference/get-lists) +* [Metrics](https://developers.klaviyo.com/en/v1-2/reference/get-metrics) +* [Flows](https://developers.klaviyo.com/en/reference/get_flows) ## Performance considerations -The connector is restricted by normal Klaviyo [requests limitation](https://apidocs.klaviyo.com/reference/api-overview#rate-limits). +The connector is restricted by Klaviyo [requests limitation](https://apidocs.klaviyo.com/reference/api-overview#rate-limits). -The Klaviyo connector should not run into Klaviyo API limitations under normal usage. Please [create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +The Klaviyo connector should not run into Klaviyo API limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you encounter any rate limit issues that are not automatically retried successfully. ## Data type map diff --git a/docs/integrations/sources/mixpanel.md b/docs/integrations/sources/mixpanel.md index fd48fe18feee..fed97ee1ff0f 100644 --- a/docs/integrations/sources/mixpanel.md +++ b/docs/integrations/sources/mixpanel.md @@ -4,43 +4,33 @@ This page contains the setup guide and reference information for the Mixpanel so ## Prerequisites -* Mixpanel [Service Account](https://developer.mixpanel.com/reference/service-accounts) -* Mixpanel [Project ID](https://help.mixpanel.com/hc/en-us/articles/115004490503-Project-Settings#project-id) -* Mixpanel [Project Timezone](https://help.mixpanel.com/hc/en-us/articles/115004547203-Manage-Timezones-for-Projects-in-Mixpanel) -* Project region `US` or `EU` - -## Setup guide - -### Step 1: Set up - - -Please read [Find Service Account](https://developer.mixpanel.com/reference/service-accounts), and create your Service Account. - - -Select the correct region \(EU or US\) for your Mixpanel project. See detail [here](https://help.mixpanel.com/hc/en-us/articles/360039135652-Data-Residency-in-EU) - - -### Step 2: Set up the Mixpanel connector in Airbyte -Choose start date, from which data will be synced - -*Note:* If `start_date` is not set, the connector will replicate data from up to one year ago by default. - - -### For Airbyte Cloud: - -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. -3. On the Set up the source page, enter the name for the Mixpanel connector and select **Mixpanel** from the Source type dropdown. -4. Select `start_date`, from which your data will need to be synced - +To set up the Harvest source connector, you'll need a Mixpanel [Service Account](https://developer.mixpanel.com/reference/service-accounts) and it's [Project ID](https://help.mixpanel.com/hc/en-us/articles/115004490503-Project-Settings#project-id), the [Project Timezone](https://help.mixpanel.com/hc/en-us/articles/115004547203-Manage-Timezones-for-Projects-in-Mixpanel), and the Project region (`US` or `EU`). + +## Set up the Mixpanel connector in Airbyte + +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Mixpanel** from the Source type dropdown. +4. Enter the name for the Mixpanel connector. +5. For **Authentication**, select **Service Account** from the dropdown and enter the [Mixpanel Service Account secret](https://developer.mixpanel.com/reference/service-accounts). +6. For **Project ID**, enter the [Mixpanel Project ID](https://help.mixpanel.com/hc/en-us/articles/115004490503-Project-Settings#project-id). +7. For **Attribution Window**, enter the number of days for the length of the attribution window. +8. For **Project Timezone**, enter the [timezone](https://help.mixpanel.com/hc/en-us/articles/115004547203-Manage-Timezones-for-Projects-in-Mixpanel) for your Mixpanel project. +9. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. If left blank, the connector will replicate data from up to one year ago by default. +10. For **End Date**, enter the date in YYYY-MM-DD format. +11. For **Region**, enter the [region](https://help.mixpanel.com/hc/en-us/articles/360039135652-Data-Residency-in-EU) for your Mixpanel project. +12. For **Date slicing window**, enter the number of days to slice through data. If you encounter RAM usage issues due to a huge amount of data in each window, try using a lower value for this parameter. +13. Click **Set up source**. ## Supported sync modes The Mixpanel source connector supports the following[ sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -* [Full Refresh](https://docs.airbyte.com/integrations/sources/mongodb#full-refresh-sync) -* [Incremental](https://docs.airbyte.com/integrations/sources/mongodb#incremental-sync) +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) -Please note, that incremental sync could return duplicated \(old records\) for the state date due to API filter limitation, which is granular to the whole day only. +Note: Incremental sync returns duplicated \(old records\) for the state date due to API filter limitation, which is granular to the whole day only. ### Supported Streams @@ -52,12 +42,9 @@ Please note, that incremental sync could return duplicated \(old records\) for t * [Cohorts](https://developer.mixpanel.com/reference/cohorts-list) \(Incremental\) * [Cohort Members](https://developer.mixpanel.com/reference/engage-query) \(Incremental\) - ## Performance considerations -* Due to quite low API rate-limits \(**60 reqs per hour**\), syncing of huge date windows may be quite long -* If you're struggling with high RAM usage, try to decrease `date_window_size` parameter in config - +Syncing huge date windows may take longer due to Mixpanel's low API rate-limits \(**60 reqs per hour**\). ## CHANGELOG diff --git a/docs/integrations/sources/notion.md b/docs/integrations/sources/notion.md index c4d2a8bbcc39..cc4ec750ba1d 100644 --- a/docs/integrations/sources/notion.md +++ b/docs/integrations/sources/notion.md @@ -74,7 +74,7 @@ The users stream does not support Incremental - Append sync mode. ## Performance considerations -The connector is restricted by Notion [request limits](https://developers.notion.com/reference/request-limits). The Notion connector should not run into Notion API limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you see any rate limit issues that are not automatically retried successfully. +The connector is restricted by Notion [request limits](https://developers.notion.com/reference/request-limits). The Notion connector should not run into Notion API limitations under normal usage. [Create an issue](https://github.com/airbytehq/airbyte/issues) if you encounter any rate limit issues that are not automatically retried successfully. ## Changelog diff --git a/docs/integrations/sources/pinterest.md b/docs/integrations/sources/pinterest.md index cbcb3262e828..436af57319a6 100644 --- a/docs/integrations/sources/pinterest.md +++ b/docs/integrations/sources/pinterest.md @@ -4,44 +4,38 @@ This page contains the setup guide and reference information for the Pinterest s ## Prerequisites -Please read [How to get your credentials](https://developers.pinterest.com/docs/api/v5/#tag/Authentication). +To set up the Pinterest source connector with Airbyte Open Source, you'll need your Pinterest [App ID and secret key](https://developers.pinterest.com/docs/getting-started/set-up-app/) and the [refresh token](https://developers.pinterest.com/docs/getting-started/authentication/#Refreshing%20an%20access%20token). -## Setup guide -## Step 1: Set up the Pinterest connector in Airbyte +## Set up the Pinterest connector in Airbyte -### For Airbyte Cloud: +### For Airbyte Cloud 1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. -3. On the Set up the source page, enter the name for the Pinterest connector and select **Pinterest** from the Source type dropdown. -4. Enter the `start_date` you want your sync to start from. `start_date` should be no older than 914 days from today, that's the restriction of the Pinterest API for some of the streams. -5. Choose `OAuth2.0` in `Authorization Method` list -6. Click on `Authenticate your Pinterest account` button -7. Proceed with OAuth authentication of your account in the pop-up window that appears after previous step -8. Click **Set up source** - -### For Airbyte OSS: - -1. Navigate to the Airbyte Open Source dashboard -2. Set the name for your source -3. Enter your `client_id` -4. Enter your `client_secret` -5. Enter your `refresh_token` -6. Enter the `start_date` you want your sync to start from.`start_date` should be no older than 914 days from today, that's the restriction of the Pinterest API for some of the streams. -5. Choose `OAuth2.0` in `Authorization Method` list - -7. Click **Set up source** +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Pinterest** from the Source type dropdown. +4. Enter the name for the Pinterest connector. +5. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. If this field is blank, Airbyte will replicate all data. As per Pinterest API restriction, the date cannot be more than 914 days in the past. +6. The **OAuth2.0** authorization method is selected by default. Click **Authenticate your Pinterest account**. Log in and authorize your Pinterest account. +7. Click **Set up source**. + +### For Airbyte Open Source + +1. Navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Pinterest** from the Source type dropdown. +4. Enter the name for the Pinterest connector. +5. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. If this field is blank, Airbyte will replicate all data. As per Pinterest API restriction, the date cannot be more than 914 days in the past. +6. The **OAuth2.0** authorization method is selected by default. For **Client ID** and **Client Secret**, enter your Pinterest [App ID and secret key](https://developers.pinterest.com/docs/getting-started/set-up-app/). For **Refresh Token**, enter your Pinterest [Refresh Token](https://developers.pinterest.com/docs/getting-started/authentication/#Refreshing%20an%20access%20token). +7. Click **Set up source**. ## Supported sync modes The Pinterest source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -| :------------------------ | :--------- | -| Full Refresh Sync | Yes | -| Incremental - Append Sync | Yes | -| SSL connection | Yes | -| Namespaces | No | +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams @@ -61,13 +55,13 @@ The Pinterest source connector supports the following [sync modes](https://docs. ## Performance considerations -The connector is restricted by normal Pinterest [requests limitation](https://developers.pinterest.com/docs/api/v5/#tag/Rate-limits). +The connector is restricted by the Pinterest [requests limitation](https://developers.pinterest.com/docs/api/v5/#tag/Rate-limits). ##### Rate Limits -Analytics streams - 300 calls per day / per user \ -Ad accounts streams (Campaigns, Ad groups, Ads) - 1000 calls per min / per user / per app \ -Boards streams - 10 calls per sec / per user / per app +- Analytics streams: 300 calls per day / per user \ +- Ad accounts streams (Campaigns, Ad groups, Ads): 1000 calls per min / per user / per app \ +- Boards streams: 10 calls per sec / per user / per app ## Changelog diff --git a/docs/integrations/sources/sentry.md b/docs/integrations/sources/sentry.md index c3dd7ccd45bf..7a51ce34b1ed 100644 --- a/docs/integrations/sources/sentry.md +++ b/docs/integrations/sources/sentry.md @@ -4,42 +4,28 @@ This page contains the setup guide and reference information for the Sentry sour ## Prerequisites -You can find or create authentication tokens within [Sentry](https://sentry.io/settings/account/api/auth-tokens/). +To set up the Sentry source connector, you'll need the Sentry [project name](https://docs.sentry.io/product/projects/), [authentication token](https://docs.sentry.io/api/auth/#auth-tokens), and [organization](https://docs.sentry.io/product/accounts/membership/). -## Setup guide -## Step 1: Set up the Sentry connector in Airbyte +## Set up the Sentry connector in Airbyte -### For Airbyte Cloud: - -1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. -3. On the Set up the source page, enter the name for the Sentry connector and select **Sentry** from the Source type dropdown. -4. Enter your `auth_token` - Sentry Authentication Token with the necessary permissions \(described below\). -4. Enter your `organization` - Organization Slug. You can check it at https://sentry.io/settings/$YOUR_ORG_HERE/. -4. Enter your `project` - The name of the Project you wanto sync. You can list it from https://sentry.io/settings/$YOUR_ORG_HERE/projects/. -4. Enter your `hostname` - Host name of Sentry API server. For self-hosted, specify your host name here. Otherwise, leave it empty. \(default: sentry.io\). -8. Click **Set up source**. - -### For Airbyte OSS: - -1. Navigate to the Airbyte Open Source dashboard. -2. Set the name for your source. -3. Enter your `auth_token` - Sentry Authentication Token with the necessary permissions \(described below\). -4. Enter your `organization` - Organization Slug. You can check it at https://sentry.io/settings/$YOUR_ORG_HERE/. -5. Enter your `project` - The name of the Project you wanto sync. You can list it from https://sentry.io/settings/$YOUR_ORG_HERE/projects/. -6. Enter your `hostname` - Host name of Sentry API server. For self-hosted, specify your host name here. Otherwise, leave it empty. \(default: sentry.io\). -7. Click **Set up source**. +1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account or navigate to the Airbyte Open Source dashboard. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Sentry** from the Source type dropdown. +4. Enter the name for the Sentry connector. +5. For **Project**, enter the name of the [Sentry project](https://docs.sentry.io/product/projects/) you want to sync. +6. For **Host Name**, enter the host name of your self-hosted Sentry API Server. If your server isn't self-hosted, leave the field blank. +7. For **Authentication Tokens**, enter the [Sentry authentication token](https://docs.sentry.io/api/auth/#auth-tokens). +8. For **Organization**, enter the [Sentry Organization](https://docs.sentry.io/product/accounts/membership/) the groups belong to. +9. Click **Set up source**. ## Supported sync modes The Sentry source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -| :---------------- | :--------- | -| Full Refresh Sync | Yes | -| Incremental Sync | No | -| SSL connection | Yes | -| Namespaces | No | +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams diff --git a/docs/integrations/sources/zendesk-chat.md b/docs/integrations/sources/zendesk-chat.md index dc10aa231c1b..8290433320d7 100644 --- a/docs/integrations/sources/zendesk-chat.md +++ b/docs/integrations/sources/zendesk-chat.md @@ -4,47 +4,41 @@ This page contains the setup guide and reference information for the Zendesk Cha ## Prerequisites -- Zendesk Account with permission to access data from accounts you want to sync -- Access Token as described in [Zendesk Chat docs](https://developer.zendesk.com/rest_api/docs/chat/auth). We recommend creating a restricted, read-only key specifically for Airbyte access. This will allow you to control which resources Airbyte should be able to access. +- A Zendesk Account with permission to access data from accounts you want to sync. +- For Airbyte Open Source, you'll need an Access Token (https://developer.zendesk.com/rest_api/docs/chat/auth). We recommend creating a restricted, read-only key specifically for Airbyte access to allow you to control which resources Airbyte should be able to access. -## Setup guide +## Set up the Zendesk Chat connector in Airbyte -## Step 1: Set up Zendesk Chat - -Generate an Access Token as described in [Zendesk Chat docs](https://developer.zendesk.com/rest_api/docs/chat/auth) - - -## Step 2: Set up the Zendesk Chat connector in Airbyte - -### For Airbyte Cloud: +### For Airbyte Cloud 1. [Log into your Airbyte Cloud](https://cloud.airbyte.io/workspaces) account. -2. In the left navigation bar, click **Sources**. In the top-right corner, click **+new source**. -3. On the Set up the source page, enter the name for the Zendesk Chat connector and select **Zendesk Chat** from the Source type dropdown. -4. Select `Authenticate your account` and log in and Authorize to the Zendesk account. -5. Enter your `subdomain`. -6. Enter your `start_time`. -7. Enter your `access_token`. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Zendesk Chat** from the Source type dropdown. +4. Enter the name for the Zendesk Chat connector. +5. If you access Zendesk Chat from a [Zendesk subdomain](https://support.zendesk.com/hc/en-us/articles/4409381383578-Where-can-I-find-my-Zendesk-subdomain-), enter the **Subdomain**. +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. Click **Authenticate your Zendesk Chat account**. Log in and authorize your Zendesk Chat account. 8. Click **Set up source**. -### For Airbyte OSS: +### For Airbyte Open Source 1. Navigate to the Airbyte Open Source dashboard. -2. Set the name for your source. -3. Enter your `subdomain`. -4. Enter your `start_time`. -5. Enter your `access_token`. -6. Click **Set up source**. +2. Click **Sources** and then click **+ New source**. +3. On the Set up the source page, select **Zendesk Chat** from the Source type dropdown. +4. Enter the name for the Zendesk Chat connector. +5. If you access Zendesk Chat from a [Zendesk subdomain](https://support.zendesk.com/hc/en-us/articles/4409381383578-Where-can-I-find-my-Zendesk-subdomain-), enter the **Subdomain**. +6. For **Start Date**, enter the date in YYYY-MM-DD format. The data added on and after this date will be replicated. +7. For Authorization Method, select **Access Token** from the dropdown and enter your Zendesk [access token](https://developer.zendesk.com/rest_api/docs/chat/auth). +8. Click **Set up source**. ## Supported sync modes The Zendesk Chat source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): -| Feature | Supported? | -| :---------------- | :--------- | -| Full Refresh Sync | Yes | -| Incremental Sync | Yes | -| SSL connection | Yes | +* [Full Refresh - Overwrite](https://docs.airbyte.com/understanding-airbyte/glossary#full-refresh-sync) +* [Full Refresh - Append](https://docs.airbyte.com/understanding-airbyte/connections/full-refresh-append) +* [Incremental - Append](https://docs.airbyte.com/understanding-airbyte/connections/incremental-append) +* [Incremental - Deduped History](https://docs.airbyte.com/understanding-airbyte/connections/incremental-deduped-history) ## Supported Streams @@ -63,7 +57,7 @@ The Zendesk Chat source connector supports the following [sync modes](https://do ## Performance considerations -The connector is restricted by normal Zendesk [requests limitation](https://developer.zendesk.com/rest_api/docs/voice-api/introduction#rate-limits). +The connector is restricted by Zendesk's [requests limitation](https://developer.zendesk.com/rest_api/docs/voice-api/introduction#rate-limits). ## Data type map diff --git a/docs/integrations/sources/zendesk-talk.md b/docs/integrations/sources/zendesk-talk.md index 66cb49f8d05c..3574bc09aa19 100644 --- a/docs/integrations/sources/zendesk-talk.md +++ b/docs/integrations/sources/zendesk-talk.md @@ -29,7 +29,7 @@ Another option is to use OAuth2.0 for authentication. See [Zendesk docs](https:/ ## Supported sync modes -The **Zendesk Talk** source connector supports the following[ sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): +The **Zendesk Talk** source connector supports the following [sync modes](https://docs.airbyte.com/cloud/core-concepts#connection-sync-modes): * Full Refresh * Incremental Sync From a44c841d0d07f0e3c6dd3eca326bf36dead04e67 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 2 Nov 2022 09:18:40 -0700 Subject: [PATCH 498/498] :window: :wrench: Upgrade husky to 8.0.1 (#18719) * Upgrade Husky * Upgrade Husky * Upgrade Husky * Upgrade Husky * Upgrade Husky --- airbyte-webapp/.husky/pre-commit | 1 + airbyte-webapp/package-lock.json | 429 +------------------------------ airbyte-webapp/package.json | 8 +- 3 files changed, 14 insertions(+), 424 deletions(-) create mode 100755 airbyte-webapp/.husky/pre-commit diff --git a/airbyte-webapp/.husky/pre-commit b/airbyte-webapp/.husky/pre-commit new file mode 100755 index 000000000000..b18fa3a6e0a3 --- /dev/null +++ b/airbyte-webapp/.husky/pre-commit @@ -0,0 +1 @@ +cd airbyte-webapp && npx --no lint-staged \ No newline at end of file diff --git a/airbyte-webapp/package-lock.json b/airbyte-webapp/package-lock.json index a5efc582dc71..6536213ddd70 100644 --- a/airbyte-webapp/package-lock.json +++ b/airbyte-webapp/package-lock.json @@ -99,7 +99,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-unused-imports": "^2.0.0", "express": "^4.18.1", - "husky": "^4.2.3", + "husky": "^8.0.1", "license-checker": "^25.0.1", "lint-staged": "^12.3.7", "mini-css-extract-plugin": "^2.6.1", @@ -18694,12 +18694,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "node_modules/compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, "node_modules/component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -22959,21 +22953,6 @@ "node": ">=8" } }, - "node_modules/find-versions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", - "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", - "dev": true, - "dependencies": { - "semver-regex": "^3.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/firebase": { "version": "9.8.3", "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.8.3.tgz", @@ -24793,192 +24772,18 @@ } }, "node_modules/husky": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", - "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", + "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", "dev": true, - "hasInstallScript": true, - "dependencies": { - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^7.0.0", - "find-versions": "^4.0.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^5.0.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" - }, "bin": { - "husky-run": "bin/run.js", - "husky-upgrade": "lib/upgrader/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/husky" - } - }, - "node_modules/husky/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/husky/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/husky/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" + "husky": "lib/bin.js" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/husky/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/husky/node_modules/cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/husky/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/husky/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/husky/node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/husky/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/typicode" } }, "node_modules/hyphenate-style-name": { @@ -34084,15 +33889,6 @@ "yaml": "^1.10.2" } }, - "node_modules/opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "dev": true, - "bin": { - "opencollective-postinstall": "index.js" - } - }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -35077,15 +34873,6 @@ "node": ">=4" } }, - "node_modules/please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "dependencies": { - "semver-compare": "^1.0.0" - } - }, "node_modules/pnp-webpack-plugin": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz", @@ -42147,12 +41934,6 @@ "semver": "bin/semver.js" } }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, "node_modules/semver-diff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", @@ -42165,18 +41946,6 @@ "node": ">=8" } }, - "node_modules/semver-regex": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz", - "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -47143,12 +46912,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -62034,12 +61797,6 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, - "compare-versions": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", - "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", - "dev": true - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -65426,15 +65183,6 @@ "path-exists": "^4.0.0" } }, - "find-versions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", - "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", - "dev": true, - "requires": { - "semver-regex": "^3.1.2" - } - }, "firebase": { "version": "9.8.3", "resolved": "https://registry.npmjs.org/firebase/-/firebase-9.8.3.tgz", @@ -66849,132 +66597,10 @@ "dev": true }, "husky": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", - "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^7.0.0", - "find-versions": "^4.0.0", - "opencollective-postinstall": "^2.0.2", - "pkg-dir": "^5.0.0", - "please-upgrade-node": "^3.2.0", - "slash": "^3.0.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", - "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dev": true, - "requires": { - "find-up": "^5.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz", + "integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==", + "dev": true }, "hyphenate-style-name": { "version": "1.0.4", @@ -73861,12 +73487,6 @@ "yaml": "^1.10.2" } }, - "opencollective-postinstall": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", - "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", - "dev": true - }, "optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -74623,15 +74243,6 @@ } } }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, "pnp-webpack-plugin": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.7.0.tgz", @@ -79627,12 +79238,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, "semver-diff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", @@ -79642,12 +79247,6 @@ "semver": "^6.3.0" } }, - "semver-regex": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz", - "integrity": "sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ==", - "dev": true - }, "send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -83475,12 +83074,6 @@ "is-symbol": "^1.0.3" } }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "dev": true - }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/airbyte-webapp/package.json b/airbyte-webapp/package.json index 58c2f077f34d..b72e1e333051 100644 --- a/airbyte-webapp/package.json +++ b/airbyte-webapp/package.json @@ -6,6 +6,7 @@ "node": ">=16.0.0" }, "scripts": { + "prepare": "cd .. && husky install airbyte-webapp/.husky", "prestart": "npm run generate-client", "start": "craco start", "prebuild": "npm run generate-client", @@ -115,7 +116,7 @@ "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-unused-imports": "^2.0.0", "express": "^4.18.1", - "husky": "^4.2.3", + "husky": "^8.0.1", "license-checker": "^25.0.1", "lint-staged": "^12.3.7", "mini-css-extract-plugin": "^2.6.1", @@ -135,11 +136,6 @@ "ts-node": "^10.8.1", "typescript": "^4.7.3" }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "lint-staged": { "src/**/*.{js,jsx,ts,tsx}": [ "eslint --fix"

    - + +
    +
    +
    + `; diff --git a/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.test.tsx b/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.test.tsx index 44c13d15a908..eebee5a77970 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.test.tsx +++ b/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/GitBlock.test.tsx @@ -17,17 +17,17 @@ const renderGitBlock = (props?: GitBlockProps) => describe("", () => { it("should render with default props", () => { - const { asFragment } = renderGitBlock(); + const component = renderGitBlock(); - expect(asFragment()).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); it("should render with overwritten props", () => { - const { asFragment } = renderGitBlock({ + const component = renderGitBlock({ titleStyle: { fontSize: "30px" }, messageStyle: { color: "blue" }, }); - expect(asFragment()).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); diff --git a/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/__snapshots__/GitBlock.test.tsx.snap b/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/__snapshots__/GitBlock.test.tsx.snap index b2d19db1e76b..89806b82f62a 100644 --- a/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/__snapshots__/GitBlock.test.tsx.snap +++ b/airbyte-webapp/src/packages/cloud/views/auth/components/GitBlock/__snapshots__/GitBlock.test.tsx.snap @@ -1,97 +1,101 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should render with default props 1`] = ` - -
    - + - + `; exports[` should render with overwritten props 1`] = ` - -
    - + - + `; diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx index 49dcddeb19c3..c79973155b48 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/FrequentlyUsedDestinations.test.tsx @@ -18,13 +18,13 @@ const renderFrequentlyUsedDestinationsComponent = (props: FrequentlyUsedDestinat describe("", () => { it("should renders with mock data without crash", () => { - const { asFragment } = renderFrequentlyUsedDestinationsComponent({ + const component = renderFrequentlyUsedDestinationsComponent({ destinations: mockData, onDestinationSelect: jest.fn(), propertyPath: "serviceType", }); - expect(asFragment()).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); it("should call provided handler with right param", async () => { diff --git a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinations.test.tsx.snap b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinations.test.tsx.snap index 45ccd7d81603..a7c421c30a87 100644 --- a/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinations.test.tsx.snap +++ b/airbyte-webapp/src/views/Connector/ServiceForm/components/FrequentlyUsedDestinations/__snapshots__/FrequentlyUsedDestinations.test.tsx.snap @@ -1,336 +1,338 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[` should renders with mock data without crash 1`] = ` - -
    + +
    -

    - Most frequently used destinations -

    -
    -
    - + Most frequently used destinations +

    +
    +
    -
    -
    - + +
    -
    -
    -
    -
    -
    - + +
    -
    -
    -
    -
    -